From 01384a10d895f23fb0d6e26dd7aee4fd2578544d Mon Sep 17 00:00:00 2001 From: "rokesh.karthikeyan" Date: Fri, 24 Sep 2021 12:54:08 +0530 Subject: [PATCH] Updated flutter widgets repository with latest release source. --- README.md | 20 +- .../syncfusion_flutter_barcodes/README.md | 2 +- .../lib/barcodes.dart | 2 + .../syncfusion_flutter_calendar/CHANGELOG.md | 20 +- .../syncfusion_flutter_calendar/README.md | 6 +- .../lib/calendar.dart | 2 +- .../appointment_engine/appointment.dart | 159 +- .../appointment_helper.dart | 108 +- .../calendar_datasource.dart | 520 +- .../month_appointment_helper.dart | 9 +- .../appointment_engine/recurrence_helper.dart | 35 +- .../recurrence_properties.dart | 58 +- .../agenda_view_layout.dart | 10 +- .../allday_appointment_layout.dart | 66 +- .../appointment_layout.dart | 47 +- .../calendar/common/calendar_view_helper.dart | 113 +- .../src/calendar/common/date_time_engine.dart | 5 +- .../lib/src/calendar/common/enums.dart | 8 + .../lib/src/calendar/common/event_args.dart | 346 +- .../resource_view/calendar_resource.dart | 41 +- .../settings/drag_and_drop_settings.dart | 257 + .../src/calendar/settings/header_style.dart | 22 + .../settings/month_view_settings.dart | 343 +- .../settings/resource_view_settings.dart | 56 +- .../settings/schedule_view_settings.dart | 289 + .../src/calendar/settings/time_region.dart | 107 +- .../settings/time_slot_view_settings.dart | 192 +- .../calendar/settings/view_header_style.dart | 28 + .../calendar/settings/week_number_style.dart | 8 + .../lib/src/calendar/sfcalendar.dart | 1496 +- .../lib/src/calendar/views/calendar_view.dart | 15152 +++++++++++----- .../lib/src/calendar/views/day_view.dart | 9 +- .../lib/src/calendar/views/month_view.dart | 15 +- .../syncfusion_flutter_charts/CHANGELOG.md | 39 + packages/syncfusion_flutter_charts/README.md | 4 +- .../syncfusion_flutter_charts/lib/charts.dart | 395 +- .../chart/annotation/annotation_settings.dart | 16 +- .../lib/src/chart/axis/axis.dart | 2700 +-- .../lib/src/chart/axis/axis_panel.dart | 799 +- .../lib/src/chart/axis/axis_renderer.dart | 221 +- .../lib/src/chart/axis/category_axis.dart | 419 +- .../lib/src/chart/axis/datetime_axis.dart | 591 +- .../chart/axis/datetime_category_axis.dart | 467 +- .../lib/src/chart/axis/logarithmic_axis.dart | 475 +- .../lib/src/chart/axis/numeric_axis.dart | 571 +- .../lib/src/chart/axis/plotband.dart | 232 +- .../lib/src/chart/base/chart_base.dart | 2925 +-- .../lib/src/chart/base/series_base.dart | 853 +- .../chart/chart_behavior/chart_behavior.dart | 4 +- .../chart_behavior/selection_behavior.dart | 30 +- .../chart/chart_behavior/zoom_behavior.dart | 17 +- .../src/chart/chart_segment/area_segment.dart | 88 +- .../src/chart/chart_segment/bar_segment.dart | 148 +- .../box_and_whisker_segment.dart | 500 +- .../chart/chart_segment/bubble_segment.dart | 234 +- .../chart/chart_segment/candle_segment.dart | 445 +- .../chart/chart_segment/chart_segment.dart | 58 +- .../chart/chart_segment/column_segment.dart | 151 +- .../chart_segment/error_bar_segment.dart | 256 + .../chart/chart_segment/fastline_segment.dart | 78 +- .../src/chart/chart_segment/hilo_segment.dart | 205 +- .../chart_segment/hiloopenclose_segment.dart | 304 +- .../chart_segment/histogram_segment.dart | 166 +- .../src/chart/chart_segment/line_segment.dart | 314 +- .../chart_segment/range_area_segment.dart | 92 +- .../chart_segment/range_column_segment.dart | 165 +- .../chart/chart_segment/scatter_segment.dart | 120 +- .../chart_segment/spline_area_segment.dart | 80 +- .../spline_range_area_segment.dart | 97 +- .../chart/chart_segment/spline_segment.dart | 354 +- .../chart_segment/stacked_area_segment.dart | 71 +- .../chart_segment/stacked_bar_segment.dart | 131 +- .../chart_segment/stacked_column_segment.dart | 131 +- .../chart_segment/stacked_line_segment.dart | 156 +- .../chart_segment/stackedarea100_segment.dart | 71 +- .../chart_segment/stackedbar100_segment.dart | 89 +- .../stackedcolumn100_segment.dart | 91 +- .../chart_segment/stackedline100_segment.dart | 154 +- .../chart_segment/step_area_segment.dart | 72 +- .../chart/chart_segment/stepline_segment.dart | 371 +- .../chart_segment/waterfall_segment.dart | 154 +- .../src/chart/chart_series/area_series.dart | 114 +- .../src/chart/chart_series/bar_series.dart | 153 +- .../chart_series/box_and_whisker_series.dart | 275 +- .../src/chart/chart_series/bubble_series.dart | 130 +- .../src/chart/chart_series/candle_series.dart | 152 +- .../src/chart/chart_series/column_series.dart | 162 +- .../chart/chart_series/error_bar_series.dart | 444 + .../chart/chart_series/fastline_series.dart | 103 +- .../chart_series/financial_series_base.dart | 50 +- .../src/chart/chart_series/hilo_series.dart | 128 +- .../chart_series/hiloopenclose_series.dart | 128 +- .../chart/chart_series/histogram_series.dart | 243 +- .../src/chart/chart_series/line_series.dart | 120 +- .../chart/chart_series/range_area_series.dart | 105 +- .../chart_series/range_column_series.dart | 166 +- .../chart/chart_series/scatter_series.dart | 161 +- .../lib/src/chart/chart_series/series.dart | 614 +- .../series_renderer_properties.dart | 661 + .../chart_series/spline_area_series.dart | 101 +- .../spline_range_area_series.dart | 102 +- .../src/chart/chart_series/spline_series.dart | 121 +- .../chart_series/stacked_area_series.dart | 115 +- .../chart_series/stacked_bar_series.dart | 119 +- .../chart_series/stacked_column_series.dart | 116 +- .../chart_series/stacked_line_series.dart | 126 +- .../chart_series/stacked_series_base.dart | 70 +- .../chart_series/stackedarea100_series.dart | 116 +- .../chart_series/stackedbar100_series.dart | 109 +- .../chart_series/stackedcolumn100_series.dart | 117 +- .../chart_series/stackedline100_series.dart | 127 +- .../chart/chart_series/step_area_series.dart | 102 +- .../chart/chart_series/stepline_series.dart | 123 +- .../chart/chart_series/waterfall_series.dart | 147 +- .../chart/chart_series/xy_data_series.dart | 513 +- .../common/cartesian_state_properties.dart | 392 + .../lib/src/chart/common/common.dart | 1753 +- .../lib/src/chart/common/data_label.dart | 73 +- .../src/chart/common/data_label_renderer.dart | 2389 +-- .../src/chart/common/interactive_tooltip.dart | 392 + .../lib/src/chart/common/marker.dart | 164 +- .../lib/src/chart/common/renderer.dart | 1080 +- .../src/chart/common/segment_properties.dart | 166 + .../common/trackball_marker_settings.dart | 163 + .../chart/series_painter/area_painter.dart | 288 +- .../src/chart/series_painter/bar_painter.dart | 282 +- .../box_and_whisker_painter.dart | 379 +- .../chart/series_painter/bubble_painter.dart | 236 +- .../chart/series_painter/candle_painter.dart | 272 +- .../chart/series_painter/column_painter.dart | 289 +- .../series_painter/error_bar_painter.dart | 192 + .../series_painter/fastline_painter.dart | 259 +- .../chart/series_painter/hilo_painter.dart | 243 +- .../series_painter/hiloopenclose_painter.dart | 242 +- .../series_painter/histogram_painter.dart | 345 +- .../chart/series_painter/line_painter.dart | 252 +- .../series_painter/range_area_painter.dart | 340 +- .../series_painter/range_column_painter.dart | 300 +- .../chart/series_painter/scatter_painter.dart | 282 +- .../series_painter/spline_area_painter.dart | 291 +- .../chart/series_painter/spline_painter.dart | 278 +- .../spline_range_area_painter.dart | 353 +- .../series_painter/stacked_area_painter.dart | 524 +- .../series_painter/stacked_bar_painter.dart | 398 +- .../stacked_column_painter.dart | 397 +- .../series_painter/stacked_line_painter.dart | 472 +- .../stackedarea100_painter.dart | 34 - .../series_painter/stackedbar100_painter.dart | 33 - .../stackedcolumn100_painter.dart | 30 - .../stackedline100_painter.dart | 29 - .../series_painter/step_area_painter.dart | 330 +- .../series_painter/stepline_painter.dart | 253 +- .../series_painter/waterfall_painter.dart | 291 +- .../accumulation_distribution_indicator.dart | 70 +- .../technical_indicators/atr_indicator.dart | 99 +- .../bollinger_bands_indicator.dart | 196 +- .../technical_indicators/ema_indicator.dart | 90 +- .../technical_indicators/macd_indicator.dart | 245 +- .../momentum_indicator.dart | 83 +- .../technical_indicators/rsi_indicator.dart | 126 +- .../technical_indicators/sma_indicator.dart | 86 +- .../stochastic_indicator.dart | 196 +- .../technical_indicator.dart | 1215 +- .../technical_indicators/tma_indicator.dart | 118 +- .../lib/src/chart/trendlines/trendlines.dart | 887 +- .../chart/trendlines/trendlines_painter.dart | 234 +- .../src/chart/user_interaction/crosshair.dart | 256 +- .../user_interaction/crosshair_painter.dart | 312 +- .../user_interaction/selection_renderer.dart | 1595 +- .../src/chart/user_interaction/trackball.dart | 1273 +- .../trackball_marker_setting_renderer.dart | 35 + .../user_interaction/trackball_painter.dart | 777 +- .../user_interaction/trackball_template.dart | 333 +- .../user_interaction/zooming_painter.dart | 156 +- .../user_interaction/zooming_panning.dart | 846 +- .../lib/src/chart/utils/enum.dart | 79 +- .../lib/src/chart/utils/helper.dart | 2677 ++- .../circular_chart/base/circular_area.dart | 774 + .../circular_chart/base/circular_base.dart | 1072 +- .../base/circular_state_properties.dart | 105 + .../src/circular_chart/base/series_base.dart | 488 +- .../circular_chart/renderer/chart_point.dart | 235 + .../renderer/circular_chart_annotation.dart | 328 + .../renderer/circular_series.dart | 889 +- .../renderer/circular_series_controller.dart | 239 + .../src/circular_chart/renderer/common.dart | 768 +- .../renderer/data_label_renderer.dart | 881 +- .../renderer/doughnut_series.dart | 143 +- .../circular_chart/renderer/pie_series.dart | 140 +- .../renderer/radial_bar_series.dart | 689 +- .../renderer/renderer_base.dart | 25 + .../renderer/renderer_extension.dart | 1001 + .../doughnut_series_painter.dart | 128 + .../series_painter/pie_chart_painter.dart | 133 + .../series_painter/radial_bar_painter.dart | 255 + .../lib/src/circular_chart/utils/enum.dart | 9 +- .../lib/src/circular_chart/utils/helper.dart | 312 +- .../lib/src/common/common.dart | 168 +- .../lib/src/common/event_args.dart | 133 +- .../lib/src/common/handcursor/web.dart | 10 +- .../lib/src/common/legend/legend.dart | 813 +- .../lib/src/common/legend/renderer.dart | 749 +- .../lib/src/common/rendering_details.dart | 30 +- .../lib/src/common/series/chart_series.dart | 42 +- .../lib/src/common/state_properties.dart | 16 + .../lib/src/common/template/rendering.dart | 235 +- .../common/user_interaction/selection.dart | 272 + .../user_interaction/selection_behavior.dart | 221 +- .../src/common/user_interaction/tooltip.dart | 1592 +- .../tooltip_rendering_details.dart | 1596 ++ .../lib/src/common/utils/enum.dart | 2 - .../lib/src/common/utils/helper.dart | 1200 +- .../lib/src/common/utils/typedef.dart | 25 +- .../src/funnel_chart/base/funnel_base.dart | 899 +- .../funnel_chart/base/funnel_plot_area.dart | 665 + .../base/funnel_state_properties.dart | 85 + .../src/funnel_chart/base/series_base.dart | 292 +- .../renderer/data_label_renderer.dart | 204 +- .../renderer/funnel_chart_painter.dart | 84 + .../funnel_chart/renderer/funnel_series.dart | 890 +- .../renderer/renderer_extension.dart | 70 + .../funnel_chart/renderer/series_base.dart | 722 + .../{series_base.dart => chart_base.dart} | 327 +- .../src/pyramid_chart/base/pyramid_base.dart | 908 +- .../pyramid_chart/base/pyramid_plot_area.dart | 689 + .../base/pyramid_state_properties.dart | 86 + .../renderer/data_label_renderer.dart | 459 +- .../renderer/pyramid_chart_painter.dart | 83 + .../renderer/pyramid_series.dart | 941 +- .../renderer/renderer_extension.dart | 69 + .../pyramid_chart/renderer/series_base.dart | 693 + .../renderer/series_controller.dart | 237 + .../lib/src/pyramid_chart/utils/common.dart | 12 +- .../lib/src/pyramid_chart/utils/helper.dart | 504 +- .../lib/src/sparkline/utils/helper.dart | 4 +- .../syncfusion_flutter_charts/pubspec.yaml | 2 +- .../interactive_scroll_viewer_internal.dart | 7 + .../lib/src/legend/legend.dart | 303 +- .../localizations/global_localizations.dart | 26 +- .../lib/src/theme/calendar_theme.dart | 209 +- .../lib/src/theme/datagrid_theme.dart | 38 + .../lib/src/tooltip/tooltip.dart | 21 +- .../lib/src/utils/helper.dart | 134 +- .../lib/src/utils/shape_helper.dart | 2417 +-- .../widgets/interactive_scroll_viewer.dart | 306 + .../lib/tooltip_internal.dart | 6 +- packages/syncfusion_flutter_core/pubspec.yaml | 2 +- .../syncfusion_flutter_datagrid/CHANGELOG.md | 15 + .../syncfusion_flutter_datagrid/README.md | 25 +- .../example/pubspec.yaml | 1 + .../lib/datagrid.dart | 129 +- .../grid_cell_renderer_base.dart | 22 - .../grid_cell_stacked_header_renderer.dart | 43 - .../grid_cell_text_field_renderer.dart | 56 - .../grid_header_cell_renderer.dart | 47 - .../grid_virtualizing_cell_renderer_base.dart | 51 - .../cell_control/grid_cell_widget.dart | 1026 -- .../cell_control/grid_header_cell_widget.dart | 452 - .../virtualizing_cells_widget.dart | 839 - .../lib/src/control/datagrid_datasource.dart | 893 - .../lib/src/control/generator/data_cell.dart | 31 - .../src/control/generator/data_cell_base.dart | 77 - .../lib/src/control/generator/data_row.dart | 280 - .../src/control/generator/data_row_base.dart | 139 - .../src/control/generator/row_generator.dart | 426 - .../control/generator/spanned_data_row.dart | 163 - .../control/helper/grid_index_resolver.dart | 245 - .../src/control/helper/sfdatagrid_helper.dart | 330 - .../lib/src/control/runtime/column_sizer.dart | 868 - .../lib/src/control/runtime/grid_column.dart | 172 - .../src/control/runtime/stacked_header.dart | 46 - .../scrollable_panel/scrollview_widget.dart | 861 - .../visual_container_helper.dart | 671 - .../visual_container_widget.dart | 367 - .../current_cell_manager.dart | 539 - .../row_selection_manager.dart | 1088 -- .../selected_row_info.dart | 13 - .../selection_helper.dart | 510 - .../selection_manager_base.dart | 183 - .../helper}/callbackargs.dart | 56 +- .../helper/datagrid_configuration.dart | 289 + .../helper/datagrid_helper.dart | 873 + .../helper/enums.dart | 64 +- .../helper/selection_helper.dart | 660 + .../runtime/cell_renderers.dart | 355 + .../src/datagrid_widget/runtime/column.dart | 1728 ++ .../datagrid_widget/runtime/generator.dart | 1396 ++ .../selection/selection_manager.dart | 1923 ++ .../lib/src/datagrid_widget/sfdatagrid.dart | 3817 ++++ .../datagrid_widget/widgets/cell_widget.dart | 972 + .../widgets/rendering_widget.dart | 2079 +++ .../widgets/scrollview_widget.dart | 1994 ++ .../lib/src/datapager/sfdatapager.dart | 424 +- .../grid_common/collections/tree_table.dart | 2295 --- .../collections/tree_table_with_counter.dart | 832 - .../collections/tree_table_with_summary.dart | 482 - ..._collection.dart => distance_counter.dart} | 832 +- .../lib/src/grid_common/enums.dart | 41 + ...nge_changed_event.dart => event_args.dart} | 102 +- ...ze_collection.dart => line_size_host.dart} | 562 +- .../lib/src/grid_common/list_base.dart | 112 - .../src/grid_common/list_generic_base.dart | 16 - .../lib/src/grid_common/math_helper.dart | 43 - .../row_column_index.dart | 7 +- .../lib/src/grid_common/scroll_axis.dart | 2863 +++ .../distance_counter_collection_base.dart | 181 - .../distance_counter_subset.dart | 302 - .../editable_line_size_host_base.dart | 205 - .../scroll_axis_base/line_scroll_axis.dart | 558 - .../scroll_axis_base/line_size_host_base.dart | 122 - .../scroll_axis_base/pixel_scroll_axis.dart | 700 - .../scroll_axis_base/scroll_axis_base.dart | 1580 -- .../scroll_axis_base/scroll_axis_region.dart | 18 - .../scroll_axis_base/scroll_info.dart | 222 - .../scroll_axis_base/scrollbar_base.dart | 90 - .../lib/src/grid_common/scrollbar.dart | 269 + .../lib/src/grid_common/tree_table.dart | 3617 ++++ .../src/grid_common/utility/double_span.dart | 66 - .../src/grid_common/utility/int_32_span.dart | 43 - ...ge_value_list.dart => utility_helper.dart} | 386 +- .../visible_line_info.dart | 98 +- .../lib/src/sfdatagrid.dart | 2179 --- .../CHANGELOG.md | 8 + .../LICENSE | 12 + .../README.md | 201 + .../analysis_options.yaml | 8 + .../example/README.md | 16 + .../example/android/.gitignore | 11 + .../example/android/app/build.gradle | 59 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 41 + .../com/example/example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/android/build.gradle | 29 + .../example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/settings.gradle | 11 + .../example/example.iml} | 0 .../example/ios/.gitignore | 33 + .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../ios/Runner.xcodeproj/project.pbxproj | 471 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../example/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 45 + .../ios/Runner/Runner-Bridging-Header.h | 0 .../example/lib/helper/save_file_mobile.dart | 37 + .../example/lib/helper/save_file_web.dart | 12 + .../example/lib/main.dart | 222 + .../example/linux/.gitignore | 0 .../example/linux/CMakeLists.txt | 0 .../example/linux/flutter/CMakeLists.txt | 87 + .../flutter/generated_plugin_registrant.cc | 0 .../flutter/generated_plugin_registrant.h | 0 .../linux/flutter/generated_plugins.cmake | 0 .../example/linux/main.cc | 0 .../example/linux/my_application.cc | 0 .../example/linux/my_application.h | 0 .../example/macos/.gitignore | 0 .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../Flutter/GeneratedPluginRegistrant.swift | 12 + .../macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../example/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../example/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../example/macos/Runner/Release.entitlements | 0 .../example/pubspec.yaml | 22 + .../example/web/favicon.png | Bin .../example/web/icons/Icon-192.png | Bin .../example/web/icons/Icon-512.png | Bin .../example/web/index.html | 0 .../example/web/manifest.json | 0 .../example/windows/.gitignore | 0 .../example/windows/CMakeLists.txt | 0 .../example/windows/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 0 .../flutter/generated_plugin_registrant.h | 0 .../windows/flutter/generated_plugins.cmake | 0 .../example/windows/runner/CMakeLists.txt | 0 .../example/windows/runner/Runner.rc | 0 .../example/windows/runner/flutter_window.cpp | 0 .../example/windows/runner/flutter_window.h | 0 .../example/windows/runner/main.cpp | 0 .../example/windows/runner/resource.h | 0 .../windows/runner/resources/app_icon.ico | Bin .../example/windows/runner/run_loop.cpp | 0 .../example/windows/runner/run_loop.h | 0 .../windows/runner/runner.exe.manifest | 0 .../example/windows/runner/utils.cpp | 0 .../example/windows/runner/utils.h | 0 .../example/windows/runner/win32_window.cpp | 0 .../example/windows/runner/win32_window.h | 0 .../lib/export.dart | 8 + .../lib/src/enum.dart | 14 + .../lib/src/export_to_excel.dart | 758 + .../lib/src/export_to_pdf.dart | 763 + .../lib/src/helper.dart | 177 + .../pubspec.yaml | 26 + .../CHANGELOG.md | 6 + .../syncfusion_flutter_datepicker/README.md | 10 +- .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + .../lib/src/date_picker/date_picker.dart | 2609 ++- .../src/date_picker/date_picker_manager.dart | 1187 +- .../hijri_date_picker_manager.dart | 818 +- .../lib/src/date_picker/month_view.dart | 334 +- .../lib/src/date_picker/picker_helper.dart | 9 +- .../lib/src/date_picker/year_view.dart | 284 +- .../syncfusion_flutter_gauges/CHANGELOG.md | 20 + packages/syncfusion_flutter_gauges/README.md | 4 +- .../syncfusion_flutter_gauges/lib/gauges.dart | 2 +- .../linear_gauge/axis/linear_axis_label.dart | 2 +- .../axis/linear_axis_renderer.dart | 14 +- .../axis/linear_axis_track_style.dart | 6 +- .../linear_gauge/axis/linear_tick_style.dart | 6 +- .../src/linear_gauge/gauge/linear_gauge.dart | 9 +- .../gauge/linear_gauge_render_widget.dart | 196 +- .../pointers/linear_bar_pointer.dart | 19 +- .../pointers/linear_bar_renderer.dart | 4 +- .../pointers/linear_marker_pointer.dart | 269 +- .../pointers/linear_shape_pointer.dart | 149 +- .../pointers/linear_shape_renderer.dart | 216 +- .../pointers/linear_widget_pointer.dart | 156 +- .../pointers/linear_widget_renderer.dart | 188 +- .../range/linear_gauge_range_renderer.dart | 6 +- .../lib/src/linear_gauge/utils/enum.dart | 22 + .../utils/linear_gauge_helper.dart | 196 +- .../axis/radial_axis_parent_widget.dart | 26 +- .../radial_gauge/axis/radial_axis_widget.dart | 8 +- .../src/radial_gauge/gauge/radial_gauge.dart | 2 +- .../pointers/marker_pointer_renderer.dart | 29 +- .../pointers/range_pointer_renderer.dart | 6 +- .../range/gauge_range_renderer.dart | 17 + .../renderers/radial_axis_renderer.dart | 2 +- packages/syncfusion_flutter_maps/CHANGELOG.md | 17 +- packages/syncfusion_flutter_maps/README.md | 12 +- .../example/pubspec.yaml | 1 - .../syncfusion_flutter_maps/lib/maps.dart | 545 +- .../lib/src/common.dart | 13 +- .../lib/src/controller/layer_controller.dart | 2 +- .../lib/src/controller/map_controller.dart | 16 + .../lib/src/controller/map_provider.dart | 3 +- .../lib/src/elements/bubble.dart | 25 +- .../lib/src/elements/data_label.dart | 5 + .../lib/src/elements/legend.dart | 7 +- .../lib/src/elements/marker.dart | 203 +- .../lib/src/elements/toolbar.dart | 13 +- .../lib/src/elements/tooltip.dart | 11 +- .../syncfusion_flutter_maps/lib/src/enum.dart | 4 +- .../lib/src/layer/layer_base.dart | 14 + .../lib/src/layer/shape_layer.dart | 660 +- .../lib/src/layer/tile_layer.dart | 826 +- .../lib/src/layer/vector_layers.dart | 524 +- .../lib/src/layer/zoomable.dart | 862 + .../lib/src/settings.dart | 76 +- .../lib/src/utils.dart | 65 +- packages/syncfusion_flutter_maps/pubspec.yaml | 2 +- .../CHANGELOG.md | 0 .../LICENSE | 0 .../README.md | 2 +- .../analysis_options.yaml | 0 .../example/README.md | 0 .../example/analysis_options.yaml | 0 .../example/android/.gitignore | 0 .../example/android/app/build.gradle | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../officechart_example/MainActivity.kt | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values/styles.xml | 0 .../app/src/profile/AndroidManifest.xml | 0 .../example/android/build.gradle | 0 .../example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android/officechart_example_android.iml | 0 .../example/android/settings.gradle | 0 .../example/ios/.gitignore | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../ios/Runner.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../example/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 0 .../example/lib/helper/save_file_mobile.dart | 36 + .../example/lib/helper/save_file_web.dart | 15 + .../example/lib/main.dart | 2 +- .../example/linux/.gitignore | 0 .../example/linux/CMakeLists.txt | 0 .../example/linux/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + .../linux/flutter/generated_plugins.cmake | 0 .../example/linux/main.cc | 0 .../example/linux/my_application.cc | 0 .../example/linux/my_application.h | 0 .../example/macos/.gitignore | 0 .../macos/Flutter/Flutter-Debug.xcconfig | 0 .../macos/Flutter/Flutter-Release.xcconfig | 0 .../Flutter/GeneratedPluginRegistrant.swift | 0 .../macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../example/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 0 .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../example/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../example/macos/Runner/Release.entitlements | 0 .../example/officechart_example.iml} | 0 .../example/pubspec.yaml | 0 .../example/web/favicon.png | Bin .../example/web/icons/Icon-192.png | Bin .../example/web/icons/Icon-512.png | Bin .../example/web/index.html | 0 .../example/web/manifest.json | 0 .../example/windows/.gitignore | 0 .../example/windows/CMakeLists.txt | 0 .../example/windows/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + .../windows/flutter/generated_plugins.cmake | 0 .../example/windows/runner/CMakeLists.txt | 0 .../example/windows/runner/Runner.rc | 0 .../example/windows/runner/flutter_window.cpp | 0 .../example/windows/runner/flutter_window.h | 0 .../example/windows/runner/main.cpp | 0 .../example/windows/runner/resource.h | 0 .../windows/runner/resources/app_icon.ico | Bin .../example/windows/runner/run_loop.cpp | 0 .../example/windows/runner/run_loop.h | 0 .../windows/runner/runner.exe.manifest | 0 .../example/windows/runner/utils.cpp | 0 .../example/windows/runner/utils.h | 0 .../example/windows/runner/win32_window.cpp | 0 .../example/windows/runner/win32_window.h | 0 .../lib/officechart.dart | 20 +- .../lib/src/chart/chart_axis.dart | 0 .../lib/src/chart/chart_category_axis.dart | 0 .../lib/src/chart/chart_collection.dart | 0 .../lib/src/chart/chart_datalabel.dart | 0 .../lib/src/chart/chart_enum.dart | 0 .../lib/src/chart/chart_impl.dart | 0 .../lib/src/chart/chart_legend.dart | 0 .../lib/src/chart/chart_plotarea.dart | 0 .../lib/src/chart/chart_serialization.dart | 0 .../lib/src/chart/chart_serie.dart | 0 .../src/chart/chart_series_collection.dart | 0 .../lib/src/chart/chart_text_area.dart | 0 .../lib/src/chart/chart_value_axis.dart | 0 .../pubspec.yaml | 2 +- .../syncfusion_officechart.iml | 0 .../CHANGELOG.md | 0 .../LICENSE | 0 .../README.md | 2 +- .../analysis_options.yaml | 0 .../example/README.md | 0 .../example/analysis_options.yaml | 0 .../example/android/.gitignore | 0 .../example/android/app/build.gradle | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../officecore_example/MainActivity.kt | 0 .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values/styles.xml | 0 .../app/src/profile/AndroidManifest.xml | 0 .../example/android/build.gradle | 0 .../example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../android/officecore_example_android.iml | 0 .../example/android/settings.gradle | 0 .../example/ios/.gitignore | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 91 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 + .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/lib/helper/save_file_mobile.dart | 36 + .../example/lib/helper/save_file_web.dart | 15 + .../example/lib/main.dart | 2 +- .../example/linux/.gitignore | 1 + .../example/linux/CMakeLists.txt | 116 + .../example/linux/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 15 + .../example/linux/main.cc | 6 + .../example/linux/my_application.cc | 105 + .../example/linux/my_application.h | 18 + .../example/macos/.gitignore | 6 + .../macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 0 .../macos/Runner.xcodeproj/project.pbxproj | 572 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 89 + .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 9 + .../AppIcon.appiconset/Contents.json | 68 + .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 46993 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 3276 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 1429 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 5933 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1243 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 14800 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 1874 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 339 + .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + .../example/macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../example/macos/Runner/Release.entitlements | 8 + .../example/officecore_example.iml | 18 + .../example/pubspec.yaml | 2 +- .../example/web/favicon.png | Bin 0 -> 917 bytes .../example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../example/web/index.html | 98 + .../example/web/manifest.json | 23 + .../example/windows/.gitignore | 17 + .../example/windows/CMakeLists.txt | 95 + .../example/windows/flutter/CMakeLists.txt | 103 + .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 15 + .../example/windows/runner/CMakeLists.txt | 18 + .../example/windows/runner/Runner.rc | 121 + .../example/windows/runner/flutter_window.cpp | 64 + .../example/windows/runner/flutter_window.h | 39 + .../example/windows/runner/main.cpp | 42 + .../example/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../example/windows/runner/run_loop.cpp | 66 + .../example/windows/runner/run_loop.h | 40 + .../windows/runner/runner.exe.manifest | 20 + .../example/windows/runner/utils.cpp | 64 + .../example/windows/runner/utils.h | 19 + .../example/windows/runner/win32_window.cpp | 245 + .../example/windows/runner/win32_window.h | 98 + .../lib/officecore.dart | 0 .../lib/src/built_in_properties.dart | 0 .../pubspec.yaml | 0 .../syncfusion_officecore.iml | 0 packages/syncfusion_flutter_pdf/CHANGELOG.md | 22 +- packages/syncfusion_flutter_pdf/README.md | 2 +- .../example/lib/helper/save_file_mobile.dart | 39 - .../example/lib/helper/save_file_web.dart | 18 - .../example/lib/main.dart | 5 +- .../example/lib/save_file_mobile.dart | 35 + .../example/lib/save_file_web.dart | 14 + .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + packages/syncfusion_flutter_pdf/lib/pdf.dart | 5 +- .../annotations/pdf_paintparams.dart | 5 +- .../compression/compressed_stream_reader.dart | 17 +- .../compression/compressed_stream_writer.dart | 151 +- .../decompressor_huffman_tree.dart | 4 +- .../pdf_text_extractor/font_structure.dart | 26 +- .../pdf_text_extractor/image_renderer.dart | 21 +- .../page_resource_loader.dart | 6 +- .../pdf_text_extractor.dart | 57 +- .../pdf_text_extractor/text_element.dart | 76 +- .../forms/pdf_button_field.dart | 16 +- .../pdf/implementation/forms/pdf_field.dart | 14 +- .../forms/pdf_field_painter.dart | 86 +- .../forms/pdf_list_box_field.dart | 19 - .../forms/pdf_list_field_item.dart | 8 +- .../src/pdf/implementation/general/utils.dart | 31 - .../graphics/fonts/pdf_cid_font.dart | 207 +- .../pdf_cjk_font_descryptor_factory.dart | 212 - .../images/decoders/image_decoder.dart | 35 +- .../src/pdf/implementation/io/pdf_reader.dart | 8 +- .../pdf/implementation/pages/pdf_layer.dart | 10 +- .../pdf/implementation/pages/pdf_page.dart | 94 +- .../pages/pdf_page_collection.dart | 2 +- .../implementation/pages/pdf_page_layer.dart | 12 +- .../automatic_fields/pdf_automatic_field.dart | 107 + .../pdf_number_convertor.dart | 110 - .../pdf_page_count_field.dart | 4 +- .../pdf_page_number_field.dart | 4 +- .../pdf_document/outlines/pdf_outline.dart | 24 +- .../implementation/primitives/pdf_name.dart | 24 +- .../security/digital_signature/asn1/asn1.dart | 136 +- .../security/digital_signature/asn1/der.dart | 19 +- .../buffered_block_padding_base.dart | 2 +- .../cryptography/data_ede_algorithm.dart | 2 +- .../cryptography/data_encryption.dart | 10 +- .../cryptography/pdf_cms_signer.dart | 8 +- .../cryptography/rsa_algorithm.dart | 2 +- .../pdf_pkcs_certificate.dart | 4 +- .../pdf_signature_dictionary.dart | 2 +- .../pkcs/password_utility.dart | 4 +- .../grid/layouting/pdf_grid_layouter.dart | 4 +- .../grid/pdf_grid_cell.dart | 9 +- .../lists/bullets/pdf_ordered_marker.dart | 2 +- .../syncfusion_flutter_pdfviewer/CHANGELOG.md | 23 +- .../syncfusion_flutter_pdfviewer/README.md | 36 +- .../lib/generated_plugin_registrant.dart | 15 - .../example/lib/main.dart | 1 + ...wiftSyncfusionFlutterPdfViewerPlugin.swift | 11 +- .../lib/src/bookmark/bookmark_item.dart | 2 + .../lib/src/bookmark/bookmark_toolbar.dart | 71 +- .../lib/src/bookmark/bookmark_view.dart | 16 +- .../lib/src/common/mobile_helper.dart | 7 + .../lib/src/common/pdf_provider.dart | 3 +- .../lib/src/common/pdfviewer_helper.dart | 6 +- .../lib/src/common/pdfviewer_plugin.dart | 4 +- .../lib/src/common/web_helper.dart | 8 + .../lib/src/control/enums.dart | 18 + .../src/control/interactive_scrollable.dart | 152 - .../lib/src/control/pdf_page_view.dart | 615 +- .../lib/src/control/pdf_scrollable.dart | 83 +- .../lib/src/control/pdfviewer_canvas.dart | 1021 +- .../lib/src/control/scroll_head.dart | 138 +- .../lib/src/control/scroll_head_overlay.dart | 373 +- .../lib/src/control/scroll_status.dart | 9 +- .../lib/src/control/single_page_view.dart | 962 + .../lib/src/pdfviewer.dart | 1544 +- .../syncfusion_flutter_pdfviewer/pubspec.yaml | 2 +- .../CHANGELOG.md | 2 +- .../example/pubspec.yaml | 7 +- .../lib/generated_plugin_registrant.dart | 15 - .../example/pubspec.yaml | 7 +- .../syncfusion_flutter_signaturepad/README.md | 143 +- .../example/pubspec.yaml | 1 - .../lib/signaturepad.dart | 57 +- .../syncfusion_flutter_sliders/CHANGELOG.md | 11 + packages/syncfusion_flutter_sliders/README.md | 10 +- .../example/lib/main.dart | 6 +- .../example/pubspec.yaml | 1 - .../lib/src/common.dart | 26 +- .../lib/src/constants.dart | 2 - .../lib/src/range_selector.dart | 80 +- .../lib/src/range_slider.dart | 150 +- .../lib/src/range_slider_base.dart | 374 +- .../lib/src/slider.dart | 199 +- .../lib/src/slider_base.dart | 531 +- .../lib/src/slider_shapes.dart | 432 +- packages/syncfusion_flutter_treemap/README.md | 2 +- .../example/pubspec.yaml | 3 + .../lib/src/controller.dart | 9 +- .../lib/src/layouts.dart | 1323 +- .../lib/src/legend.dart | 9 +- .../lib/treemap.dart | 29 +- .../syncfusion_flutter_treemap/pubspec.yaml | 2 +- .../syncfusion_flutter_xlsio/CHANGELOG.md | 2 +- packages/syncfusion_flutter_xlsio/README.md | 2 +- .../example/lib/helper/save_file_mobile.dart | 61 +- .../example/lib/helper/save_file_web.dart | 17 +- .../example/lib/main.dart | 2 +- .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + .../flutter/generated_plugin_registrant.cc | 2 + .../flutter/generated_plugin_registrant.h | 2 + .../lib/src/xlsio/calculate/calc_engine.dart | 22 +- .../lib/src/xlsio/calculate/formula_info.dart | 32 +- .../src/xlsio/general/autofit_manager.dart | 2 +- .../src/xlsio/general/serialize_workbook.dart | 7 +- .../lib/src/xlsio/hyperlinks/hyperlink.dart | 4 - .../hyperlinks/hyperlink_collection.dart | 1 - .../lib/src/xlsio/range/range.dart | 6 +- .../src/xlsio/security/security_helper.dart | 60 +- .../lib/src/xlsio/worksheet/worksheet.dart | 56 +- .../syncfusion_flutter_xlsio/lib/xlsio.dart | 98 +- .../syncfusion_flutter_xlsio/pubspec.yaml | 4 +- packages/syncfusion_localizations/README.md | 2 +- .../example/pubspec.yaml | 6 +- .../generated_syncfusion_localizations.dart | 2391 +-- .../lib/src/l10n/syncfusion_af.arb | 15 +- .../lib/src/l10n/syncfusion_am.arb | 55 +- .../lib/src/l10n/syncfusion_ar.arb | 3 +- .../lib/src/l10n/syncfusion_az.arb | 39 +- .../lib/src/l10n/syncfusion_be.arb | 31 +- .../lib/src/l10n/syncfusion_bg.arb | 15 +- .../lib/src/l10n/syncfusion_bn.arb | 47 +- .../lib/src/l10n/syncfusion_bs.arb | 29 +- .../lib/src/l10n/syncfusion_ca.arb | 3 +- .../lib/src/l10n/syncfusion_cs.arb | 11 +- .../lib/src/l10n/syncfusion_da.arb | 9 +- .../lib/src/l10n/syncfusion_de.arb | 31 +- .../lib/src/l10n/syncfusion_el.arb | 41 +- .../lib/src/l10n/syncfusion_en.arb | 6 + .../lib/src/l10n/syncfusion_en.json | 1 + .../lib/src/l10n/syncfusion_es.arb | 13 +- .../lib/src/l10n/syncfusion_et.arb | 19 +- .../lib/src/l10n/syncfusion_eu.arb | 3 +- .../lib/src/l10n/syncfusion_fa.arb | 25 +- .../lib/src/l10n/syncfusion_fi.arb | 5 +- .../lib/src/l10n/syncfusion_fil.arb | 1 + .../lib/src/l10n/syncfusion_fr.arb | 25 +- .../lib/src/l10n/syncfusion_gl.arb | 1 + .../lib/src/l10n/syncfusion_gu.arb | 43 +- .../lib/src/l10n/syncfusion_he.arb | 33 +- .../lib/src/l10n/syncfusion_hi.arb | 37 +- .../lib/src/l10n/syncfusion_hr.arb | 27 +- .../lib/src/l10n/syncfusion_hu.arb | 21 +- .../lib/src/l10n/syncfusion_hy.arb | 43 +- .../lib/src/l10n/syncfusion_id.arb | 41 +- .../lib/src/l10n/syncfusion_is.arb | 19 +- .../lib/src/l10n/syncfusion_it.arb | 25 +- .../lib/src/l10n/syncfusion_ja.arb | 21 +- .../lib/src/l10n/syncfusion_ka.arb | 55 +- .../lib/src/l10n/syncfusion_kk.arb | 43 +- .../lib/src/l10n/syncfusion_km.arb | 53 +- .../lib/src/l10n/syncfusion_kn.arb | 37 +- .../lib/src/l10n/syncfusion_ko.arb | 65 +- .../lib/src/l10n/syncfusion_ky.arb | 49 +- .../lib/src/l10n/syncfusion_lo.arb | 53 +- .../lib/src/l10n/syncfusion_lt.arb | 29 +- .../lib/src/l10n/syncfusion_lv.arb | 23 +- .../lib/src/l10n/syncfusion_mk.arb | 33 +- .../lib/src/l10n/syncfusion_ml.arb | 45 +- .../lib/src/l10n/syncfusion_mn.arb | 25 +- .../lib/src/l10n/syncfusion_mr.arb | 29 +- .../lib/src/l10n/syncfusion_ms.arb | 3 +- .../lib/src/l10n/syncfusion_my.arb | 61 +- .../lib/src/l10n/syncfusion_nb.arb | 11 +- .../lib/src/l10n/syncfusion_ne.arb | 55 +- .../lib/src/l10n/syncfusion_nl.arb | 33 +- .../lib/src/l10n/syncfusion_pa.arb | 55 +- .../lib/src/l10n/syncfusion_pl.arb | 35 +- .../lib/src/l10n/syncfusion_ps.arb | 33 +- .../lib/src/l10n/syncfusion_pt.arb | 9 +- .../lib/src/l10n/syncfusion_pt_PT.arb | 3 +- .../lib/src/l10n/syncfusion_ro.arb | 7 +- .../lib/src/l10n/syncfusion_ru.arb | 15 +- .../lib/src/l10n/syncfusion_si.arb | 41 +- .../lib/src/l10n/syncfusion_sk.arb | 11 +- .../lib/src/l10n/syncfusion_sl.arb | 17 +- .../lib/src/l10n/syncfusion_sq.arb | 49 +- .../lib/src/l10n/syncfusion_sr.arb | 25 +- .../lib/src/l10n/syncfusion_sv.arb | 13 +- .../lib/src/l10n/syncfusion_sw.arb | 3 +- .../lib/src/l10n/syncfusion_ta.arb | 39 +- .../lib/src/l10n/syncfusion_te.arb | 35 +- .../lib/src/l10n/syncfusion_th.arb | 53 +- .../lib/src/l10n/syncfusion_tl.arb | 1 + .../lib/src/l10n/syncfusion_tr.arb | 27 +- .../lib/src/l10n/syncfusion_uk.arb | 27 +- .../lib/src/l10n/syncfusion_ur.arb | 71 +- .../lib/src/l10n/syncfusion_uz.arb | 21 +- .../lib/src/l10n/syncfusion_vi.arb | 23 +- .../lib/src/l10n/syncfusion_zh.arb | 61 +- .../lib/src/l10n/syncfusion_zh_HK.arb | 61 +- .../lib/src/l10n/syncfusion_zh_TW.arb | 61 +- .../lib/src/l10n/syncfusion_zu.arb | 1 + .../example/lib/helper/save_file_mobile.dart | 39 - .../example/lib/helper/save_file_web.dart | 18 - .../example/lib/helper/save_file_mobile.dart | 39 - .../example/lib/helper/save_file_web.dart | 18 - 1002 files changed, 107915 insertions(+), 67134 deletions(-) create mode 100644 packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/error_bar_segment.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/chart_series/error_bar_series.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series_renderer_properties.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/common/cartesian_state_properties.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/common/interactive_tooltip.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/common/segment_properties.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/common/trackball_marker_settings.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/series_painter/error_bar_painter.dart delete mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart delete mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart delete mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart delete mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_marker_setting_renderer.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_area.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_state_properties.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/chart_point.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_chart_annotation.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series_controller.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_base.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_extension.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/doughnut_series_painter.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/pie_chart_painter.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/radial_bar_painter.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/common/state_properties.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip_rendering_details.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_plot_area.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_state_properties.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_chart_painter.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/renderer_extension.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/series_base.dart rename packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/{series_base.dart => chart_base.dart} (57%) create mode 100644 packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_plot_area.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_state_properties.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_chart_painter.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/renderer_extension.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_base.dart create mode 100644 packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_controller.dart create mode 100644 packages/syncfusion_flutter_core/lib/interactive_scroll_viewer_internal.dart create mode 100644 packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_cell_widget.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart rename packages/syncfusion_flutter_datagrid/lib/src/{control => datagrid_widget/helper}/callbackargs.dart (87%) create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart rename packages/syncfusion_flutter_datagrid/lib/src/{control => datagrid_widget}/helper/enums.dart (79%) create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart rename packages/syncfusion_flutter_datagrid/lib/src/grid_common/{scroll_axis_base/distance_range_counter_collection.dart => distance_counter.dart} (54%) create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart rename packages/syncfusion_flutter_datagrid/lib/src/grid_common/{scroll_axis_base/range_changed_event.dart => event_args.dart} (70%) rename packages/syncfusion_flutter_datagrid/lib/src/grid_common/{scroll_axis_base/line_size_collection.dart => line_size_host.dart} (55%) delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart rename packages/syncfusion_flutter_datagrid/lib/src/grid_common/{scroll_axis_base => }/row_column_index.dart (86%) create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_region.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart create mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/int_32_span.dart rename packages/syncfusion_flutter_datagrid/lib/src/grid_common/{scroll_axis_base/sorted_range_value_list.dart => utility_helper.dart} (53%) rename packages/syncfusion_flutter_datagrid/lib/src/grid_common/{scroll_axis_base => }/visible_line_info.dart (81%) delete mode 100644 packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/CHANGELOG.md create mode 100644 packages/syncfusion_flutter_datagrid_export/LICENSE create mode 100644 packages/syncfusion_flutter_datagrid_export/README.md create mode 100644 packages/syncfusion_flutter_datagrid_export/analysis_options.yaml create mode 100644 packages/syncfusion_flutter_datagrid_export/example/README.md create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/.gitignore create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/build.gradle create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/drawable-v21/launch_background.xml rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/android/app/src/main/res/drawable/launch_background.xml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values-night/styles.xml create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/build.gradle create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/gradle.properties create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/syncfusion_flutter_datagrid_export/example/android/settings.gradle rename packages/{syncfusion_officechart/example/officechart_example.iml => syncfusion_flutter_datagrid_export/example/example.iml} (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/ios/.gitignore create mode 100644 packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/AppFrameworkInfo.plist rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Flutter/Debug.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Flutter/Release.xcconfig (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/{syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace => syncfusion_flutter_datagrid_export/example/ios/Runner.xcworkspace}/contents.xcworkspacedata (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/AppDelegate.swift (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Base.lproj/Main.storyboard (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Info.plist rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/ios/Runner/Runner-Bridging-Header.h (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/example/lib/main.dart rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/.gitignore (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/CMakeLists.txt (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/linux/flutter/CMakeLists.txt rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/flutter/generated_plugin_registrant.cc (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/flutter/generated_plugin_registrant.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/flutter/generated_plugins.cmake (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/main.cc (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/my_application.cc (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/linux/my_application.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/.gitignore (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Flutter/Flutter-Debug.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Flutter/Flutter-Release.xcconfig (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner.xcodeproj/project.pbxproj (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/{syncfusion_officechart/example/ios => syncfusion_flutter_datagrid_export/example/macos}/Runner.xcworkspace/contents.xcworkspacedata (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/AppDelegate.swift (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Base.lproj/MainMenu.xib (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Configs/AppInfo.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Configs/Debug.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Configs/Release.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Configs/Warnings.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/DebugProfile.entitlements (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Info.plist (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/MainFlutterWindow.swift (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/macos/Runner/Release.entitlements (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/web/favicon.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/web/icons/Icon-192.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/web/icons/Icon-512.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/web/index.html (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/web/manifest.json (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/.gitignore (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/CMakeLists.txt (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/flutter/CMakeLists.txt (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/flutter/generated_plugin_registrant.cc (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/flutter/generated_plugin_registrant.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/flutter/generated_plugins.cmake (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/CMakeLists.txt (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/Runner.rc (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/flutter_window.cpp (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/flutter_window.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/main.cpp (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/resource.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/resources/app_icon.ico (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/run_loop.cpp (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/run_loop.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/runner.exe.manifest (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/utils.cpp (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/utils.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/win32_window.cpp (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_datagrid_export}/example/windows/runner/win32_window.h (100%) create mode 100644 packages/syncfusion_flutter_datagrid_export/lib/export.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/lib/src/enum.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart create mode 100644 packages/syncfusion_flutter_datagrid_export/pubspec.yaml create mode 100644 packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/CHANGELOG.md (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/LICENSE (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/README.md (98%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/analysis_options.yaml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/README.md (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/analysis_options.yaml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/.gitignore (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/app/build.gradle (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/app/src/debug/AndroidManifest.xml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/app/src/main/AndroidManifest.xml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/android/app/src/main/res/drawable/launch_background.xml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/app/src/main/res/values/styles.xml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/app/src/profile/AndroidManifest.xml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/build.gradle (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/gradle.properties (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/officechart_example_android.iml (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/android/settings.gradle (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/ios/.gitignore (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/ios/Flutter/AppFrameworkInfo.plist (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Flutter/Debug.xcconfig (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Flutter/Release.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/ios/Runner.xcodeproj/project.pbxproj (100%) rename packages/{syncfusion_officechart/example/macos/Runner.xcworkspace => syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace}/contents.xcworkspacedata (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/{syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace => syncfusion_flutter_officechart/example/ios/Runner.xcworkspace}/contents.xcworkspacedata (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/AppDelegate.swift (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/ios/Runner/Info.plist (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/ios/Runner/Runner-Bridging-Header.h (100%) create mode 100644 packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart create mode 100644 packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/lib/main.dart (98%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/.gitignore (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/CMakeLists.txt (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/linux/flutter/CMakeLists.txt (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/flutter/generated_plugin_registrant.cc (86%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/flutter/generated_plugin_registrant.h (93%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/flutter/generated_plugins.cmake (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/main.cc (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/my_application.cc (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/linux/my_application.h (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/.gitignore (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Flutter/Flutter-Debug.xcconfig (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Flutter/Flutter-Release.xcconfig (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/macos/Flutter/GeneratedPluginRegistrant.swift (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner.xcodeproj/project.pbxproj (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename packages/{syncfusion_officecore/example/ios => syncfusion_flutter_officechart/example/macos}/Runner.xcworkspace/contents.xcworkspacedata (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/AppDelegate.swift (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Base.lproj/MainMenu.xib (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Configs/AppInfo.xcconfig (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Configs/Debug.xcconfig (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Configs/Release.xcconfig (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Configs/Warnings.xcconfig (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/DebugProfile.entitlements (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Info.plist (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/MainFlutterWindow.swift (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/macos/Runner/Release.entitlements (100%) rename packages/{syncfusion_officecore/example/officecore_example.iml => syncfusion_flutter_officechart/example/officechart_example.iml} (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/example/pubspec.yaml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/web/favicon.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/web/icons/Icon-192.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/web/icons/Icon-512.png (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/web/index.html (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/web/manifest.json (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/.gitignore (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/CMakeLists.txt (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/flutter/CMakeLists.txt (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/flutter/generated_plugin_registrant.cc (87%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/flutter/generated_plugin_registrant.h (93%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/flutter/generated_plugins.cmake (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/CMakeLists.txt (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/Runner.rc (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/flutter_window.cpp (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/flutter_window.h (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/main.cpp (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/resource.h (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/resources/app_icon.ico (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/run_loop.cpp (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/run_loop.h (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/runner.exe.manifest (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/utils.cpp (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/utils.h (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/win32_window.cpp (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officechart}/example/windows/runner/win32_window.h (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/officechart.dart (95%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_axis.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_category_axis.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_collection.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_datalabel.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_enum.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_impl.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_legend.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_plotarea.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_serialization.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_serie.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_series_collection.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_text_area.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/lib/src/chart/chart_value_axis.dart (100%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/pubspec.yaml (91%) rename packages/{syncfusion_officechart => syncfusion_flutter_officechart}/syncfusion_officechart.iml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/CHANGELOG.md (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/LICENSE (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/README.md (92%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/analysis_options.yaml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/README.md (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/analysis_options.yaml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/.gitignore (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/app/build.gradle (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/app/src/debug/AndroidManifest.xml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/app/src/main/AndroidManifest.xml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt (100%) create mode 100644 packages/syncfusion_flutter_officecore/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/app/src/main/res/values/styles.xml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/app/src/profile/AndroidManifest.xml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/build.gradle (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/gradle.properties (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/officecore_example_android.iml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/android/settings.gradle (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/ios/.gitignore (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/ios/Flutter/AppFrameworkInfo.plist (100%) create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Flutter/Release.xcconfig rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/ios/Runner.xcodeproj/project.pbxproj (100%) rename packages/{syncfusion_officecore/example/macos/Runner.xcworkspace => syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace}/contents.xcworkspacedata (100%) create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/AppDelegate.swift create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/Main.storyboard rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/ios/Runner/Info.plist (100%) create mode 100644 packages/syncfusion_flutter_officecore/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart create mode 100644 packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/lib/main.dart (99%) create mode 100644 packages/syncfusion_flutter_officecore/example/linux/.gitignore create mode 100644 packages/syncfusion_flutter_officecore/example/linux/CMakeLists.txt rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/linux/flutter/CMakeLists.txt (100%) create mode 100644 packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.cc create mode 100644 packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.h create mode 100644 packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugins.cmake create mode 100644 packages/syncfusion_flutter_officecore/example/linux/main.cc create mode 100644 packages/syncfusion_flutter_officecore/example/linux/my_application.cc create mode 100644 packages/syncfusion_flutter_officecore/example/linux/my_application.h create mode 100644 packages/syncfusion_flutter_officecore/example/macos/.gitignore create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Release.xcconfig rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/macos/Flutter/GeneratedPluginRegistrant.swift (100%) create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.pbxproj create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/AppDelegate.swift create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Debug.xcconfig create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Release.xcconfig create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Warnings.xcconfig create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/DebugProfile.entitlements create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Info.plist create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/MainFlutterWindow.swift create mode 100644 packages/syncfusion_flutter_officecore/example/macos/Runner/Release.entitlements create mode 100644 packages/syncfusion_flutter_officecore/example/officecore_example.iml rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/example/pubspec.yaml (89%) create mode 100644 packages/syncfusion_flutter_officecore/example/web/favicon.png create mode 100644 packages/syncfusion_flutter_officecore/example/web/icons/Icon-192.png create mode 100644 packages/syncfusion_flutter_officecore/example/web/icons/Icon-512.png create mode 100644 packages/syncfusion_flutter_officecore/example/web/index.html create mode 100644 packages/syncfusion_flutter_officecore/example/web/manifest.json create mode 100644 packages/syncfusion_flutter_officecore/example/windows/.gitignore create mode 100644 packages/syncfusion_flutter_officecore/example/windows/CMakeLists.txt create mode 100644 packages/syncfusion_flutter_officecore/example/windows/flutter/CMakeLists.txt create mode 100644 packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.cc create mode 100644 packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.h create mode 100644 packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugins.cmake create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/CMakeLists.txt create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/Runner.rc create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.cpp create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.h create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/main.cpp create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/resource.h create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/resources/app_icon.ico create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.cpp create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.h create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/runner.exe.manifest create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/utils.cpp create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/utils.h create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.cpp create mode 100644 packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.h rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/lib/officecore.dart (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/lib/src/built_in_properties.dart (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/pubspec.yaml (100%) rename packages/{syncfusion_officecore => syncfusion_flutter_officecore}/syncfusion_officecore.iml (100%) delete mode 100644 packages/syncfusion_flutter_pdf/example/lib/helper/save_file_mobile.dart delete mode 100644 packages/syncfusion_flutter_pdf/example/lib/helper/save_file_web.dart create mode 100644 packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart create mode 100644 packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart delete mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/utils.dart delete mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart delete mode 100644 packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart delete mode 100644 packages/syncfusion_flutter_pdfviewer/example/lib/generated_plugin_registrant.dart delete mode 100644 packages/syncfusion_flutter_pdfviewer/lib/src/control/interactive_scrollable.dart create mode 100644 packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart delete mode 100644 packages/syncfusion_flutter_pdfviewer_web/example/lib/generated_plugin_registrant.dart delete mode 100644 packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart delete mode 100644 packages/syncfusion_officechart/example/lib/helper/save_file_web.dart delete mode 100644 packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart delete mode 100644 packages/syncfusion_officecore/example/lib/helper/save_file_web.dart diff --git a/README.md b/README.md index 85eca5f50..1b82004d0 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,7 @@ Also, you can view the samples code from [this repository](https://github.com/sy | [syncfusion_flutter_xlsio](./packages/syncfusion_flutter_xlsio/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_xlsio.svg)](https://pub.dev/packages/syncfusion_flutter_xlsio) | [![pub points](https://badges.bar/syncfusion_flutter_xlsio/pub%20points)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | [![popularity](https://badges.bar/syncfusion_flutter_xlsio/popularity)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | [![likes](https://badges.bar/syncfusion_flutter_xlsio/likes)](https://pub.dev/packages/syncfusion_flutter_xlsio/score) | | [syncfusion_flutter_datepicker](./packages/syncfusion_flutter_datepicker/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_datepicker.svg)](https://pub.dev/packages/syncfusion_flutter_datepicker) | [![pub points](https://badges.bar/syncfusion_flutter_datepicker/pub%20points)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | [![popularity](https://badges.bar/syncfusion_flutter_datepicker/popularity)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | [![likes](https://badges.bar/syncfusion_flutter_datepicker/likes)](https://pub.dev/packages/syncfusion_flutter_datepicker/score) | | [syncfusion_flutter_maps](./packages/syncfusion_flutter_maps/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_maps.svg)](https://pub.dev/packages/syncfusion_flutter_maps) | [![pub points](https://badges.bar/syncfusion_flutter_maps/pub%20points)](https://pub.dev/packages/syncfusion_flutter_maps/score) | [![popularity](https://badges.bar/syncfusion_flutter_maps/popularity)](https://pub.dev/packages/syncfusion_flutter_maps/score) | [![likes](https://badges.bar/syncfusion_flutter_maps/likes)](https://pub.dev/packages/syncfusion_flutter_maps/score) | -| [syncfusion_flutter_treemap](./packages/syncfusion_flutter_treemap/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_treemap.svg)](https://pub.dev/packages/syncfusion_flutter_treemap) | [![pub points](https://badges.bar/syncfusion_flutter_treemap/pub%20points)](https://pub.dev/packages/syncfusion_flutter_treemap/score) | [![popularity](https://badges.bar/syncfusion_flutter_treemap/popularity)](https://pub.dev/packages/syncfusion_flutter_treemap/score) | [![likes](https://badges.bar/syncfusion_flutter_treemap/likes)](https://pub.dev/packages/syncfusion_flutter_treemap/score) | -| [syncfusion_flutter_gauges](./packages/syncfusion_flutter_gauges/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_gauges.svg)](https://pub.dev/packages/syncfusion_flutter_gauges) | [![pub points](https://badges.bar/syncfusion_flutter_gauges/pub%20points)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![popularity](https://badges.bar/syncfusion_flutter_gauges/popularity)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![likes](https://badges.bar/syncfusion_flutter_gauges/likes)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | +| [syncfusion_flutter_gauges](./packages/syncfusion_flutter_gauges/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_gauges.svg)](https://pub.dev/packages/syncfusion_flutter_gauges) | [![pub points](https://badges.bar/syncfusion_flutter_gauges/pub%20points)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![popularity](https://badges.bar/syncfusion_flutter_gauges/popularity)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | [![likes](https://badges.bar/syncfusion_flutter_gauges/likes)](https://pub.dev/packages/syncfusion_flutter_gauges/score) | | [syncfusion_flutter_sliders](./packages/syncfusion_flutter_sliders/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_sliders.svg)](https://pub.dev/packages/syncfusion_flutter_sliders) | [![pub points](https://badges.bar/syncfusion_flutter_sliders/pub%20points)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | [![popularity](https://badges.bar/syncfusion_flutter_sliders/popularity)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | [![likes](https://badges.bar/syncfusion_flutter_sliders/likes)](https://pub.dev/packages/syncfusion_flutter_sliders/score) | | [syncfusion_flutter_signaturepad](./packages/syncfusion_flutter_signaturepad/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_signaturepad.svg)](https://pub.dev/packages/syncfusion_flutter_signaturepad) | [![pub points](https://badges.bar/syncfusion_flutter_signaturepad/pub%20points)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | [![popularity](https://badges.bar/syncfusion_flutter_signaturepad/popularity)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | [![likes](https://badges.bar/syncfusion_flutter_signaturepad/likes)](https://pub.dev/packages/syncfusion_flutter_signaturepad/score) | | [syncfusion_flutter_barcodes](./packages/syncfusion_flutter_barcodes/) | | [![pub package](https://img.shields.io/pub/v/syncfusion_flutter_barcodes.svg)](https://pub.dev/packages/syncfusion_flutter_barcodes) | [![pub points](https://badges.bar/syncfusion_flutter_barcodes/pub%20points)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | [![popularity](https://badges.bar/syncfusion_flutter_barcodes/popularity)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | [![likes](https://badges.bar/syncfusion_flutter_barcodes/likes)](https://pub.dev/packages/syncfusion_flutter_barcodes/score) | @@ -116,17 +115,12 @@ Run your application either using `F5` or `Run > Start Debugging`. Explore the full capabilities of our Flutter widgets on your device by installing our sample browser applications from the below app stores, and view samples code in GitHub.

- - - -

-

- - - -

-

- + + +

+

+ +

## Useful links diff --git a/packages/syncfusion_flutter_barcodes/README.md b/packages/syncfusion_flutter_barcodes/README.md index 141e83ed8..a8166ff66 100644 --- a/packages/syncfusion_flutter_barcodes/README.md +++ b/packages/syncfusion_flutter_barcodes/README.md @@ -134,4 +134,4 @@ The following screenshot illustrates the result of the previous code sample. 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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 +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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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_barcodes/lib/barcodes.dart b/packages/syncfusion_flutter_barcodes/lib/barcodes.dart index 4da70cc7e..c60b884ac 100644 --- a/packages/syncfusion_flutter_barcodes/lib/barcodes.dart +++ b/packages/syncfusion_flutter_barcodes/lib/barcodes.dart @@ -5,6 +5,8 @@ /// /// To use, import `package:syncfusion_flutter_barcodes/barcodes.dart`. /// +/// {@youtube 560 315 https://www.youtube.com/watch?v=ckAHrT2CR8A} +/// /// See also: /// * [Syncfusion Flutter Barcodes product page](https://www.syncfusion.com/flutter-widgets/flutter-barcodes) /// * [User guide documentation](https://help.syncfusion.com/flutter/barcode/getting-started) diff --git a/packages/syncfusion_flutter_calendar/CHANGELOG.md b/packages/syncfusion_flutter_calendar/CHANGELOG.md index 733821cd8..5f3043455 100644 --- a/packages/syncfusion_flutter_calendar/CHANGELOG.md +++ b/packages/syncfusion_flutter_calendar/CHANGELOG.md @@ -1,4 +1,22 @@ -## [19.2.44] +## Unreleased + +**Features** +* Provided resize, drag-and-drop support to reschedule appointments in the event calendar. + +**Breaking changes** +* The default `cellEndPadding` value has been changed in the SfCalendar. + +**Enhancements** +* Now the calendar will return the recurrence appointment details in the given custom data type instead of `Appointment` type by overriding the `convertAppointmentToObject` method of the `CalendarDataSource`. + +## [19.2.55] - 08/11/2021 +**Bug fixes** +* Now, the appointment will not intersect when the end time and start time of different appointment is 24 hours for two consecutive days. + +**Features** +* Provided support to customize the background color of the all-day panel. + +## [19.2.44] - 06/30/2021 **Features** * Provided support to display week numbers of the year. * Provided ID, recurrence ID, and appointment type support. diff --git a/packages/syncfusion_flutter_calendar/README.md b/packages/syncfusion_flutter_calendar/README.md index 88b2acf3c..acaa1e9ee 100644 --- a/packages/syncfusion_flutter_calendar/README.md +++ b/packages/syncfusion_flutter_calendar/README.md @@ -85,6 +85,10 @@ The Flutter Calendar widget has built-in configurable views such as day, week, w ![week_numbers](https://cdn.syncfusion.com/content/images/FTControl/Calendar/calendar-weeknumber.png) +* **Resize, drag and drop** - Resize and drag-and-drop support have been added for rescheduling appointments in the event calendar. + +![drag_drop_resize](https://cdn.syncfusion.com/content/images/FTControl/Flutter/calendar-resize-drag-drop.gif) + * **Quick view navigation** - Navigate among calendar views easily using the header date picker views button in the calendar header and clicking month cell and view headers. ![quick_view_navigation](https://cdn.syncfusion.com/content/images/FTControl/Calendar/flutter-calendar-quickview-navigation.png) @@ -316,4 +320,4 @@ class Meeting { 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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_flutter_calendar/lib/calendar.dart b/packages/syncfusion_flutter_calendar/lib/calendar.dart index c63c1a418..af2aceb50 100644 --- a/packages/syncfusion_flutter_calendar/lib/calendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/calendar.dart @@ -16,6 +16,7 @@ export 'src/calendar/common/calendar_controller.dart'; export 'src/calendar/common/enums.dart'; export 'src/calendar/common/event_args.dart'; export 'src/calendar/resource_view/calendar_resource.dart'; +export 'src/calendar/settings/drag_and_drop_settings.dart'; export 'src/calendar/settings/header_style.dart'; export 'src/calendar/settings/month_view_settings.dart'; export 'src/calendar/settings/resource_view_settings.dart'; @@ -24,5 +25,4 @@ export 'src/calendar/settings/time_region.dart'; export 'src/calendar/settings/time_slot_view_settings.dart'; export 'src/calendar/settings/view_header_style.dart'; export 'src/calendar/settings/week_number_style.dart'; - export 'src/calendar/sfcalendar.dart'; 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 4d6d3ff53..b7e07aa3e 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 @@ -14,6 +14,25 @@ import '../common/enums.dart'; /// _Note:_ The [startTime] and [endTime] properties must not be null to render /// an appointment. /// +/// See also: +/// * [CalendarDataSource], to set and handle the appointment collection to the +/// calendar. +/// * [SfCalendar.appointmentBuilder], to set custom widget for the appointment +/// view in the calendar +/// * [SfCalendar.loadMoreWidgetBuilder], the widget which will be displayed +/// when the appointments loading on the view in calendar. +/// * [SfCalendar.appointmentTextStyle], to customize the appointment text, when +/// the builder not added. +/// * [SfCalendar.appointmentTimeTextFormat], to customize the time text format +/// in the appointment view of calendar. +/// * Knowledge base: [How to customize appointment using builder](https://www.syncfusion.com/kb/12191/how-to-customize-the-appointments-using-custom-builder-in-the-flutter-calendar) +/// * Knowledge base: [How to load appointments on demand](https://www.syncfusion.com/kb/12658/how-to-load-appointments-on-demand-in-flutter-calendar) +/// * Knowledge base: [How to style appointments](https://www.syncfusion.com/kb/12162/how-to-style-the-appointment-in-the-flutter-calendar) +/// * Knowledge base: [How to format appointment time](https://www.syncfusion.com/kb/11989/how-to-format-the-appointment-time-in-the-flutter-calendar) +/// * Knowledge base: [How to create time table](https://www.syncfusion.com/kb/12392/how-to-create-time-table-using-flutter-event-calendar) +/// * Knowledge base: [How to add a custom appointments of business objects](https://www.syncfusion.com/kb/11529/how-to-add-a-custom-appointments-or-objects-in-the-flutter-calendar) +/// * Knowledge base: [How to delete an appointment](https://www.syncfusion.com/kb/11522/how-to-delete-an-appointment-in-the-flutter-calendar) +/// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -77,6 +96,17 @@ class Appointment with Diagnosticable { /// /// Defaults to `DateTime.now()`. /// + /// See also: + /// * [CalendarDataSource.getStartTime], which maps the custom business + /// objects corresponding property to this property. + /// * [isAllDay], which defines whether the event is all-day long or not + /// * [endTime], the date time value in which the appointment will end + /// * [startTimeZone], the timezone for the start time, the appointment will + /// render by converting the start time based on the [startTimeZone], and + /// [SfCalendar.timeZone]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -124,6 +154,17 @@ class Appointment with Diagnosticable { /// hour the appointment will be rendered on the all day panel of the time /// slot views in [SfCalendar]. /// + /// See also: + /// * [CalendarDataSource.getEndTime], which maps the custom business + /// objects corresponding property to this property. + /// * [isAllDay], which defines whether the event is all-day long or not + /// * [startTime], the date time value in which the appointment will start. + /// * [endTimeZone], the timezone for the end time, the appointment will + /// render by converting the end time based on the [endTimeZone], and + /// [SfCalendar.timeZone]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -171,6 +212,20 @@ class Appointment with Diagnosticable { /// /// Defaults to `false`. /// + /// See also: + /// * [CalendarDataSource.isAllDay], which maps the custom business + /// objects corresponding property to this property. + /// * [startTime], the date time value in which the appointment will start. + /// * [endTime], the date time value in which the appointment will end + /// * [startTimeZone], the timezone for the start time, the appointment will + /// render by converting the start time based on the [startTimeZone], and + /// [SfCalendar.timeZone]. + /// * [endTimeZone], the timezone for the start time, the appointment will + /// render by converting the end time based on the [endTimeZone], and + /// [SfCalendar.timeZone]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -210,6 +265,13 @@ class Appointment with Diagnosticable { /// /// Defaults to ` ` represents empty string. /// + /// See also: + /// * [CalendarDataSource.getSubject], which maps the custom business + /// objects corresponding property to this property. + /// * [SfCalendar.appointmentTextStyle], to customize the appointment text, + /// when the builder not added. + /// * Knowledge base: [How to style appointments](https://www.syncfusion.com/kb/12162/how-to-style-the-appointment-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -250,6 +312,15 @@ class Appointment with Diagnosticable { /// /// Defaults to `Colors.lightBlue`. /// + /// See also: + /// * [CalendarDataSource.getColor], which maps the custom business + /// objects corresponding property to this property. + /// * [SfCalendar.appointmentTextStyle], to customize the appointment text, + /// when the builder not added. + /// * [SfCalendar.appointmentBuilder], to set custom widget for the + /// appointment view in the calendar + /// * Knowledge base: [How to style appointments](https://www.syncfusion.com/kb/12162/how-to-style-the-appointment-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -292,6 +363,13 @@ class Appointment with Diagnosticable { /// /// Defaults to null. /// + /// See also: + /// * [CalendarDataSource.getStartTimeZone], which maps the custom business + /// objects corresponding property to this property. + /// * [startTime], the date time value in which the appointment will start. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -338,6 +416,16 @@ class Appointment with Diagnosticable { /// zone's and it's value falls invalid, the appointment will render with half /// an hour duration from the start time of the appointment. /// + /// See also: + /// * [CalendarDataSource.getEndTimeZone], which maps the custom business + /// objects corresponding property to this property. + /// * [endTime], the date time value in which the appointment will end. + /// * [endTimeZone], the timezone for the end time, the appointment will + /// render by converting the end time based on the [endTimeZone], and + /// [SfCalendar.timeZone]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -389,6 +477,20 @@ class Appointment with Diagnosticable { /// /// Defaults to null. /// + /// See also: + /// * [CalendarDataSource.getRecurrenceRule], which maps the custom business + /// objects corresponding property to this property. + /// * [RecurrenceProperties], which used to create the recurrence rule based + /// on the values set to these properties. + /// * [SfCalendar.generateRRule], which used to generate recurrence rule + /// based on the [RecurrenceProperties] values. + /// * [SfCalendar.getRecurrenceDateTimeCollection], to get the recurrence date + /// time collection based on the given recurrence rule and start date. + /// * Knowledge base: [How to use a negative value for bysetpos in rrule](https://www.syncfusion.com/kb/12552/how-to-use-a-negative-value-for-bysetpos-in-a-rrule-of-recurrence-appointment-in-the) + /// * Knowledge base: [How to get the recurrence date collection](https://www.syncfusion.com/kb/12344/how-to-get-the-recurrence-date-collection-in-the-flutter-calendar) + /// * Knowledge base: [How to add recurring appointments until specified date](https://www.syncfusion.com/kb/12158/how-to-add-recurring-appointments-until-the-specified-date-in-the-flutter-calendar) + /// + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -441,6 +543,17 @@ class Appointment with Diagnosticable { /// /// Defaults to `[]`. /// + /// See also: + /// * [CalendarDataSource.getRecurrenceExceptionDates], which maps the custom + /// business objects corresponding property to this property. + /// * [recurrenceRule], which used to generate the recurrence appointment + /// based on the rule set. + /// * [RecurrenceProperties], which used to create the recurrence rule based + /// on the values set to these properties. + /// * [SfCalendar.generateRRule], which used to generate recurrence rule + /// based on the [RecurrenceProperties] values. + /// * Knowledge base: [How to exclude the dates from the recurrence appointments](https://www.syncfusion.com/kb/12161/how-to-exclude-the-dates-from-recurrence-appointments-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -497,6 +610,12 @@ class Appointment with Diagnosticable { /// /// Defaults to null. /// + /// See also: + /// * [CalendarDataSource.getNotes], which maps the custom business objects + /// corresponding property to this property. + /// * [location], which used to store the location data of the appointment in + /// the calendar. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -553,6 +672,12 @@ class Appointment with Diagnosticable { /// /// Defaults to null. /// + /// See also: + /// * [CalendarDataSource.getLocation], which maps the custom business objects + /// corresponding property to this property. + /// * [notes], which used to store some additional data or information about + /// the appointment in the calendar. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -608,12 +733,18 @@ class Appointment with Diagnosticable { /// resource in calendar view. /// /// See also: - /// - /// * [CalendarResource], the resource data for calendar. + /// * [CalendarDataSource.getResourceIds], which maps the custom business + /// objects corresponding property to this property. + /// * [CalendarResource], object which contains the resource data. /// * [ResourceViewSettings], the settings have properties which allow to /// customize the resource view of the [SfCalendar]. /// * [CalendarResource.id], the unique id for the [CalendarResource] view of /// [SfCalendar]. + /// * [CalendarDataSource], which used to set the resources collection to the + /// calendar. + /// * Knowledge base: [How to add appointment for the selected resource using appointment editor](https://www.syncfusion.com/kb/12109/how-to-add-appointment-for-the-selected-resources-using-appointment-editor-in-the-flutter) + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) + /// * Knowledge base: [How to handle appointments for muliple resources](https://www.syncfusion.com/kb/11812/how-to-handle-appointments-for-multiple-resources-in-the-flutter-calendar) /// /// ``` dart /// @@ -678,6 +809,14 @@ class Appointment with Diagnosticable { /// Exception recurrence appointment should not have a RRule. If the exception /// appointment has RRule, it will not be considered. /// + /// See also: + /// * [CalendarDataSource.getRecurrenceId], which maps the custom business + /// objects corresponding property to this property. + /// * [id], which used to set unique identification number for the + /// [Appointment] object. + /// * [appointmentType], which used to identify the different appointment + /// types. + /// /// ```dart ///AppointmentDataSource _getDataSource() { /// List appointments = []; @@ -721,7 +860,17 @@ class Appointment with Diagnosticable { /// The exception appointment should have the different [id] value compared /// to the pattern appointment. But the [recurrenceId] of the exception /// appointment and the [id] value of the pattern appointment should be same. + /// + /// See also: + /// * [CalendarDataSource.id], which maps the custom business objects + /// corresponding property to this property. + /// * [recurrenceId], which used to create an exception/changed occurrence + /// appointment. + /// * [appointmentType], which used to identify the different appointment + /// types. + /// /// ```dart + /// /// AppointmentDataSource _getDataSource() { /// List appointments = []; /// @@ -754,10 +903,14 @@ class Appointment with Diagnosticable { ///Specifies the appointment type, which is used to distinguish appointments /// based on their functionality. + /// /// This is read-only. + /// /// Defines the appointment type for the [Appointment]. /// - /// Also refer: [AppointmentType]. + /// Also refer: + /// [AppointmentType], to know more about the available appointment types in + /// calendar. /// /// ``` dart ///Widget build(BuildContext context) { diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart index 94cd9a1af..c9a03ff23 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/appointment_helper.dart @@ -12,6 +12,7 @@ import 'appointment.dart'; import 'calendar_datasource.dart'; import 'recurrence_helper.dart'; +// ignore: avoid_classes_with_only_static_members /// Holds the static helper methods used for appointment rendering in calendar. class AppointmentHelper { /// Return the date with start time value for the date value. @@ -34,6 +35,17 @@ class AppointmentHelper { return DateTimeHelper.getDateTimeValue(addDays(getNextMonthDate(date), -1)); } + /// Return the difference duration between two date time values without + /// daylight savings. + static Duration getDifference(DateTime startDate, DateTime endDate) { + final Duration duration = endDate.difference(startDate); + if (startDate.timeZoneOffset == endDate.timeZoneOffset) { + return duration; + } + + return duration + (endDate.timeZoneOffset - startDate.timeZoneOffset); + } + /// Return the date time value by adding the days in date. static DateTime addDaysWithTime( DateTime date, int days, int hour, int minute, int second) { @@ -60,8 +72,7 @@ class AppointmentHelper { appointment.actualStartTime.month && appointment.actualEndTime.year == appointment.actualStartTime.year) && - appointment.actualEndTime - .difference(appointment.actualStartTime) + getDifference(appointment.actualStartTime, appointment.actualEndTime) .inDays > 0; } @@ -95,8 +106,9 @@ class AppointmentHelper { case CalendarView.week: case CalendarView.workWeek: { - return appEndTime.difference(appStartTime).inDays <= 0 && - appStartTime.day != appEndTime.day; + return getDifference(appStartTime, appEndTime).inDays <= 0 && + appStartTime.day != appEndTime.day && + appEndTime.hour != 0; } case CalendarView.month: { @@ -109,10 +121,10 @@ class AppointmentHelper { if (appStartTime.isAfter(viewStartDate)) { final int appointmentStartWeek = - appStartTime.difference(viewStartDate).inDays ~/ + getDifference(viewStartDate, appStartTime).inDays ~/ DateTime.daysPerWeek; final int appointmentEndWeek = - appEndTime.difference(viewStartDate).inDays ~/ + getDifference(viewStartDate, appEndTime).inDays ~/ DateTime.daysPerWeek; return appointmentStartWeek != appointmentEndWeek; } @@ -124,9 +136,9 @@ class AppointmentHelper { /// Returns recurrence icon details for appointment view. static TextSpan getRecurrenceIcon( - Color color, double textSize, bool isRecurrenceApointment) { + Color color, double textSize, bool isRecurrenceAppointment) { final IconData recurrenceIconData = - isRecurrenceApointment ? Icons.autorenew : Icons.sync_disabled; + isRecurrenceAppointment ? Icons.autorenew : Icons.sync_disabled; return TextSpan( text: String.fromCharCode(recurrenceIconData.codePoint), style: TextStyle( @@ -161,13 +173,13 @@ class AppointmentHelper { DateTime date, SfLocalizations localization) { final DateTime exactStartTime = convertToStartTime(appointment.exactStartTime); - final String totalDays = (convertToEndTime(appointment.exactEndTime) - .difference(exactStartTime) + final String totalDays = (getDifference( + exactStartTime, convertToEndTime(appointment.exactEndTime)) .inDays + 1) .toString(); final String currentDate = - (convertToEndTime(date).difference(exactStartTime).inDays + 1) + (getDifference(exactStartTime, convertToEndTime(date)).inDays + 1) .toString(); return appointment.subject + @@ -264,7 +276,7 @@ class AppointmentHelper { notes: appointment.notes, location: appointment.location, isSpanned: appointment.isSpanned, - resourceIds: appointment.resourceIds, + resourceIds: CalendarViewHelper.cloneList(appointment.resourceIds), recurrenceId: appointment.recurrenceId, id: appointment.id); copyAppointment.actualStartTime = appointment.actualStartTime; @@ -540,15 +552,14 @@ class AppointmentHelper { minimumAppointmentMinutes > timeIntervalMinutes ? timeIntervalMinutes : minimumAppointmentMinutes; - if (currentAppointmentEndTime - .difference(currentAppointmentStartTime) + if (getDifference(currentAppointmentStartTime, currentAppointmentEndTime) .inMinutes < minimumAppointmentMinutes) { currentAppointmentEndTime = currentAppointmentStartTime .add(Duration(minutes: minimumAppointmentMinutes)); } - if (appointmentEndTime.difference(appointmentStartTime).inMinutes < + if (getDifference(appointmentStartTime, appointmentEndTime).inMinutes < minimumAppointmentMinutes) { appointmentEndTime = appointmentStartTime .add(Duration(minutes: minimumAppointmentMinutes)); @@ -971,9 +982,12 @@ class AppointmentHelper { appointment.exactStartTime.month == appointment.exactEndTime.month) && appointment.exactStartTime.isBefore(appointment.exactEndTime) && - (appointment.exactEndTime.difference(appointment.exactStartTime)) + getDifference( + appointment.exactStartTime, appointment.exactEndTime) .inDays == 0 && + (appointment.exactEndTime.hour != 0 || + appointment.exactEndTime.minute != 0) && !appointment.isAllDay && !isTimelineView) { for (int i = 0; i < 2; i++) { @@ -1000,12 +1014,12 @@ class AppointmentHelper { ? appointment.actualStartTime : convertTimeToAppointmentTimeZone( appointment.actualStartTime, - appointment.startTimeZone, - calendarTimeZone); + calendarTimeZone, + appointment.startTimeZone); spannedAppointment.endTime = spannedAppointment.isAllDay ? appointment.actualEndTime : convertTimeToAppointmentTimeZone(appointment.actualEndTime, - appointment.endTimeZone, calendarTimeZone); + calendarTimeZone, appointment.endTimeZone); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate @@ -1047,14 +1061,14 @@ class AppointmentHelper { ? appointment.actualStartTime : convertTimeToAppointmentTimeZone( appointment.actualStartTime, - appointment.startTimeZone, - calendarTimeZone); + calendarTimeZone, + appointment.startTimeZone); spannedAppointment.endTime = spannedAppointment.isAllDay ? appointment.actualEndTime : convertTimeToAppointmentTimeZone( appointment.actualEndTime, - appointment.endTimeZone, - calendarTimeZone); + calendarTimeZone, + appointment.endTimeZone); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate @@ -1087,14 +1101,14 @@ class AppointmentHelper { ? appointment.actualStartTime : convertTimeToAppointmentTimeZone( appointment.actualStartTime, - appointment.startTimeZone, - calendarTimeZone); + calendarTimeZone, + appointment.startTimeZone); spannedAppointment.endTime = spannedAppointment.isAllDay ? appointment.actualEndTime : convertTimeToAppointmentTimeZone( appointment.actualEndTime, - appointment.endTimeZone, - calendarTimeZone); + calendarTimeZone, + appointment.endTimeZone); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate @@ -1134,14 +1148,14 @@ class AppointmentHelper { ? appointment.actualStartTime : convertTimeToAppointmentTimeZone( appointment.actualStartTime, - appointment.startTimeZone, - calendarTimeZone); + calendarTimeZone, + appointment.startTimeZone); spannedAppointment.endTime = spannedAppointment.isAllDay ? appointment.actualEndTime : convertTimeToAppointmentTimeZone( appointment.actualEndTime, - appointment.endTimeZone, - calendarTimeZone); + calendarTimeZone, + appointment.endTimeZone); // Adding Spanned Appointment only when the Appointment range // within the VisibleDate @@ -1187,24 +1201,24 @@ class AppointmentHelper { ? occurrenceAppointment.actualStartTime : convertTimeToAppointmentTimeZone( occurrenceAppointment.actualStartTime, - occurrenceAppointment.startTimeZone, - calendarTimeZone); + calendarTimeZone, + occurrenceAppointment.startTimeZone); - final int minutes = appointment.actualEndTime - .difference(appointment.actualStartTime) - .inMinutes; + final int minutes = + getDifference(appointment.actualStartTime, appointment.actualEndTime) + .inMinutes; occurrenceAppointment.actualEndTime = DateTimeHelper.getDateTimeValue( addDuration( occurrenceAppointment.actualStartTime, Duration(minutes: minutes))); occurrenceAppointment.endTime = occurrenceAppointment.isAllDay ? occurrenceAppointment.actualEndTime : convertTimeToAppointmentTimeZone(occurrenceAppointment.actualEndTime, - occurrenceAppointment.endTimeZone, calendarTimeZone); + calendarTimeZone, occurrenceAppointment.endTimeZone); occurrenceAppointment.isSpanned = _isSpanned(occurrenceAppointment) && - (occurrenceAppointment.endTime - .difference(occurrenceAppointment.startTime) + getDifference(occurrenceAppointment.startTime, + occurrenceAppointment.endTime) .inDays > - 0); + 0; occurrenceAppointment.exactStartTime = occurrenceAppointment.actualStartTime; occurrenceAppointment.exactEndTime = occurrenceAppointment.actualEndTime; @@ -1250,7 +1264,7 @@ class AppointmentHelper { calendarAppointmentCollection.add(item); item.isSpanned = _isSpanned(item) && - (appEndTime.difference(appStartTime).inDays > 0); + getDifference(appStartTime, appEndTime).inDays > 0; } } else { for (int i = 0; i < dataSource.length; i++) { @@ -1260,8 +1274,8 @@ class AppointmentHelper { final DateTime appStartTime = app.startTime; final DateTime appEndTime = app.endTime; - app.isSpanned = - _isSpanned(app) && (appEndTime.difference(appStartTime).inDays > 0); + app.isSpanned = _isSpanned(app) && + getDifference(appStartTime, appEndTime).inDays > 0; calendarAppointmentCollection.add(app); } } @@ -1327,8 +1341,8 @@ class AppointmentHelper { !appointment.isAllDay) { appointment.endTime = convertTimeToAppointmentTimeZone( addDuration(appointment.actualStartTime, const Duration(minutes: 30)), - appointment.endTimeZone, - scheduleTimeZone); + scheduleTimeZone, + appointment.endTimeZone); appointment.actualEndTime = !appointment.isAllDay ? convertTimeToAppointmentTimeZone( appointment.endTime, appointment.endTimeZone, scheduleTimeZone) @@ -1358,8 +1372,8 @@ class AppointmentHelper { final List recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( rule, appointment.actualStartTime, - recurrenceDuration: - appointment.exactEndTime.difference(appointment.exactStartTime), + recurrenceDuration: getDifference( + appointment.exactStartTime, appointment.exactEndTime), specificStartDate: visibleStartDate, specificEndDate: visibleEndDate); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart index 3abd351e6..90db0414b 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/calendar_datasource.dart @@ -20,6 +20,38 @@ import '../resource_view/calendar_resource.dart'; /// Allows to add and remove an [Appointment] from the collection and also /// allows to reset the appointment collection for [SfCalendar]. /// +/// See also: +/// * [Appointment], the object which holds the details of the appointment. +/// * [CalendarResource], the object which holds the details of the resource +/// in the calendar. +/// * [SfCalendar.loadMoreWidgetBuilder], allows to build an widget which will +/// display when appointments loaded on demand in the calendar. +/// * [SfCalendar.resourceViewHeaderBuilder], to set custom widget for the +/// resource view in the calendar. +/// * [resourceViewSettings], to customize the resource view in the calendar. +/// * [SfCalendar.appointmentBuilder], to set custom widget for the appointment +/// view in the calendar. +/// * [SfCalendar.appointmentTextStyle], to customize the text style for the +/// appointments in calendar. +/// * [SfCalendar.appointmentTimeTextFormat], to customize the time format for +/// the appointment view in calendar. +/// * Knowledge base: [How to perform the crud operations using firestore database](https://www.syncfusion.com/kb/12661/how-to-perform-the-crud-operations-in-flutter-calendar-using-firestore-database) +/// * Knowledge base: [How to load appointments on demand](https://www.syncfusion.com/kb/12658/how-to-load-appointments-on-demand-in-flutter-calendar) +/// * Knowledge base: [How to load google calendar events in iOS](https://www.syncfusion.com/kb/12647/how-to-load-the-google-calendar-events-to-the-flutter-calendar-sfcalendar-in-ios) +/// * Knowledge base: [How to get the appointments between the given start and end date](https://www.syncfusion.com/kb/12549/how-to-get-the-appointments-between-the-given-start-and-end-date-in-the-flutter-calendar) +/// * Knowledge base: [How to get the current month appointments](https://www.syncfusion.com/kb/12477/how-to-get-the-current-month-appointments-in-the-flutter-calendar) +/// * Knowledge base: [How to load data from offline sqlite database](https://www.syncfusion.com/kb/12399/how-to-load-data-from-offline-sqlite-database-to-flutter-calendar) +/// * Knowledge base: [How to create time table](https://www.syncfusion.com/kb/12392/how-to-create-time-table-using-flutter-event-calendar) +/// * Knowledge base: [How to add google calendar events](https://www.syncfusion.com/kb/12116/how-to-add-google-calendar-events-to-the-flutter-event-calendar-sfcalendar) +/// * Knowledge base: [How to add a custom appointments of business objects](https://www.syncfusion.com/kb/11529/how-to-add-a-custom-appointments-or-objects-in-the-flutter-calendar) +/// * Knowledge base: [How to add additional attributes in events](https://www.syncfusion.com/kb/12209/how-to-add-additional-attributes-in-events-in-the-flutter-calendar) +/// * Knowledge base: [How to work with the firebase database and flutter calendar for appointments](https://www.syncfusion.com/kb/12067/how-to-work-with-the-firebase-database-and-the-flutter-calendar-for-appointments) +/// * Knowledge base: [How to integrate [SfCalendar] with [SfDateRangePicker]](https://www.syncfusion.com/kb/12047/how-to-integrate-event-calendar-sfcalendar-with-date-picker-sfdaterangepicker-in-flutter) +/// * Knowledge base: [How to handle appointments for multiple resources](https://www.syncfusion.com/kb/11812/how-to-handle-appointments-for-multiple-resources-in-the-flutter-calendar) +/// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) +/// * Knowledge base: [How to design and configure your appointment editor](https://www.syncfusion.com/kb/11204/how-to-design-and-configure-your-appointment-editor-in-flutter-calendar) +/// * Knowledge base: [How to load offline json data](https://www.syncfusion.com/kb/11466/how-to-load-the-json-data-offline-for-the-flutter-calendar-appointments) +/// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -121,11 +153,42 @@ import '../resource_view/calendar_resource.dart'; /// return MeetingDataSource(appointments); /// } /// ``` -abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { +@optionalTypeArgs +abstract class CalendarDataSource + extends CalendarDataSourceChangeNotifier { /// Tha collection of appointments to be rendered on [SfCalendar]. /// /// Defaults to `null`. /// + /// See also: + /// * [Appointment], the object to hold the data for the event in the + /// calendar. + /// * [resources], to set and handle the resource collection to the + /// [SfCalendar]. + /// * [SfCalendar.appointmentBuilder], to set custom widget for the + /// appointment view in the calendar + /// * [SfCalendar.loadMoreWidgetBuilder], the widget which will be displayed + /// when the appointments loading on the view in calendar. + /// * [SfCalendar.appointmentTextStyle], to customize the appointment text, + /// when the builder not added. + /// * [SfCalendar.appointmentTimeTextFormat], to customize the time text + /// format in the appointment view of calendar. + /// * [handleLoadMore], method to load the appointment when the view changed + /// in calendar, and on [CalendarView.schedule] when the view reached it's + /// start or end position. + /// * Knowledge base: [How to perform the crud operations using firestore database](https://www.syncfusion.com/kb/12661/how-to-perform-the-crud-operations-in-flutter-calendar-using-firestore-database) + /// * Knowledge base: [How to load appointments on demand](https://www.syncfusion.com/kb/12658/how-to-load-appointments-on-demand-in-flutter-calendar) + /// * Knowledge base: [How to create time table](https://www.syncfusion.com/kb/12392/how-to-create-time-table-using-flutter-event-calendar) + /// * Knowledge base: [How to add a custom appointments of business objects](https://www.syncfusion.com/kb/11529/how-to-add-a-custom-appointments-or-objects-in-the-flutter-calendar) + /// * Knowledge base: [How to add additional attributes in events](https://www.syncfusion.com/kb/12209/how-to-add-additional-attributes-in-events-in-the-flutter-calendar) + /// * Knowledge base: [How to work with the firebase database and flutter calendar for appointments](https://www.syncfusion.com/kb/12067/how-to-work-with-the-firebase-database-and-the-flutter-calendar-for-appointments) + /// * Knowledge base: [How to integrate [SfCalendar] with [SfDateRangePicker]](https://www.syncfusion.com/kb/12047/how-to-integrate-event-calendar-sfcalendar-with-date-picker-sfdaterangepicker-in-flutter) + /// * Knowledge base: [How to handle appointments for multiple resources](https://www.syncfusion.com/kb/11812/how-to-handle-appointments-for-multiple-resources-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to design and configure your appointment editor](https://www.syncfusion.com/kb/11204/how-to-design-and-configure-your-appointment-editor-in-flutter-calendar) + /// * Knowledge base: [How to load offline json data](https://www.syncfusion.com/kb/11466/how-to-load-the-json-data-offline-for-the-flutter-calendar-appointments) + /// * Knowledge base: [How to delete an appointment](https://www.syncfusion.com/kb/11522/how-to-delete-an-appointment-in-the-flutter-calendar) + /// /// ```dat /// /// class _AppointmentDataSource extends CalendarDataSource { @@ -144,6 +207,16 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// endDate - optional - The end date till which /// to obtain the visible appointments. /// + /// See also: + /// * [Appointment], the object to hold the data for the event in the + /// calendar. + /// * [getPatternAppointment], which allows to get the pattern appointment of + /// the given occurrence appointment in calendar. + /// * [getOccurrenceAppointment], which allows to get the occurrence + /// appointment of the pattern appointment in the calendar. + /// * Knowledge base: [How to get the appointments between the given start and end date](https://www.syncfusion.com/kb/12549/how-to-get-the-appointments-between-the-given-start-and-end-date-in-the-flutter-calendar) + /// * Knowledge base: [How to get the current month appointments](https://www.syncfusion.com/kb/12477/how-to-get-the-current-month-appointments-in-the-flutter-calendar) + /// /// ```dart /// /// class MyAppState extends State { @@ -246,70 +319,79 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// date - required - The date on which the /// occurrence appointment is requested. /// + /// See also: + /// * [Appointment], the object to hold the data for the event in the + /// calendar. + /// * [getVisibleAppointments], which allows to get the appointment + /// collection between the given date range. + /// * [getPatternAppointment], which allows to get the pattern appointment of + /// the given occurrence appointment in calendar. + /// * [SfCalendar.getRecurrenceDateTimeCollection], which used to get the + /// recurrence date time collection for the given recurrence rule. + /// /// ```dart /// /// class MyAppState extends State{ + /// late CalendarController _calendarController; + /// late _AppointmentDataSource _dataSource; + /// late Appointment recurrenceApp; /// - /// CalendarController _calendarController; - /// _AppointmentDataSource _dataSource; - /// Appointment recurrenceApp; - /// @override - /// initState(){ - /// _calendarController = CalendarController(); - /// _dataSource = _getCalendarDataSource(); - /// super.initState(); - /// } + /// @override + /// initState(){ + /// _calendarController = CalendarController(); + /// _dataSource = _getCalendarDataSource(); + /// super.initState(); + /// } /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfCalendar( - /// view: CalendarView.month, - /// controller: _calendarController, - /// dataSource: _dataSource, - /// onTap: (CalendarTapDetails details) { - /// DateTime date = details.date; - /// String calendarTimeZone = ''; - /// Appointment appointment = _dataSource.getOccurrenceAppointment( - /// recurrenceApp, date, calendarTimeZone); - /// }, - /// ), + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// controller: _calendarController, + /// dataSource: _dataSource, + /// initialDisplayDate: DateTime(2020,11,27,9), + /// onTap: (CalendarTapDetails details) { + /// final DateTime? date = details.date; + /// final Appointment? occurrenceAppointment = + /// _dataSource.getOccurrenceAppointment( + /// recurrenceApp, date!, ''); + /// }, + /// ), /// ), - /// ); - /// } - ///} - /// - /// _AppointmentDataSource _getCalendarDataSource() { - /// List appointments = []; - /// recurrenceApp = appointments.add(Appointment( - /// startTime: DateTime(2020,11,27,9), - /// endTime: DateTime(2020,11,27,9).add(Duration(hours: 2)), - /// subject: 'Meeting', - /// color: Colors.cyanAccent, - /// startTimeZone: '', - /// endTimeZone: '', - /// recurrenceRule: 'FREQ=DAILY;INTERVAL=2;COUNT=5', - /// )); - /// appointments.add(recurrenceApp); - /// appointments.add(Appointment( - /// startTime: DateTime(2020,11,28,5), - /// endTime: DateTime(2020,11,30,7), - /// subject: 'Discussion', - /// color: Colors.orangeAccent, - /// startTimeZone: '', - /// endTimeZone: '', - /// isAllDay: true + /// ); + /// } + /// _AppointmentDataSource _getCalendarDataSource() { + /// final List appointments = []; + /// recurrenceApp = Appointment( + /// startTime: DateTime(2020,11,27,9), + /// endTime: DateTime(2020,11,27,9).add(const Duration(hours: 2)), + /// subject: 'Meeting', + /// color: Colors.cyanAccent, + /// startTimeZone: '', + /// endTimeZone: '', + /// recurrenceRule: 'FREQ=DAILY;INTERVAL=2;COUNT=5', + /// ); + /// appointments.add(recurrenceApp); + /// appointments.add(Appointment( + /// startTime: DateTime(2020,11,28,5), + /// endTime: DateTime(2020,11,30,7), + /// subject: 'Discussion', + /// color: Colors.orangeAccent, + /// startTimeZone: '', + /// endTimeZone: '', + /// isAllDay: true /// )); - /// return _AppointmentDataSource(appointments); + /// return _AppointmentDataSource(appointments); /// } /// } - /// /// class _AppointmentDataSource extends CalendarDataSource { - /// _AppointmentDataSource(List source){ + /// _AppointmentDataSource(List source){ /// appointments = source; - /// } - ///} + /// } + /// } + /// /// ``` Appointment? getOccurrenceAppointment( Object? patternAppointment, DateTime date, String calendarTimeZone) { @@ -360,70 +442,80 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// occurrenceAppointment - required - The occurrence appointment for which /// the Pattern appointment is obtained. /// + /// * [Appointment], the object to hold the data for the event in the + /// calendar. + /// * [getVisibleAppointments], which allows to get the appointment + /// collection between the given date range. + /// * [getOccurrenceAppointment], which allows to get the occurrence + /// appointment of the given pattern appointment in calendar. + /// * [SfCalendar.getRecurrenceDateTimeCollection], which used to get the + /// recurrence date time collection for the given recurrence rule. + /// /// ```dart /// /// class MyAppState extends State{ - /// - /// CalendarController _calendarController; - /// _AppointmentDataSource _dataSource; - /// Appointment recurrenceApp; - /// @override - /// initState(){ - /// _calendarController = CalendarController(); - /// _dataSource = _getCalendarDataSource(); - /// super.initState(); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return MaterialApp( - /// home: Scaffold( - /// body: SfCalendar( - /// view: CalendarView.month, - /// controller: _calendarController, - /// dataSource: _dataSource, - /// onTap: (CalendarTapDetails details) { - /// DateTime date = details.date; - /// String calendarTimeZone = ''; - /// Appointment appointment = _dataSource.getPatternAppointment( - /// occurrenceAppointment, calendarTimeZone); - /// }, - /// ), - /// ), - /// ); - /// } - ///} - /// - /// _AppointmentDataSource _getCalendarDataSource() { - /// List appointments = []; - /// recurrenceApp = appointments.add(Appointment( - /// startTime: DateTime(2020,11,27,9), - /// endTime: DateTime(2020,11,27,9).add(Duration(hours: 2)), - /// subject: 'Meeting', - /// color: Colors.cyanAccent, - /// startTimeZone: '', - /// endTimeZone: '', - /// recurrenceRule: 'FREQ=DAILY;INTERVAL=2;COUNT=5', - /// )); - /// appointments.add(recurrenceApp); - /// appointments.add(Appointment( - /// startTime: DateTime(2020,11,28,5), - /// endTime: DateTime(2020,11,30,7), - /// subject: 'Discussion', - /// color: Colors.orangeAccent, - /// startTimeZone: '', - /// endTimeZone: '', - /// isAllDay: true + /// late CalendarController _calendarController; + /// late _AppointmentDataSource _dataSource; + /// late Appointment recurrenceApp; + /// @override + /// initState(){ + /// _calendarController = CalendarController(); + /// _dataSource = _getCalendarDataSource(); + /// super.initState(); + /// } + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// controller: _calendarController, + /// dataSource: _dataSource, + /// initialDisplayDate: DateTime(2020,11,27,9), + /// onTap: (CalendarTapDetails details) { + /// if(details.appointments!.isNotEmpty && + /// details.appointments != null){ + /// final dynamic occurrenceAppointment = + /// details.appointments![0]; + /// final Appointment? patternAppointment = + /// _dataSource.getPatternAppointment( + /// occurrenceAppointment, '') as Appointment?; + /// } + /// }, + /// ), + /// ), + /// ); + /// } + /// _AppointmentDataSource _getCalendarDataSource() { + /// List appointments = []; + /// recurrenceApp = Appointment( + /// startTime: DateTime(2020,11,27,9), + /// endTime: DateTime(2020,11,27,9).add(Duration(hours: 2)), + /// subject: 'Meeting', + /// color: Colors.cyanAccent, + /// startTimeZone: '', + /// endTimeZone: '', + /// recurrenceRule: 'FREQ=DAILY;INTERVAL=2;COUNT=5', + /// ); + /// appointments.add(recurrenceApp); + /// appointments.add(Appointment( + /// startTime: DateTime(2020,11,28,5), + /// endTime: DateTime(2020,11,30,7), + /// subject: 'Discussion', + /// color: Colors.orangeAccent, + /// startTimeZone: '', + /// endTimeZone: '', + /// isAllDay: true /// )); - /// return _AppointmentDataSource(appointments); + /// return _AppointmentDataSource(appointments); /// } /// } - /// /// class _AppointmentDataSource extends CalendarDataSource { - /// _AppointmentDataSource(List source){ + /// _AppointmentDataSource(List source){ /// appointments = source; - /// } - ///} + /// } + /// } + /// /// ``` Object? getPatternAppointment( Object? occurrenceAppointment, String calendarTimeZone) { @@ -452,7 +544,8 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { dataSourceAppointments[i]; if ((dataSourceAppointment.id == occurrenceCalendarAppointment.recurrenceId) || - (dataSourceAppointment.id == occurrenceCalendarAppointment.id)) { + (occurrenceCalendarAppointment.recurrenceId == null && + dataSourceAppointment.id == occurrenceCalendarAppointment.id)) { return dataSourceAppointment.data; } } @@ -464,6 +557,21 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// /// Defaults to `null`. /// + /// See also: + /// * [CalendarResource], the object to hold the data for the resource in the + /// calendar. + /// * [appointments], to set and handle the resource collection to the + /// [SfCalendar]. + /// * [TimeRegion], the object to hold the data for the special time region in + /// the calendar. + /// * [SfCalendar.resourceViewHeaderBuilder], to set custom widget for the + /// resource view in the calendar. + /// * [resourceViewSettings], to customize the resource view in the calendar. + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) + /// * Knowledge base: [How to handle appointments for multiple resources](https://www.syncfusion.com/kb/11812/how-to-handle-appointments-for-multiple-resources-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) + /// * Knowledge base: [How to add appointment for the selected resources using appointment editor](https://www.syncfusion.com/kb/12109/how-to-add-appointment-for-the-selected-resources-using-appointment-editor-in-the-flutter) + /// /// ```dart /// /// class AppointmentDataSource extends CalendarDataSourceResource { @@ -488,7 +596,17 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// It is mandatory to override this method to set custom appointments /// collection to the [appointments]. /// - /// See also: [Appointment.startTime] + /// See also: + /// * [Appointment.startTime], the date time value in which the appointment + /// will start. + /// * [isAllDay], which used to map the custom appointment's + /// [Appointment.isAllDay] property to the [Appointment]. + /// * [getEndTime], which used to map the custom appointment's + /// [Appointment.endTime] property to the [Appointment]. + /// * [getStartTimeZone], which used to map the custom appointment's + /// [Appointment.startTimeZone] property to the [Appointment]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) /// /// ```dart /// @override @@ -509,7 +627,17 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// It is mandatory to override this method to set custom appointments /// collection to the [appointments]. /// - /// See also: [Appointment.endTime] + /// See also: + /// * [Appointment.endTime], the date time value in which the appointment + /// will end. + /// * [isAllDay], which used to map the custom appointment's + /// [Appointment.isAllDay] property to the [Appointment]. + /// * [getStartTime], which used to map the custom appointment + /// [Appointment.startTime] property to the [Appointment]. + /// * [getEndTimeZone], which used to map the custom appointment's + /// [Appointment.endTimeZone] property to the [Appointment]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) /// /// ```dart /// @override @@ -527,7 +655,12 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.subject] + /// See also: + /// * [Appointment.subject], which holds the subject of the appointment which + /// will be displayed on the event UI. + /// * [SfCalendar.appointmentTextStyle], to customize the appointment text, + /// when the builder not added. + /// * Knowledge base: [How to style appointments](https://www.syncfusion.com/kb/12162/how-to-style-the-appointment-in-the-flutter-calendar) /// /// ```dart /// @override @@ -545,7 +678,19 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.isAllDay] + /// See also: + /// * [Appointment.isAllDay], which defines whether the event is a day long or + /// not. + /// * [getStartTime], which used to map the custom appointment + /// [Appointment.startTime] property to the [Appointment]. + /// * [getEndTime], which used to map the custom appointment + /// [Appointment.endTime] property to the [Appointment]. + /// * [getStartTimeZone], which used to map the custom appointment's + /// [Appointment.startTimeZone] property to the [Appointment]. + /// * [getEndTimeZone], which used to map the custom appointment's + /// [Appointment.endTimeZone] property to the [Appointment]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) /// /// ```dart /// @override @@ -563,7 +708,14 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.color] + /// See also: + /// * [Appointment.color], which fills the background of the appointment view + /// in the [SfCalendar]. + /// * [SfCalendar.appointmentTextStyle], to customize the appointment text, + /// when the builder not added. + /// * [SfCalendar.appointmentBuilder], to set custom widget for the + /// appointment view in the calendar + /// * Knowledge base: [How to style appointments](https://www.syncfusion.com/kb/12162/how-to-style-the-appointment-in-the-flutter-calendar) /// /// ```dart /// @override @@ -581,7 +733,12 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.notes] + /// See also: + /// * [Appointment.notes], which stored some note of the appointment in the + /// calendar. + /// * [getLocation], which maps the custom appointment's + /// [Appointment.location] property to the [Appointment] + /// /// /// ```dart /// @override @@ -599,7 +756,11 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.location]. + /// See also: + /// * [Appointment.location], which used to store the location data of the + /// event in the calendar. + /// * [getNotes], which maps the custom appointment's [Appointment.notes] + /// property to the [Appointment] /// /// ```dart /// @override @@ -617,7 +778,14 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.startTimeZone]. + /// See also: + /// * [Appointment.startTimeZone], which sets the time zone for the start time + /// of the appointment. + /// * [getEndTime], which used to map the custom appointment's + /// [Appointment.endTime] property to the [Appointment]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// /// ```dart /// @override @@ -635,7 +803,13 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.endTimeZone]. + /// See also: + /// * [Appointment.endTimeZone], which sets the time zone for the end time + /// of the appointment. + /// * [getStartTime], which used to map the custom appointment's + /// [Appointment.startTime] property to the [Appointment]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) /// /// ```dart /// @override @@ -653,7 +827,18 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.recurrenceRule]. + /// See also: + /// * [Appointment.recurrenceRule], which used to recur the appointment based + /// on the given rule. + /// * [RecurrenceProperties], which used to create the recurrence rule based + /// on the values set to these properties. + /// * [SfCalendar.generateRRule], which used to generate recurrence rule + /// based on the [RecurrenceProperties] values. + /// * [SfCalendar.getRecurrenceDateTimeCollection], to get the recurrence date + /// time collection based on the given recurrence rule and start date. + /// * Knowledge base: [How to use a negative value for bysetpos in rrule](https://www.syncfusion.com/kb/12552/how-to-use-a-negative-value-for-bysetpos-in-a-rrule-of-recurrence-appointment-in-the) + /// * Knowledge base: [How to get the recurrence date collection](https://www.syncfusion.com/kb/12344/how-to-get-the-recurrence-date-collection-in-the-flutter-calendar) + /// * Knowledge base: [How to add recurring appointments until specified date](https://www.syncfusion.com/kb/12158/how-to-add-recurring-appointments-until-the-specified-date-in-the-flutter-calendar) /// /// ```dart /// @override @@ -671,7 +856,16 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.recurrenceExceptionDates]. + /// See also: + /// * [Appointment.recurrenceExceptionDates], which used to exclude some dates + /// from the recurrence series of the appointment. + /// * [getRecurrenceRule],which used to map the custom appointment's + /// [Appointment.recurrenceRule] property of the [Appointment]. + /// * [RecurrenceProperties], which used to create the recurrence rule based + /// on the values set to these properties. + /// * [SfCalendar.generateRRule], which used to generate recurrence rule + /// based on the [RecurrenceProperties] values. + /// * Knowledge base: [How to exclude the dates from the recurrence appointments](https://www.syncfusion.com/kb/12161/how-to-exclude-the-dates-from-recurrence-appointments-in-the-flutter-calendar) /// /// ```dart /// @override @@ -688,9 +882,18 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// [appointments]. /// /// See also: - /// /// * [Appointment.resourceIds], the ids of the [CalendarResource] that shares /// this [Appointment]. + /// * [CalendarResource], object which contains the resource data. + /// * [ResourceViewSettings], the settings have properties which allow to + /// customize the resource view of the [SfCalendar]. + /// * [CalendarResource.id], the unique id for the [CalendarResource] view of + /// [SfCalendar]. + /// * [resources], which used to set and handle the resources collection to + /// the calendar. + /// * Knowledge base: [How to add appointment for the selected resource using appointment editor](https://www.syncfusion.com/kb/12109/how-to-add-appointment-for-the-selected-resources-using-appointment-editor-in-the-flutter) + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) + /// * Knowledge base: [How to handle appointments for muliple resources](https://www.syncfusion.com/kb/11812/how-to-handle-appointments-for-multiple-resources-in-the-flutter-calendar) /// /// ```dart /// @override @@ -708,7 +911,11 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.recurrenceId]. + /// See also: + /// * [Appointment.recurrenceId], which used to handle the changed or + /// exception occurrence appointments in calendar. + /// * [getId], which used to map the custom appointment's [Appointment.id] + /// property of the [Appointment]. /// /// ```dart /// @override @@ -726,7 +933,11 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// _Note:_ It is applicable only when the custom appointments set to the /// [appointments]. /// - /// See also: [Appointment.id]. + /// See also: + /// * [Appointment.id], which holds an unique identification number for the + /// appointment in the calendar. + /// * [getRecurrenceRule], which used to map the custom appointment's + /// [Appointment.recurrenceRule] property to the [Appointment]. /// /// ```dart /// @override @@ -736,11 +947,64 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// ``` Object? getId(int index) => null; + /// Converts the [Appointment] data to the custom business object data. + /// + /// Note_:_ When business object used to set data for [SfCalendar], this + /// method must be implemented to get the data with business object type when + /// drag and drop and appointment resizing enabled. + /// + /// ```dart + /// + ///class _DataSource extends CalendarDataSource<_Meeting> { + /// _DataSource(List<_Meeting> source) { + /// appointments = source; + /// } + /// + /// @override + /// DateTime getStartTime(int index) { + /// return appointments![index].from as DateTime; + /// } + /// + /// @override + /// DateTime getEndTime(int index) { + /// return appointments![index].to as DateTime; + /// } + /// + /// @override + /// String getSubject(int index) { + /// return appointments![index].content as String; + /// } + /// + /// @override + /// Color getColor(int index) { + /// return appointments![index].background as Color; + /// } + /// + /// @override + /// _Meeting convertAppointmentToObject( + /// _Meeting customData, Appointment appointment) { + /// return _Meeting( + /// from: appointment.startTime, + /// to: appointment.endTime, + /// content: appointment.subject, + /// background: appointment.color, + /// isAllDay: appointment.isAllDay); + /// } + /// } + /// + /// ``` + T? convertAppointmentToObject(T customData, Appointment appointment) => null; + /// Called when loadMoreAppointments function is called from the /// loadMoreWidgetBuilder. /// Call the [notifyListeners] to notify the calendar for data source changes. /// - /// See also: [SfCalendar.loadMoreWidgetBuilder] + /// See also: + /// * [SfCalendar.loadMoreWidgetBuilder], which used to set custom widget, + /// which will be displayed when the appointment is loading on view. + /// * [notifyListeners], to add, remove or reset the appointment and resource + /// collection. + /// * Knowledge base: [How to load appointments on demand](https://www.syncfusion.com/kb/12658/how-to-load-appointments-on-demand-in-flutter-calendar) /// /// ```dart /// @override @@ -778,7 +1042,9 @@ abstract class CalendarDataSource extends CalendarDataSourceChangeNotifier { /// Signature for callback that reports that a appointment collection set to the /// [CalendarDataSource] modified. /// -/// See also: [CalendarDataSourceAction] +/// See also: +/// [CalendarDataSourceAction], the actions which can be performed using the +/// calendar. typedef CalendarDataSourceCallback = void Function( CalendarDataSourceAction, List); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart index 40596647b..3156cc8da 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/month_appointment_helper.dart @@ -4,6 +4,7 @@ import 'package:syncfusion_flutter_core/core.dart'; import '../common/calendar_view_helper.dart'; import 'appointment_helper.dart'; +// ignore: avoid_classes_with_only_static_members /// Holds the static helper methods used for appointment rendering in calendar /// month view. class MonthAppointmentHelper { @@ -249,8 +250,12 @@ class MonthAppointmentHelper { /// swap list index value. /// Eg., app1 start with Nov3 10AM and ends with Nov5 11AM and app2 starts /// with Nov3 9AM and ends with Nov4 11AM then swap the app1 before of app2. - return (startTime2.difference(endTime2).inMinutes.abs()) - .compareTo(startTime1.difference(endTime1).inMinutes.abs()); + return (AppointmentHelper.getDifference(endTime2, startTime2) + .inMinutes + .abs()) + .compareTo(AppointmentHelper.getDifference(endTime1, startTime1) + .inMinutes + .abs()); } static void _updateAppointmentPosition( diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart index 949e92566..b1d0e2d84 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_engine/recurrence_helper.dart @@ -6,6 +6,7 @@ import '../common/enums.dart' show RecurrenceType, RecurrenceRange, WeekDays; import 'appointment_helper.dart'; import 'recurrence_properties.dart'; +// ignore: avoid_classes_with_only_static_members /// Holds the static helper methods used for handling recurrence in calendar. class RecurrenceHelper { /// Check the recurrence appointment in between the visible date range. @@ -130,8 +131,9 @@ class RecurrenceHelper { /// Total days difference between the visible start date and recurrence /// start date. - final int difference = - visibleInitialDate.difference(recurrenceInitialDate).inDays; + final int difference = AppointmentHelper.getDifference( + recurrenceInitialDate, visibleInitialDate) + .inDays; final int dayDifference = difference % dailyDayGap; /// Valid recurrences in between the visible start date and recurrence @@ -441,7 +443,8 @@ class RecurrenceHelper { /// Calculate the total days between the recurrence start and visible /// start date. - int daysDifference = specificStartDate.difference(startDate).inDays; + int daysDifference = + AppointmentHelper.getDifference(startDate, specificStartDate).inDays; final DateTime recurrenceEndDate = addDate.add(recurrenceDuration); /// Calculate day difference between the recurrence start and end date. @@ -731,7 +734,7 @@ class RecurrenceHelper { } } else if (byDay == 'BYDAY') { int tempCount = 0; - final int nthWeekDay = _getWeekDay(byDayValue); + final int nthWeekDay = _getWeekDay(byDayValue) % DateTime.daysPerWeek; final int bySetPosValue = int.parse(bySetPosCount); void _updateValidDate() { @@ -946,7 +949,7 @@ class RecurrenceHelper { int tempCount = 0; final int monthIndex = int.parse(byMonthCount); final int bySetPosValue = int.parse(bySetPosCount); - final int nthWeekDay = _getWeekDay(byDayValue); + final int nthWeekDay = _getWeekDay(byDayValue) % DateTime.daysPerWeek; void _updateValidNextDate() { while (true) { @@ -1323,7 +1326,8 @@ class RecurrenceHelper { if (dayKey.isNotEmpty) { if (count != 0) { - final Duration tempTimeSpan = addDate.difference(prevDate); + final Duration tempTimeSpan = + AppointmentHelper.getDifference(prevDate, addDate); if (tempTimeSpan <= diffTimeSpan) { isValidRecurrence = false; } else { @@ -1492,14 +1496,15 @@ class RecurrenceHelper { rRule = rRule + ';UNTIL=' + format.format(endDate!); } - if (DateTime( + if (AppointmentHelper.getDifference( + startDate, + DateTime( startDate.year, startDate.month + recurrenceProperties.interval, startDate.day, startDate.hour, startDate.minute, - startDate.second) - .difference(startDate) < + startDate.second)) < diffTimeSpan) { isValidRecurrence = false; } @@ -1567,14 +1572,15 @@ class RecurrenceHelper { rRule = rRule + ';UNTIL=' + format.format(endDate!); } - if (DateTime( + if (AppointmentHelper.getDifference( + startDate, + DateTime( startDate.year + recurrenceProperties.interval, startDate.month, startDate.day, startDate.hour, startDate.minute, - startDate.second) - .difference(startDate) < + startDate.second)) < diffTimeSpan) { isValidRecurrence = false; } @@ -1597,7 +1603,8 @@ class RecurrenceHelper { ? appStartTime : recPropStartDate; final DateTime? endDate = recPropEndDate; - final Duration diffTimeSpan = appEndTime.difference(appStartTime); + final Duration diffTimeSpan = + AppointmentHelper.getDifference(appStartTime, appEndTime); int recCount = 0; final DateTime prevDate = DateTime.utc(1); const bool isValidRecurrence = true; @@ -1818,7 +1825,7 @@ class RecurrenceHelper { /// Returns the last week date for the given weekday. static DateTime _getLastWeekDay(DateTime date, int dayOfWeek) { final DateTime currentDate = date; - final int currentDateWeek = currentDate.weekday; + final int currentDateWeek = currentDate.weekday % DateTime.daysPerWeek; int otherMonthCount = -currentDateWeek + (dayOfWeek - DateTime.daysPerWeek); if (otherMonthCount.abs() >= DateTime.daysPerWeek) { otherMonthCount += DateTime.daysPerWeek; 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 095c0a482..d805255fc 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 @@ -12,6 +12,20 @@ import '../common/enums.dart'; /// [Appointment.recurrenceRule] for [Appointment] using the /// [SfCalendar.generateRRule] method. /// +/// See more: +/// * [Appointment.recurrenceRule], which used to recur the appointment based +/// on the given rule. +/// * [SfCalendar.generateRRule], which used to generate recurrence rule +/// based on the [RecurrenceProperties] values. +/// * [SfCalendar.parseRRule], which used to get the recurrence properties +/// based on the given recurrence rule. +/// * [SfCalendar.getRecurrenceDateTimeCollection], to get the recurrence date +/// time collection based on the given recurrence rule and start date. +/// * Knowledge base: [How to use a negative value for bysetpos in rrule](https://www.syncfusion.com/kb/12552/how-to-use-a-negative-value-for-bysetpos-in-a-rrule-of-recurrence-appointment-in-the) +/// * Knowledge base: [How to get the recurrence date collection](https://www.syncfusion.com/kb/12344/how-to-get-the-recurrence-date-collection-in-the-flutter-calendar) +/// * Knowledge base: [How to add recurring appointments until specified date](https://www.syncfusion.com/kb/12158/how-to-add-recurring-appointments-until-the-specified-date-in-the-flutter-calendar) +/// * Knowledge base: [How to get the recurrence properties from recurrence rule](https://www.syncfusion.com/kb/12370/how-to-get-the-recurrence-properties-from-rrule-in-the-flutter-calendar) +/// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -87,7 +101,8 @@ class RecurrenceProperties with Diagnosticable { /// /// Defaults to `RecurrenceType.daily`. /// - /// Also refer: [RecurrenceType]. + /// Also refer: + /// * [RecurrenceType], to know more about the available recurrence types. /// /// ```dart ///Widget build(BuildContext context) { @@ -143,6 +158,9 @@ class RecurrenceProperties with Diagnosticable { /// _Note:_ It is applicable only when the [recurrenceRange] set as /// [RecurrenceRange.count]. /// + /// See also: + /// * [recurrenceRange], to define the range for the recurring event. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -190,6 +208,9 @@ class RecurrenceProperties with Diagnosticable { /// /// The [Appointment] starts to recur from the date set to this property. /// + /// See also: + /// * [endDate], on which date the recurring event will end. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -242,7 +263,10 @@ class RecurrenceProperties with Diagnosticable { /// _Note:_ it is applicable only when the [recurrenceRange] set as /// [RecurrenceRange.endDate]. /// - /// Defaults to the next date of current date. + /// Defaults to null. + /// + /// See also: + /// * [startDate], on or after the given date the recurring will be start. /// /// ```dart ///Widget build(BuildContext context) { @@ -294,6 +318,10 @@ class RecurrenceProperties with Diagnosticable { /// /// Defaults to `1`. /// + /// See also: + /// * [recurrenceType], to define the recurrent type for the event. + /// * [recurrenceRange], to define the range for the recurring event. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -341,7 +369,9 @@ class RecurrenceProperties with Diagnosticable { /// /// Defaults to `RecurrenceRange.noEndDate`. /// - /// Also refer: [RecurrenceRange]. + /// Also refer: + /// * [RecurrenceRange], to know more about the available recurrence range in + /// calendar. /// /// ```dart ///Widget build(BuildContext context) { @@ -391,6 +421,11 @@ class RecurrenceProperties with Diagnosticable { /// The [Appointment] will recur on the [WeekDays] set to this property, /// when the [recurrenceType] set as [RecurrenceType.weekly]. /// + /// See also: + /// * [WeekDays], to know more about the available week days to define. + /// * [recurrenceType], which used to define the the type of the recurring + /// event. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -444,6 +479,11 @@ class RecurrenceProperties with Diagnosticable { /// The [Appointment] will recur on the [Week] set to this property, /// when the [recurrenceType] set as [RecurrenceType.year]. /// + /// See also: + /// * [recurrenceType], which used to define the the type of the recurring + /// event. + /// * Knowledge base: [How to use a negative value for bysetpos in rrule](https://www.syncfusion.com/kb/12552/how-to-use-a-negative-value-for-bysetpos-in-a-rrule-of-recurrence-appointment-in-the) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -492,6 +532,10 @@ class RecurrenceProperties with Diagnosticable { /// The [Appointment] will recur on the day set to this property on specific /// month, when the [recurrenceType] set as [RecurrenceType.year]. /// + /// See also: + /// * [recurrenceType], which used to define the the type of the recurring + /// event. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -541,6 +585,10 @@ class RecurrenceProperties with Diagnosticable { /// week, when the [recurrenceType] set as [RecurrenceType.year] or /// [RecurrenceType.month]. /// + /// See also: + /// * [recurrenceType], which used to define the the type of the recurring + /// event. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -592,6 +640,10 @@ class RecurrenceProperties with Diagnosticable { /// The [Appointment] will recur on the month set to this property on specific /// year, when the [recurrenceType] set as [RecurrenceType.year]. /// + /// See also: + /// * [recurrenceType], which used to define the the type of the recurring + /// event. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( 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 38eed9c53..4b033a667 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 @@ -7,6 +7,7 @@ import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../../../calendar.dart'; import '../appointment_engine/appointment_helper.dart'; import '../common/calendar_view_helper.dart'; import '../common/event_args.dart'; @@ -33,7 +34,8 @@ class AgendaViewLayout extends StatefulWidget { this.isMobilePlatform, this.appointmentBuilder, this.width, - this.height); + this.height, + this.calendar); /// Defines the month view customization details. final MonthViewSettings? monthViewSettings; @@ -84,6 +86,9 @@ class AgendaViewLayout extends StatefulWidget { /// Defines the height of the agenda appointment layout widget. final double height; + /// Defines the calendar widget. + final SfCalendar calendar; + @override _AgendaViewLayoutState createState() => _AgendaViewLayoutState(); } @@ -132,7 +137,8 @@ class _AgendaViewLayoutState extends State { final CalendarAppointmentDetails details = CalendarAppointmentDetails( widget.selectedDate!, List.unmodifiable([ - CalendarViewHelper.getAppointmentDetail(view.appointment!) + CalendarViewHelper.getAppointmentDetail( + view.appointment!, widget.calendar.dataSource) ]), view.appointmentRect!.outerRect); final Widget child = widget.appointmentBuilder!(context, details); 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 bde90cc7d..bda57e944 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 @@ -169,7 +169,7 @@ class _AllDayAppointmentLayoutState extends State { date, List.unmodifiable([ CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!) + appointmentView.appointment!, widget.calendar.dataSource) ]), Rect.fromLTWH( appointmentView.appointmentRect!.left, @@ -190,9 +190,11 @@ class _AllDayAppointmentLayoutState extends State { final double cellWidth = (widget.width - widget.timeLabelWidth) / widget.visibleDates.length; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + widget.calendar.cellEndPadding, widget.isMobilePlatform); + /// Calculate the maximum appointment width based on cell end padding. - final double maxAppointmentWidth = - cellWidth - widget.calendar.cellEndPadding; + final double maxAppointmentWidth = cellWidth - cellEndPadding; for (int i = 0; i < keys.length; i++) { final int index = keys[i]; final DateTime date = widget.visibleDates[index]; @@ -213,7 +215,8 @@ class _AllDayAppointmentLayoutState extends State { CalendarAppointmentDetails( date, List.unmodifiable( - CalendarViewHelper.getCustomAppointments(moreAppointments)), + CalendarViewHelper.getCustomAppointments( + moreAppointments, widget.calendar.dataSource)), Rect.fromLTWH( widget.isRTL ? widget.width - xPosition - maxAppointmentWidth @@ -270,6 +273,9 @@ class _AllDayAppointmentLayoutState extends State { ? 2 : kAllDayAppointmentHeight * 0.1; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + widget.calendar.cellEndPadding, widget.isMobilePlatform); + /// Calculate the maximum position of the appointment this widget can hold. final int position = widget.allDayPainterHeight ~/ kAllDayAppointmentHeight; for (int i = 0; i < _appointmentCollection.length; i++) { @@ -284,7 +290,7 @@ class _AllDayAppointmentLayoutState extends State { Rect.fromLTRB( ((widget.visibleDates.length - appointmentView.endIndex) * cellWidth) + - widget.calendar.cellEndPadding, + cellEndPadding, (kAllDayAppointmentHeight * appointmentView.position) .toDouble(), (widget.visibleDates.length - appointmentView.startIndex) * @@ -303,7 +309,7 @@ class _AllDayAppointmentLayoutState extends State { .toDouble(), (appointmentView.endIndex * cellWidth) + widget.timeLabelWidth - - widget.calendar.cellEndPadding, + cellEndPadding, ((kAllDayAppointmentHeight * appointmentView.position) + kAllDayAppointmentHeight - 1) @@ -838,6 +844,8 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { } _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + calendar.cellEndPadding, isMobilePlatform); final List keys = moreAppointmentIndex.keys.toList(); for (int i = 0; i < keys.length; i++) { if (child == null) { @@ -846,8 +854,7 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { final int index = keys[i]; final double leftPosition = isRTL - ? ((visibleDates.length - index - 1) * _cellWidth) + - calendar.cellEndPadding + ? ((visibleDates.length - index - 1) * _cellWidth) + cellEndPadding : timeLabelWidth + (index * _cellWidth); final Offset offset = Offset(leftPosition, maximumBottomPosition); final bool isHit = result.addWithPaintOffset( @@ -923,7 +930,9 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; const double appointmentHeight = kAllDayAppointmentHeight - 1; - final double maxAppointmentWidth = _cellWidth - calendar.cellEndPadding; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + calendar.cellEndPadding, isMobilePlatform); + final double maxAppointmentWidth = _cellWidth - cellEndPadding; final List keys = moreAppointmentIndex.keys.toList(); for (int i = 0; i < keys.length; i++) { if (child == null) { @@ -939,8 +948,7 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { child.parentData! as CalendarParentData; final int index = keys[i]; final double leftPosition = isRTL - ? ((visibleDates.length - index - 1) * _cellWidth) + - calendar.cellEndPadding + ? ((visibleDates.length - index - 1) * _cellWidth) + cellEndPadding : timeLabelWidth + (index * _cellWidth); childParentData.offset = Offset(leftPosition, maximumBottomPosition); child = childAfter(child); @@ -950,6 +958,7 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { @override void paint(PaintingContext context, Offset offset) { _textPainter.textScaleFactor = _textScaleFactor; + double leftPosition = 0, rightPosition = size.width; if (view == CalendarView.day) { _rectPainter.strokeWidth = 0.5; _rectPainter.color = @@ -964,7 +973,27 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { isRTL ? size.width - timeLabelWidth + 0.5 : timeLabelWidth - 0.5, size.height), _rectPainter); + + leftPosition = isRTL ? 0 : timeLabelWidth; + rightPosition = isRTL ? size.width - timeLabelWidth : size.width; + + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + calendar.viewHeaderHeight, view); + _rectPainter.color = calendar.timeSlotViewSettings.allDayPanelColor ?? + calendarTheme.allDayPanelColor!; + context.canvas.drawRect( + Rect.fromLTRB( + isRTL ? size.width - timeLabelWidth : 0, + viewHeaderHeight, + isRTL ? size.width : timeLabelWidth, + size.height), + _rectPainter); } + _rectPainter.color = calendar.timeSlotViewSettings.allDayPanelColor ?? + calendarTheme.allDayPanelColor!; + context.canvas.drawRect( + Rect.fromLTRB(leftPosition, 0, rightPosition, size.height), + _rectPainter); _rectPainter.isAntiAlias = true; _cellWidth = (size.width - timeLabelWidth) / visibleDates.length; @@ -1127,6 +1156,8 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { if (child != null) { final double endYPosition = allDayPainterHeight - kAllDayAppointmentHeight; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + calendar.cellEndPadding, isMobilePlatform); final List keys = moreAppointmentIndex.keys.toList(); for (final int index in keys) { if (child == null) { @@ -1135,7 +1166,7 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { final double xPosition = isRTL ? ((visibleDates.length - index - 1) * _cellWidth) + - calendar.cellEndPadding + cellEndPadding : timeLabelWidth + (index * _cellWidth); context.paintChild(child, Offset(xPosition, endYPosition)); child = childAfter(child); @@ -1316,6 +1347,8 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { final int index = DateTimeHelper.getIndex( visibleDates, selectionNotifier.value!.selectedDate!); Decoration? selectionDecoration = calendar.selectionDecoration; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + calendar.cellEndPadding, isMobilePlatform); /// Set the default selection decoration background color with opacity /// value based on theme brightness when selected date hold all day @@ -1352,13 +1385,10 @@ class _AllDayAppointmentRenderObject extends CustomCalendarRenderObject { double xValue = timeLabelWidth + (index * _cellWidth); if (isRTL) { xValue = size.width - xValue - _cellWidth; - rect = Rect.fromLTRB(xValue + calendar.cellEndPadding, 0, - xValue + _cellWidth, kAllDayAppointmentHeight - 1); + rect = Rect.fromLTRB(xValue + cellEndPadding, 0, xValue + _cellWidth, + kAllDayAppointmentHeight - 1); } else { - rect = Rect.fromLTRB( - xValue, - 0, - xValue + _cellWidth - calendar.cellEndPadding, + rect = Rect.fromLTRB(xValue, 0, xValue + _cellWidth - cellEndPadding, kAllDayAppointmentHeight - 1); } diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart index cb7dfda0e..a0b1734a3 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/appointment_layout/appointment_layout.dart @@ -218,7 +218,7 @@ class _AppointmentLayoutState extends State { date, List.unmodifiable([ CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!) + appointmentView.appointment!, widget.calendar.dataSource) ]), Rect.fromLTWH( appointmentView.appointmentRect!.left, @@ -257,7 +257,7 @@ class _AppointmentLayoutState extends State { date, List.unmodifiable( CalendarViewHelper.getCustomAppointments( - moreAppointments)), + moreAppointments, widget.calendar.dataSource)), Rect.fromLTWH(moreRegionRect.left, moreRegionRect.top, moreRegionRect.width, moreRegionRect.height), isMoreAppointmentRegion: true)); @@ -528,7 +528,8 @@ class _AppointmentLayoutState extends State { (cellHeight - startPosition) / maximumDisplayCount; // right side padding used to add padding on appointment view right side // in month view - final double cellEndPadding = widget.calendar.cellEndPadding; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + widget.calendar.cellEndPadding, widget.isMobilePlatform); for (int i = 0; i < _appointmentCollection.length; i++) { final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { @@ -632,7 +633,8 @@ class _AppointmentLayoutState extends State { final double cellWidth = width / count; final double cellHeight = widget.timeIntervalHeight; double xPosition = timeLabelWidth; - final double cellEndPadding = widget.calendar.cellEndPadding; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + widget.calendar.cellEndPadding, widget.isMobilePlatform); final int timeInterval = CalendarViewHelper.getTimeInterval( widget.calendar.timeSlotViewSettings); @@ -660,7 +662,10 @@ class _AppointmentLayoutState extends State { if (column == -1 || appointment.isSpanned || - (appointment.endTime.difference(appointment.startTime).inDays > 0) || + AppointmentHelper.getDifference( + appointment.startTime, appointment.endTime) + .inDays > + 0 || appointment.isAllDay) { continue; } @@ -681,11 +686,10 @@ class _AppointmentLayoutState extends State { timeLabelWidth; } - Duration difference = - appointment.actualEndTime.difference(appointment.actualStartTime); + Duration difference = AppointmentHelper.getDifference( + appointment.actualStartTime, appointment.actualEndTime); final double minuteHeight = cellHeight / timeInterval; double yPosition = totalMins * minuteHeight; - double height = difference.inMinutes * minuteHeight; if (widget.calendar.timeSlotViewSettings.minimumAppointmentDuration != null && @@ -793,7 +797,8 @@ class _AppointmentLayoutState extends State { final double cellWidth = widget.timeIntervalHeight; double xPosition = 0; double yPosition = 0; - final double cellEndPadding = widget.calendar.cellEndPadding; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + widget.calendar.cellEndPadding, widget.isMobilePlatform); final double slotHeight = isResourceEnabled ? widget.resourceItemHeight! : widget.height; final double timelineAppointmentHeight = _getTimelineAppointmentHeight( @@ -842,7 +847,8 @@ class _AppointmentLayoutState extends State { } final DateTime endTime = appointment.actualEndTime; - final Duration difference = endTime.difference(startTime); + final Duration difference = + AppointmentHelper.getDifference(startTime, endTime); /// The width for the appointment UI, calculated based on the date /// difference between the start and end time of the appointment. @@ -914,15 +920,17 @@ class _AppointmentLayoutState extends State { double yPosition = 0; final int timeInterval = CalendarViewHelper.getTimeInterval( widget.calendar.timeSlotViewSettings); - final double cellEndPadding = widget.calendar.cellEndPadding; + final double cellEndPadding = CalendarViewHelper.getCellEndPadding( + widget.calendar.cellEndPadding, widget.isMobilePlatform); final int viewStartHour = widget.calendar.timeSlotViewSettings.startHour.toInt(); final double viewStartMinutes = (widget.calendar.timeSlotViewSettings.startHour - viewStartHour) * 60; final double timelineAppointmentHeight = _getTimelineAppointmentHeight( widget.calendar.timeSlotViewSettings, widget.view); - final double slotHeight = - isResourceEnabled ? widget.resourceItemHeight! : widget.height; + final double slotHeight = isResourceEnabled + ? widget.resourceItemHeight! - cellEndPadding + : widget.height - cellEndPadding; for (int i = 0; i < _appointmentCollection.length; i++) { final AppointmentView appointmentView = _appointmentCollection[i]; if (appointmentView.canReuse || appointmentView.appointment == null) { @@ -1031,8 +1039,8 @@ class _AppointmentLayoutState extends State { if (widget.calendar.timeSlotViewSettings.minimumAppointmentDuration != null && widget.calendar.timeSlotViewSettings.minimumAppointmentDuration! > - appointment.actualEndTime - .difference(appointment.actualStartTime)) { + AppointmentHelper.getDifference( + appointment.actualStartTime, appointment.actualEndTime)) { final double minWidth = AppointmentHelper.getAppointmentHeightFromDuration( widget.calendar.timeSlotViewSettings.minimumAppointmentDuration, @@ -1041,15 +1049,12 @@ class _AppointmentLayoutState extends State { width = width > minWidth ? width : minWidth; } - width = width - cellEndPadding; final Radius cornerRadius = Radius.circular( (appointmentHeight * 0.1) > 2 ? 2 : (appointmentHeight * 0.1)); + width = width > 1 ? width - 1 : 0; final RRect rect = RRect.fromRectAndRadius( - Rect.fromLTWH( - widget.isRTL ? xPosition - width : xPosition, - yPosition, - width > 0 ? width : 0, - appointmentHeight > 1 ? appointmentHeight - 1 : 0), + Rect.fromLTWH(widget.isRTL ? xPosition - width : xPosition, yPosition, + width, appointmentHeight > 1 ? appointmentHeight - 1 : 0), cornerRadius); appointmentView.appointmentRect = rect; } 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 83bee303d..0faa2513f 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 @@ -7,6 +7,7 @@ import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../../../calendar.dart'; +import '../appointment_engine/appointment_helper.dart'; import '../appointment_engine/calendar_datasource.dart'; import '../resource_view/calendar_resource.dart'; import '../settings/month_view_settings.dart'; @@ -45,6 +46,7 @@ typedef UpdateCalendarState = void Function( //// the given width agenda view will render the web UI. const double _kMobileViewWidth = 767; +// ignore: avoid_classes_with_only_static_members /// Holds the static helper methods used for calendar views rendering /// in calendar. class CalendarViewHelper { @@ -259,7 +261,10 @@ class CalendarViewHelper { if (appointment.isAllDay) { return appointment.subject + 'All day'; } else if (appointment.isSpanned || - appointment.endTime.difference(appointment.startTime).inDays > 0) { + AppointmentHelper.getDifference( + appointment.startTime, appointment.endTime) + .inDays > + 0) { return appointment.subject + DateFormat('hh mm a dd/MMMM/yyyy') .format(appointment.startTime) @@ -433,6 +438,19 @@ class CalendarViewHelper { } } + /// Return the cell end padding based on platform of calendar widget. + static double getCellEndPadding(double cellEndPadding, bool isMobile) { + if (cellEndPadding != -1) { + return cellEndPadding; + } + + if (isMobile) { + return 3; + } + + return 6; + } + /// method to check whether the view changed callback can triggered or not. static bool shouldRaiseViewChangedCallback( ViewChangedCallback? onViewChanged) { @@ -456,6 +474,27 @@ class CalendarViewHelper { return onSelectionChanged != null; } + /// Method to check whether the appointment resize start callback can trigger + /// or not. + static bool shouldRaiseAppointmentResizeStartCallback( + AppointmentResizeStartCallback? onAppointmentResizeStart) { + return onAppointmentResizeStart != null; + } + + /// Method to check whether the appointment resize update callback can trigger + /// or not. + static bool shouldRaiseAppointmentResizeUpdateCallback( + AppointmentResizeUpdateCallback? onAppointmentResizeUpdate) { + return onAppointmentResizeUpdate != null; + } + + /// Method to check whether the appointment resize end callback can trigger + /// or not. + static bool shouldRaiseAppointmentResizeEndCallback( + AppointmentResizeEndCallback? onAppointmentResizeEnd) { + return onAppointmentResizeEnd != null; + } + /// method that raise the calendar tapped callback with the given parameters static void raiseCalendarTapCallback( SfCalendar calendar, @@ -491,6 +530,38 @@ class CalendarViewHelper { calendar.onViewChanged!(ViewChangedDetails(visibleDates)); } + /// Method that raises the appointment resize start callback with the given + /// parameters. + static void raiseAppointmentResizeStartCallback( + SfCalendar calendar, dynamic appointment, CalendarResource? resource) { + calendar.onAppointmentResizeStart!( + AppointmentResizeStartDetails(appointment, resource)); + } + + /// Method that raises the appointment resize update callback with the given + /// parameters. + static void raiseAppointmentResizeUpdateCallback( + SfCalendar calendar, + dynamic appointment, + CalendarResource? resource, + DateTime? resizingTime, + Offset resizingOffset) { + calendar.onAppointmentResizeUpdate!(AppointmentResizeUpdateDetails( + appointment, resource, resizingTime, resizingOffset)); + } + + /// Method that raises the appointment resize end callback with the given + /// parameters. + static void raiseAppointmentResizeEndCallback( + SfCalendar calendar, + dynamic appointment, + CalendarResource? resource, + DateTime? startTime, + DateTime? endTime) { + calendar.onAppointmentResizeEnd!( + AppointmentResizeEndDetails(appointment, resource, startTime, endTime)); + } + /// Check the calendar view is timeline view or not. static bool isTimelineView(CalendarView view) { switch (view) { @@ -511,24 +582,33 @@ class CalendarViewHelper { /// converts the given schedule appointment collection to their custom /// appointment collection static List getCustomAppointments( - List? appointments) { + List? appointments, CalendarDataSource? dataSource) { final List customAppointments = []; if (appointments == null) { return customAppointments; } for (int i = 0; i < appointments.length; i++) { - customAppointments.add(getAppointmentDetail(appointments[i])); + customAppointments.add(getAppointmentDetail(appointments[i], dataSource)); } return customAppointments; } /// Returns the appointment details with given appointment type. - static dynamic getAppointmentDetail(CalendarAppointment appointment) { + static dynamic getAppointmentDetail( + CalendarAppointment appointment, CalendarDataSource? dataSource) { if (appointment.recurrenceRule != null && appointment.recurrenceRule!.isNotEmpty) { - return appointment.convertToCalendarAppointment(); + final Appointment appointmentObject = + appointment.convertToCalendarAppointment(); + if (appointment.data is Appointment) { + return appointmentObject; + } else { + return dataSource!.convertAppointmentToObject( + appointment.data, appointmentObject) ?? + appointmentObject; + } } else { return appointment.data; } @@ -699,6 +779,20 @@ class AppointmentView { /// Defines the resource view index of the appointment. int resourceIndex = -1; + + /// Clones and return new instance of the appointment view. + AppointmentView clone() { + return AppointmentView() + ..appointmentRect = appointmentRect + ..appointment = appointment + ..canReuse = canReuse + ..startIndex = startIndex + ..endIndex = endIndex + ..position = position + ..maxPositions = maxPositions + ..isSpanned = isSpanned + ..resourceIndex = resourceIndex; + } } /// Appointment data for calendar. @@ -863,6 +957,7 @@ class CalendarAppointment { if (other is CalendarAppointment) { otherAppointment = other; } + return CalendarViewHelper.isSameTimeSlot( otherAppointment.startTime, startTime) && CalendarViewHelper.isSameTimeSlot(otherAppointment.endTime, endTime) && @@ -876,11 +971,14 @@ class CalendarAppointment { otherAppointment.isAllDay == isAllDay && otherAppointment.notes == notes && otherAppointment.location == location && - otherAppointment.resourceIds == resourceIds && + !CalendarViewHelper.isCollectionEqual( + otherAppointment.resourceIds, resourceIds) && otherAppointment.recurrenceId == recurrenceId && - otherAppointment.id == otherAppointment.id && + otherAppointment.id == id && + otherAppointment.data == data && otherAppointment.subject == subject && otherAppointment.color == color && + otherAppointment.recurrenceRule == recurrenceRule && CalendarViewHelper.isDateCollectionEqual( otherAppointment.recurrenceExceptionDates, recurrenceExceptionDates); @@ -899,6 +997,7 @@ class CalendarAppointment { hashList(resourceIds), recurrenceId, id, + data, startTime, endTime, subject, diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart index a5891fd90..9bb1c7044 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/date_time_engine.dart @@ -1,6 +1,8 @@ import 'package:syncfusion_flutter_core/core.dart'; +import '../appointment_engine/appointment_helper.dart'; import 'enums.dart'; +// ignore: avoid_classes_with_only_static_members /// Holds the static helper methods used for date calculation in calendar. class DateTimeHelper { /// Calculate the visible dates count based on calendar view @@ -316,7 +318,8 @@ class DateTimeHelper { /// Returns week number for the given date. static int getWeekNumberOfYear(DateTime date) { final DateTime yearEndDate = DateTime(date.year - 1, 12, 31); - final int dayOfYear = date.difference(yearEndDate).inDays; + final int dayOfYear = + AppointmentHelper.getDifference(yearEndDate, date).inDays; int weekNumber = (dayOfYear - date.weekday + 10) ~/ 7; if (weekNumber < 1) { weekNumber = getWeeksInYear(date.year - 1); diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart index 2d77a2eec..9b7f01a45 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/enums.dart @@ -1,4 +1,8 @@ /// A direction in which the [SfCalendar] month view navigates. +/// +/// See also: +/// * [ViewNavigationMode], which allows to navigate through calendar views with +/// different modes. enum MonthNavigationDirection { /// - MonthNavigationDirection.vertical, Navigates in top and bottom direction vertical, @@ -208,6 +212,10 @@ enum CalendarDataSourceAction { } /// Available view navigation modes for [SfCalendar]. +/// +/// See also: +/// * [MonthNavigationDirection], which used to define the navigation direction +/// for month view in calendar. enum ViewNavigationMode { /// - ViewNavigationMode.snap, allows to switching to previous/next views /// through swipe interaction in SfCalendar. diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart index 54378f1e6..9b8728d53 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/common/event_args.dart @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; import '../resource_view/calendar_resource.dart'; import '../settings/time_region.dart'; import 'enums.dart'; @@ -8,6 +9,12 @@ import 'enums.dart'; /// The dates that visible on the view changes in [SfCalendar]. /// /// Details for [ViewChangedCallback], such as [visibleDates]. +/// +/// See also: +/// * [SfCalendar.onViewChanged], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [ViewChangedCallback], signature when the current visible dates changed +/// in calendar. @immutable class ViewChangedDetails { /// Creates details for [ViewChangedCallback]. @@ -23,7 +30,10 @@ class ViewChangedDetails { /// [targetElement]. /// /// See also: -/// [CalendarTapCallback] +/// * [SfCalendar.onTap], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [CalendarTapCallback], signature when any of the calendar elements +/// tapped. @immutable class CalendarTapDetails extends CalendarTouchDetails { /// Creates details for [CalendarTapCallback]. @@ -38,7 +48,10 @@ class CalendarTapDetails extends CalendarTouchDetails { /// [targetElement]. /// /// See also: -/// [CalendarLongPressCallback] +/// * [SfCalendar.onLongPress], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [CalendarLongPressCallback], signature when any of the calendar elements +/// long pressed. @immutable class CalendarLongPressDetails extends CalendarTouchDetails { /// Creates details for [CalendarLongPressCallback] @@ -53,7 +66,10 @@ class CalendarLongPressDetails extends CalendarTouchDetails { /// such as [date], and [resource]. /// /// See also: -/// [CalendarSelectionChangedCallback] +/// * [SfCalendar.onSelectionChanged], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [CalendarSelectionChangedCallback], signature when any of the selected +/// cell changed in calendar. @immutable class CalendarSelectionDetails { /// Creates details for [CalendarSelectionChangedCallback]. @@ -72,10 +88,11 @@ class CalendarSelectionDetails { /// Base class for [CalendarTapDetails] and [CalendarLongPressDetails]. /// /// See also: -/// [CalendarTapDetails] -/// [CalendarLongPressDetails] -/// [CalendarTapCallback] -/// [CalendarLongPressCallback] +/// [CalendarTapDetails], to pass the tapped details information. +/// [CalendarLongPressDetails], to pass the long pressed details information. +/// [CalendarTapCallback], signature when any calendar element tapped. +/// [CalendarLongPressCallback], signature when any calendar element is long +/// pressed. @immutable class CalendarTouchDetails { /// Creates details for [CalendarTapCallback] and [CalendarLongPressCallback]. @@ -96,38 +113,233 @@ class CalendarTouchDetails { final CalendarResource? resource; } +/// The appointment that starts resizing on view in [SfCalendar] +/// +/// Details for [AppointmentResizeStartCallback], such as [appointment], and +/// [resource] +/// +/// See also: +/// * [SfCalendar.onAppointmentResizeStart], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [AppointmentResizeStartCallback], signature when appointment starts +/// resizing in calendar. +class AppointmentResizeStartDetails { + /// Creates details for [AppointmentResizeStartCallback]. + const AppointmentResizeStartDetails(this.appointment, this.resource); + + /// The appointment that starts resizing on view in [SfCalendar]. + final dynamic appointment; + + /// The resource associated with the resizing appointment in timeline views. + final CalendarResource? resource; +} + +/// The appointment that resizing on view in [SfCalendar] +/// +/// Details for [AppointmentResizeUpdateCallback], such as [appointment], +/// [resizingTime], [resizingOffset] and [resource]. +/// +/// See also: +/// * [SfCalendar.onAppointmentResizeUpdate], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [AppointmentResizeUpdateCallback], signature when appointment resizing in +/// calendar. +class AppointmentResizeUpdateDetails { + /// Creates details for [AppointmentResizeUpdateCallback]. + const AppointmentResizeUpdateDetails( + this.appointment, this.resource, this.resizingTime, this.resizingOffset); + + /// The appointment that resizing on view in [SfCalendar]. + final dynamic appointment; + + /// The resource associated with the resizing appointment in timeline views. + final CalendarResource? resource; + + /// The current resizing time value of the appointment. + final DateTime? resizingTime; + + /// The current resize position of the appointment. + final Offset? resizingOffset; +} + +/// The appointment that resizing ends on view in [SfCalendar] +/// +/// Details for [AppointmentResizeEndCallback], such as [appointment], +/// [startTime], [endTime] and [resource]. +/// +/// See also: +/// * [SfCalendar.onAppointmentResizeEnd], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [AppointmentResizeEndCallback], signature when appointment resizing ends +/// in calendar. +class AppointmentResizeEndDetails { + /// Creates details for [AppointmentResizeUpdateCallback]. + const AppointmentResizeEndDetails( + this.appointment, this.resource, this.startTime, this.endTime); + + /// The appointment that resized on view in [SfCalendar]. + final dynamic appointment; + + /// The resource associated with the rescheduled appointment in timeline + /// views. + final CalendarResource? resource; + + /// The updated start time of the reschedule appointment. + final DateTime? startTime; + + /// The updated end time of the reschedule appointment. + final DateTime? endTime; +} + +/// The appointment that starts dragging on view in [SfCalendar] +/// +/// Details for [AppointmentDragStartCallback], such as [appointment], and +/// [resource] +/// +/// See also: +/// * [SfCalendar.onDragStart], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [AppointmentDragStartCallback], signature when appointment starts +/// dragging in calendar. +@immutable +class AppointmentDragStartDetails { + /// Creates details for [AppointmentDragStartCallback]. + const AppointmentDragStartDetails(this.appointment, this.resource); + + /// The dragging appointment details. + final Object? appointment; + + /// The resource of the dragging appointment. + final CalendarResource? resource; +} + +/// The appointment that dragging on view in [SfCalendar] +/// +/// Details for [AppointmentDragUpdateCallback], such as [appointment], +/// [sourceResource], [targetResource], [draggingPosition] and [draggingTime]. +/// +/// See also: +/// * [SfCalendar.onDragUpdate], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [AppointmentDragUpdateCallback], signature when appointment dragging in +/// calendar. +@immutable +class AppointmentDragUpdateDetails { + /// Creates details for [AppointmentDragUpdateCallback]. + const AppointmentDragUpdateDetails(this.appointment, this.sourceResource, + this.targetResource, this.draggingPosition, this.draggingTime); + + /// The dragging appointment. + final Object? appointment; + + /// The source resource of the dragging appointment. + final CalendarResource? sourceResource; + + /// The resource in which the appointment dragging. + final CalendarResource? targetResource; + + /// The current dragging position. + final Offset? draggingPosition; + + /// The current dragging time of the appointment. + final DateTime? draggingTime; +} + +/// The appointment that dragging ends on view in [SfCalendar] +/// +/// Details for [AppointmentDragEndCallback], such as [appointment], +/// [sourceResource], [targetResource] and [droppingTime]. +/// +/// See also: +/// * [SfCalendar.onDragEnd], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. +/// * [AppointmentDragEndCallback], signature when appointment dragging ends +/// in calendar. +class AppointmentDragEndDetails { + /// Creates details for [AppointmentDragEndCallback]. + const AppointmentDragEndDetails(this.appointment, this.sourceResource, + this.targetResource, this.droppingTime); + + /// The dropping appointment. + final Object? appointment; + + /// The source resource of the dropping appointment. + final CalendarResource? sourceResource; + + /// The dropping resource of the appointment. + final CalendarResource? targetResource; + + /// The dropping time. + final DateTime? droppingTime; +} + /// Signature for a function that creates a widget based on month /// header details. +/// +/// See also: +/// * [SfCalendar.scheduleViewMonthHeaderBuilder], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef ScheduleViewMonthHeaderBuilder = Widget Function( BuildContext context, ScheduleViewMonthHeaderDetails details); /// Signature for a function that creates a widget based on month cell details. +/// +/// See also: +/// * [SfCalendar.monthCellBuilder], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef MonthCellBuilder = Widget Function( BuildContext context, MonthCellDetails details); /// Signature for a function that creates a widget based on appointment details. +/// +/// See also: +/// * [SfCalendar.appointmentBuilder], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef CalendarAppointmentBuilder = Widget Function(BuildContext context, CalendarAppointmentDetails calendarAppointmentDetails); /// Signature for a function that creates a widget based on time region details. +/// +/// See also: +/// * [SfCalendar.timeRegionBuilder], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef TimeRegionBuilder = Widget Function( BuildContext context, TimeRegionDetails timeRegionDetails); /// Signature for the function that create the widget based on load /// more details. +/// +/// See also: +/// * [SfCalendar.loadMoreWidgetBuilder], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef LoadMoreWidgetBuilder = Widget Function( BuildContext context, LoadMoreCallback loadMoreAppointments); /// Signature for the function that have no arguments and return no data, but /// that return a [Future] to indicate when their work is complete. +/// +/// See also: +/// * [SfCalendar.loadMoreWidgetBuilder], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef LoadMoreCallback = Future Function(); /// Signature for a function that creates a widget based on resource /// header details. +/// +/// See also: +/// * [SfCalendar.resourceViewHeaderBuilder], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef ResourceViewHeaderBuilder = Widget Function( BuildContext context, ResourceViewHeaderDetails details); /// Contains the details that needed on month cell builder. +/// +/// Details for the [MonthCellBuilder], such as [date], [appointments], +/// [visibleDates] and [bounds]. +/// +/// See also: +/// * [SfCalendar.monthCellBuilder], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. class MonthCellDetails { /// Default constructor to store the details needed in month cell builder const MonthCellDetails( @@ -147,6 +359,11 @@ class MonthCellDetails { } /// Contains the details that needed on schedule view month header builder. +/// +/// See also: +/// * [SfCalendar.scheduleViewMonthHeaderBuilder], which receives the +/// information. +/// * [SfCalendar], which passes the information to one of its receiver. class ScheduleViewMonthHeaderDetails { /// Default constructor to store the details needed in builder const ScheduleViewMonthHeaderDetails(this.date, this.bounds); @@ -159,6 +376,10 @@ class ScheduleViewMonthHeaderDetails { } /// Contains the details that needed on appointment view builder. +/// +/// See also: +/// * [SfCalendar.appointmentBuilder], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. class CalendarAppointmentDetails { /// Default constructor to store the details needed in appointment builder. const CalendarAppointmentDetails(this.date, this.appointments, this.bounds, @@ -182,6 +403,10 @@ class CalendarAppointmentDetails { } /// Contains the details that needed on special region view builder. +/// +/// See also: +/// * [SfCalendar.timeRegionBuilder], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. class TimeRegionDetails { /// Default constructor to store the details needed in time region builder. TimeRegionDetails(this.region, this.date, this.bounds); @@ -198,6 +423,10 @@ class TimeRegionDetails { } /// Contains the details that needed on resource view header builder. +/// +/// See also: +/// * [SfCalendar.resourceViewHeaderBuilder], which receives the information. +/// * [SfCalendar], which passes the information to one of its receiver. class ResourceViewHeaderDetails { /// Default constructor to store the details needed in resource view /// header builder. @@ -218,6 +447,10 @@ class ResourceViewHeaderDetails { /// in the [ViewChangedDetails]. /// /// Used by [SfCalendar.onViewChanged]. +/// +/// See also: +/// * [SfCalendar.onViewChanged], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef ViewChangedCallback = void Function( ViewChangedDetails viewChangedDetails); @@ -227,6 +460,10 @@ typedef ViewChangedCallback = void Function( /// performed on element available in the [CalendarTapDetails]. /// /// Used by[SfCalendar.onTap]. +/// +/// See also: +/// * [SfCalendar.onTap], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef CalendarTapCallback = void Function( CalendarTapDetails calendarTapDetails); @@ -237,6 +474,10 @@ typedef CalendarTapCallback = void Function( /// action performed on element available in the [CalendarLongPressDetails]. /// /// Used by[SfCalendar.onLongPress]. +/// +/// See also: +/// * [SfCalendar.onLongPress], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef CalendarLongPressCallback = void Function( CalendarLongPressDetails calendarLongPressDetails); @@ -248,5 +489,96 @@ typedef CalendarLongPressCallback = void Function( /// performed on element available in the [CalendarSelectionDetails]. /// /// Used by[SfCalendar.onSelectionChanged]. +/// +/// See also: +/// * [SfCalendar.onSelectionChanged], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. typedef CalendarSelectionChangedCallback = void Function( CalendarSelectionDetails calendarSelectionDetails); + +/// Signature for callback that reports that a appointment starts resizing in +/// [SfCalendar]. +/// +/// The resizing appointment and resource details when the appointment starts +/// to resize available in the [AppointmentResizeStartDetails]. +/// +/// Used by[SfCalendar.onAppointmentResizeStart]. +/// +/// See also: +/// * [SfCalendar.onAppointmentResizeStart], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. +typedef AppointmentResizeStartCallback = void Function( + AppointmentResizeStartDetails appointmentResizeStartDetails); + +/// Signature for callback that reports that a appointment resizing in +/// [SfCalendar]. +/// +/// The resizing appointment, position, time and resource details when the +/// appointment resizing available in the [AppointmentResizeUpdateDetails]. +/// +/// Used by[SfCalendar.onAppointmentResizeUpdate]. +/// +/// See also: +/// * [SfCalendar.onAppointmentResizeUpdate], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. +typedef AppointmentResizeUpdateCallback = void Function( + AppointmentResizeUpdateDetails appointmentResizeUpdateDetails); + +/// Signature for callback that reports that a appointment resizing completed in +/// [SfCalendar]. +/// +/// The resizing appointment, start time, end time and resource details when +/// the appointment resizing ends available in the +/// [AppointmentResizeEndDetails]. +/// +/// Used by[SfCalendar.onAppointmentResizeEnd]. +/// +/// See also: +/// * [SfCalendar.onAppointmentResizeEnd], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. +typedef AppointmentResizeEndCallback = void Function( + AppointmentResizeEndDetails appointmentResizeEndDetails); + +/// Signature for callback that reports that a appointment starts dragging in +/// [SfCalendar]. +/// +/// The dragging appointment and resource details when the appointment starts +/// to drag available in the [AppointmentDragStartDetails]. +/// +/// Used by[SfCalendar.onDragStart]. +/// +/// See also: +/// * [SfCalendar.onDragStart], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. +typedef AppointmentDragStartCallback = void Function( + AppointmentDragStartDetails appointmentDragStartDetails); + +/// Signature for callback that reports that a appointment dragging in +/// [SfCalendar]. +/// +/// The dragging appointment, position, time, sourceResource and tergetResource +/// details when the appointment resizing available +/// in the [AppointmentDragUpdateDetails]. +/// +/// Used by[SfCalendar.onDragUpdate]. +/// +/// See also: +/// * [SfCalendar.onDragUpdate], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. +typedef AppointmentDragUpdateCallback = void Function( + AppointmentDragUpdateDetails appointmentDragUpdateDetails); + +/// Signature for callback that reports that a appointment dragging completed in +/// [SfCalendar]. +/// +/// The dragging appointment, sourceResource, targetResource and droppingTime +/// when the appointment dragging ends available in the +/// [AppointmentDragEndDetails]. +/// +/// Used by[SfCalendar.onDragEnd]. +/// +/// See also: +/// * [SfCalendar.onDragEnd], which matches this signature. +/// * [SfCalendar], which uses this signature in one of it's callback. +typedef AppointmentDragEndCallback = void Function( + AppointmentDragEndDetails appointmentDragEndDetails); 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 11b1b0cc7..1033c8fc3 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 @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; /// The resource data for calendar. /// @@ -19,6 +20,19 @@ import 'package:flutter/material.dart'; /// The [id] property must not be null, to filter appointments based on /// resource. /// +/// See also: +/// * [Appointment], the object to hold the data for the appointment in the +/// calendar. +/// * [TimeRegion], the object to hold the data for the special time region in +/// the calendar. +/// * [SfCalendar.resourceViewHeaderBuilder], to set custom widget for the +/// resource view in the calendar. +/// * [resourceViewSettings], to customize the resource view in the calendar. +/// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) +/// * Knowledge base: [How to handle appointments for multiple resources](https://www.syncfusion.com/kb/11812/how-to-handle-appointments-for-multiple-resources-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) +/// * Knowledge base: [How to add appointment for the selected resources using appointment editor](https://www.syncfusion.com/kb/12109/how-to-add-appointment-for-the-selected-resources-using-appointment-editor-in-the-flutter) +/// /// ``` dart /// ///@override @@ -82,9 +96,9 @@ class CalendarResource with Diagnosticable { /// [ResourceViewSettings.displayNameTextStyle] property. /// /// See also: - /// - /// * [ResourceViewSettings], the settings have properties which allow to - /// customize the resource view of the [SfCalendar]. + /// * [ResourceViewSettings.displayNameTextStyle], which allows to customize + /// the display name text in the resource view in [SfCalendar]. + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) /// /// ``` dart /// @@ -115,7 +129,6 @@ class CalendarResource with Diagnosticable { /// The unique id for the [CalendarResource] view of [SfCalendar]. /// /// See also: - /// /// * [Appointment.resourceIds], the ids of the [CalendarResource] that shares /// an [Appointment]. /// * [TimeRegion.resourceIds], the ids of the [CalendarResource] that shares @@ -154,6 +167,11 @@ class CalendarResource with Diagnosticable { /// /// Defaults to `Colors.lightBlue`. /// + /// See also: + /// * [ResourceViewSettings], to customize the resource view in the calendar. + /// * [SfCalendar.resourceViewHeaderBuilder], to customize the resource view + /// with a custom widget in calendar. + /// /// ``` dart /// ///DataSource _getCalendarDataSource() { @@ -186,12 +204,15 @@ class CalendarResource with Diagnosticable { /// `true`. /// /// See also: - /// - /// * [ResourceViewSettings], the settings have properties which allow to - /// customize the resource view of the [SfCalendar]. - /// * [ResourceViewSettings.showAvatar], shows a circle that represents a - /// user. - /// * [ImageProvider], commonly used to add image in flutter + /// * [ResourceViewSettings], which allows to customize the resource view in + /// the calendar. + /// * [ResourceViewSettings.showAvatar], allows to display the [image] on the + /// resource view. + /// * [ImageProvider], commonly used to add image in flutter. + /// * [SfCalendar.resourceViewHeaderBuilder], to customize the resource view + /// with a custom widget in calendar. + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) /// /// ``` dart /// 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 new file mode 100644 index 000000000..6ce0c634e --- /dev/null +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/settings/drag_and_drop_settings.dart @@ -0,0 +1,257 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// The settings have properties which allow to customize the drag and drop +/// environment of the [SfCalendar]. +/// +/// Allows to customize the [allowNavigation], [allowScroll], +/// [autoNavigateDelay], [indicatorTimeFormat], [showTimeIndicator] +/// and [timeIndicatorStyle] in the [SfCalendar]. +/// +/// See also: +/// * [monthViewSettings], which allows to customize the month view of +/// the calendar. +/// * [timeSlotViewSettings], which allows to customize the timeslot view +/// of the calendar. +/// +/// ``` dart +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: SfCalendar( +/// view: CalendarView.month, +/// showWeekNumber: true, +/// allowDragAndDrop: true, +/// dragAndDropSettings: DragAndDropSettings( +/// allowNavigation: true, +/// allowScroll: true, +/// autoNavigateDelay: Duration(seconds: 1), +/// indicatorTimeFormat: 'HH:mm a', +/// showTimeIndicator: true, +/// timeIndicatorStyle: TextStyle(color: Colors.cyan), +/// ), +/// ), +/// ); +/// } +@immutable +class DragAndDropSettings with Diagnosticable { + /// Creates a Drag and Drop settings for calendar. + /// + /// The properties allows to customize the Drag and Drop of [SfCalendar]. + const DragAndDropSettings({ + this.allowNavigation = true, + this.allowScroll = true, + this.showTimeIndicator = true, + this.timeIndicatorStyle, + this.indicatorTimeFormat = 'HH:mm a', + this.autoNavigateDelay = const Duration(seconds: 1), + }); + + /// Allows view navigation when the dragging appointment reaches the start or + /// end position of the view. + /// + /// See also: + /// * [allowScroll], which allows to auto scroll the timeslot views when it + /// reaches the start or end position of view port. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// ), + /// ); + /// } + final bool allowNavigation; + + /// Allows to scroll the view when the dragging appointment reaches the + /// view port start or end position in timeslot views. + /// + /// See also: + /// * [allowNavigation], which allows to navigate to next or previous view + /// when the dragging appointment reaches the start or end position + /// of the view. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// ), + /// ); + /// } + final bool allowScroll; + + /// Displays the time indicator on the time ruler view. + /// + /// The indicator will display the dragging appointment time on the time ruler + /// view of calendar. + /// + /// See also: + /// * [timeIndicatorStyle], which used to customize the time indicator text. + /// * [indicatorTimeFormat], which used to format the time indicator text + /// in calendar. + /// + /// Note: + /// When the [timeSlotViewSettings.timeRulerSize], when this property + /// set as 0, the time indicator will not displayed even + /// the [showTimeIndicator] set as true. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// ), + /// ); + /// } + final bool showTimeIndicator; + + /// Allows to set style for the time indicator text. + /// + /// See also: + /// * [showTimeIndicator], which allows to display the time indicator. + /// * [indicatorTimeFormat], which used to format the time indicator text + /// in calendar. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// timeIndicatorStyle: TextStyle(color: Colors.cyan), + /// ), + /// ), + /// ); + /// } + final TextStyle? timeIndicatorStyle; + + /// Allows to format the time indicator text. + /// + /// See also: + /// * [showTimeIndicator], which allows to display the time indicator. + /// * [timeIndicatorStyle], which used to customize the time indicator text. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// ), + /// ); + /// } + final String indicatorTimeFormat; + + ///The delay to hold the appointment on the view start or end position, + /// to navigate to next or previous view. + /// + /// See also: + /// * [allowViewNavigation], which allows to navigate to next or + /// previous date when the dragging appointment reaches the start or + /// end position of the view. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// ), + /// ); + /// } + final Duration autoNavigateDelay; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + late final DragAndDropSettings otherSetting; + if (other is DragAndDropSettings) { + otherSetting = other; + } + return otherSetting.allowNavigation == allowNavigation && + otherSetting.allowScroll == allowScroll && + otherSetting.showTimeIndicator == showTimeIndicator && + otherSetting.timeIndicatorStyle == timeIndicatorStyle && + otherSetting.indicatorTimeFormat == indicatorTimeFormat && + otherSetting.autoNavigateDelay == autoNavigateDelay; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty('allowNavigation', allowNavigation)); + properties.add(DiagnosticsProperty('allowScroll', allowScroll)); + properties + .add(DiagnosticsProperty('showTimeIndicator', showTimeIndicator)); + properties.add(DiagnosticsProperty( + 'timeIndicatorStyle', timeIndicatorStyle)); + properties.add(DiagnosticsProperty( + 'indicatorTimeFormat', indicatorTimeFormat)); + properties.add( + DiagnosticsProperty('autoNavigateDelay', autoNavigateDelay)); + } + + @override + int get hashCode { + return hashValues(allowNavigation, allowScroll, showTimeIndicator, + timeIndicatorStyle, indicatorTimeFormat, autoNavigateDelay); + } +} 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 27d391096..864e28fcc 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 @@ -8,6 +8,15 @@ import 'package:flutter/material.dart'; /// /// ![header with different style in calendar](https://help.syncfusion.com/flutter/calendar/images/headers/header-style.png) /// +/// See also: +/// * [SfCalendar.headerHeight], which allows to customize seize of the header +/// view in calendar. +/// * [SfCalendar.headerDateFormat], to format the date string in the header +/// view of calendar. +/// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12144/how-to-style-a-header-in-the-flutter-calendar) +/// * Knowledge base: [How to add custom header and view header](https://www.syncfusion.com/kb/10997/how-to-add-custom-header-and-view-header-in-the-flutter-calendar) +/// +/// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -36,6 +45,11 @@ class CalendarHeaderStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [SfCalendar.headerDateFormat], to format the date string in the header + /// view of calendar. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12144/how-to-style-a-header-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -55,6 +69,9 @@ class CalendarHeaderStyle with Diagnosticable { /// /// Defaults to `TextAlign.start`. /// + /// See also: + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12144/how-to-style-a-header-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -77,6 +94,11 @@ class CalendarHeaderStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [SfCalendar.headerHeight], which allows to customize seize of the header + /// view in calendar. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12144/how-to-style-a-header-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( 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 6f5fa6a9a..2c88ce0ba 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 @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; import '../common/enums.dart'; /// The settings have properties which allow to customize the month view of @@ -10,6 +11,29 @@ import '../common/enums.dart'; /// [showTrailingAndLeadingDates] and [navigationDirection] in month view of /// [SfCalendar]. /// +/// See also: +/// * [TimeSlotViewSettings], which is used to customize the timeslot view of +/// calendar. +/// * [SfCalendar.monthCellBuilder], which used to set the custom widget for +/// month cell in calendar. +/// * [SfCalendar.monthViewSettings], to know more about the customization of +/// month view in calendar. +/// * [SfCalendar.blackoutDates], which allows to restrict the interaction for a +/// particular date in month views of calendar. +/// * [SfCalendar.blackoutDatesTextStyle], which used to customize the blackout +/// dates text style in the month view of calendar. +/// * Knowledge base: [How to customize agenda view height based on widget height](https://www.syncfusion.com/kb/11013/how-to-customize-agenda-view-height-based-on-the-flutter-calendar-widget-height) +/// * Knowledge base: [How to customize agenda item height](https://www.syncfusion.com/kb/11015/how-to-customize-the-agenda-item-height-in-the-flutter-calendar) +/// * Knowledge base: [How to show appointment in agenda view using programmatic date selection](https://www.syncfusion.com/kb/11525/how-to-show-the-appointment-in-agenda-view-using-the-programmatic-date-selection-in-the) +/// * Knowledge base: [How to customize the blackout dates](https://www.syncfusion.com/kb/11987/how-to-customize-the-blackout-dates-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) +/// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) +/// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12157/how-to-change-the-number-of-weeks-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) +/// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) +/// * Knowledge base: [How to clear the appointment in month agenda view using onViewChanged callback](https://www.syncfusion.com/kb/12089/how-to-clear-the-appointments-in-month-agenda-view-using-onviewchange-callback-in-the) +/// /// ```dart /// ///Widget build(BuildContext context) { @@ -54,6 +78,11 @@ class MonthViewSettings with Diagnosticable { /// /// Defaults to `EE`. /// + /// See also: + /// * [ViewHeaderStyle], which is used to customize the view header view of + /// the calendar. + /// * Knowledge base: [How to format the view header day and date format](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -116,6 +145,21 @@ class MonthViewSettings with Diagnosticable { /// /// ![month agenda item height as 70](https://help.syncfusion.com/flutter/calendar/images/monthview/agenda-item-height.png) /// + /// See more: + /// * [showAgenda], which allows to display agenda view as a part of + /// month view in calendar. + /// * [agendaViewHeight], which is the size for agenda view on month view of + /// calendar. + /// * [agendaStyle], which is used to customize the agenda view on month view + /// of calendar. + /// * [appointmentDisplayMode], which allows to customize the display mode + /// of appointment view in month cells of calendar. + /// * Knowledge base: [How to customize agenda view height based on widget height](https://www.syncfusion.com/kb/11013/how-to-customize-agenda-view-height-based-on-the-flutter-calendar-widget-height) + /// * Knowledge base: [How to customize agenda item height](https://www.syncfusion.com/kb/11015/how-to-customize-the-agenda-item-height-in-the-flutter-calendar) + /// * Knowledge base: [How to show appointment in agenda view using programmatic date selection](https://www.syncfusion.com/kb/11525/how-to-show-the-appointment-in-agenda-view-using-the-programmatic-date-selection-in-the) + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// * Knowledge base: [How to clear the appointment in month agenda view using onViewChanged callback](https://www.syncfusion.com/kb/12089/how-to-clear-the-appointments-in-month-agenda-view-using-onviewchange-callback-in-the) + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -164,6 +208,21 @@ class MonthViewSettings with Diagnosticable { /// [MonthCellStyle.leadingDatesTextStyle] and /// [MonthCellStyle.trailingDatesTextStyle] properties in [MonthCellStyle]. /// + /// See also: + /// * [numberOfWeeksInView], which allows to customize the displaying week + /// count in month view of calendar. + /// * [monthCellStyle.leadingDatesBackgroundColor], which fills the background + /// of the leading dates cell in month view of calendar. + /// * [monthCellStyle.leadingDatesTextStyle], which is the style for the + /// leading dates text in month view of calendar. + /// * [monthCellStyle.trailingDatesBackgroundColor], which fills the + /// background of the trailing dates cell in month view of calendar. + /// * [monthCellStyle.trailingDatesTextStyle], which is the style for the + /// trailing dates text in month view of calendar. + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12157/how-to-change-the-number-of-weeks-in-the-flutter-calendar) + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -190,6 +249,25 @@ class MonthViewSettings with Diagnosticable { /// /// Defaults to null. /// + /// See more: + /// * [agendaStyle], which used to customize the agenda view on month view + /// of calendar. + /// * [appointmentDisplayMode], which is used to customize the appointment + /// display mode in month cells of calendar. + /// * [SfCalendar.monthCellBuilder], which used to set the custom widget for + /// month cell in calendar. + /// * [SfCalendar.blackoutDates], which allows to restrict the interaction for + /// a particular date in month views of calendar. + /// * [SfCalendar.blackoutDatesTextStyle], which used to customize the + /// blackout dates text style in the month view of calendar. + /// * [SfCalendarTheme], to handle theming with calendar for giving consistent + /// look. + /// * Knowledge base: [How to customize the blackout dates](https://www.syncfusion.com/kb/11987/how-to-customize-the-blackout-dates-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -251,6 +329,22 @@ class MonthViewSettings with Diagnosticable { /// [AgendaStyle.dayTextStyle], [AgendaStyle.dateTextStyle] and /// [AgendaStyle.appointmentTextStyle] in month agenda view of calendar. /// + /// See also: + /// * [monthCellStyle], which used to customize the month cell of month view + /// in calendar. + /// * [showAgenda], which allows to display agenda view as a part of + /// month view in calendar. + /// * [agendaViewHeight], which is the size for agenda view on month view of + /// calendar. + /// * [appointmentDisplayMode], which is used to customize the appointment + /// display mode in month cells of calendar. + /// * Knowledge base: [How to customize agenda view height based on widget height](https://www.syncfusion.com/kb/11013/how-to-customize-agenda-view-height-based-on-the-flutter-calendar-widget-height) + /// * Knowledge base: [How to customize agenda item height](https://www.syncfusion.com/kb/11015/how-to-customize-the-agenda-item-height-in-the-flutter-calendar) + /// * Knowledge base: [How to show appointment in agenda view using programmatic date selection](https://www.syncfusion.com/kb/11525/how-to-show-the-appointment-in-agenda-view-using-the-programmatic-date-selection-in-the) + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// * Knowledge base: [How to clear the appointment in month agenda view using onViewChanged callback](https://www.syncfusion.com/kb/12089/how-to-clear-the-appointments-in-month-agenda-view-using-onviewchange-callback-in-the) + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -282,7 +376,6 @@ class MonthViewSettings with Diagnosticable { /// ); /// } /// ``` - final AgendaStyle agendaStyle; /// The number of weeks to display in [ SfCalendar ]'s month view. @@ -292,7 +385,14 @@ class MonthViewSettings with Diagnosticable { /// _Note:_ If this property is set to a value less than or equal to ' 4, ' /// the trailing and lead dates style will not be updated. /// - /// See also: [MonthCellStyle] to know about leading and trailing dates style. + /// See also: + /// * [showTrailingAndLeadingDates], which used to restrict the rendering of + /// leading and trailing dates on month view of calendar. + /// * [showAgenda], which allows to display agenda view as a part of + /// month view in calendar. + /// * [agendaViewHeight], which is the size for agenda view on month view of + /// calendar. + /// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12157/how-to-change-the-number-of-weeks-in-the-flutter-calendar) /// /// ```dart ///Widget build(BuildContext context) { @@ -326,6 +426,15 @@ class MonthViewSettings with Diagnosticable { /// less (available for only 4 dots) and the indicator count is 10, then 4 /// indicators will be shown /// + /// See also: + /// * [appointmentDisplayMode], which allows to customize the display mode + /// of appointment view in month cells of calendar. + /// * [showAgenda], which allows to display agenda view as a part of + /// month view in calendar. + /// * [agendaViewHeight], which is the size for agenda view on month view of + /// calendar. + /// * [agendaItemHeight], which is the size for every single appointment view + /// in agenda view of month view in calendar. /// /// ```dart ///Widget build(BuildContext context) { @@ -349,7 +458,19 @@ class MonthViewSettings with Diagnosticable { /// /// Defaults to `MonthAppointmentDisplayMode.indicator`. /// - /// Also refer: [MonthAppointmentDisplayMode]. + /// See also: + /// * [MonthAppointmentDisplayMode], to know more about the available display + /// options for appointment view in month cell of calendar. + /// * [appointmentDisplayCount], which allows to customize the number of + /// appointment displaying on a month cell of month view in calendar. + /// * [showAgenda], which allows to display agenda view as a part of + /// month view in calendar. + /// * [agendaViewHeight], which is the size for agenda view on month view of + /// calendar. + /// * [agendaItemHeight], which is the size for every single appointment view + /// in agenda view of month view in calendar. + /// * Knowledge base: [How to handle the appointment display mode](https://www.syncfusion.com/kb/12338/how-to-handle-the-appointment-display-mode-in-the-flutter-calendar) + /// /// /// ```dart ///Widget build(BuildContext context) { @@ -385,8 +506,20 @@ class MonthViewSettings with Diagnosticable { /// ![calendar month view with agenda](https://help.syncfusion.com/flutter/calendar/images/monthview/appointment-indicator-count.png) /// /// see also: - /// [agendaHeight]. - /// [agendaItemHeight] + /// * [agendaViewHeight], which is the size for agenda view on month view of + /// calendar. + /// * [agendaItemHeight], which is the size for every appointment in the + /// agenda view of month view in calendar. + /// * [agendaStyle], which is used to customize the agenda view on month view + /// of calendar. + /// * [appointmentDisplayMode], which allows to customize the display mode + /// of appointment view in month cells of calendar. + /// * Knowledge base: [How to customize agenda view height based on widget height](https://www.syncfusion.com/kb/11013/how-to-customize-agenda-view-height-based-on-the-flutter-calendar-widget-height) + /// * Knowledge base: [How to customize agenda item height](https://www.syncfusion.com/kb/11015/how-to-customize-the-agenda-item-height-in-the-flutter-calendar) + /// * Knowledge base: [How to show appointment in agenda view using programmatic date selection](https://www.syncfusion.com/kb/11525/how-to-show-the-appointment-in-agenda-view-using-the-programmatic-date-selection-in-the) + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// * Knowledge base: [How to clear the appointment in month agenda view using onViewChanged callback](https://www.syncfusion.com/kb/12089/how-to-clear-the-appointments-in-month-agenda-view-using-onviewchange-callback-in-the) + /// * Knowledge base: [How to show a custom agenda view](https://www.syncfusion.com/kb/11016/how-to-show-a-custom-agenda-view-in-the-flutter-calendar) /// /// ```dart ///Widget build(BuildContext context) { @@ -413,6 +546,22 @@ class MonthViewSettings with Diagnosticable { /// /// ![month agenda view height as 400](https://help.syncfusion.com/flutter/calendar/images/monthview/agendaview-height.png) /// + /// See also: + /// * [showAgenda], which allows to display agenda view as a part of + /// month view in calendar. + /// * [agendaItemHeight], which is the size for every appointment in the + /// agenda view of month view in calendar. + /// * [agendaStyle], which is used to customize the agenda view on month view + /// of calendar. + /// * [appointmentDisplayMode], which allows to customize the display mode + /// of appointment view in month cells of calendar. + /// * Knowledge base: [How to customize agenda view height based on widget height](https://www.syncfusion.com/kb/11013/how-to-customize-agenda-view-height-based-on-the-flutter-calendar-widget-height) + /// * Knowledge base: [How to customize agenda item height](https://www.syncfusion.com/kb/11015/how-to-customize-the-agenda-item-height-in-the-flutter-calendar) + /// * Knowledge base: [How to show appointment in agenda view using programmatic date selection](https://www.syncfusion.com/kb/11525/how-to-show-the-appointment-in-agenda-view-using-the-programmatic-date-selection-in-the) + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// * Knowledge base: [How to clear the appointment in month agenda view using onViewChanged callback](https://www.syncfusion.com/kb/12089/how-to-clear-the-appointments-in-month-agenda-view-using-onviewchange-callback-in-the) + /// * Knowledge base: [How to show a custom agenda view](https://www.syncfusion.com/kb/11016/how-to-show-a-custom-agenda-view-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -440,7 +589,15 @@ class MonthViewSettings with Diagnosticable { /// /// Defaults to `MonthNavigationDirection.horizontal`. /// - /// Also refer: [MonthNavigationDirection]. + /// See also: + /// * [MonthNavigationDirection], to know more about the available navigation + /// direction in month view of calendar. + /// * [SfCalendar.viewNavigationMode], which allows to customize the view + /// navigation mode for calendar. + /// * [SfCalendar.showNavigationArrow], which allows to navigation to previous + /// and next view of calendar programmatically. + /// * Knowledge base: [How to restrict the view navigation](https://www.syncfusion.com/kb/12554/how-to-restrict-the-view-navigation-in-the-flutter-calendar) + /// * Knowledge base: [How to navigate to the previous or next view using navigation arrows](https://www.syncfusion.com/kb/12247/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter) /// /// ```dart ///Widget build(BuildContext context) { @@ -530,6 +687,22 @@ class MonthViewSettings with Diagnosticable { /// Allows to customize the [backgroundColor], [dayTextStyle], [dateTextStyle] /// and [appointmentTextStyle] in month agenda view of calendar. /// +/// See also: +/// * [MonthCellStyle], which used to customize the month cell of month view +/// in calendar. +/// * [MonthViewSettings.showAgenda], which allows to display agenda view as a +/// part of month view in calendar. +/// * [MonthViewSettings.agendaViewHeight], which is the size for agenda view on +/// month view of calendar. +/// * [MonthViewSettings.appointmentDisplayMode], which is used to customize the +/// appointment display mode in month cells of calendar. +/// * Knowledge base: [How to customize agenda view height based on widget height](https://www.syncfusion.com/kb/11013/how-to-customize-agenda-view-height-based-on-the-flutter-calendar-widget-height) +/// * Knowledge base: [How to customize agenda item height](https://www.syncfusion.com/kb/11015/how-to-customize-the-agenda-item-height-in-the-flutter-calendar) +/// * Knowledge base: [How to show appointment in agenda view using programmatic date selection](https://www.syncfusion.com/kb/11525/how-to-show-the-appointment-in-agenda-view-using-the-programmatic-date-selection-in-the) +/// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) +/// * Knowledge base: [How to clear the appointment in month agenda view using onViewChanged callback](https://www.syncfusion.com/kb/12089/how-to-clear-the-appointments-in-month-agenda-view-using-onviewchange-callback-in-the) +/// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) +/// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -581,6 +754,18 @@ class AgendaStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [SfCalendar.appointmentBuilder], which used to set custom widget for + /// appointment view in calendar + /// * [dayTextStyle], which used to customize the day text in the agenda + /// view of calendar. + /// * [dateTextStyle], which used to customize the date text in the agenda + /// view of calendar. + /// * [backgroundColor], which fills the background of the agenda view in + /// calendar. + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -622,6 +807,16 @@ class AgendaStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [appointmentTextStyle], which used to customize the text on the + /// agenda view of calendar. + /// * [dateTextStyle], which used to customize the date text in the + /// agenda view of calendar. + /// * [backgroundColor], which fills the background of the agenda view in + /// calendar. + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// * Knowledge base: [How to show a custom agenda view](https://www.syncfusion.com/kb/11016/how-to-show-a-custom-agenda-view-in-the-flutter-calendar) + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -663,6 +858,16 @@ class AgendaStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [appointmentTextStyle], which used to customize the text on the + /// agenda view of calendar. + /// * [dayTextStyle], which used to customize the day text in the + /// agenda view of calendar. + /// * [backgroundColor], which fills the background of the agenda view in + /// calendar. + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// * Knowledge base: [How to show a custom agenda view](https://www.syncfusion.com/kb/11016/how-to-show-a-custom-agenda-view-in-the-flutter-calendar) + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -704,6 +909,16 @@ class AgendaStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [appointmentTextStyle], which used to customize the text on the + /// agenda view of calendar. + /// * [dateTextStyle], which used to customize the date text in the + /// agenda view of calendar. + /// * [dayTextStyle], which used to customize the day text in the + /// agenda view of calendar. + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// * Knowledge base: [How to show a custom agenda view](https://www.syncfusion.com/kb/11016/how-to-show-a-custom-agenda-view-in-the-flutter-calendar) + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -786,6 +1001,25 @@ class AgendaStyle with Diagnosticable { /// [leadingDatesBackgroundColor] and [trailingDatesBackgroundColor] in month /// cells of month view in calendar. /// +/// See more: +/// * [AgendaStyle], which used to customize the agenda view on month view +/// of calendar. +/// * [MonthViewSettings.appointmentDisplayMode], which is used to customize the +/// appointment display mode in month cells of calendar. +/// * [SfCalendar.monthCellBuilder], which used to set the custom widget for +/// month cell in calendar. +/// * [SfCalendar.blackoutDates], which allows to restrict the interaction for +/// a particular date in month views of calendar. +/// * [SfCalendar.blackoutDatesTextStyle], which used to customize the +/// blackout dates text style in the month view of calendar. +/// * [SfCalendarTheme], to handle theming with calendar for giving consistent +/// look. +/// * Knowledge base: [How to customize the blackout dates](https://www.syncfusion.com/kb/11987/how-to-customize-the-blackout-dates-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) +/// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) +/// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -866,6 +1100,19 @@ class MonthCellStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [backgroundColor], which fills the background of the month cell in + /// calendar. + /// * [leadingDatesTextStyle], which used to customize the text style for + /// the leading dates text in month cell of month view. + /// * [trailingDatesTextStyle], which used to customize the text style for + /// the trailing dates text in month cell of month view. + /// * [SfCalendar.todayTextStyle], which used to customize the text style + /// for the today text cell in the month view of calendar. + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -949,6 +1196,20 @@ class MonthCellStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [textStyle], which used to customize the text style for the text in + /// month cell of calendar. + /// * [leadingDatesTextStyle], which used to customize the text style for + /// the leading dates text in month cell of month view. + /// * [SfCalendar.todayTextStyle], which used to customize the text style + /// for the today text cell in the month view of calendar. + /// * [trailingDatesBackgroundColor], which fills the background of the + /// trailing dates cells of month view in calendar. + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -990,6 +1251,20 @@ class MonthCellStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [textStyle], which used to customize the text style for the text in + /// month cell of calendar. + /// * [trailingDatesTextStyle], which used to customize the text style for + /// the trailing dates text in month cell of month view. + /// * [SfCalendar.todayTextStyle], which used to customize the text style + /// for the today text cell in the month view of calendar. + /// * [leadingDatesBackgroundColor], which fills the background of the + /// leading dates cells of month view in calendar. + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -1031,6 +1306,20 @@ class MonthCellStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [todayBackgroundColor], which used to fill the background of the today + /// date month cell in the calendar. + /// * [trailingDatesBackgroundColor], which fills the background of the + /// trailing dates cells of month view in calendar. + /// * [leadingDatesBackgroundColor], which fills the background of the + /// leading dates cells of month view in calendar. + /// * [textStyle], which used to customize the text style of texts in month + /// cell in month view of calendar. + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -1072,6 +1361,20 @@ class MonthCellStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [backgroundColor], which used to fill the background of the month cells + /// in the calendar. + /// * [trailingDatesBackgroundColor], which fills the background of the + /// trailing dates cells of month view in calendar. + /// * [leadingDatesBackgroundColor], which fills the background of the + /// leading dates cells of month view in calendar. + /// * [SfCalendar.todayTextStyle], which used to customize the text style of + /// texts in today date month cell of calendar. + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -1113,6 +1416,20 @@ class MonthCellStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [backgroundColor], which used to fill the background of the month cells + /// in the calendar. + /// * [todayBackgroundColor], which fills the background of the today date + /// cell of month view in calendar. + /// * [leadingDatesBackgroundColor], which fills the background of the + /// leading dates cells of month view in calendar. + /// * [trailingDatesTextStyle], which used to customize the text style of + /// text in the trailing dates month cell of calendar. + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -1154,6 +1471,20 @@ class MonthCellStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [backgroundColor], which used to fill the background of the month cells + /// in the calendar. + /// * [todayBackgroundColor], which fills the background of the today date + /// cell of month view in calendar. + /// * [trailingDatesBackgroundColor], which fills the background of the + /// trailing dates cells of month view in calendar. + /// * [leadingDatesTextStyle], which used to customize the text style of + /// text in the leading dates month cell of calendar. + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the month cell based on the appointment using builder](https://www.syncfusion.com/kb/12210/how-to-customize-the-month-cell-based-on-the-appointment-using-builder-in-the-flutter) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( 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 b35486033..b75e601bb 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 @@ -8,10 +8,14 @@ import 'package:flutter/material.dart'; /// [size], and [displayNameTextStyle] in resource view of calendar. /// /// See also: +/// * [CalendarResource], which holds the data for the resource in the +/// * [CalendarDataSource.resources], which used to set and handle the resource +/// collection for the calendar. +/// * [SfCalendar.resourceViewHeaderBuilder], which used to set custom widget +/// for the resource view header in calendar. +/// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) +/// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) /// -/// * [CalendarResource], the resource data for calendar. -/// * [CalendarDataSource.resources], the collection of resource to be displayed -/// in the timeline views of [SfCalendar]. /// /// ```dart ///@override @@ -76,10 +80,12 @@ class ResourceViewSettings with Diagnosticable { /// [SfCalendar] /// /// See also: - /// - /// * [CalendarResource], the resource data for calendar. - /// * [CalendarDataSource.resources], the collection of resource to be - /// displayed in the timeline views of [SfCalendar]. + /// * [CalendarResource], the object which holds the data for the resource in + /// the calendar + /// * [CalendarDataSource.resources], which set and handle the resource + /// collection for the calendar. + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) /// /// ```dart ///@override @@ -108,10 +114,14 @@ class ResourceViewSettings with Diagnosticable { /// appearance of various components of the calendar. /// /// See also: - /// - /// * [CalendarResource], the resource data for calendar. - /// * [CalendarDataSource.resources], the collection of resource to be - /// displayed in the timeline views of [SfCalendar]. + /// * [CalendarResource], the object which holds the data for the resource in + /// the calendar + /// * [CalendarDataSource.resources], which set and handle the resource + /// collection for the calendar. + /// * [SfCalendar.resourceViewHeaderBuilder], which allows to set custom + /// widget for the resource view header.in calendar. + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) /// /// ```dart ///@override @@ -140,10 +150,14 @@ class ResourceViewSettings with Diagnosticable { /// Defaults to `75`. /// /// See also: - /// - /// * [CalendarResource], the resource data for calendar. - /// * [CalendarDataSource.resources], the collection of resource to be - /// displayed in the timeline views of [SfCalendar]. + /// * [CalendarResource], the object which holds the data for the resource in + /// the calendar + /// * [CalendarDataSource.resources], which set and handle the resource + /// collection for the calendar. + /// * [SfCalendar.resourceViewHeaderBuilder], which allows to set custom + /// widget for the resource view header.in calendar. + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) /// /// ```dart ///@override @@ -175,10 +189,14 @@ class ResourceViewSettings with Diagnosticable { /// /// /// See also: - /// - /// * [CalendarResource], the resource data for calendar. - /// * [CalendarDataSource.resources], the collection of resource to be - /// displayed in the timeline views of [SfCalendar]. + /// * [CalendarResource], the object which holds the data for the resource in + /// the calendar + /// * [CalendarDataSource.resources], which set and handle the resource + /// collection for the calendar. + /// * [SfCalendar.resourceViewHeaderBuilder], which allows to set custom + /// widget for the resource view header.in calendar. + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) /// /// ```dart ///@override 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 25f0834a2..8567b733c 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 @@ -8,6 +8,21 @@ import 'package:flutter/material.dart'; /// [dayHeaderSettings], [appointmentTextStyle], [appointmentItemHeight] and /// [hideEmptyScheduleWeek] in schedule view of calendar. /// +/// See also: +/// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom +/// widget for month header view of schedule view in calendar. +/// * [SfCalendar.scheduleViewSettings], to know about the available +/// customization options for schedule view. +/// * [MonthViewSettings], to know more about the customization options for +/// the month view of calendar. +/// * [TimeSlotViewSettings], which is used to customize the timeslot view of +/// calendar. +/// * Knowledge base: [How to customize appointment height in schedule view](https://www.syncfusion.com/kb/12226/how-to-customize-the-appointment-height-in-schedule-view-of-the-flutter-calendar) +/// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) +/// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) +/// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) +/// /// ``` dart /// /// @override @@ -48,6 +63,18 @@ class ScheduleViewSettings with Diagnosticable { /// [MonthHeaderSettings.labelTextStyle] in month label style of schedule view /// in calendar. /// + /// See also: + /// * [weekHeaderSettings], which used to customize the week header view + /// of the schedule view in calendar. + /// * [dayHeaderSettings], which used to customize the day header view of the + /// schedule view in calendar. + /// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom + /// widget for month header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// ///@override @@ -80,6 +107,17 @@ class ScheduleViewSettings with Diagnosticable { /// [WeekHeaderSettings.labelTextStyle] in week label style of schedule view /// in calendar. /// + /// See also: + /// * [monthHeaderSettings], which used to customize the month header view + /// of the schedule view in calendar. + /// * [dayHeaderSettings], which used to customize the day header view of the + /// schedule view in calendar. + /// * [hideEmptyScheduleWeek], which used to hide the week header if the week + /// doesn't have any appointment on it. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -113,6 +151,15 @@ class ScheduleViewSettings with Diagnosticable { /// [DayHeaderSettings.dayTextStyle] and [DayHeaderSettings.dateTextStyle] in /// day label style of schedule view in calendar. /// + /// See also: + /// * [weekHeaderSettings], which used to customize the week header view + /// of the schedule view in calendar. + /// * [monthHeaderSettings], which used to customize the month header view of + /// the schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -149,6 +196,15 @@ class ScheduleViewSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [appointmentItemHeight], which is the size for the appointment view in + /// the schedule view of calendar. + /// * [SfCalendar.appointmentBuilder], which used to set custom widget for + /// appointment view in calendar + /// * Knowledge base: [How to customize appointment height in schedule view](https://www.syncfusion.com/kb/12226/how-to-customize-the-appointment-height-in-schedule-view-of-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -171,6 +227,17 @@ class ScheduleViewSettings with Diagnosticable { /// view of [SfCalendar],. /// /// Defaults to `-1`. + /// + /// See also: + /// * [appointmentTextStyle], which used to customize the text style for the + /// text on the appointment view in calendar. + /// * [SfCalendar.appointmentBuilder], which used to set custom widget for + /// appointment view in calendar + /// * Knowledge base: [How to customize appointment height in schedule view](https://www.syncfusion.com/kb/12226/how-to-customize-the-appointment-height-in-schedule-view-of-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// + /// /// ``` dart /// /// @override @@ -192,6 +259,16 @@ class ScheduleViewSettings with Diagnosticable { /// /// Defaults to false. /// + /// See more: + /// * [weekHeaderSettings], which used to customize the week header view + /// of the schedule view in calendar. + /// * [appointmentItemHeight], which is the size for the appointment view in + /// the schedule view of calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -265,6 +342,18 @@ class ScheduleViewSettings with Diagnosticable { /// [backgroundColor] and [monthTextStyle] in month label style of schedule view /// in calendar. /// +/// See also: +/// * [WeekHeaderSettings], which used to customize the week header view +/// of the schedule view in calendar. +/// * [DayHeaderSettings], which used to customize the day header view of the +/// schedule view in calendar. +/// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom +/// widget for month header view of schedule view in calendar. +/// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) +/// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) +/// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) +/// /// ``` dart /// ///@override @@ -306,6 +395,18 @@ class MonthHeaderSettings with Diagnosticable { /// /// Defaults to `MMMM yyyy`. /// + /// See also: + /// * [monthTextStyle], which used to customize the text style for the text + /// in the month header view in calendar. + /// * [textAlign], which aligns the month header text in the month header view + /// of calendar. + /// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom + /// widget for month header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -328,6 +429,16 @@ class MonthHeaderSettings with Diagnosticable { /// /// Defaults to `150`. /// + /// See also: + /// * [backgroundColor], which fills the background of the month header view + /// of schedule view in calendar. + /// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom + /// widget for month header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -349,6 +460,18 @@ class MonthHeaderSettings with Diagnosticable { /// /// Defaults to `TextAlign.center`. /// + /// See also: + /// * [monthTextStyle], which used to customize the text style for the text + /// in the month header view in calendar. + /// * [monthFormat], which formats the text on the month header view of + /// schedule view in calendar. + /// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom + /// widget for month header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -374,6 +497,16 @@ class MonthHeaderSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [height], which is the size for the month header view of schedule view + /// in calendar. + /// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom + /// widget for month header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -399,6 +532,18 @@ class MonthHeaderSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [textAlign], which aligns the month header text in the month header view + /// of calendar. + /// * [monthFormat], which formats the text on the month header view of + /// schedule view in calendar. + /// * [SfCalendar.scheduleViewMonthHeaderBuilder], which used to set custom + /// widget for month header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -463,6 +608,17 @@ class MonthHeaderSettings with Diagnosticable { /// [textAlign], [backgroundColor] and [weekTextStyle] in week label style of /// schedule view in calendar. /// +/// See also: +/// * [MonthHeaderSettings], which used to customize the month header view +/// of the schedule view in calendar. +/// * [DayHeaderSettings], which used to customize the day header view of the +/// schedule view in calendar. +/// * [ScheduleViewSettings.hideEmptyScheduleWeek], which used to hide the week +/// header if the week doesn't have any appointment on it. +/// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) +/// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) +/// /// ``` dart /// /// @override @@ -507,6 +663,17 @@ class WeekHeaderSettings with Diagnosticable { /// /// Defaults to null. /// + /// See also: + /// * [endDateFormat], which used to format the end date text in the week + /// header view of schedule view in calendar. + /// * [textAlign], which used to align the text on the week header view of + /// schedule view in calendar. + /// * [weekTextStyle], which used to customize the text style for the text in + /// the week header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -530,6 +697,17 @@ class WeekHeaderSettings with Diagnosticable { /// /// Defaults to null. /// + /// See also: + /// * [startDateFormat], which used to format the start date text in the week + /// header view of schedule view in calendar. + /// * [textAlign], which used to align the text on the week header view of + /// schedule view in calendar. + /// * [weekTextStyle], which used to customize the text style for the text in + /// the week header view of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -553,6 +731,21 @@ class WeekHeaderSettings with Diagnosticable { /// /// Defaults to `30`. /// + /// See also: + /// * [backgroundColor], which used to fill the background of the week header + /// view in the schedule view of calendar. + /// * [weekTextStyle], which used to customize the text style for the text in + /// the week header view of schedule view in calendar. + /// * [startDateFormat], which used to format the start date text in the week + /// header view of schedule view in calendar. + /// * [endDateFormat], which used to format the end date text in the week + /// header view of schedule view in calendar. + /// * [textAlign], which used to align the text on the week header view of + /// schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -576,6 +769,21 @@ class WeekHeaderSettings with Diagnosticable { /// /// Defaults to `TextAlign.start`. /// + /// See also: + /// * [backgroundColor], which used to fill the background of the week header + /// view in the schedule view of calendar. + /// * [weekTextStyle], which used to customize the text style for the text in + /// the week header view of schedule view in calendar. + /// * [startDateFormat], which used to format the start date text in the week + /// header view of schedule view in calendar. + /// * [endDateFormat], which used to format the end date text in the week + /// header view of schedule view in calendar. + /// * [height], which is the size for the week header view in the schedule + /// view of calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -601,6 +809,21 @@ class WeekHeaderSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [height], which is the size for the week header view in the schedule + /// view of calendar. + /// * [weekTextStyle], which used to customize the text style for the text in + /// the week header view of schedule view in calendar. + /// * [startDateFormat], which used to format the start date text in the week + /// header view of schedule view in calendar. + /// * [endDateFormat], which used to format the end date text in the week + /// header view of schedule view in calendar. + /// * [textAlign], which used to align the text on the week header view of + /// schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -626,6 +849,21 @@ class WeekHeaderSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [backgroundColor], which used to fill the background of the week header + /// view in the schedule view of calendar. + /// * [height], which is the size for the week header view in the schedule + /// view of calendar. + /// * [startDateFormat], which used to format the start date text in the week + /// header view of schedule view in calendar. + /// * [endDateFormat], which used to format the end date text in the week + /// header view of schedule view in calendar. + /// * [textAlign], which used to align the text on the week header view of + /// schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -689,6 +927,15 @@ class WeekHeaderSettings with Diagnosticable { /// Allows to customize the [dayFormat], [width], [dayTextStyle] /// and [dateTextStyle] in day label style of schedule view in calendar. /// +/// See also: +/// * [WeekHeaderSettings], which used to customize the week header view +/// of the schedule view in calendar. +/// * [MonthHeaderSettings], which used to customize the month header view of +/// the schedule view in calendar. +/// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) +/// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) +/// /// ``` dart /// /// @override @@ -732,6 +979,15 @@ class DayHeaderSettings with Diagnosticable { /// /// Defaults to `EEE`. /// + /// See also: + /// * [dayTextStyle], which used to customize the text style for the day text + /// in the day header of schedule view in calendar. + /// * [dateTextStyle], which used to customize the text style for the date + /// text in the day header of schedule view in calendar + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -753,6 +1009,17 @@ class DayHeaderSettings with Diagnosticable { /// /// Defaults to `50`. /// + /// See also: + /// * [dayTextStyle], which used to customize the text style for the day text + /// in the day header of schedule view in calendar. + /// * [dateTextStyle], which used to customize the text style for the date + /// text in the day header of schedule view in calendar. + /// * [dayFormat], which used to format the day text in the day header view + /// of schedule view in calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -778,6 +1045,17 @@ class DayHeaderSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [dateTextStyle], which used to customize the text style for the date + /// text in the day header of schedule view in calendar. + /// * [dayFormat], which used to format the day text in the day header view + /// of schedule view in calendar. + /// * [width], which is the size for the week header view of schedule view of + /// calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -808,6 +1086,17 @@ class DayHeaderSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [dayTextStyle], which used to customize the text style for the day text + /// in the day header of schedule view in calendar. + /// * [dayFormat], which used to format the day text in the day header view + /// of schedule view in calendar. + /// * [width], which is the size for the week header view of schedule view of + /// calendar. + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override 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 107efe12b..f6ec5d814 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 @@ -1,5 +1,7 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart' show IterableDiagnostics; @@ -10,6 +12,18 @@ import 'package:syncfusion_flutter_datepicker/datepicker.dart' /// Note: If time region have both the [text] and [iconData] then the region /// will draw icon only. /// +/// See also: +/// * [SfCalendar.timeRegionBuilder], to set custom widget for the time regions +/// in the calendar +/// * [SfCalendar.specialRegions], which allows to set and handle the time +/// region collection fo the calendar and date range picker. +/// * Knowledge base: [How to customize special regions with builder](https://www.syncfusion.com/kb/12192/how-to-customize-the-special-time-region-using-custom-builder-in-the-flutter-calendar) +/// * Knowledge base: [How to create time table](https://www.syncfusion.com/kb/12392/how-to-create-time-table-using-flutter-event-calendar) +/// * Knowledge base: [How to add a special region dynamically using onTap and onViewChanged](https://www.syncfusion.com/kb/11729/how-to-add-a-special-region-dynamically-using-ontap-onviewchanged-callbacks-of-the-flutter) +/// * Knowledge base: [How to use multiple recurrence rule in special region](https://www.syncfusion.com/kb/11730/how-to-use-multiple-recurrence-rule-rrule-in-special-region-using-flutter-calendar) +/// * Knowledge base: [How to highlight the weekends](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) +/// * Knowledge base: [How to highlight the lunch hours](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) +/// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -56,6 +70,14 @@ class TimeRegion with Diagnosticable { /// /// Defaults to 'DateTime.now()'. /// + /// See also: + /// * [endTime], the date time value in which the time region will end. + /// * [timeZone], the time zone for the time region, the region will be render + /// by converting the given time based on [timeZone] and + /// [SfCalendar.timeZone]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -87,6 +109,14 @@ class TimeRegion with Diagnosticable { /// /// Defaults to 'DateTime.now()'. /// + /// See also: + /// * [startTime], the date time value in which the time region will start. + /// * [timeZone], the time zone for the time region, the region will be render + /// by converting the given time based on [timeZone] and + /// [SfCalendar.timeZone]. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -117,6 +147,14 @@ class TimeRegion with Diagnosticable { /// Note: If time region have both the text and icon data then it will draw /// icon only. /// + /// See also: + /// * [iconData], the icon which will be displayed on the time region view. + /// * [textStyle], which used to customize the style of the text on the time + /// region view. + /// * Knowledge base: [How to highlight the weekends](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight the lunch hours](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// * Knowledge base: [How to add a special region dynamically using onTap and onViewChanged](https://www.syncfusion.com/kb/11729/how-to-add-a-special-region-dynamically-using-ontap-onviewchanged-callbacks-of-the-flutter) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -148,6 +186,17 @@ class TimeRegion with Diagnosticable { /// /// Defaults to null. /// + /// See also; + /// * [RecurrenceProperties], which used to create the recurrence rule based + /// on the values set to these properties. + /// * [SfCalendar.generateRRule], which used to generate recurrence rule + /// based on the [RecurrenceProperties] values. + /// * [SfCalendar.getRecurrenceDateTimeCollection], to get the recurrence date + /// time collection based on the given recurrence rule and start date. + /// * Knowledge base: [How to use a negative value for bysetpos in rrule](https://www.syncfusion.com/kb/12552/how-to-use-a-negative-value-for-bysetpos-in-a-rrule-of-recurrence-appointment-in-the) + /// * Knowledge base: [How to get the recurrence date collection](https://www.syncfusion.com/kb/12344/how-to-get-the-recurrence-date-collection-in-the-flutter-calendar) + /// * Knowledge base: [How to use multiple recurrence rule in special region](https://www.syncfusion.com/kb/11730/how-to-use-multiple-recurrence-rule-rrule-in-special-region-using-flutter-calendar) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -178,6 +227,16 @@ class TimeRegion with Diagnosticable { /// Used to specify the background color of [TimeRegion]. /// + /// See also: + /// * [textStyle], which used to customize the style for the text on the + /// time region view. + /// * [iconData], the icon will which will be displayed on the time region + /// view. + /// * [SfCalendar.timeRegionBuilder], to set custom widget for the time + /// regions in the calendar + /// * Knowledge base: [How to customize special regions with builder](https://www.syncfusion.com/kb/12192/how-to-customize-the-special-time-region-using-custom-builder-in-the-flutter-calendar) + /// + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -216,6 +275,15 @@ class TimeRegion with Diagnosticable { /// placed in the region. /// 4. It does not restrict the appointment rendering on specified region /// + /// See also: + /// * [SfCalendar.onTap], the callback which notifies when the calendar + /// element tapped on view. + /// * [SfCalendar.onLongPress], the callback which notifies when the calendar + /// element long pressed on view. + /// * Knowledge base: [How to add a special region dynamically using onTap and onViewChanged](https://www.syncfusion.com/kb/11729/how-to-add-a-special-region-dynamically-using-ontap-onviewchanged-callbacks-of-the-flutter) + /// * Knowledge base: [How to highlight the weekends](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight the lunch hours](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -243,6 +311,12 @@ class TimeRegion with Diagnosticable { /// Used to specify the time zone of [TimeRegion] start and end time. /// + /// See also: + /// * [endTime], the date time value in which the time region will end. + /// * [startTime], the date time value in which the time region will start. + /// * [SfCalendar.timeZone], to set the timezone for the calendar. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -271,6 +345,14 @@ class TimeRegion with Diagnosticable { /// Used to specify the text style for [TimeRegion] text and icon. /// + /// See also: + /// * [color], which used to fill the background of the time region view. + /// * [iconData], the icon will which will be displayed on the time region + /// view. + /// * [SfCalendar.timeRegionBuilder], to set custom widget for the time + /// regions in the calendar + /// * Knowledge base: [How to customize special regions with builder](https://www.syncfusion.com/kb/12192/how-to-customize-the-special-time-region-using-custom-builder-in-the-flutter-calendar) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -303,6 +385,15 @@ class TimeRegion with Diagnosticable { /// Note: If time region have both the text and icon then it will draw icon /// only. /// + /// See also: + /// * [text], the string which will be displayed on the time region view. + /// * [textStyle], which used to customize the style of the text on the time + /// region view. + /// * Knowledge base: [How to highlight the weekends](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight the lunch hours](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// * Knowledge base: [How to add a special region dynamically using onTap and onViewChanged](https://www.syncfusion.com/kb/11729/how-to-add-a-special-region-dynamically-using-ontap-onviewchanged-callbacks-of-the-flutter) + /// + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -334,6 +425,16 @@ class TimeRegion with Diagnosticable { /// [recurrenceRule]. If it is not empty, then recurrence region not applied /// to specified collection of dates in [recurrenceExceptionDates]. /// + /// See also: + /// * [recurrenceRule], which used to generate the recurrence time region + /// based on the rule set. + /// * [RecurrenceProperties], which used to create the recurrence rule based + /// on the values set to these properties. + /// * [SfCalendar.generateRRule], which used to generate recurrence rule + /// based on the [RecurrenceProperties] values. + /// * Knowledge base: [How to use multiple recurrence rule in special region](https://www.syncfusion.com/kb/11730/how-to-use-multiple-recurrence-rule-rrule-in-special-region-using-flutter-calendar) + /// * Knowledge base: [How to exclude the dates from the recurrence appointments](https://www.syncfusion.com/kb/12161/how-to-exclude-the-dates-from-recurrence-appointments-in-the-flutter-calendar) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -371,12 +472,14 @@ class TimeRegion with Diagnosticable { /// resource in calendar view. /// /// See also: - /// - /// * [CalendarResource], the resource data for calendar. + /// * [CalendarResource], object which contains the resource data. /// * [ResourceViewSettings], the settings have properties which allow to /// customize the resource view of the [SfCalendar]. /// * [CalendarResource.id], the unique id for the [CalendarResource] view of /// [SfCalendar]. + /// * [CalendarDataSource], which used to set the resources collection to the + /// calendar. + /// * Knowledge base: [How to add resources](https://www.syncfusion.com/kb/12070/how-to-add-resources-in-the-flutter-calendar) /// ///```dart /// 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 ba46ba0a5..cb6da7749 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 @@ -1,5 +1,7 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_calendar/calendar.dart'; /// The settings have properties which allow to customize the time slot views /// of the [SfCalendar]. @@ -8,6 +10,25 @@ import 'package:flutter/material.dart'; /// [timeInterval], [timeIntervalHeight], [timeFormat], [dateFormat],[dayFormat] /// and [timeRulerSize] in time slot views of calendar. /// +/// See also: +/// * [MonthViewSettings], to know more about the customization options for +/// the month view of calendar. +/// * [ScheduleViewSettings], to know more about the customization options for +/// the schedule view of calendar. +/// * [TimeRegion], which used to customize an particular cell in the timeslot +/// views of calendar. +/// * Knowledge base: [How to customize time label](https://www.syncfusion.com/kb/11008/how-to-customize-the-time-label-in-the-flutter-calendar) +/// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) +/// * Knowledge base: [How to set the arbitrary height to an appointment](https://www.syncfusion.com/kb/12279/how-to-set-the-arbitrary-height-to-appointments-in-the-flutter-calendar) +/// * Knowledge base: [How to auto fit the calendar to screen height](https://www.syncfusion.com/kb/12231/how-to-autofit-the-calendar-to-screen-height-in-the-flutter-calendar) +/// * Knowledge base: [How to customize the timeline appointment height](https://www.syncfusion.com/kb/12147/how-to-customize-the-timeline-appointment-height-in-the-flutter-calendar) +/// * Knowledge base: [How to change working days and hours](https://www.syncfusion.com/kb/12146/how-to-change-the-working-days-and-hours-in-the-flutter-calendar) +/// * Knowledge base: [How to format the view header day and date format](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) +/// * Knowledge base: [How to add custom fonts](https://www.syncfusion.com/kb/12101/how-to-add-custom-fonts-in-the-flutter-calendar) +/// * Knowledge base: [How to format the date and time in timeline views](https://www.syncfusion.com/kb/11997/how-to-format-the-date-and-time-in-timeline-views-in-the-flutter-calendar) +/// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11899/how-to-apply-theming-in-flutter-calendar) +/// * Knowledge base: [How to highlight working and non working hours](https://www.syncfusion.com/kb/11711/how-to-highlight-the-working-and-non-working-hours-in-the-flutter-calendar) +/// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -49,7 +70,8 @@ class TimeSlotViewSettings with Diagnosticable { this.dateFormat = 'd', this.dayFormat = 'EE', this.timeRulerSize = -1, - this.timeTextStyle}) + this.timeTextStyle, + this.allDayPanelColor}) : assert(startHour >= 0 && startHour <= 24), assert(endHour >= 0 && endHour <= 24), assert(timeIntervalHeight >= -1), @@ -64,6 +86,16 @@ class TimeSlotViewSettings with Diagnosticable { /// /// Defaults to `0`. /// + /// See more: + /// * [endHour], which is the end hour for the time slot views in calendar. + /// * [TimeRegion], which allows to customize the particular time region in + /// the timeslot views of calendar. + /// * [nonWorkingDays], which restricts the rendering of mentioned week days, + /// in the [CalendarView.workWeek] and [CalendarView.timelineWorkWeek] views + /// of calendar. + /// * Knowledge base: [How to change working days and hours](https://www.syncfusion.com/kb/12146/how-to-change-the-working-days-and-hours-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight working and non working hours](https://www.syncfusion.com/kb/11711/how-to-highlight-the-working-and-non-working-hours-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -96,6 +128,17 @@ class TimeSlotViewSettings with Diagnosticable { /// /// Defaults to `24`. /// + /// See more: + /// * [startHour], which is the start hour for the time slot views in + /// calendar. + /// * [TimeRegion], which allows to customize the particular time region in + /// the timeslot views of calendar. + /// * [nonWorkingDays], which restricts the rendering of mentioned week days, + /// in the [CalendarView.workWeek] and [CalendarView.timelineWorkWeek] views + /// of calendar. + /// * Knowledge base: [How to change working days and hours](https://www.syncfusion.com/kb/12146/how-to-change-the-working-days-and-hours-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight working and non working hours](https://www.syncfusion.com/kb/11711/how-to-highlight-the-working-and-non-working-hours-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -129,6 +172,16 @@ class TimeSlotViewSettings with Diagnosticable { /// _Note:_ This is only applicable only when the calendar view set as /// [CalendarView.workWeek] or [CalendarView.timelineWorkWeek] view. /// + /// See also: + /// * [startHour], which is the start hour for the timeslot views of the + /// calendar. + /// * [endHour], which is the end hour for the timeslot view of the calendar. + /// * [TimeRegion], which is used to customize the specific time region in the + /// timeslot views of the calendar. + /// * Knowledge base: [How to change working days and hours](https://www.syncfusion.com/kb/12146/how-to-change-the-working-days-and-hours-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight working and non working hours](https://www.syncfusion.com/kb/11711/how-to-highlight-the-working-and-non-working-hours-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight the weekends](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -200,6 +253,18 @@ class TimeSlotViewSettings with Diagnosticable { /// /// This property applicable only for day, week and work week view of calendar /// + /// See also: + /// * [timeInterval], which is the duration of every single timeslot in the + /// timeslot views of calendar. + /// * [timeIntervalWidth], which is the width for every single timeslot in + /// the timeline views of calendar. + /// * [minimumAppointmentDuration], which is the minimum duration for the + /// appointment, if the appointment duration is too small to display on view. + /// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to set the arbitrary height to an appointment](https://www.syncfusion.com/kb/12279/how-to-set-the-arbitrary-height-to-appointments-in-the-flutter-calendar) + /// * Knowledge base: [How to auto fit the calendar to screen height](https://www.syncfusion.com/kb/12231/how-to-autofit-the-calendar-to-screen-height-in-the-flutter-calendar) + /// * To know more about time slot views in calendar [refer here](https://help.syncfusion.com/flutter/calendar/timeslot-views) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -232,7 +297,19 @@ class TimeSlotViewSettings with Diagnosticable { /// auto-fit to the screen width. /// /// See also: - /// To know more about time slot views in calendar [refer here](https://help.syncfusion.com/flutter/calendar/timeslot-views) + /// * [timeInterval], which is the duration of every single timeslot in the + /// timeslot views of calendar. + /// * [timeIntervalHeight], which is the height for every single timeslot in + /// the day,week and work week views of calendar. + /// * [minimumAppointmentDuration], which is the minimum duration for the + /// appointment, if the appointment duration is too small to display on view. + /// * [timelineAppointmentHeight], which is the height for appointment view + /// in the timeline views of calendar. + /// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to set the arbitrary height to an appointment](https://www.syncfusion.com/kb/12279/how-to-set-the-arbitrary-height-to-appointments-in-the-flutter-calendar) + /// * Knowledge base: [How to auto fit the calendar to screen height](https://www.syncfusion.com/kb/12231/how-to-autofit-the-calendar-to-screen-height-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the timeline appointment height](https://www.syncfusion.com/kb/12147/how-to-customize-the-timeline-appointment-height-in-the-flutter-calendar) + /// * To know more about time slot views in calendar [refer here](https://help.syncfusion.com/flutter/calendar/timeslot-views) /// /// ```dart ///Widget build(BuildContext context) { @@ -258,6 +335,25 @@ class TimeSlotViewSettings with Diagnosticable { /// /// Defaults to `h a`. /// + /// See also: + /// * [timeTextStyle], which is the text style for the time text in the + /// timeslot views of calendar. + /// * [timeRulerSize], which is the size for the time ruler, which displays + /// the time label in timeslot views of calendar. + /// * [timeInterval], which is the duration of every single timeslot in the + /// timeslot views of calendar. + /// * [startHour], which is the start hour for the timeslot views of the + /// calendar. + /// * [endHour], which is the end hour for the timeslot view of the calendar. + /// * [dayFormat], which is the format for the day text in view header view + /// of the timeslot views in calendar. + /// * [dateFormat], which is the format for the date text in the view header + /// view of the timeslot views of calendar. + /// * Knowledge base: [How to customize time label](https://www.syncfusion.com/kb/11008/how-to-customize-the-time-label-in-the-flutter-calendar) + /// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to format the view header day and date format](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) + /// * Knowledge base: [How to format the date and time in timeline views](https://www.syncfusion.com/kb/11997/how-to-format-the-date-and-time-in-timeline-views-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -290,6 +386,18 @@ class TimeSlotViewSettings with Diagnosticable { /// [CalendarView.timelineDay], [CalendarView.timelineWeek] and /// [CalendarView.timelineWorkWeek] view in [SfCalendar]. /// + /// See also: + /// * [timeInterval], which is the duration of every single timeslot in the + /// timeslot views of calendar. + /// * [minimumAppointmentDuration], which is the minimum duration for the + /// appointment, if the appointment duration is too small to display on view. + /// * [timeIntervalWidth], which is the width for every single timeslot in + /// the day,week and work week views of calendar. + /// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to set the arbitrary height to an appointment](https://www.syncfusion.com/kb/12279/how-to-set-the-arbitrary-height-to-appointments-in-the-flutter-calendar) + /// * Knowledge base: [How to auto fit the calendar to screen height](https://www.syncfusion.com/kb/12231/how-to-autofit-the-calendar-to-screen-height-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the timeline appointment height](https://www.syncfusion.com/kb/12147/how-to-customize-the-timeline-appointment-height-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -324,6 +432,19 @@ class TimeSlotViewSettings with Diagnosticable { /// _Note:_ The value set to this property will be applicable, only when an /// [Appointment] duration value lesser than this property. /// + /// See also: + /// * [timeInterval], which is the duration of every single timeslot in the + /// timeslot views of calendar. + /// * [timeIntervalWidth], which is the width for every single timeslot in + /// the timeline views of calendar. + /// * [timeIntervalHeight], which is the height for every single timeslot in + /// the timeline views of calendar. + /// * [timelineAppointmentHeight], which is the height for appointment view + /// in the timeline views of calendar. + /// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to set the arbitrary height to an appointment](https://www.syncfusion.com/kb/12279/how-to-set-the-arbitrary-height-to-appointments-in-the-flutter-calendar) + /// * Knowledge base: [How to auto fit the calendar to screen height](https://www.syncfusion.com/kb/12231/how-to-autofit-the-calendar-to-screen-height-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -355,6 +476,15 @@ class TimeSlotViewSettings with Diagnosticable { /// /// Defaults to `EE`. /// + /// See also: + /// * [timeTextStyle], which is the text style for the time text in the + /// timeslot views of calendar. + /// * [dayFormat], which is the format for the day text in view header view + /// of the timeslot views in calendar. + /// * [ViewHeaderStyle], which is used to customize the view header view of + /// the calendar. + /// * Knowledge base: [How to format the view header day and date format](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -385,6 +515,15 @@ class TimeSlotViewSettings with Diagnosticable { /// /// Defaults to `d`. /// + /// See also: + /// * [timeTextStyle], which is the text style for the time text in the + /// timeslot views of calendar. + /// * [dateFormat], which is the format for the date text in view header view + /// of the timeslot views in calendar. + /// * [ViewHeaderStyle], which is used to customize the view header view of + /// the calendar. + /// * Knowledge base: [How to format the view header day and date format](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -415,6 +554,16 @@ class TimeSlotViewSettings with Diagnosticable { /// /// Defaults to `-1`. /// + /// See also: + /// * [timeTextStyle], which is the text style for the time text in the + /// timeslot views of calendar. + /// * [timeFormat], which used to format the time text in the timeslot views + /// of calendar. + /// * [timeInterval], which is the duration of every single timeslot in the + /// timeslot views of calendar. + /// * Knowledge base: [How to customize time label](https://www.syncfusion.com/kb/11008/how-to-customize-the-time-label-in-the-flutter-calendar) + /// * Knowledge base: [How to format the date and time in timeline views](https://www.syncfusion.com/kb/11997/how-to-format-the-date-and-time-in-timeline-views-in-the-flutter-calendar) + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -447,6 +596,18 @@ class TimeSlotViewSettings with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [timeFormat], which is used to format the time text in the timeslotviews + /// of the calendar. + /// * [timeRulerSize], which is the size for the time ruler, which displays + /// the time label in timeslot views of calendar. + /// * [timeInterval], which is the duration of every single timeslot in the + /// timeslot views of calendar. + /// * Knowledge base: [How to customize time label](https://www.syncfusion.com/kb/11008/how-to-customize-the-time-label-in-the-flutter-calendar) + /// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to format the view header day and date format](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) + /// * Knowledge base: [How to format the date and time in timeline views](https://www.syncfusion.com/kb/11997/how-to-format-the-date-and-time-in-timeline-views-in-the-flutter-calendar) + /// /// ```dart /// Widget build(BuildContext context) { /// return Container( @@ -476,6 +637,33 @@ class TimeSlotViewSettings with Diagnosticable { /// ``` final TextStyle? timeTextStyle; + /// The color which fills the [SfCalendar] all day panel background. + /// + /// Defaults to null. + /// + /// Using a [SfCalendarTheme] gives more fine-grained control over the + /// appearance of various components of the calendar. + /// + /// See also: + /// * [backgroundColor], will fill the background of the calendar. + /// *[CalendarHeaderStyle.backgroundColor], will fill the header background + /// of the calendar. + /// *[ViewHeaderStyle.backgroundColor], will fill the view header background + /// of the calendar. + /// + /// ```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.week, + /// timeSlotViewSettings: TimeSlotViewSettings( + /// allDayPanelColor : Colors.green), + /// ), + /// ); + /// } + /// ``` + final Color? allDayPanelColor; + @override bool operator ==(dynamic other) { if (identical(this, other)) { 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 2a865f64d..0c13a6dc8 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 @@ -8,6 +8,13 @@ import 'package:flutter/material.dart'; /// /// ![view header with different style in calendar](https://help.syncfusion.com/flutter/calendar/images/headers/viewheader-style.png) /// +/// See also: +/// * [SfCalendar.viewHeaderHeight], to customize the size of the view header +/// view in calendar. +/// * Knowledge base: [How to format day and date of view header](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) +/// * Knowledge base: [How to add custom header and view header](https://www.syncfusion.com/kb/10997/how-to-add-custom-header-and-view-header-in-the-flutter-calendar) +/// * Knowledge base: [How to highlight tapped date in view header](https://www.syncfusion.com/kb/12469/how-to-highlight-the-tapped-view-header-in-the-flutter-calendar) +/// /// ```dart /// ///Widget build(BuildContext context) { @@ -38,6 +45,15 @@ class ViewHeaderStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [dayTextStyle], which used to customize the text style of the day text + /// in the view header view. + /// * [dateTextStyle], which used to customize the text style of the date text + /// in the view header view. + /// * [SfCalendar.viewHeaderHeight], which customizes the size of the view + /// header view in the calendar. + /// + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -66,6 +82,12 @@ class ViewHeaderStyle with Diagnosticable { /// The text color set to this doesn't apply for the today cell in view header /// of day/week/workweek view. /// + /// See also: + /// * [dayTextStyle], which used to customize the text style of the day text + /// in the view header view. + /// * [backgroundColor], which fills the background of the view header view + /// in calendar. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -88,6 +110,12 @@ class ViewHeaderStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [dateTextStyle], which used to customize the text style of the date text + /// in the view header view. + /// * [backgroundColor], which fills the background of the view header view + /// in calendar. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( 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 15e16ba49..c2a6196d1 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 @@ -35,6 +35,10 @@ class WeekNumberStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar /// + /// See also: + /// * [textStyle], which used to apply style for the text in the week number + /// view in calendar. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( @@ -57,6 +61,10 @@ class WeekNumberStyle with Diagnosticable { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [backgroundColor], which used to fill the background of the weeknumber + /// panel in the calendar. + /// /// ```dart ///Widget build(BuildContext context) { /// return Container( diff --git a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart index 7196f2e0e..aa9c8ae46 100644 --- a/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart +++ b/packages/syncfusion_flutter_calendar/lib/src/calendar/sfcalendar.dart @@ -1,13 +1,13 @@ import 'dart:math' as math; import 'dart:ui'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; - import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:syncfusion_flutter_core/core.dart'; @@ -29,6 +29,7 @@ import 'common/enums.dart'; import 'common/event_args.dart'; import 'resource_view/calendar_resource.dart'; import 'resource_view/resource_view.dart'; +import 'settings/drag_and_drop_settings.dart'; import 'settings/header_style.dart'; import 'settings/month_view_settings.dart'; import 'settings/resource_view_settings.dart'; @@ -99,15 +100,21 @@ typedef _CalendarHeaderCallback = void Function(double width); /// {@youtube 560 315 https://www.youtube.com/watch?v=3OROjbAQS8Y} /// /// See also: -/// [SfCalendarThemeData] -/// [CalendarHeaderStyle] -/// [ViewHeaderStyle] -/// [MonthViewSettings] -/// [TimeSlotViewSettings] -/// [ResourceViewSettings] -/// [ScheduleViewSettings] -/// [MonthCellStyle] -/// [AgendaStyle]. +/// * [SfCalendarThemeData], to set consistent look for calendar elements. +/// * [CalendarHeaderStyle], to customize the header view of the calendar. +/// * [ViewHeaderStyle], to customize the view header view of the calendar. +/// * [MonthViewSettings], to customize the month view of the calendar. +/// * [TimeSlotViewSettings], to customize the timeslot views of the calendar. +/// * [ResourceViewSettings], to customize the resource view of the calendar. +/// * [ScheduleViewSettings], to customize the schedule view of the calendar. +/// * [MonthCellStyle], to customize the month cells of the calendar. +/// * [AgendaStyle], to customize the month agenda view of the calendar. +/// * [showDatePickerButton], to show date picker for quickly navigating to a +/// different date. +/// * [allowedViews], to show list of calendar views on header view for quick +/// navigation. +/// * [SfDateRangePicker], material widget, which used to select date, dates, +/// range, and ranges of dates. /// /// /// ```dart @@ -198,7 +205,7 @@ class SfCalendar extends StatefulWidget { this.showDatePickerButton = false, this.allowViewNavigation = false, this.showCurrentTimeIndicator = true, - this.cellEndPadding = 1, + this.cellEndPadding = -1, this.viewNavigationMode = ViewNavigationMode.snap, this.allowedViews, this.specialRegions, @@ -207,12 +214,21 @@ class SfCalendar extends StatefulWidget { this.showWeekNumber = false, this.weekNumberStyle = const WeekNumberStyle(), this.resourceViewHeaderBuilder, + this.allowAppointmentResize = false, + this.onAppointmentResizeStart, + this.onAppointmentResizeUpdate, + this.onAppointmentResizeEnd, + this.allowDragAndDrop = false, + this.dragAndDropSettings = const DragAndDropSettings(), + this.onDragStart, + this.onDragUpdate, + this.onDragEnd, }) : assert(firstDayOfWeek >= 1 && firstDayOfWeek <= 7), assert(headerHeight >= 0), assert(viewHeaderHeight >= -1), assert(minDate == null || maxDate == null || minDate.isBefore(maxDate)), assert(minDate == null || maxDate == null || maxDate.isAfter(minDate)), - assert(cellEndPadding >= 0), + assert(cellEndPadding >= -1), initialDisplayDate = initialDisplayDate ?? DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 08, 45, 0), @@ -228,17 +244,22 @@ class SfCalendar extends StatefulWidget { /// With this builder, you can set widget and then initiate the process of /// loading more appointments by calling ‘loadMoreAppointments’ callback /// which is passed as a parameter to this builder. The ‘loadMoreAppointments’ - /// will inturn call the [CalendarDataSource.handleLoadMore' method, where you - /// have to load the appointments. + /// will in turn call the [CalendarDataSource.handleLoadMore' method, where + /// you have to load the appointments. /// /// The widget returned from this builder will be rendered based on calendar /// widget width and height. /// - /// Note: This callback will be called after the onViewChanged callback. + /// _Note:_ This callback will be called after the onViewChanged callback. /// The widget returned from this builder will be removed from [SfCalendar] /// when [CalendarDataSource.notifyListeners] is called. /// - /// See also: [CalendarDataSource.handleLoadMore] + /// See also: + /// * [CalendarDataSource.handleLoadMore], to handle the appointment loading, + /// when the indicator is displaying. + /// * [CalendarDataSource.notifyListeners], to add, remove or reset the + /// appointment collection. + /// * Knowledge base: [How to load appointments on demand](https://www.syncfusion.com/kb/12658/how-to-load-appointments-on-demand-in-flutter-calendar) /// /// ``` dart /// @override @@ -279,7 +300,12 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to null. /// - /// See also: [SfCalendar.onViewChanged] + /// See also: + /// * [SfCalendar.onViewChanged], the callback to notify that the view + /// changed, this also contains the visible dates collection. + /// * [view], to specify the view to be rendered on initially in calendar. + /// * [CalendarController.view], to change the calendar view dynamically. + /// * Knowledge base: [How to do date navigates easily](https://www.syncfusion.com/kb/12019/how-to-do-date-navigations-easily-with-flutter-calendar) /// /// ``` dart /// Widget build(BuildContext context) { @@ -303,11 +329,15 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to 'false'. /// - /// See also: [showDatePickerButton], to show date picker for quickly - /// navigating to a different date. [allowedViews] to show list of calendar - /// views on header view for quick navigation. + /// See also: + /// * [showDatePickerButton], to show date picker for quickly navigating to a + /// different date. + /// * [allowedViews] to show list of calendar views on header view for quick + /// navigation. + /// * Knowledge base: [How to switch between views](https://www.syncfusion.com/kb/10944/how-to-switch-between-views-of-the-flutter-calendar) /// /// ``` dart + /// /// Widget build(BuildContext context) { /// return Container( /// child: SfCalendar( @@ -327,6 +357,13 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to `false`. /// + /// see also: + /// * [allowedViews] to show list of calendar views on header view for quick + /// navigation. + /// * [SfDateRangePicker], material widget which displays on the header of + /// calendar, when the header date tapped. + /// * [How to add indicator in month cells of date range picker](https://www.syncfusion.com/kb/12119/how-to-add-the-indicator-in-the-month-cells-of-the-date-range-picker-sfdaterangepicker-when) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -346,6 +383,10 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to `true`. /// + /// see also: + /// * [todayHighlightColor], to customize the color of the indicator. + /// + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -362,7 +403,17 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to `CalendarView.day`. /// - /// Also refer: [CalendarView]. + /// See also: + /// * [CalendarView], to know about available calendar views. + /// * [SfCalendar.onViewChanged], the callback to notify that the view + /// changed, this also contains the visible dates collection. + /// * [CalendarController.view], to change the calendar view dynamically. + /// * [showDatePickerButton], to show date picker for quickly navigating to a + /// different date. + /// * [allowedViews] to show list of calendar views on header view for quick + /// navigation. + /// * Knowledge base: [How to switch between views](https://www.syncfusion.com/kb/10944/how-to-switch-between-views-of-the-flutter-calendar) + /// /// /// ``` dart ///Widget build(BuildContext context) { @@ -389,8 +440,10 @@ class SfCalendar extends StatefulWidget { /// render dates based on the date set to this property. /// /// See also: - /// [initialDisplayDate]. - /// [maxDate]. + /// * [initialDisplayDate], the date which displayed initially on calendar. + /// * [maxDate], which is the maximum available date for the calendar. + /// * [CalendarController.displayDate], which move to the desired date + /// dynamically. /// /// ``` dart ///Widget build(BuildContext context) { @@ -418,8 +471,10 @@ class SfCalendar extends StatefulWidget { /// render dates based on the date set to this property. /// /// See also: - /// [initialDisplayDate]. - /// [minDate]. + /// * [initialDisplayDate], the date which displayed initially on calendar. + /// * [minDate], which is the least available date for the calendar. + /// * [CalendarController.displayDate], which move to the desired date + /// dynamically. /// /// ``` dart /// @@ -438,8 +493,13 @@ class SfCalendar extends StatefulWidget { /// A builder that builds a widget, replaces the month cell in the /// calendar month view. /// - /// Note: Month cell appointments are not shown when the month cell builder, - /// builds the custom widget for month view. + /// See also: + /// * [MonthViewSettings.appointmentDisplayMode], to customize the appointment + /// display mode in the month cell. + /// * [appointmentBuilder], to build custom widget for appointments in + /// calendar. + /// * Knowledge base: [How to customize month cells](https://www.syncfusion.com/kb/12016/how-to-customize-the-month-cells-of-the-flutter-calendar) + /// * Knowledge base: [How to customize month cell with appointment count](https://www.syncfusion.com/kb/12306/how-to-customize-the-month-cell-with-appointment-count-in-the-flutter-calendar) /// /// ```dart ///@override @@ -466,11 +526,19 @@ class SfCalendar extends StatefulWidget { /// week, workweek, month, schedule and timeline day, week, workweek, /// month views. /// - /// Note: In month view, this builder callback will be used to build + /// _Note:_ In month view, this builder callback will be used to build /// appointment views for appointments displayed in both month cell and /// agenda views when the MonthViewSettings.appointmentDisplayMode is /// set to appointment. /// + /// See also: + /// * [CalendarDataSource], to set and handle the appointments in the calendar + /// * [Appointment], to know more about the appointment and it's properties in + /// calendar. + /// * [appointmentTextStyle], to customize the appointment text, when the + /// builder not added. + /// * Knowledge base: [How to customize appointment using builder](https://www.syncfusion.com/kb/12191/how-to-customize-the-appointments-using-custom-builder-in-the-flutter-calendar) + /// /// ```dart /// CalendarController _controller = CalendarController(); /// @@ -544,6 +612,13 @@ class SfCalendar extends StatefulWidget { /// A builder that builds a widget that replaces the time region view in day, /// week, workweek, and timeline day, week, workweek views. /// + /// See also: + /// * [specialRegions], to set the special time regions in the calendar. + /// * [TimeRegion], to now the details about the special time regions and it's + /// properties in calendar. + /// * Knowledge base: [How to customize special regions with builder](https://www.syncfusion.com/kb/12192/how-to-customize-the-special-time-region-using-custom-builder-in-the-flutter-calendar) + /// + /// /// ```dart /// /// List _getTimeRegions() { @@ -597,55 +672,58 @@ class SfCalendar extends StatefulWidget { /// If it does not match, the provided string will be used as-is. /// The supported sets of skeletons are as follows. /// - /// ICU Name Skeleton - /// -------- -------- - /// DAY d - /// ABBR_WEEKDAY E - /// WEEKDAY EEEE - /// ABBR_STANDALONE_MONTH LLL - /// STANDALONE_MONTH LLLL - /// NUM_MONTH M - /// NUM_MONTH_DAY Md - /// NUM_MONTH_WEEKDAY_DAY MEd - /// ABBR_MONTH MMM - /// ABBR_MONTH_DAY MMMd - /// ABBR_MONTH_WEEKDAY_DAY MMMEd - /// MONTH MMMM - /// MONTH_DAY MMMMd - /// MONTH_WEEKDAY_DAY MMMMEEEEd - /// ABBR_QUARTER QQQ - /// QUARTER QQQQ - /// YEAR y - /// YEAR_NUM_MONTH yM - /// YEAR_NUM_MONTH_DAY yMd - /// YEAR_NUM_MONTH_WEEKDAY_DAY yMEd - /// YEAR_ABBR_MONTH yMMM - /// YEAR_ABBR_MONTH_DAY yMMMd - /// YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd - /// YEAR_MONTH yMMMM - /// YEAR_MONTH_DAY yMMMMd - /// YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd - /// YEAR_ABBR_QUARTER yQQQ - /// YEAR_QUARTER yQQQQ - /// HOUR24 H - /// HOUR24_MINUTE Hm - /// HOUR24_MINUTE_SECOND Hms - /// HOUR j - /// HOUR_MINUTE jm - /// HOUR_MINUTE_SECOND jms - /// HOUR_MINUTE_GENERIC_TZ jmv - /// HOUR_MINUTE_TZ jmz - /// HOUR_GENERIC_TZ jv - /// HOUR_TZ jz - /// MINUTE m - /// MINUTE_SECOND ms - /// SECOND s + /// ICU Name Skeleton + /// -------- -------- + /// DAY d + /// ABBR_WEEKDAY E + /// WEEKDAY EEEE + /// ABBR_STANDALONE_MONTH LLL + /// STANDALONE_MONTH LLLL + /// NUM_MONTH M + /// NUM_MONTH_DAY Md + /// NUM_MONTH_WEEKDAY_DAY MEd + /// ABBR_MONTH MMM + /// ABBR_MONTH_DAY MMMd + /// ABBR_MONTH_WEEKDAY_DAY MMMEd + /// MONTH MMMM + /// MONTH_DAY MMMMd + /// MONTH_WEEKDAY_DAY MMMMEEEEd + /// ABBR_QUARTER QQQ + /// QUARTER QQQQ + /// YEAR y + /// YEAR_NUM_MONTH yM + /// YEAR_NUM_MONTH_DAY yMd + /// YEAR_NUM_MONTH_WEEKDAY_DAY yMEd + /// YEAR_ABBR_MONTH yMMM + /// YEAR_ABBR_MONTH_DAY yMMMd + /// YEAR_ABBR_MONTH_WEEKDAY_DAY yMMMEd + /// YEAR_MONTH yMMMM + /// YEAR_MONTH_DAY yMMMMd + /// YEAR_MONTH_WEEKDAY_DAY yMMMMEEEEd + /// YEAR_ABBR_QUARTER yQQQ + /// YEAR_QUARTER yQQQQ + /// HOUR24 H + /// HOUR24_MINUTE Hm + /// HOUR24_MINUTE_SECOND Hms + /// HOUR j + /// HOUR_MINUTE jm + /// HOUR_MINUTE_SECOND jms + /// HOUR_MINUTE_GENERIC_TZ jmv + /// HOUR_MINUTE_TZ jmz + /// HOUR_GENERIC_TZ jv + /// HOUR_TZ jz + /// MINUTE m + /// MINUTE_SECOND ms + /// SECOND s /// /// Defaults to null. /// /// See also: - /// [onViewChanged]. - /// [DateFormat]. + /// * [onViewChanged], to get the current visible dates collection + /// * [headerHeight], to customize the size of the header view in calendar. + /// * [DateFormat], to know about the different available formats. + /// * Knowledge base: [How to change the month header format](https://www.syncfusion.com/kb/12550/how-to-change-the-month-header-format-in-the-flutter-calendar) + /// /// /// ``` dart /// @override @@ -665,6 +743,10 @@ class SfCalendar extends StatefulWidget { /// A builder that builds a widget, replace the schedule month header /// widget in calendar schedule view. /// + /// See also: + /// * [scheduleViewSettings], to customize the schedule view of calendar. + /// * Knowledge base: [Customize the schedule view month header using builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// /// ```dart /// @override /// Widget build(BuildContext context) { @@ -694,9 +776,14 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to `7` which indicates `DateTime.sunday`. /// + /// See also: + /// * [showWeekNumber], to display the week number of the year. + /// * Knowledge base: [How to change the first day of week](https://www.syncfusion.com/kb/12222/how-to-change-the-first-day-of-week-in-the-flutter-calendar) + /// + /// /// ``` dart /// - ///Widget build(BuildContext context) { + /// Widget build(BuildContext context) { /// return Container( /// child: SfCalendar( /// view: CalendarView.week, @@ -719,6 +806,10 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to null. /// + /// See also: + /// * [DateFormat], to know more about the available formats. + /// * Knowledge base: [How to format appointment time](https://www.syncfusion.com/kb/11989/how-to-format-the-appointment-time-in-the-flutter-calendar) + /// /// ``` dart /// ///Widget build(BuildContext context) { @@ -743,6 +834,12 @@ class SfCalendar extends StatefulWidget { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [SfCalendarTheme], to handle theming with calendar for giving consistent + /// look. + /// * [backgroundColor], which fills the background of the calendar. + /// * Knowledge base: [How to customize the cell border](https://www.syncfusion.com/kb/12118/how-to-customize-the-cell-border-in-the-flutter-calendar) + /// /// ``` dart /// ///Widget build(BuildContext context) { @@ -767,6 +864,17 @@ class SfCalendar extends StatefulWidget { /// [ScheduleViewSettings.appointmentItemHeight] and /// [ScheduleViewSettings.hideEmptyScheduleWeek] in schedule view of calendar. /// + /// See also: + /// * [scheduleViewMonthHeaderBuilder], to customize the schedule view month + /// header with a custom widget. + /// * [ScheduleViewSettings], to know about the available customization + /// options for schedule view. + /// * Knowledge base: [How to customize appointment height in schedule view](https://www.syncfusion.com/kb/12226/how-to-customize-the-appointment-height-in-schedule-view-of-the-flutter-calendar) + /// * Knowledge base: [How to customize day, week, month header of schedule view](https://www.syncfusion.com/kb/12178/how-to-customize-the-day-week-month-header-of-schedule-view-in-the-flutter-calendar) + /// * Knowledge base: [How to customize schedule view month header with builder](https://www.syncfusion.com/kb/12064/how-to-customize-the-schedule-view-month-header-using-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to view schedule](https://www.syncfusion.com/kb/11803/how-to-view-schedule-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the schedule view](https://www.syncfusion.com/kb/11795/how-to-customize-the-schedule-view-in-the-flutter-calendar) + /// /// ``` dart /// /// @override @@ -795,7 +903,14 @@ class SfCalendar extends StatefulWidget { /// /// ![header with different style in calendar](https://help.syncfusion.com/flutter/calendar/images/headers/header-style.png) /// - /// See also: [CalendarHeaderStyle]. + /// See also: + /// * [CalendarHeaderStyle], to know about the available customization options + /// for header view. + /// * [headerHeight], to customize the size of the header view in calendar. + /// * [headerDateFormat], to format the date string in the header view of + /// calendar. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12144/how-to-style-a-header-in-the-flutter-calendar) + /// * Knowledge base: [How to add custom header and view header](https://www.syncfusion.com/kb/10997/how-to-add-custom-header-and-view-header-in-the-flutter-calendar) /// /// ```dart ///Widget build(BuildContext context) { @@ -820,7 +935,14 @@ class SfCalendar extends StatefulWidget { /// /// ![view header with different style in calendar](https://help.syncfusion.com/flutter/calendar/images/headers/viewheader-style.png) /// - /// See also: [ViewHeaderStyle]. + /// See also: + /// * [ViewHeaderStyle], to know about the available customization options for + /// view header view in calendar. + /// * [viewHeaderHeight], to customize the size of the view header view in + /// calendar. + /// * Knowledge base: [How to format day and date of view header](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) + /// * Knowledge base: [How to add custom header and view header](https://www.syncfusion.com/kb/10997/how-to-add-custom-header-and-view-header-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight tapped date in view header](https://www.syncfusion.com/kb/12469/how-to-highlight-the-tapped-view-header-in-the-flutter-calendar) /// /// ```dart /// @@ -845,6 +967,14 @@ class SfCalendar extends StatefulWidget { /// /// ![header height as 100 in calendar](https://help.syncfusion.com/flutter/calendar/images/headers/header-height.png) /// + /// See also: + /// * [headerStyle] to know about the available customization options + /// for header view. + /// * [headerDateFormat], to format the date string in the header view of + /// calendar. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12144/how-to-style-a-header-in-the-flutter-calendar) + /// * Knowledge base: [How to add custom header and view header](https://www.syncfusion.com/kb/10997/how-to-add-custom-header-and-view-header-in-the-flutter-calendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -862,11 +992,19 @@ class SfCalendar extends StatefulWidget { /// Adds padding at the right end of a cell to interact when the calendar /// cells have appointments. /// - /// defaults to '1'. + /// defaults to '-1'. /// - /// Note: This is not applicable for month agenda and schedule view + /// _Note:_ This is not applicable for month agenda and schedule view /// appointments. /// + /// See also: + /// * [onTap], to get the tapped elements, date and other details, when an + /// element tapped in calendar. + /// * [onLongPress], to get the tapped elements, date and other details when + /// an element long tapped in calendar. + /// * [dataSource], to set and handle appointments with calendar. + /// * Knowledge base: [How to interact with calendar cell when appointments loaded](https://www.syncfusion.com/kb/12218/how-to-interact-with-event-calendar-cell-when-appointments-loaded-in-the-flutter-calendar) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -891,7 +1029,15 @@ class SfCalendar extends StatefulWidget { /// of month view, for agenda view appointments styling can be achieved by /// using the [MonthViewSettings.agendaStyle.appointmentTextStyle]. /// - /// See also: [AgendaStyle]. + /// See also: + /// * [AgendaStyle], to customize the agenda view of month view in calendar. + /// * [dataSource], to set and handle the appointments for calendar. + /// * [appointmentBuilder], to set an custom widget for the appointment UI. + /// * [Appointment], to know more about the appointment and it's properties in + /// calendar. + /// * [appointmentTimeTextFormat], to customize the time text format in the + /// appointment view of calendar. + /// * Knowledge base: [How to style appointments](https://www.syncfusion.com/kb/12162/how-to-style-the-appointment-in-the-flutter-calendar) /// /// ```dart /// @@ -917,6 +1063,13 @@ class SfCalendar extends StatefulWidget { /// /// ![view header height as 100 in calendar](https://help.syncfusion.com/flutter/calendar/images/headers/viewheader-height.png) /// + /// See also: + /// * [viewHeaderStyle], to know about the available customization options for + /// view header view in calendar. + /// * Knowledge base: [How to format day and date of view header](https://www.syncfusion.com/kb/12339/how-to-format-the-view-header-day-and-date-in-the-flutter-calendar) + /// * Knowledge base: [How to add custom header and view header](https://www.syncfusion.com/kb/10997/how-to-add-custom-header-and-view-header-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight tapped date in view header](https://www.syncfusion.com/kb/12469/how-to-highlight-the-tapped-view-header-in-the-flutter-calendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -942,9 +1095,17 @@ class SfCalendar extends StatefulWidget { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [showCurrentTimeIndicator], to display an indicator in the current time + /// of the day in calendar. + /// * [todayTextStyle], to customize the today text in calendar. + /// * [SfCalendarTheme], to handle theming with calendar for giving consistent + /// look. + /// * Knowledge base: [How to customize the current day color](https://www.syncfusion.com/kb/12336/how-to-customize-the-current-day-color-in-the-flutter-calendar) + /// /// ```dart /// - ///Widget build(BuildContext context) { + /// Widget build(BuildContext context) { /// return Container( /// child: SfCalendar( /// view: CalendarView.week, @@ -973,10 +1134,15 @@ class SfCalendar extends StatefulWidget { /// appearance of various components of the calendar. /// /// See also: - /// [ViewHeaderStyle], - /// [ScheduleViewSettings], - /// [MonthViewSettings], - /// To know more about the view header customization refer here [https://help.syncfusion.com/flutter/calendar/headers#view-header] + /// * [todayHighlightColor], to customize the highlighting color of the today + /// cell in calendar. + /// * [ViewHeaderStyle], to know about the available customization options for + /// view header view in calendar. + /// * [ScheduleViewSettings], to know about the available customization + /// options for schedule view in calendar. + /// * [MonthViewSettings], to know about the available customization options + /// for month vie win calendar. + /// * To know more about the view header customization refer here [https://help.syncfusion.com/flutter/calendar/headers#view-header] /// /// ```dart ///Widget build(BuildContext context) { @@ -999,6 +1165,12 @@ class SfCalendar extends StatefulWidget { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [cellBorderColor], will fill the cell border of the calendar. + /// * [SfCalendarTheme], to handle theming with calendar for giving consistent + /// look. + /// * Knowledge base: [How to add an image as background](https://www.syncfusion.com/kb/12243/how-to-add-an-image-as-background-in-the-flutter-calendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -1024,6 +1196,14 @@ class SfCalendar extends StatefulWidget { /// _Note:_ Header view does not show arrow when calendar view as /// [CalendarView.schedule] /// + /// See also: + /// * [CalendarController.forward], to navigate to next view of calendar + /// programmatically with animation. + /// * [CalendarController.backward], to navigate to previous view of calendar + /// programmatically with animation. + /// * Knowledge base: [How to navigate to the previous or next view using navigation arrows](https://www.syncfusion.com/kb/12247/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter) + /// + /// /// ``` dart ///Widget build(BuildContext context) { /// return Container( @@ -1037,15 +1217,23 @@ class SfCalendar extends StatefulWidget { /// ``` final bool showNavigationArrow; - /// Specifies the view navigation for [SfCalendar] ] to - /// show dates for the next or previous views. - + /// Specifies the view navigation for [SfCalendar] to show dates for the next + /// or previous views. + /// /// Defaults to ViewNavigationMode.snap. - + /// /// Not applicable when the [view] set as [CalendarView.schedule]. - /// It will not impact scrolling timeslot views, - /// [controller.forward], [controller.backward] - /// and [showNavigationArrow]. + /// It will not impact scrolling timeslot views, [controller.forward], + /// [controller.backward] and [showNavigationArrow]. + /// + /// See also: + /// * [minDate], to restrict the navigation and interaction to the dates + /// before to this date in calendar. + /// * [maxDate], to restrict the navigation and interaction to the dates after + /// to this date in calendar. + /// * [showNavigationArrow], to enable the navigation arrow, which enables to + /// navigate to the next/previous view of calendar programmatically. + /// * Knowledge base: [How to restrict view navigation](https://www.syncfusion.com/kb/12554/how-to-restrict-the-view-navigation-in-the-flutter-calendar) /// /// ``` dart ///Widget build(BuildContext context) { @@ -1072,6 +1260,22 @@ class SfCalendar extends StatefulWidget { /// [TimeSlotViewSettings.dayFormat], and [TimeSlotViewSettings.timeRulerSize] /// in time slot views of calendar. /// + /// See also: + /// * [monthViewSettings], to know more about the customization options for + /// the month view of calendar. + /// * [TimeSlotViewSettings], to know more about the customization options for + /// the month view of calendar. + /// * [scheduleViewSettings], to know more about the customization options for + /// the schedule view of calendar. + /// * [specialRegions], which allows to customize particular time region in + /// the time slot views of calendar. + /// * Knowledge base: [How to customize time label](https://www.syncfusion.com/kb/11008/how-to-customize-the-time-label-in-the-flutter-calendar) + /// * Knowledge base: [How to change the time interval width and height](https://www.syncfusion.com/kb/12322/how-to-change-the-time-interval-width-and-height-in-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to set the arbitrary height to an appointment](https://www.syncfusion.com/kb/12279/how-to-set-the-arbitrary-height-to-appointments-in-the-flutter-calendar) + /// * Knowledge base: [How to auto fit the calendar to screen height](https://www.syncfusion.com/kb/12231/how-to-autofit-the-calendar-to-screen-height-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the timeline appointment height](https://www.syncfusion.com/kb/12147/how-to-customize-the-timeline-appointment-height-in-the-flutter-calendar) + /// * Knowledge base: [How to change working days and hours](https://www.syncfusion.com/kb/12146/how-to-change-the-working-days-and-hours-in-the-flutter-calendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -1103,10 +1307,15 @@ class SfCalendar extends StatefulWidget { /// views. /// /// See also: - /// - /// * [CalendarResource], the resource data for calendar. + /// * [CalendarResource], the object which contains the data for the resource + /// in calendar. /// * [dataSource.resources], the collection of resource to be displayed in /// the timeline views of [SfCalendar]. + /// * [ResourceViewSettings], which contains options to customize the + /// resource view of calendar. + /// * [resourceViewHeaderBuilder], to set custom widget for the resource + /// header in the calendar. + /// * Knowledge base: [How to customize the resource view](https://www.syncfusion.com/kb/12351/how-to-customize-the-resource-view-in-the-flutter-calendar) /// /// ```dart ///@override @@ -1168,6 +1377,24 @@ class SfCalendar extends StatefulWidget { /// [MonthViewSettings.appointmentDisplayCount], and /// [MonthViewSettings.navigationDirection] in month view of calendar. /// + /// See also: + /// * [monthCellBuilder], to customize the month cell with custom widget in + /// calendar. + /// * [MonthViewSettings], to know more about the customization of month view + /// in calendar. + /// * [blackoutDates], which allows to restrict the interaction for a + /// particular date in month views of calendar. + /// * [blackoutDatesTextStyle], which used to customize the blackout dates + /// text style in the month view of calendar. + /// * Knowledge base: [How to customize agenda view height based on widget height](https://www.syncfusion.com/kb/11013/how-to-customize-agenda-view-height-based-on-the-flutter-calendar-widget-height) + /// * Knowledge base: [How to customize agenda item height](https://www.syncfusion.com/kb/11015/how-to-customize-the-agenda-item-height-in-the-flutter-calendar) + /// * Knowledge base: [How to show appointment in agenda view using programmatic date selection](https://www.syncfusion.com/kb/11525/how-to-show-the-appointment-in-agenda-view-using-the-programmatic-date-selection-in-the) + /// * Knowledge base: [How to customize the blackout dates](https://www.syncfusion.com/kb/11987/how-to-customize-the-blackout-dates-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the leading and trailing dates](https://www.syncfusion.com/kb/11988/how-to-customize-the-leading-and-trailing-dates-of-the-month-cells-in-the-flutter-calendar) + /// * Knowledge base: [How to style the month cell](https://www.syncfusion.com/kb/12090/how-to-style-the-month-cell-in-the-flutter-calendar) + /// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12157/how-to-change-the-number-of-weeks-in-the-flutter-calendar) + /// * Knowledge base: [How to customize the agenda view appointment](https://www.syncfusion.com/kb/12271/how-to-customize-the-agenda-view-appointment-using-the-style-properties-in-flutter-calendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -1197,6 +1424,13 @@ class SfCalendar extends StatefulWidget { /// Styling of the blackout dates can be handled using the /// [blackoutDatesTextStyle] property in [SfCalendar]. /// + /// See also: + /// *[blackoutDatesTextStyle], to customize the blackout dates text in + /// calendar. + /// * Knowledge base: (How to update blackout dates using onViewChanged)[https://www.syncfusion.com/kb/12368/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-calendar] + /// * Knowledge base: (How to add active dates)[https://www.syncfusion.com/kb/12073/how-to-add-active-dates-in-the-flutter-calendar] + /// * Knowledge base: (How to customize the blackout dates )[https://www.syncfusion.com/kb/12003/how-to-customize-the-blackout-dates-in-the-flutter-event-calendar-sfcalendar] + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1229,7 +1463,14 @@ class SfCalendar extends StatefulWidget { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// - /// See also: [blackoutDates]. + /// See also: + /// * [blackoutDates], which will add blackout dates to the calendar. + /// * [SfCalendarTheme], to handle theming with calendar for giving consistent + /// look. + /// * Knowledge base: (How to update blackout dates using onViewChanged)[https://www.syncfusion.com/kb/12368/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-calendar] + /// * Knowledge base: (How to add active dates)[https://www.syncfusion.com/kb/12073/how-to-add-active-dates-in-the-flutter-calendar] + /// * Knowledge base: (How to customize the blackout dates )[https://www.syncfusion.com/kb/12003/how-to-customize-the-blackout-dates-in-the-flutter-event-calendar-sfcalendar] + /// /// /// ``` dart /// @@ -1259,6 +1500,14 @@ class SfCalendar extends StatefulWidget { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// + /// See also: + /// * [initialSelectedDate], to select date when calendar displays initially + /// * [CalendarController.selectedDate], to select date programmatically in + /// calendar in the runtime. + /// * [SfCalendarTheme], to handle theming with calendar for giving consistent + /// look. + /// * Knowledge base: [How to customize the selection using decoration](https://www.syncfusion.com/kb/12245/how-to-customize-the-selection-using-decoration-in-the-flutter-calendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -1288,6 +1537,20 @@ class SfCalendar extends StatefulWidget { /// Defaults to `DateTime(DateTime.now().year, DateTime.now().month, /// DateTime.now().day, 08, 45, 0)`. /// + /// See also: + /// * [CalendarController.displayDate], to change the display date of calendar + /// programmatically on run time. + /// * [CalendarController.forward], to navigate to next view of calendar + /// programmatically. + /// * [CalendarController.backward], to navigate to previous view of calendar + /// Programmatically. + /// * [onViewChanged], the callback which will notify that the current visible + /// dates were changed in calendar. + /// * [initialSelectedDate], to selected date programmatically on calendar + /// initially. + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12654/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-calendar) + /// * Knowledge base: [How to programmatically navigate to a date](https://www.syncfusion.com/kb/12139/how-to-programmatically-navigate-to-the-date-in-the-flutter-event-calendar-sfcalendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -1316,8 +1579,13 @@ class SfCalendar extends StatefulWidget { /// Defaults to null. /// /// See also: - /// [Appointment.startTimeZone]. - /// [Appointment.endTimeZone]. + /// * [Appointment.startTimeZone], to handle time zone for appointment's start + /// time. + /// * [Appointment.endTimeZone], to handle time zone for appointment's end + /// time. + /// * [The documentation for time zone](https://help.syncfusion.com/flutter/calendar/timezone) + /// + /// /// /// ```dart /// @@ -1339,6 +1607,20 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to null. /// + /// See also: + /// * [CalendarController.selectedDate], to selected date programmatically on + /// calendar in the runtime. + /// * [selectionDecoration], to add decoration for the selected cell in + /// calendar. + /// * [initialDisplayDate], to navigate to the given date on calendar + /// initially. + /// * [CalendarController.displayDate], to navigate to the given date on + /// calendar programmatically in runtime. + /// * [onSelectionChanged], the callback which notifies when the selected cell + /// changed on calendar. + /// * Knowledge base: [How to get the selected dates using selection changed callback](https://www.syncfusion.com/kb/12551/how-to-get-the-selected-dates-using-selection-changed-callback-in-the-flutter-calendar) + /// * Knowledge base: [How to programmatically select the dates](https://www.syncfusion.com/kb/12115/how-to-programmatically-select-the-dates-in-the-flutter-calendar) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -1368,7 +1650,19 @@ class SfCalendar extends StatefulWidget { /// available /// in the [ViewChangedDetails]. /// - /// See also: [ViewChangedDetails]. + /// See also: + /// * [ViewChangedDetails], which contains the visible dates collection of the + /// current visible view. + /// * [initialDisplayDate], to display calendar on a desired date in initial. + /// * [CalendarController.displayDate], to change the display date of calendar + /// programmatically on run time. + /// * [CalendarController.forward], to navigate to next view of calendar + /// programmatically. + /// * [CalendarController.backward], to navigate to previous view of calendar + /// Programmatically. + /// * Knowledge base: [How to update blackout dates using onViewChanged](https://www.syncfusion.com/kb/12368/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-calendar) + /// * Knowledge base: [How to clear the appointments in the month agenda view using onViewChange](https://www.syncfusion.com/kb/12089/how-to-clear-the-appointments-in-month-agenda-view-using-onviewchange-callback-in-the) + /// * Knowledge base: [How to add a special region dynamically using onTap, onViewChanged](https://www.syncfusion.com/kb/11729/how-to-add-a-special-region-dynamically-using-ontap-onviewchanged-callbacks-of-the-flutter) /// /// ```dart /// @@ -1392,8 +1686,23 @@ class SfCalendar extends StatefulWidget { /// performed on element available in the [CalendarTapDetails]. /// /// see also: - /// [CalendarTapDetails]. - /// [CalendarElement] + /// * [CalendarTapDetails], which contains the details of the tapped element + /// in the calendar. + /// * [CalendarElement], to know about the different available elements in the + /// calendar. + /// * [initialSelectedDate], to selected the date programmatically on calendar + /// initially. + /// * [CalendarController.selectedDate], to selected date programmatically on + /// calendar in the runtime. + /// * [selectionDecoration], to add decoration for the selected cell in + /// calendar. + /// * [onSelectionChanged], the callback which notifies when the selected cell + /// changed on calendar. + /// * Knowledge base: [How to get appointment details for the onTap](https://www.syncfusion.com/kb/10999/how-to-get-appointment-details-from-the-ontap-event-of-the-flutter-calendar) + /// * Knowledge base: [How to add a special region dynamically using onTap, onViewChanged](https://www.syncfusion.com/kb/11729/how-to-add-a-special-region-dynamically-using-ontap-onviewchanged-callbacks-of-the-flutter) + /// * Knowledge base: [How to interact with event calendar with appointments loaded](https://www.syncfusion.com/kb/12218/how-to-interact-with-event-calendar-cell-when-appointments-loaded-in-the-flutter-calendar) + /// * Knowledge base: [How to add the appointments using the onTap](https://www.syncfusion.com/kb/12300/how-to-add-the-appointments-using-the-ontap-callback-in-the-flutter-calendar) + /// * Knowledge base: [How to design and configure your appointment editor](https://www.syncfusion.com/kb/11204/how-to-design-and-configure-your-appointment-editor-in-flutter-calendar) /// /// ```dart /// @@ -1419,8 +1728,15 @@ class SfCalendar extends StatefulWidget { /// performed on element available in the [CalendarLongPressDetails]. /// /// see also: - /// [CalendarLongPressDetails]. - /// [CalendarElement] + /// * [CalendarLongPressDetails], which contains the details of the long + /// pressed element in the calendar. + /// * [CalendarElement], to know about the different available elements in the + /// calendar. + /// * [selectionDecoration], to add decoration for the selected cell in + /// calendar. + /// * [onSelectionChanged], the callback which notifies when the selected cell + /// changed on calendar. + /// * Knowledge base: [How to handle the long press action](https://www.syncfusion.com/kb/12121/how-to-handle-the-long-press-action-on-date-selection-in-the-flutter-calendar) /// /// ```dart /// @@ -1445,7 +1761,19 @@ class SfCalendar extends StatefulWidget { /// its resource details. /// /// see also: - /// [initialSelectedDate], and [controller.selectedDate]. + /// * [CalendarSelectionDetails], which contains the details of the selected + /// element in the calendar. + /// * [CalendarElement], to know about the different available elements in the + /// calendar. + /// * [initialSelectedDate], to selected the date programmatically on calendar + /// initially. + /// * [CalendarController.selectedDate], to selected date programmatically on + /// calendar in the runtime. + /// * [selectionDecoration], to add decoration for the selected cell in + /// calendar. + /// * [onTap], the callback which notifies when the calendar elements tapped + /// on view. + /// * Knowledge base: [How to get the selected dates using the selection changed](https://www.syncfusion.com/kb/12551/how-to-get-the-selected-dates-using-selection-changed-callback-in-the-flutter-calendar) /// /// ```dart /// @@ -1472,7 +1800,34 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to null. /// - /// see also: [CalendarDataSource] + /// see also: + /// * [CalendarDataSource], to add and handle the appointments and resource on + /// the calendar. + /// * [Appointment], the object which holds the details of the appointment. + /// * [CalendarResource], the object which holds the details of the resource + /// in the calendar. + /// * [loadMoreWidgetBuilder], allows to build an widget which will display + /// when appointments loaded on demand in the calendar. + /// * [resourceViewHeaderBuilder], to set custom widget for the resource view + /// in the calendar. + /// * [resourceViewSettings], to customize the resource view in the calendar. + /// * [appointmentBuilder], to set custom widget for the appointment view in + /// the calendar. + /// * [appointmentTextStyle], to customize the text style for the appointments + /// in calendar. + /// * [appointmentTimeTextFormat], to customize the time format for the + /// appointment view in calendar. + /// * Knowledge base: [How to perform the crud operations using firestore database](https://www.syncfusion.com/kb/12661/how-to-perform-the-crud-operations-in-flutter-calendar-using-firestore-database) + /// * Knowledge base: [How to load appointments on demand](https://www.syncfusion.com/kb/12658/how-to-load-appointments-on-demand-in-flutter-calendar) + /// * Knowledge base: [How to load google calendar events in iOS](https://www.syncfusion.com/kb/12647/how-to-load-the-google-calendar-events-to-the-flutter-calendar-sfcalendar-in-ios) + /// * Knowledge base: [How to get the appointments between the given start and end date](https://www.syncfusion.com/kb/12549/how-to-get-the-appointments-between-the-given-start-and-end-date-in-the-flutter-calendar) + /// * Knowledge base: [How to get the current month appointments](https://www.syncfusion.com/kb/12477/how-to-get-the-current-month-appointments-in-the-flutter-calendar) + /// * Knowledge base: [How to load data from offline sqlite database](https://www.syncfusion.com/kb/12399/how-to-load-data-from-offline-sqlite-database-to-flutter-calendar) + /// * Knowledge base: [How to create time table](https://www.syncfusion.com/kb/12392/how-to-create-time-table-using-flutter-event-calendar) + /// * Knowledge base: [How to add google calendar events](https://www.syncfusion.com/kb/12116/how-to-add-google-calendar-events-to-the-flutter-event-calendar-sfcalendar) + /// * Knowledge base: [How to add a custom appointments of business objects](https://www.syncfusion.com/kb/11529/how-to-add-a-custom-appointments-or-objects-in-the-flutter-calendar) + /// * Knowledge base: [How to delete an appointment](https://www.syncfusion.com/kb/11522/how-to-delete-an-appointment-in-the-flutter-calendar) + /// /// /// ```dart /// @@ -1523,6 +1878,18 @@ class SfCalendar extends StatefulWidget { /// /// It also used to restrict interaction on time slots. /// + /// See also: + /// * [timeRegionBuilder], to set custom widget for the time regions in the + /// calendar + /// * [TimeRegion], to now the details about the special time regions and it's + /// properties in calendar. + /// * Knowledge base: [How to customize special regions with builder](https://www.syncfusion.com/kb/12192/how-to-customize-the-special-time-region-using-custom-builder-in-the-flutter-calendar) + /// * Knowledge base: [How to create time table](https://www.syncfusion.com/kb/12392/how-to-create-time-table-using-flutter-event-calendar) + /// * Knowledge base: [How to add a special region dynamically using onTap and onViewChanged](https://www.syncfusion.com/kb/11729/how-to-add-a-special-region-dynamically-using-ontap-onviewchanged-callbacks-of-the-flutter) + /// * Knowledge base: [How to use multiple recurrence rule in special region](https://www.syncfusion.com/kb/11730/how-to-use-multiple-recurrence-rule-rrule-in-special-region-using-flutter-calendar) + /// * Knowledge base: [How to highlight the weekends](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// * Knowledge base: [How to highlight the lunch hours](https://www.syncfusion.com/kb/11712/how-to-highlight-the-weekends-in-the-flutter-calendar) + /// /// ``` dart /// Widget build(BuildContext context) { /// return Container( @@ -1557,7 +1924,10 @@ class SfCalendar extends StatefulWidget { /// /// Defaults to false /// - /// see also: [weekNumberStyle] + /// see also: + /// * [weekNumberStyle], to customize the week number view in the calendar. + /// * [firstDayOfWeek], to change the first day of the week in the calendar. + /// /// /// ``` dart /// Widget build(BuildContext context) { @@ -1576,9 +1946,14 @@ class SfCalendar extends StatefulWidget { /// Using a [SfCalendarTheme] gives more fine-grained control over the /// appearance of various components of the calendar. /// - /// Defaults to null + /// Defaults to false /// - /// see also: [showWeekNumber] + /// see also: + /// * [showWeekNumber], to display the week number view on the calendar views. + /// * [WeekNumberStyle], to know more about the available customization + /// options for week number view in calendar. + /// * [SfCalendarTheme], to handle theming with calendar for giving consistent + /// look. /// /// ``` dart /// Widget build(BuildContext context) { @@ -1600,8 +1975,11 @@ class SfCalendar extends StatefulWidget { /// Defaults to null. /// /// see also: - /// [ResourceViewSettings]. - /// [CalendarResource] + /// * [resourceViewSettings], to customize the resource view in the calendar + /// * [CalendarResource], the object which holds the data of the resource in + /// calendar. + /// * [dataSource.resources], the collection of resource to be displayed in + /// the timeline views of [SfCalendar]. /// /// ``` dart /// Widget build(BuildContext context) { @@ -1638,6 +2016,153 @@ class SfCalendar extends StatefulWidget { /// } final ResourceViewHeaderBuilder? resourceViewHeaderBuilder; + /// Allows to drag and drop the appointment, to reschedule this into + /// new date time. + /// + /// Defaults to false. + /// + /// See also: + /// * [allowAppointmentResize], which allows to resize the appointment to + /// reschedule. + /// * [dragAndDropSettings], which allows to customize the drag and + /// drop environment. + /// + /// _Note:_ This doesn't applicable when the [view] set as + /// [CalendarView.schedule]. This doesn't applicable when the [view] set as + /// [CalendarView.month] and [CalendarView.timelineMonth] in mobile layout. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// ), + /// ); + /// } + final bool allowDragAndDrop; + + /// Allows to customize the drag and drop environment. + /// + /// See also: + /// * [monthViewSettings], which allows to customize the month view of + /// the calendar. + /// * [timeSlotViewSettings], which allows to customize the timeslot view + /// of the calendar. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// ), + /// ); + /// } + final DragAndDropSettings dragAndDropSettings; + + /// Called whenever the appointment starts to drag in the [SfCalendar]. + /// + /// The callback arguments contains the dragging appointment and associated + /// resource details. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// onDragStart: (AppointmentDragStartDetails details) { + /// dynamic appointment = details.appointment!; + /// CalendarResource? resource = details.resource; + /// }), + /// ); + /// } + final AppointmentDragStartCallback? onDragStart; + + /// Called whenever the appointment is dragging in the [SfCalendar]. + /// + /// The callback arguments contains the dragging appointment, dragging time, + /// dragging offset, source resource and target resource details. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// onDragUpdate: (AppointmentDragUpdateDetails details) { + /// dynamic appointment = details.appointment!; + /// CalendarResource? sourceResource = details.sourceResource; + /// CalendarResource? targetResource = details.targetResource; + /// Offset? draggingPosition = details.draggingPosition; + /// DateTime? draggingTime = details.draggingTime; + /// }), + /// ); + /// } + final AppointmentDragUpdateCallback? onDragUpdate; + + /// Called when the dragging appointment is dropped in the [SfCalendar]. + /// + /// The callback arguments contains the dropped appointment, dropping time, + /// source and target resource details. + /// + /// ``` dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCalendar( + /// view: CalendarView.month, + /// showWeekNumber: true, + /// allowDragAndDrop: true, + /// dragAndDropSettings: DragAndDropSettings( + /// allowNavigation: true, + /// allowScroll: true, + /// autoNavigateDelay: Duration(seconds: 1), + /// indicatorTimeFormat: 'HH:mm a', + /// showTimeIndicator: true, + /// ), + /// onDragEnd: (AppointmentDragEndDetails details) { + /// dynamic appointment = details.appointment!; + /// CalendarResource? sourceResource = details.sourceResource; + /// CalendarResource? targetResource = details.targetResource; + /// DateTime? draggingTime = details.droppingTime; + /// }), + /// ); + /// } + final AppointmentDragEndCallback? onDragEnd; + /// An object that used for programmatic date navigation and date selection /// in [SfCalendar]. /// @@ -1665,10 +2190,24 @@ class SfCalendar extends StatefulWidget { /// controller, the listener will listen and notify whenever the selected /// date, display date changed in the [SfCalendar]. /// - /// See also: [CalendarController]. - /// /// Defaults to null. /// + /// See also: + /// * [CalendarController], to know more about the controller and it's usage + /// with the calendar. + /// * [initialSelectedDate], to select dates initially on the calendar. + /// * [initialDisplayDate], the date which shown when the calendar displayed + /// initially. + /// * [onViewChanged], the callback which notifies when the current view + /// visible date changed on calendar. + /// * [onSelectionChanged], the callback which notifies when the selected cell + /// changed on the calendar. + /// * Knowledge base: [How to programmatically navigate to adjacent days](https://www.syncfusion.com/kb/12654/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-calendar) + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12141/how-to-do-programmatic-navigation-using-flutter-calendar) + /// * Knowledge base: [How to programmatically select the dates](https://www.syncfusion.com/kb/12115/how-to-programmatically-select-the-dates-in-the-flutter-calendar) + /// * Knowledge base: [How to move to a specific time while switching view](https://www.syncfusion.com/kb/10943/how-to-move-to-a-specific-time-while-switching-from-month-to-day-view-in-the-flutter) + /// + /// /// This example demonstrates how to use the [CalendarController] for /// [SfCalendar]. /// @@ -1700,6 +2239,122 @@ class SfCalendar extends StatefulWidget { /// ``` final CalendarController? controller; + /// Allows to reschedule the appointment by resizing the appointment. + /// + /// Defaults to `false`. + /// + /// _Note:_ The appointment resizing is not applicable for mobile platform. + /// + /// See also: + /// • [allowDragAndDrop], which allows to reschedule a appointment by drag + /// and drop to different date time. + /// + /// ```dart + /// + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCalendar( + /// allowAppointmentResize: true, + /// ), + /// ); + /// } + /// + /// ```` + final bool allowAppointmentResize; + + /// Called whenever the appointment starts to resizing in [SfCalendar]. + /// + /// The resizing appointment and resource details when the appointment starts + /// to resize available in the [AppointmentResizeStartDetails]. + /// + /// See also: + /// * [AppointmentResizeStartDetails], which contains the details of the + /// resizing appointment in the calendar. + /// * [onAppointmentResizeUpdate], callback to notifies when the appointment + /// resizing. + /// * [onAppointmentResizeEnd], callback to notify when the appointment + /// resizing ends. + /// + /// ```dart + /// + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.week, + /// onAppointmentResizeStart: (AppointmentResizeStartDetails details){ + /// dynamic appointment = details.appointment!; + /// CalendarResource? resource = details.resource; + /// }, + /// ), + /// ); + /// } + /// + /// ``` + final AppointmentResizeStartCallback? onAppointmentResizeStart; + + /// Called whenever the appointment resizing in [SfCalendar]. + /// + /// The resizing appointment, position, time and resource details when the + /// appointment resizing available in the [AppointmentResizeUpdateDetails]. + /// + /// See also: + /// * [AppointmentResizeUpdateDetails], which contains the details of the + /// resizing appointment details in the calendar. + /// * [onAppointmentResizeStart], callback to notifies when the appointment + /// start resizing. + /// * [onAppointmentResizeEnd], callback to notify when the appointment + /// resizing ends. + /// + /// ```dart + /// + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.week, + /// onAppointmentResizeUpdate: (AppointmentResizeUpdateDetails + /// details){ + /// dynamic appointment = details.appointment!; + /// CalendarResource? resource = details.resource; + /// DateTime time = details.resizingTime!; + /// Offset position = details.resizingOffset!; + /// }, + /// ), + /// ); + /// } + /// + /// ``` + final AppointmentResizeUpdateCallback? onAppointmentResizeUpdate; + + /// Called whenever the appointment resizing end in the [SfCalendar]. + /// + /// The resizing appointment, start time, end time and resource details when + /// the appointment resizing ends available in the + /// [AppointmentResizeEndDetails]. + /// + /// See also: + /// * [AppointmentResizeEndDetails], which contains the details of the + /// resized appointment details in the calendar. + /// * [onAppointmentResizeStart], callback to notifies when the appointment + /// start resizing. + /// * [onAppointmentResizeUpdate], callback to notifies when the appointment + /// resizing. + /// + /// ```dart + /// + /// return Container( + /// child: SfCalendar( + /// view: CalendarView.week, + /// onAppointmentResizeEnd: (AppointmentResizeEndDetails details){ + /// dynamic appointment = details.appointment!; + /// CalendarResource? resource = details.resource; + /// DateTime time = details.startTime!; + /// DateTime endTime = details.endTime!; + /// }, + /// ), + /// ); + /// } + /// + /// ``` + final AppointmentResizeEndCallback? onAppointmentResizeEnd; + /// Returns the date time collection at which the recurrence appointment will /// recur /// @@ -1717,6 +2372,13 @@ class SfCalendar extends StatefulWidget { /// /// return `List` /// + /// See also: + /// * [Appointment.recurrenceRule], to set the recurrence rule for the + /// [Appointment], which will recur based on the given rule. + /// * [TimeRegion.recurrenceRule], to set the recurrence rule for the + /// [TimeRegion], which will recur based on the given rule. + /// * Knowledge base: [How to get the recurrence date collection](https://www.syncfusion.com/kb/12344/how-to-get-the-recurrence-date-collection-in-the-flutter-calendar) + /// ///```dart /// /// DateTime dateTime = DateTime(2020, 03, 15); @@ -1752,6 +2414,14 @@ class SfCalendar extends StatefulWidget { /// /// returns `RecurrenceProperties`. /// + /// See also: + /// * [RecurrenceProperties], to know more about the available recurrence + /// properties which helps to create the recurrence rule. + /// * [generateRRule], to generate recurrence rule based on the + /// [RecurrenceProperties]. + /// * Knowledge base: [How to get the recurrence properties from rrule](https://www.syncfusion.com/kb/12370/how-to-get-the-recurrence-properties-from-rrule-in-the-flutter-calendar) + /// + /// /// ```dart /// /// DateTime dateTime = DateTime(2020, 03, 15); @@ -1775,6 +2445,16 @@ class SfCalendar extends StatefulWidget { /// /// returns `String`. /// + /// See also: + /// * [RecurrenceProperties], to know more about the available recurrence + /// properties which helps to create the recurrence rule. + /// * [parseRRule], to generate the recurrence properties based on the + /// recurrence rule. + /// * [Appointment.recurrenceRule], to set the recurrence rule for the + /// [Appointment], which will recur based on the given rule. + /// * [TimeRegion.recurrenceRule], to set the recurrence rule for the + /// [TimeRegion], which will recur based on the given rule. + /// /// ```dart /// /// RecurrenceProperties recurrenceProperties = @@ -1884,6 +2564,16 @@ class SfCalendar extends StatefulWidget { .toDiagnosticsNode(name: 'specialRegions')); properties.add(DiagnosticsProperty( 'resourceViewHeaderBuilder', resourceViewHeaderBuilder)); + properties + .add(DiagnosticsProperty('allowDragAndDrop', allowDragAndDrop)); + properties.add( + dragAndDropSettings.toDiagnosticsNode(name: 'dragAndDropSettings')); + properties.add(DiagnosticsProperty( + 'onDragStart', onDragStart)); + properties.add(DiagnosticsProperty( + 'onDragUpdate', onDragUpdate)); + properties.add(DiagnosticsProperty( + 'onDragEnd', onDragEnd)); } } @@ -1899,7 +2589,8 @@ class _SfCalendarState extends State /// Used to get the scrolled position to update the header value. ScrollController? _agendaScrollController, _resourcePanelScrollController; - late ValueNotifier _agendaSelectedDate; + late ValueNotifier _agendaSelectedDate, + _timelineMonthWeekNumberNotifier; ValueNotifier _headerUpdateNotifier = ValueNotifier(null); late String _locale; @@ -2047,6 +2738,8 @@ class _SfCalendarState extends State _scheduleDisplayDate = _controller.displayDate!; _controller.view ??= widget.view; _view = _controller.view!; + _timelineMonthWeekNumberNotifier = + ValueNotifier(_controller.displayDate); if (_selectedDate != null) { _updateSelectionChangedCallback(); } @@ -2093,10 +2786,14 @@ class _SfCalendarState extends State final SfCalendarThemeData calendarThemeData = SfCalendarTheme.of(context); final ThemeData themeData = Theme.of(context); _calendarTheme = calendarThemeData.copyWith( - todayHighlightColor: - calendarThemeData.todayHighlightColor ?? themeData.accentColor, - selectionBorderColor: - calendarThemeData.selectionBorderColor ?? themeData.accentColor); + todayHighlightColor: calendarThemeData.todayHighlightColor ?? + themeData.colorScheme.secondary, + selectionBorderColor: calendarThemeData.selectionBorderColor ?? + themeData.colorScheme.secondary, + timeIndicatorTextStyle: calendarThemeData.timeIndicatorTextStyle + .copyWith( + color: calendarThemeData.timeIndicatorTextStyle.color ?? + themeData.colorScheme.secondary)); //// localeOf(context) returns the locale from material app when SfCalendar locale value as null _locale = Localizations.localeOf(context).toString(); _localizations = SfLocalizations.of(context); @@ -2843,6 +3540,9 @@ class _SfCalendarState extends State if (_view == CalendarView.timelineMonth) { _currentViewVisibleDates = DateTimeHelper.getCurrentMonthDates(_currentViewVisibleDates); + if (widget.showWeekNumber) { + _timelineMonthWeekNumberNotifier.value = _currentViewVisibleDates[0]; + } } } @@ -3221,8 +3921,8 @@ class _SfCalendarState extends State []; for (final CalendarAppointment appointment in _visibleAppointments) { if (appointment.isAllDay || - appointment.actualEndTime - .difference(appointment.actualStartTime) + AppointmentHelper.getDifference( + appointment.actualStartTime, appointment.actualEndTime) .inDays > 0) { allDayAppointments.add(appointment); @@ -3239,10 +3939,10 @@ class _SfCalendarState extends State _allDayAppointmentViewCollection .sort((AppointmentView app1, AppointmentView app2) { if (app1.appointment != null && app2.appointment != null) { - return (app2.appointment!.endTime - .difference(app2.appointment!.startTime)) > - (app1.appointment!.endTime - .difference(app1.appointment!.startTime)) + return AppointmentHelper.getDifference( + app2.appointment!.startTime, app2.appointment!.endTime) > + AppointmentHelper.getDifference( + app1.appointment!.startTime, app1.appointment!.endTime) ? 1 : 0; } @@ -3551,6 +4251,8 @@ class _SfCalendarState extends State } } + final Duration duration = AppointmentHelper.getDifference( + appointment.actualStartTime, appointment.actualEndTime); for (int k = recursiveDates.length - 1; k >= 0; k--) { final DateTime recurrenceDate = recursiveDates[k]; bool isExceptionDate = false; @@ -3568,10 +4270,7 @@ class _SfCalendarState extends State if (!isExceptionDate) { final DateTime recurrenceEndDate = DateTimeHelper.getDateTimeValue( - addDuration( - recurrenceDate, - appointment.actualEndTime - .difference(appointment.actualStartTime))); + addDuration(recurrenceDate, duration)); if (recurrenceEndDate.isAfter(currentMaxDate)) { currentMaxDate = recurrenceEndDate; break; @@ -3676,8 +4375,8 @@ class _SfCalendarState extends State final List recursiveDates = RecurrenceHelper.getRecurrenceDateTimeCollection( rule, appointment.actualStartTime, - recurrenceDuration: appointment.actualEndTime - .difference(appointment.actualStartTime), + recurrenceDuration: AppointmentHelper.getDifference( + appointment.actualStartTime, appointment.actualEndTime), specificStartDate: startDate, specificEndDate: endDate); @@ -4383,7 +5082,8 @@ class _SfCalendarState extends State _isMobilePlatform, widget.appointmentBuilder, _minWidth - viewPadding, - panelHeight), + panelHeight, + widget), )), onTapUp: (TapUpDetails details) { _removeDatePicker(); @@ -4804,7 +5504,8 @@ class _SfCalendarState extends State DateTime(currentDate.year, currentDate.month, currentDate.day), widget.dataSource != null && !AppointmentHelper.isCalendarAppointment(widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments(currentAppointments) + ? CalendarViewHelper.getCustomAppointments( + currentAppointments, widget.dataSource) : currentAppointments, CalendarElement.viewHeader, null); @@ -4814,7 +5515,8 @@ class _SfCalendarState extends State DateTime(currentDate.year, currentDate.month, currentDate.day), widget.dataSource != null && !AppointmentHelper.isCalendarAppointment(widget.dataSource!) - ? CalendarViewHelper.getCustomAppointments(currentAppointments) + ? CalendarViewHelper.getCustomAppointments( + currentAppointments, widget.dataSource) : currentAppointments, CalendarElement.viewHeader, null); @@ -4866,7 +5568,7 @@ class _SfCalendarState extends State !AppointmentHelper.isCalendarAppointment( widget.dataSource!) ? CalendarViewHelper.getCustomAppointments( - selectedAppointment) + selectedAppointment, widget.dataSource) : selectedAppointment, CalendarElement.appointment, null); @@ -4878,7 +5580,7 @@ class _SfCalendarState extends State !AppointmentHelper.isCalendarAppointment( widget.dataSource!) ? CalendarViewHelper.getCustomAppointments( - selectedAppointment) + selectedAppointment, widget.dataSource) : selectedAppointment, CalendarElement.appointment, null); @@ -5297,39 +5999,41 @@ class _SfCalendarState extends State color: widget.headerStyle.backgroundColor ?? _calendarTheme.headerBackgroundColor, child: _CalendarHeaderView( - _currentViewVisibleDates, - widget.headerStyle, - null, - _view, - widget.monthViewSettings.numberOfWeeksInView, - _calendarTheme, - isRTL, - _locale, - widget.showNavigationArrow, - _controller, - widget.maxDate, - widget.minDate, - _minWidth, - widget.headerHeight, - widget.timeSlotViewSettings.nonWorkingDays, - widget.monthViewSettings.navigationDirection, - widget.showDatePickerButton, - _showHeader, - widget.allowedViews, - widget.allowViewNavigation, - _localizations, - _removeDatePicker, - _headerUpdateNotifier, - _viewChangeNotifier, - _handleOnTapForHeader, - _handleOnLongPressForHeader, - widget.todayHighlightColor, - _textScaleFactor, - _isMobilePlatform, - widget.headerDateFormat, - true, - widget.todayTextStyle, - )), + _currentViewVisibleDates, + widget.headerStyle, + null, + _view, + widget.monthViewSettings.numberOfWeeksInView, + _calendarTheme, + isRTL, + _locale, + widget.showNavigationArrow, + _controller, + widget.maxDate, + widget.minDate, + _minWidth, + widget.headerHeight, + widget.timeSlotViewSettings.nonWorkingDays, + widget.monthViewSettings.navigationDirection, + widget.showDatePickerButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + true, + widget.todayTextStyle, + widget.showWeekNumber, + widget.weekNumberStyle, + _timelineMonthWeekNumberNotifier)), ), ), Positioned( @@ -6144,39 +6848,41 @@ class _SfCalendarState extends State color: widget.headerStyle.backgroundColor ?? _calendarTheme.headerBackgroundColor, child: _CalendarHeaderView( - _currentViewVisibleDates, - widget.headerStyle, - null, - _view, - widget.monthViewSettings.numberOfWeeksInView, - _calendarTheme, - isRTL, - _locale, - widget.showNavigationArrow, - _controller, - widget.maxDate, - widget.minDate, - _minWidth, - widget.headerHeight, - widget.timeSlotViewSettings.nonWorkingDays, - widget.monthViewSettings.navigationDirection, - widget.showDatePickerButton, - _showHeader, - widget.allowedViews, - widget.allowViewNavigation, - _localizations, - _removeDatePicker, - _headerUpdateNotifier, - _viewChangeNotifier, - _handleOnTapForHeader, - _handleOnLongPressForHeader, - widget.todayHighlightColor, - _textScaleFactor, - _isMobilePlatform, - widget.headerDateFormat, - !_isScheduleStartLoadMore && !_isNeedLoadMore, - widget.todayTextStyle, - )), + _currentViewVisibleDates, + widget.headerStyle, + null, + _view, + widget.monthViewSettings.numberOfWeeksInView, + _calendarTheme, + isRTL, + _locale, + widget.showNavigationArrow, + _controller, + widget.maxDate, + widget.minDate, + _minWidth, + widget.headerHeight, + widget.timeSlotViewSettings.nonWorkingDays, + widget.monthViewSettings.navigationDirection, + widget.showDatePickerButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + !_isScheduleStartLoadMore && !_isNeedLoadMore, + widget.todayTextStyle, + widget.showWeekNumber, + widget.weekNumberStyle, + _timelineMonthWeekNumberNotifier)), ), ), Positioned( @@ -6708,8 +7414,8 @@ class _SfCalendarState extends State if (app.resourceIds != null && app.resourceIds!.isNotEmpty && app.resourceIds!.contains(resource.id)) { - selectedResourceAppointments - .add(CalendarViewHelper.getAppointmentDetail(app)); + selectedResourceAppointments.add( + CalendarViewHelper.getAppointmentDetail(app, widget.dataSource)); } } @@ -6762,6 +7468,7 @@ class _SfCalendarState extends State widget.minDate, widget.maxDate, _localizations, + _timelineMonthWeekNumberNotifier, _updateCalendarState, _getCalendarStateDetails, key: _customScrollViewKey, @@ -6788,39 +7495,41 @@ class _SfCalendarState extends State color: widget.headerStyle.backgroundColor ?? _calendarTheme.headerBackgroundColor, child: _CalendarHeaderView( - _currentViewVisibleDates, - widget.headerStyle, - currentViewDate, - _view, - widget.monthViewSettings.numberOfWeeksInView, - _calendarTheme, - isRTL, - _locale, - widget.showNavigationArrow, - _controller, - widget.maxDate, - widget.minDate, - width, - widget.headerHeight, - widget.timeSlotViewSettings.nonWorkingDays, - widget.monthViewSettings.navigationDirection, - widget.showDatePickerButton, - _showHeader, - widget.allowedViews, - widget.allowViewNavigation, - _localizations, - _removeDatePicker, - _headerUpdateNotifier, - _viewChangeNotifier, - _handleOnTapForHeader, - _handleOnLongPressForHeader, - widget.todayHighlightColor, - _textScaleFactor, - _isMobilePlatform, - widget.headerDateFormat, - !_isNeedLoadMore, - widget.todayTextStyle, - )), + _currentViewVisibleDates, + widget.headerStyle, + currentViewDate, + _view, + widget.monthViewSettings.numberOfWeeksInView, + _calendarTheme, + isRTL, + _locale, + widget.showNavigationArrow, + _controller, + widget.maxDate, + widget.minDate, + width, + widget.headerHeight, + widget.timeSlotViewSettings.nonWorkingDays, + widget.monthViewSettings.navigationDirection, + widget.showDatePickerButton, + _showHeader, + widget.allowedViews, + widget.allowViewNavigation, + _localizations, + _removeDatePicker, + _headerUpdateNotifier, + _viewChangeNotifier, + _handleOnTapForHeader, + _handleOnLongPressForHeader, + widget.todayHighlightColor, + _textScaleFactor, + _isMobilePlatform, + widget.headerDateFormat, + !_isNeedLoadMore, + widget.todayTextStyle, + widget.showWeekNumber, + widget.weekNumberStyle, + _timelineMonthWeekNumberNotifier)), ), _addResourcePanel(isResourceEnabled, resourceViewSize, height, isRTL), _addCustomScrollView(widget.headerHeight, resourceViewSize, isRTL, @@ -7235,6 +7944,10 @@ class _SfCalendarState extends State (CalendarAppointment app1, CalendarAppointment app2) => AppointmentHelper.orderAppointmentsAscending( app1.isAllDay, app2.isAllDay)); + agendaAppointments.sort( + (CalendarAppointment app1, CalendarAppointment app2) => + AppointmentHelper.orderAppointmentsAscending( + app1.isSpanned, app2.isSpanned)); int index = -1; //// Agenda appointment view top padding as 5. @@ -7271,7 +7984,8 @@ class _SfCalendarState extends State agendaAppointments = [agendaAppointments[index]]; if (widget.dataSource != null && !AppointmentHelper.isCalendarAppointment(widget.dataSource!)) { - return CalendarViewHelper.getCustomAppointments(agendaAppointments); + return CalendarViewHelper.getCustomAppointments( + agendaAppointments, widget.dataSource); } return agendaAppointments; @@ -7330,7 +8044,8 @@ class _SfCalendarState extends State _isMobilePlatform, widget.appointmentBuilder, width, - height), + height, + widget), onTapUp: (TapUpDetails details) { _handleTapForAgenda(details, null); }, @@ -7440,7 +8155,8 @@ class _SfCalendarState extends State _isMobilePlatform, widget.appointmentBuilder, width - _agendaDateViewWidth, - painterHeight), + painterHeight, + widget), ], ), ), @@ -7586,11 +8302,15 @@ class _CalendarHeaderView extends StatefulWidget { this.isMobilePlatform, this.headerDateFormat, this.enableInteraction, - this.todayTextStyle); + this.todayTextStyle, + this.showWeekNumber, + this.weekNumberStyle, + this.timelineMonthWeekNumberNotifier); final List visibleDates; final TextStyle? todayTextStyle; final CalendarHeaderStyle headerStyle; + final WeekNumberStyle weekNumberStyle; final SfCalendarThemeData calendarTheme; final DateTime? currentDate; final CalendarView view; @@ -7620,6 +8340,8 @@ class _CalendarHeaderView extends StatefulWidget { final bool isMobilePlatform; final String? headerDateFormat; final bool enableInteraction; + final bool showWeekNumber; + final ValueNotifier timelineMonthWeekNumberNotifier; @override _CalendarHeaderViewState createState() => _CalendarHeaderViewState(); @@ -7631,7 +8353,9 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { @override void initState() { widget.valueChangeNotifier.addListener(_updateHeaderChanged); - _calendarViews = _getCalendarViewsText(widget.localizations); + widget.timelineMonthWeekNumberNotifier + .addListener(_updateWeekNumberChangedForTimelineMonth); + _calendarViews = _getCalendarViewsText(widget.localizations, true); super.initState(); } @@ -7642,7 +8366,15 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { widget.valueChangeNotifier.addListener(_updateHeaderChanged); } - _calendarViews = _getCalendarViewsText(widget.localizations); + if (widget.timelineMonthWeekNumberNotifier != + oldWidget.timelineMonthWeekNumberNotifier) { + oldWidget.timelineMonthWeekNumberNotifier + .removeListener(_updateWeekNumberChangedForTimelineMonth); + widget.timelineMonthWeekNumberNotifier + .addListener(_updateWeekNumberChangedForTimelineMonth); + } + + _calendarViews = _getCalendarViewsText(widget.localizations, true); super.didUpdateWidget(oldWidget); } @@ -7668,6 +8400,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } final String headerString = _getHeaderText(); + final String weekNumberString = _getWeekNumberString(); final double totalArrowWidth = arrowWidth * 2; /// Show calendar views on header when it is not empty. @@ -7675,6 +8408,8 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { widget.allowedViews != null && widget.allowedViews!.isNotEmpty; double todayIconWidth = 0; double dividerWidth = 0; + double weekNumberPanelWidth = 0, weekNumberPanelHeight = 0; + double weekNumberTextWidth = 0; final List children = []; Color? headerTextColor = widget.headerStyle.textStyle != null ? widget.headerStyle.textStyle!.color @@ -7712,7 +8447,18 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } double headerTextWidth = 0; - if (!widget.isMobilePlatform) { + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + final TextStyle weekNumberTextStyle = widget.weekNumberStyle.textStyle ?? + widget.calendarTheme.weekNumberTextStyle; + + final Color? weekNumberBackgroundColor = + widget.weekNumberStyle.backgroundColor ?? + widget.calendarTheme.weekNumberBackgroundColor; + + final bool weekNumberEnabled = widget.showWeekNumber && + (widget.view == CalendarView.day || isTimelineView); + if (!widget.isMobilePlatform || + (widget.isMobilePlatform && weekNumberEnabled)) { final Size headerTextSize = _getTextWidgetWidth( headerString, widget.height, @@ -7847,15 +8593,48 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { } } + final double headerHeight = + maxHeaderHeight != 0 && maxHeaderHeight <= widget.height + ? maxHeaderHeight + : widget.height; + headerWidth = widget.width - calendarViewWidth - todayIconWidth - dividerWidth - totalArrowWidth; - final double headerHeight = - maxHeaderHeight != 0 && maxHeaderHeight <= widget.height - ? maxHeaderHeight - : widget.height; + + if (weekNumberEnabled) { + weekNumberPanelWidth = (headerWidth - headerTextWidth) + padding; + final double minimumWeekNumberWidth = headerWidth / 4; + if (weekNumberPanelWidth < minimumWeekNumberWidth) { + weekNumberPanelWidth = minimumWeekNumberWidth + padding; + headerWidth = headerTextWidth - weekNumberPanelWidth; + } else { + headerWidth = headerTextWidth; + } + final Size weekNumberPanelSize = _getTextWidgetWidth( + widget.localizations.weeknumberLabel + weekNumberString + ' ', + headerHeight, + weekNumberPanelWidth, + context, + style: weekNumberTextStyle); + weekNumberTextWidth = weekNumberPanelSize.width > weekNumberPanelWidth + ? weekNumberPanelWidth + : weekNumberPanelSize.width + padding; + weekNumberPanelHeight = weekNumberPanelSize.height; + final double occupiedWidth = widget.width - + calendarViewWidth - + todayIconWidth - + dividerWidth - + totalArrowWidth - + weekNumberPanelWidth - + headerWidth; + if (occupiedWidth < 0) { + headerWidth += occupiedWidth; + } + } + final List dates = widget.visibleDates; if (!DateTimeHelper.canMoveToNextView( widget.view, @@ -7956,34 +8735,33 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { alignment: Alignment.centerLeft, padding: const EdgeInsets.symmetric(horizontal: 5), child: Row( - mainAxisAlignment: _getAlignmentFromTextAlign(), - children: widget.showDatePickerButton - ? [ - Flexible( - child: Text(headerString, - style: headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)), - Icon( - widget.isPickerShown - ? Icons.arrow_drop_up - : Icons.arrow_drop_down, - color: arrowColor, - size: headerTextStyle.fontSize ?? 14, - ) - ] - : [ - Flexible( - child: Text(headerString, - style: headerTextStyle, - maxLines: 1, - overflow: TextOverflow.clip, - softWrap: false, - textDirection: TextDirection.ltr)) - ], - )), + mainAxisAlignment: _getAlignmentFromTextAlign(), + children: widget.showDatePickerButton + ? [ + Flexible( + child: Text(headerString, + style: headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)), + Icon( + widget.isPickerShown + ? Icons.arrow_drop_up + : Icons.arrow_drop_down, + color: arrowColor, + size: headerTextStyle.fontSize ?? 14, + ), + ] + : [ + Flexible( + child: Text(headerString, + style: headerTextStyle, + maxLines: 1, + overflow: TextOverflow.clip, + softWrap: false, + textDirection: TextDirection.ltr)) + ])), ), )), ) @@ -8045,7 +8823,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { : Icons.arrow_drop_down, color: arrowColor, size: headerTextStyle.fontSize ?? 14, - ) + ), ] : [ Flexible( @@ -8054,13 +8832,57 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { maxLines: 1, overflow: TextOverflow.clip, softWrap: false, - textDirection: TextDirection.ltr)) + textDirection: TextDirection.ltr)), ], )), ), )), ); + final Widget weekNumberWidget = weekNumberEnabled + ? Semantics( + label: + widget.localizations.weeknumberLabel + ' ' + weekNumberString, + child: Container( + width: isCenterAlignment + ? weekNumberTextWidth + : weekNumberPanelWidth, + height: weekNumberPanelHeight, + alignment: _getHeaderAlignment(), + child: Container( + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all(Radius.circular(padding)), + shape: BoxShape.rectangle, + color: weekNumberBackgroundColor), + alignment: Alignment.center, + width: weekNumberTextWidth, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + child: Text( + widget.localizations.weeknumberLabel, + textAlign: TextAlign.center, + textScaleFactor: widget.textScaleFactor, + style: weekNumberTextStyle, + overflow: TextOverflow.ellipsis, + maxLines: 1, + )), + Flexible( + child: Text( + ' ' + weekNumberString, + textAlign: TextAlign.center, + style: weekNumberTextStyle, + textScaleFactor: widget.textScaleFactor, + maxLines: 1, + )) + ], + )), + ), + ) + : Container(); + final Color? leftArrowSplashColor = prevArrowColor != arrowColor || !widget.enableInteraction ? Colors.transparent @@ -8204,11 +9026,13 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { ); List rowChildren = []; + if (widget.headerStyle.textAlign == TextAlign.left || widget.headerStyle.textAlign == TextAlign.start) { if (widget.isMobilePlatform) { rowChildren = [ headerText, + weekNumberWidget, todayIcon, calendarViewIcon, leftArrow, @@ -8219,6 +9043,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { leftArrow, rightArrow, headerText, + weekNumberWidget, todayIcon, dividerWidget, ]; @@ -8239,6 +9064,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { rightArrow, calendarViewIcon, todayIcon, + weekNumberWidget, headerText, ]; } else { @@ -8248,6 +9074,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { rowChildren.add(dividerWidget); rowChildren.add(todayIcon); + rowChildren.add(weekNumberWidget); rowChildren.add(headerText); rowChildren.add(leftArrow); rowChildren.add(rightArrow); @@ -8262,6 +9089,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { rowChildren = [ leftArrow, headerText, + weekNumberWidget, todayIcon, dividerWidget, calendarViewIcon, @@ -8271,6 +9099,7 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { rowChildren = [ leftArrow, headerText, + weekNumberWidget, rightArrow, todayIcon, dividerWidget, @@ -8290,6 +9119,8 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { @override void dispose() { widget.valueChangeNotifier.removeListener(_updateHeaderChanged); + widget.valueChangeNotifier + .removeListener(_updateWeekNumberChangedForTimelineMonth); super.dispose(); } @@ -8297,6 +9128,14 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { setState(() {}); } + void _updateWeekNumberChangedForTimelineMonth() { + if (!mounted) { + return; + } + + setState(() {}); + } + void _backward() { if (!widget.enableInteraction) { return; @@ -8416,6 +9255,51 @@ class _CalendarHeaderViewState extends State<_CalendarHeaderView> { ); } + String _getWeekNumberString() { + switch (widget.view) { + case CalendarView.day: + case CalendarView.timelineDay: + { + return DateTimeHelper.getWeekNumberOfYear(widget.visibleDates[0]) + .toString(); + } + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.month: + case CalendarView.schedule: + return ''; + case CalendarView.timelineWeek: + for (int i = 0; i < widget.visibleDates.length; i++) { + final DateTime date = widget.visibleDates[i]; + if (date.weekday == DateTime.monday) { + return DateTimeHelper.getWeekNumberOfYear(date).toString(); + } + } + break; + case CalendarView.timelineWorkWeek: + for (int i = 0; i < widget.visibleDates.length; i++) { + final DateTime date = widget.visibleDates[i]; + if (date.weekday == DateTime.monday) { + return DateTimeHelper.getWeekNumberOfYear(date).toString(); + } else if (widget.nonWorkingDays.contains(DateTime.monday)) { + final int midDate = widget.visibleDates.length ~/ 2; + return DateTimeHelper.getWeekNumberOfYear( + widget.visibleDates[midDate]) + .toString(); + } + } + break; + case CalendarView.timelineMonth: + { + return DateTimeHelper.getWeekNumberOfYear( + widget.timelineMonthWeekNumberNotifier.value!) + .toString(); + } + } + return DateTimeHelper.getWeekNumberOfYear(widget.visibleDates[0]) + .toString(); + } + String _getHeaderText() { String monthFormat = 'MMMM'; final String? headerDateFormat = @@ -8563,6 +9447,8 @@ class _ScheduleLabelPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + /// Draw the week label. if (!isMonthLabel) { if (isDisplayDate) { @@ -8781,6 +9667,7 @@ class _ScheduleLabelPainter extends CustomPainter { accessibilityText = DateFormat('MMMM yyyy', locale).format(startDate).toString(); } + semanticsBuilder.add(CustomPainterSemantics( rect: Rect.fromLTWH(left, top, size.width, cellHeight), properties: SemanticsProperties( @@ -9566,21 +10453,62 @@ Size _getTextWidgetWidth( return Size(textWidth + 10, textHeight + 10); } -Map _getCalendarViewsText(SfLocalizations localizations) { +Map _getCalendarViewsText(SfLocalizations localizations, + [bool isUpperCase = false]) { final Map calendarViews = {}; - calendarViews[CalendarView.day] = localizations.allowedViewDayLabel; - calendarViews[CalendarView.week] = localizations.allowedViewWeekLabel; - calendarViews[CalendarView.workWeek] = localizations.allowedViewWorkWeekLabel; + calendarViews[CalendarView.day] = localizations.allowedViewDayLabel.isNotEmpty + ? localizations.allowedViewDayLabel + : isUpperCase + ? 'DAY' + : 'Day'; + calendarViews[CalendarView.week] = + localizations.allowedViewWeekLabel.isNotEmpty + ? localizations.allowedViewWeekLabel + : isUpperCase + ? 'WEEK' + : 'Week'; + calendarViews[CalendarView.workWeek] = + localizations.allowedViewWorkWeekLabel.isNotEmpty + ? localizations.allowedViewWorkWeekLabel + : isUpperCase + ? 'WORK WEEK' + : 'Work Week'; calendarViews[CalendarView.timelineDay] = - localizations.allowedViewTimelineDayLabel; + localizations.allowedViewTimelineDayLabel.isNotEmpty + ? localizations.allowedViewTimelineDayLabel + : isUpperCase + ? 'TIMELINE DAY' + : 'Timeline Day'; calendarViews[CalendarView.timelineWeek] = - localizations.allowedViewTimelineWeekLabel; + localizations.allowedViewTimelineWeekLabel.isNotEmpty + ? localizations.allowedViewTimelineWeekLabel + : isUpperCase + ? 'TIMELINE WEEK' + : 'Timeline Week'; calendarViews[CalendarView.timelineMonth] = - localizations.allowedViewTimelineMonthLabel; + localizations.allowedViewTimelineMonthLabel.isNotEmpty + ? localizations.allowedViewTimelineMonthLabel + : isUpperCase + ? 'TIMELINE MONTH' + : 'Timeline Month'; calendarViews[CalendarView.timelineWorkWeek] = - localizations.allowedViewTimelineWorkWeekLabel; - calendarViews[CalendarView.month] = localizations.allowedViewMonthLabel; - calendarViews[CalendarView.schedule] = localizations.allowedViewScheduleLabel; + localizations.allowedViewTimelineWorkWeekLabel.isNotEmpty + ? localizations.allowedViewTimelineWorkWeekLabel + : isUpperCase + ? 'TIMELINE WORK WEEK' + : 'Timeline Work Week'; + calendarViews[CalendarView.month] = + localizations.allowedViewMonthLabel.isNotEmpty + ? localizations.allowedViewMonthLabel + : isUpperCase + ? 'MONTH' + : 'Month'; + calendarViews[CalendarView.schedule] = + localizations.allowedViewScheduleLabel.isNotEmpty + ? localizations.allowedViewScheduleLabel + : isUpperCase + ? 'SCHEDULE' + : 'Schedule'; return calendarViews; } 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 e8b6e534a..698f1c8af 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 @@ -12,6 +12,8 @@ import 'package:syncfusion_flutter_core/core_internal.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import '../../../calendar.dart'; +import '../appointment_engine/appointment.dart'; import '../appointment_engine/appointment_helper.dart'; import '../appointment_engine/recurrence_helper.dart'; import '../appointment_layout/allday_appointment_layout.dart'; @@ -21,6 +23,7 @@ import '../common/calendar_view_helper.dart'; import '../common/date_time_engine.dart'; import '../common/enums.dart'; import '../resource_view/calendar_resource.dart'; +import '../settings/drag_and_drop_settings.dart'; import '../settings/month_view_settings.dart'; import '../settings/time_region.dart'; import '../settings/time_slot_view_settings.dart'; @@ -62,6 +65,7 @@ class CustomCalendarScrollView extends StatefulWidget { this.minDate, this.maxDate, this.localizations, + this.timelineMonthWeekNumberNotifier, this.updateCalendarState, this.getCalendarState, {Key? key}) @@ -104,6 +108,10 @@ class CustomCalendarScrollView extends StatefulWidget { /// selection and it set to null on month appointment selection. final ValueNotifier agendaSelectedDate; + /// Notifier to update the weeknumber of timeline month view based on scroll + /// changed. + final ValueNotifier timelineMonthWeekNumberNotifier; + /// Holds the special time region of calendar widget. final List? specialRegions; @@ -245,8 +253,14 @@ class _CustomCalendarScrollViewState extends State final FocusScopeNode _focusNode = FocusScopeNode(); + late ValueNotifier<_DragPaintDetails> _dragDetails; + Offset? _dragDifferenceOffset; + Timer? _timer; + @override void initState() { + _dragDetails = ValueNotifier<_DragPaintDetails>( + _DragPaintDetails(position: ValueNotifier(null))); widget.controller.forward = widget.isRTL ? _moveToPreviousViewWithAnimation : _moveToNextViewWithAnimation; @@ -434,6 +448,12 @@ class _CustomCalendarScrollViewState extends State _updateCalendarStateDetails.currentDate = widget.controller.displayDate!; widget.updateCalendarState(_updateCalendarStateDetails); + if (widget.calendar.showWeekNumber && + widget.view == CalendarView.timelineMonth) { + widget.timelineMonthWeekNumberNotifier.value = + widget.controller.displayDate!; + } + _updateVisibleDates(); _updateMoveToDate(); _position = 0; @@ -481,6 +501,36 @@ class _CustomCalendarScrollViewState extends State } final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + final bool isNeedDragAndDrop = widget.calendar.allowDragAndDrop && + widget.view != CalendarView.schedule && + (!widget.isMobilePlatform || + (widget.view != CalendarView.month && + widget.view != CalendarView.timelineMonth)); + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); + final double resourceItemHeight = isResourceEnabled + ? CalendarViewHelper.getResourceItemHeight( + widget.calendar.resourceViewSettings.size, + widget.height - viewHeaderHeight - timeLabelWidth, + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length) + : 0; + final double resourceViewSize = + isResourceEnabled ? widget.calendar.resourceViewSettings.size : 0; + final bool isMonthView = widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth; + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform); + final Widget customScrollWidget = GestureDetector( child: CustomScrollViewerLayout( _addViews(), @@ -494,47 +544,203 @@ class _CustomCalendarScrollViewState extends State _focusNode.requestFocus(); } }, - onHorizontalDragStart: isTimelineView ? null : _onHorizontalStart, - onHorizontalDragUpdate: isTimelineView ? null : _onHorizontalUpdate, - onHorizontalDragEnd: isTimelineView ? null : _onHorizontalEnd, - onVerticalDragStart: isHorizontalNavigation ? null : _onVerticalStart, - onVerticalDragUpdate: isHorizontalNavigation ? null : _onVerticalUpdate, - onVerticalDragEnd: isHorizontalNavigation ? null : _onVerticalEnd, + onHorizontalDragStart: isTimelineView + ? null + : (DragStartDetails dragStartDetails) { + _onHorizontalStart( + dragStartDetails, + isResourceEnabled, + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + isNeedDragAndDrop); + }, + onHorizontalDragUpdate: isTimelineView + ? null + : (DragUpdateDetails dragUpdateDetails) { + _onHorizontalUpdate( + dragUpdateDetails, + isResourceEnabled, + isMonthView, + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth, + isNeedDragAndDrop); + }, + onHorizontalDragEnd: isTimelineView + ? null + : (DragEndDetails dragEndDetails) { + _onHorizontalEnd( + dragEndDetails, + isResourceEnabled, + isTimelineView, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth, + isNeedDragAndDrop); + }, + onVerticalDragStart: isHorizontalNavigation + ? null + : (DragStartDetails dragStartDetails) { + _onVerticalStart( + dragStartDetails, + isResourceEnabled, + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + isNeedDragAndDrop); + }, + onVerticalDragUpdate: isHorizontalNavigation + ? null + : (DragUpdateDetails dragUpdateDetails) { + _onVerticalUpdate( + dragUpdateDetails, + isResourceEnabled, + isMonthView, + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth, + isNeedDragAndDrop); + }, + onVerticalDragEnd: isHorizontalNavigation + ? null + : (DragEndDetails dragEndDetails) { + _onVerticalEnd( + dragEndDetails, + isResourceEnabled, + isTimelineView, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth, + isNeedDragAndDrop); + }, ); - - return Stack( - children: [ - Positioned( - left: leftPosition, - right: rightPosition, - bottom: bottomPosition, - top: topPosition, - child: FocusScope( - node: _focusNode, - onKey: _onKeyDown, - child: isTimelineView - ? Listener( - onPointerSignal: _handlePointerSignal, - child: RawGestureDetector( - gestures: { - HorizontalDragGestureRecognizer: - GestureRecognizerFactoryWithHandlers< - HorizontalDragGestureRecognizer>( - () => HorizontalDragGestureRecognizer(), - (HorizontalDragGestureRecognizer instance) { - instance.onUpdate = _handleDragUpdate; - instance.onStart = _handleDragStart; - instance.onEnd = _handleDragEnd; - instance.onCancel = _handleDragCancel; - }, - ) - }, - behavior: HitTestBehavior.opaque, - child: customScrollWidget), - ) - : customScrollWidget, - )), - ], + return GestureDetector( + onLongPressStart: (LongPressStartDetails details) { + _handleLongPressStart(details, isNeedDragAndDrop, isTimelineView, + isResourceEnabled, viewHeaderHeight, timeLabelWidth); + }, + onLongPressMoveUpdate: isNeedDragAndDrop + ? (LongPressMoveUpdateDetails details) { + _handleLongPressMove( + details.localPosition, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth); + } + : null, + onLongPressEnd: isNeedDragAndDrop + ? (LongPressEndDetails details) { + _handleLongPressEnd( + details.localPosition, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth); + } + : null, + child: Stack( + children: [ + Positioned( + left: leftPosition, + right: rightPosition, + bottom: bottomPosition, + top: topPosition, + child: FocusScope( + node: _focusNode, + onKey: _onKeyDown, + child: isTimelineView + ? Listener( + onPointerSignal: _handlePointerSignal, + child: RawGestureDetector( + gestures: { + HorizontalDragGestureRecognizer: + GestureRecognizerFactoryWithHandlers< + HorizontalDragGestureRecognizer>( + () => HorizontalDragGestureRecognizer(), + (HorizontalDragGestureRecognizer instance) { + instance.onUpdate = + (DragUpdateDetails details) { + _handleDragUpdate( + details, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth, + isNeedDragAndDrop, + resourceViewSize); + }; + instance.onStart = + (DragStartDetails details) { + _handleDragStart( + details, + isNeedDragAndDrop, + isTimelineView, + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth, + resourceViewSize); + }; + instance.onEnd = (DragEndDetails details) { + _handleDragEnd( + details, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth, + isNeedDragAndDrop); + }; + instance.onCancel = _handleDragCancel; + }, + ) + }, + behavior: HitTestBehavior.opaque, + child: customScrollWidget), + ) + : customScrollWidget, + )), + Positioned( + left: 0, + right: 0, + bottom: 0, + top: 0, + child: IgnorePointer( + child: RepaintBoundary( + child: _DraggingAppointmentWidget( + _dragDetails, + widget.isRTL, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.calendar.appointmentTextStyle, + widget.calendar.dragAndDropSettings, + widget.view, + _updateCalendarStateDetails.allDayPanelHeight, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + widget.calendarTheme, + widget.calendar, + widget.width, + widget.height)))) + ], + ), ); } @@ -546,670 +752,2137 @@ class _CustomCalendarScrollViewState extends State super.dispose(); } - /// Get the scroll layout current child view state based on its visible dates. - GlobalKey<_CalendarViewState>? _getCurrentViewByVisibleDates() { - _CalendarView? view; - for (int i = 0; i < _children.length; i++) { - final _CalendarView currentView = _children[i]; - if (currentView.visibleDates == _currentViewVisibleDates) { - view = currentView; - break; + void _handleAppointmentDragStart( + AppointmentView appointmentView, + bool isTimelineView, + Offset details, + bool isResourceEnabled, + double viewHeaderHeight, + double timeLabelWidth) { + final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; + _dragDetails.value.appointmentView = appointmentView; + _dragDifferenceOffset = null; + final Offset appointmentPosition = Offset( + widget.isRTL + ? appointmentView.appointmentRect!.right + : appointmentView.appointmentRect!.left, + appointmentView.appointmentRect!.top); + double xPosition; + double yPosition; + if (isTimelineView) { + xPosition = (appointmentPosition.dx - + currentState._scrollController!.position.pixels) - + details.dx; + if (widget.isRTL) { + xPosition = currentState._scrollController!.offset + + currentState._scrollController!.position.viewportDimension; + xPosition = xPosition - + ((currentState._scrollController!.position.viewportDimension + + currentState._scrollController!.position.maxScrollExtent) - + appointmentPosition.dx); + xPosition -= details.dx; + } + yPosition = appointmentPosition.dy + + viewHeaderHeight + + timeLabelWidth - + details.dy; + if (isResourceEnabled) { + yPosition -= currentState._timelineViewVerticalScrollController!.offset; + } + _dragDifferenceOffset = Offset(xPosition, yPosition); + } else if (widget.view == CalendarView.month) { + xPosition = appointmentPosition.dx - details.dx; + yPosition = appointmentPosition.dy + viewHeaderHeight; + yPosition = yPosition - details.dy; + _dragDifferenceOffset = Offset(xPosition, yPosition); + } else { + final double allDayHeight = currentState._isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : currentState._allDayHeight; + xPosition = appointmentPosition.dx - details.dx; + yPosition = appointmentPosition.dy + + viewHeaderHeight + + allDayHeight - + currentState._scrollController!.position.pixels; + if (appointmentView.appointment!.isAllDay || + appointmentView.appointment!.isSpanned) { + yPosition = appointmentPosition.dy + viewHeaderHeight; } + yPosition = yPosition - details.dy; + _dragDifferenceOffset = Offset(xPosition, yPosition); } - if (view == null) { - return null; + CalendarResource? selectedResource; + int _selectedResourceIndex = -1; + + if (isResourceEnabled) { + yPosition = details.dy - viewHeaderHeight - timeLabelWidth; + yPosition += currentState._timelineViewVerticalScrollController!.offset; + _selectedResourceIndex = currentState._getSelectedResourceIndex( + yPosition, viewHeaderHeight, timeLabelWidth); + selectedResource = + widget.calendar.dataSource!.resources![_selectedResourceIndex]; + } + _dragDetails.value.position.value = details + _dragDifferenceOffset!; + _dragDetails.value.draggingTime = + yPosition <= 0 && widget.view != CalendarView.month && !isTimelineView + ? null + : _dragDetails.value.appointmentView!.appointment!.actualStartTime; + final dynamic dragStartAppointment = _getCalendarAppointmentToObject( + appointmentView.appointment!, widget.calendar); + if (widget.calendar.onDragStart != null) { + widget.calendar.onDragStart!( + AppointmentDragStartDetails(dragStartAppointment, selectedResource)); + } + } + + void _handleLongPressStart( + LongPressStartDetails details, + bool isNeedDragAndDrop, + bool isTimelineView, + bool isResourceEnabled, + double viewHeaderHeight, + double timeLabelWidth) { + final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; + AppointmentView? appointmentView = + _getDragAppointment(details, currentState); + if (!isNeedDragAndDrop || appointmentView == null) { + _dragDetails.value.position.value = null; + return; } - // ignore: avoid_as - return view.key! as GlobalKey<_CalendarViewState>; + currentState._removeAllWidgetHovering(); + appointmentView = appointmentView.clone(); + _handleAppointmentDragStart( + appointmentView, + isTimelineView, + details.localPosition, + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth); } - /// Handle start of the scroll, set the scroll start position and check - /// the start position as start or end of timeline scroll controller. - /// If the timeline view scroll starts at min or max scroll position then - /// move the previous view to end of the scroll or move the next view to - /// start of the scroll and set the drag as timeline scroll controller drag. - void _handleDragStart(DragStartDetails details) { - if (!CalendarViewHelper.isTimelineView(widget.view)) { + AppointmentView? _getDragAppointment( + LongPressStartDetails details, _CalendarViewState currentState) { + if (CalendarViewHelper.isTimelineView(widget.view)) { + return currentState._handleTouchOnTimeline(null, details); + } else if (widget.view == CalendarView.month) { + return currentState._handleTouchOnMonthView(null, details); + } + + return currentState._handleTouchOnDayView(null, details); + } + + void _handleLongPressMove( + Offset details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double resourceItemHeight, + double weekNumberPanelWidth) { + if (_dragDetails.value.appointmentView == null) { return; } - final GlobalKey<_CalendarViewState> viewKey = - _getCurrentViewByVisibleDates()!; - _timelineScrollStartPosition = - viewKey.currentState!._scrollController!.position.pixels; - _timelineStartPosition = details.globalPosition.dx; - _isNeedTimelineScrollEnd = false; - /// If the timeline view scroll starts at min or max scroll position then - /// move the previous view to end of the scroll or move the next view to - /// start of the scroll - if (_timelineScrollStartPosition >= - viewKey.currentState!._scrollController!.position.maxScrollExtent) { - _positionTimelineView(); - } else if (_timelineScrollStartPosition <= - viewKey.currentState!._scrollController!.position.minScrollExtent) { - _positionTimelineView(); + final Offset appointmentPosition = details + _dragDifferenceOffset!; + final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; + final double allDayHeight = currentState._isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : currentState._allDayHeight; + + final double timeIntervalHeight = currentState._getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + currentState.widget.visibleDates.length, + currentState._allDayHeight, + widget.isMobilePlatform); + if (isTimelineView) { + _updateAutoScrollDragTimelineView( + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth); + } else { + _updateNavigationDayView( + currentState, + appointmentPosition, + viewHeaderHeight, + allDayHeight, + timeIntervalHeight, + timeLabelWidth, + isResourceEnabled, + isTimelineView, + isMonthView, + details, + weekNumberPanelWidth); + } + + _dragDetails.value.position.value = appointmentPosition; + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + } + + Future _updateNavigationDayView( + _CalendarViewState currentState, + Offset appointmentPosition, + double viewHeaderHeight, + double allDayHeight, + double timeIntervalHeight, + double timeLabelWidth, + bool isResourceEnabled, + bool isTimelineView, + bool isMonthView, + Offset details, + double weekNumberPanelWidth) async { + if (_dragDetails.value.appointmentView == null) { + return; } - /// Set the drag as timeline scroll controller drag. - if (viewKey.currentState!._scrollController!.hasClients) { - _drag = viewKey.currentState!._scrollController!.position - .drag(details, _disposeDrag); + double navigationThresholdValue = 0; + if (widget.view == CalendarView.day) { + navigationThresholdValue = + _dragDetails.value.appointmentView!.appointmentRect!.width * 0.1; } - } - /// Handles the scroll update, if the scroll moves after the timeline max - /// scroll position or before the timeline min scroll position then check the - /// scroll start position if it is start or end of the timeline scroll view - /// then pass the touch to custom scroll view and set the timeline view - /// drag as null; - void _handleDragUpdate(DragUpdateDetails details) { - if (!CalendarViewHelper.isTimelineView(widget.view)) { - return; + double rtlValue = 0; + if (widget.isRTL) { + rtlValue = _dragDetails.value.appointmentView!.appointmentRect!.width; } - final GlobalKey<_CalendarViewState> viewKey = - _getCurrentViewByVisibleDates()!; - /// Calculate the scroll difference by current scroll position and start - /// scroll position. - final double difference = - details.globalPosition.dx - _timelineStartPosition; - if (_timelineScrollStartPosition >= - viewKey.currentState!._scrollController!.position.maxScrollExtent && - ((difference < 0 && !widget.isRTL) || - (difference > 0 && widget.isRTL))) { - /// Set the scroll position as timeline scroll start position and the - /// value used on horizontal update method. - _scrollStartPosition = _timelineStartPosition; - _drag?.cancel(); + final bool isHorizontalNavigation = + widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month; - /// Move the touch(drag) to custom scroll view. - _onHorizontalUpdate(details); + if (widget.calendar.dragAndDropSettings.allowScroll && + widget.view != CalendarView.month && + appointmentPosition.dy <= viewHeaderHeight + allDayHeight && + currentState._scrollController!.position.pixels != 0) { + if (_timer != null) { + return; + } + _timer = Timer(const Duration(milliseconds: 200), () async { + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy <= + viewHeaderHeight + allDayHeight && + currentState._scrollController!.position.pixels != 0) { + Future _updateScrollPosition() async { + double scrollPosition = + currentState._scrollController!.position.pixels - + timeIntervalHeight; + if (scrollPosition < 0) { + scrollPosition = 0; + } + await currentState._scrollController!.position.moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy <= + viewHeaderHeight + allDayHeight && + currentState._scrollController!.position.pixels != 0) { + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + } - /// Enable boolean value used to trigger the horizontal end animation on - /// drag end. - _isNeedTimelineScrollEnd = true; + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); + } else if (widget.calendar.dragAndDropSettings.allowScroll && + widget.view != CalendarView.month && + appointmentPosition.dy + + _dragDetails.value.appointmentView!.appointmentRect!.height >= + widget.height && + currentState._scrollController!.position.pixels != + currentState._scrollController!.position.maxScrollExtent) { + if (_timer != null) { + return; + } + _timer = Timer(const Duration(milliseconds: 200), () async { + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy + + _dragDetails + .value.appointmentView!.appointmentRect!.height >= + widget.height && + currentState._scrollController!.position.pixels != + currentState._scrollController!.position.maxScrollExtent) { + Future _updateScrollPosition() async { + double scrollPosition = + currentState._scrollController!.position.pixels + + timeIntervalHeight; + if (scrollPosition > + currentState._scrollController!.position.maxScrollExtent) { + scrollPosition = + currentState._scrollController!.position.maxScrollExtent; + } - /// Remove the timeline view drag or scroll. - _disposeDrag(); - return; - } else if (_timelineScrollStartPosition <= - viewKey.currentState!._scrollController!.position.minScrollExtent && - ((difference > 0 && !widget.isRTL) || - (difference < 0 && widget.isRTL))) { - /// Set the scroll position as timeline scroll start position and the - /// value used on horizontal update method. - _scrollStartPosition = _timelineStartPosition; - _drag?.cancel(); + await currentState._scrollController!.position.moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy + + _dragDetails + .value.appointmentView!.appointmentRect!.height >= + widget.height && + currentState._scrollController!.position.pixels != + currentState._scrollController!.position.maxScrollExtent) { + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + } - /// Move the touch(drag) to custom scroll view. - _onHorizontalUpdate(details); + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); + } else if (widget.calendar.dragAndDropSettings.allowNavigation && + ((isHorizontalNavigation && + (appointmentPosition.dx + + _dragDetails.value.appointmentView!.appointmentRect! + .width) - + rtlValue >= + widget.width) || + (!isHorizontalNavigation && + (appointmentPosition.dy + + _dragDetails + .value.appointmentView!.appointmentRect!.height >= + widget.height)))) { + if (_timer != null) { + return; + } + _timer = + Timer.periodic(widget.calendar.dragAndDropSettings.autoNavigateDelay, + (Timer timer) async { + if (_dragDetails.value.position.value != null && + ((isHorizontalNavigation && + (_dragDetails.value.position.value!.dx + + _dragDetails.value.appointmentView! + .appointmentRect!.width) - + rtlValue >= + widget.width + navigationThresholdValue) || + (!isHorizontalNavigation && + _dragDetails.value.position.value!.dy + + _dragDetails.value.appointmentView!.appointmentRect! + .height >= + widget.height))) { + if (widget.isRTL) { + _moveToPreviousViewWithAnimation(); + } else { + _moveToNextViewWithAnimation(); + } - /// Enable boolean value used to trigger the horizontal end animation on - /// drag end. - _isNeedTimelineScrollEnd = true; + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); + } else if (widget.calendar.dragAndDropSettings.allowNavigation && + ((isHorizontalNavigation && + (appointmentPosition.dx + navigationThresholdValue) - + rtlValue <= + 0) || + (!isHorizontalNavigation && + appointmentPosition.dy <= viewHeaderHeight))) { + if (_timer != null) { + return; + } + _timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) async { + if (_dragDetails.value.position.value != null && + ((isHorizontalNavigation && + (_dragDetails.value.position.value!.dx + + navigationThresholdValue) - + rtlValue <= + 0) || + (!isHorizontalNavigation && + _dragDetails.value.position.value!.dy <= + viewHeaderHeight))) { + if (widget.isRTL) { + _moveToNextViewWithAnimation(); + } else { + _moveToPreviousViewWithAnimation(); + } - /// Remove the timeline view drag or scroll. - _disposeDrag(); + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); + } + } + + Future _updateAutoScrollDragTimelineView( + _CalendarViewState currentState, + Offset appointmentPosition, + double viewHeaderHeight, + double timeIntervalHeight, + double resourceItemHeight, + bool isResourceEnabled, + Offset details, + bool isMonthView, + double allDayHeight, + bool isTimelineView, + double timeLabelWidth, + double weekNumberPanelWidth) async { + if (_dragDetails.value.appointmentView == null) { return; } - _drag?.update(details); - } + double rtlValue = 0; + if (widget.isRTL) { + rtlValue = _dragDetails.value.appointmentView!.appointmentRect!.width; + } + if (widget.calendar.dragAndDropSettings.allowScroll && + appointmentPosition.dx - rtlValue <= 0 && + ((widget.isRTL && + currentState._scrollController!.position.pixels != + currentState._scrollController!.position.maxScrollExtent) || + (!widget.isRTL && + currentState._scrollController!.position.pixels != 0))) { + if (_timer != null) { + return; + } + _timer = Timer(const Duration(milliseconds: 200), () async { + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dx - rtlValue <= 0 && + ((widget.isRTL && + currentState._scrollController!.position.pixels != + currentState + ._scrollController!.position.maxScrollExtent) || + (!widget.isRTL && + currentState._scrollController!.position.pixels != 0))) { + Future _updateScrollPosition() async { + double scrollPosition = + currentState._scrollController!.position.pixels - + timeIntervalHeight; + if (widget.isRTL) { + scrollPosition = currentState._scrollController!.position.pixels + + timeIntervalHeight; + } + if (!widget.isRTL && scrollPosition < 0) { + scrollPosition = 0; + } else if (widget.isRTL && + scrollPosition > + currentState._scrollController!.position.maxScrollExtent) { + scrollPosition = + currentState._scrollController!.position.maxScrollExtent; + } + await currentState._scrollController!.position.moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dx - rtlValue <= 0 && + ((widget.isRTL && + currentState._scrollController!.position.pixels != + currentState + ._scrollController!.position.maxScrollExtent) || + (!widget.isRTL && + currentState._scrollController!.position.pixels != + 0))) { + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + _updateAutoViewNavigationTimelineView( + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue); + } + } - /// Handle the scroll end to update the timeline view scroll or custom scroll - /// view scroll based on [_isNeedTimelineScrollEnd] value - void _handleDragEnd(DragEndDetails details) { - if (_isNeedTimelineScrollEnd) { - _isNeedTimelineScrollEnd = false; - _onHorizontalEnd(details); + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + _updateAutoViewNavigationTimelineView( + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue); + } + }); + } else if (widget.calendar.dragAndDropSettings.allowScroll && + (appointmentPosition.dx + + _dragDetails + .value.appointmentView!.appointmentRect!.width) - + rtlValue >= + widget.width && + ((widget.isRTL && + currentState._scrollController!.position.pixels != 0) || + (!widget.isRTL && + currentState._scrollController!.position.pixels != + currentState + ._scrollController!.position.maxScrollExtent))) { + if (_timer != null) { + return; + } + _timer = Timer(const Duration(milliseconds: 200), () async { + if (_dragDetails.value.position.value != null && + (_dragDetails.value.position.value!.dx + + _dragDetails + .value.appointmentView!.appointmentRect!.width) - + rtlValue >= + widget.width && + ((widget.isRTL && + currentState._scrollController!.position.pixels != 0) || + (!widget.isRTL && + currentState._scrollController!.position.pixels != + currentState + ._scrollController!.position.maxScrollExtent))) { + Future _updateScrollPosition() async { + double scrollPosition = + currentState._scrollController!.position.pixels + + timeIntervalHeight; + if (widget.isRTL) { + scrollPosition = currentState._scrollController!.position.pixels - + timeIntervalHeight; + } + if (!widget.isRTL && + scrollPosition > + currentState._scrollController!.position.maxScrollExtent) { + scrollPosition = + currentState._scrollController!.position.maxScrollExtent; + } else if (widget.isRTL && scrollPosition < 0) { + scrollPosition = 0; + } + + await currentState._scrollController!.position.moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + + if (_dragDetails.value.position.value != null && + (_dragDetails.value.position.value!.dx + + _dragDetails.value.appointmentView!.appointmentRect! + .width) - + rtlValue >= + widget.width && + ((widget.isRTL && + currentState._scrollController!.position.pixels != 0) || + (!widget.isRTL && + currentState._scrollController!.position.pixels != + currentState._scrollController!.position + .maxScrollExtent))) { + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + _updateAutoViewNavigationTimelineView( + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue); + } + } + + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + _updateAutoViewNavigationTimelineView( + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue); + } + }); + } + + _updateAutoViewNavigationTimelineView( + currentState, + appointmentPosition, + viewHeaderHeight, + timeIntervalHeight, + resourceItemHeight, + isResourceEnabled, + details, + isMonthView, + allDayHeight, + isTimelineView, + timeLabelWidth, + weekNumberPanelWidth, + rtlValue); + + if (_dragDetails.value.appointmentView == null) { return; } - _isNeedTimelineScrollEnd = false; - _drag?.end(details); - } + if (isResourceEnabled) { + if (widget.calendar.dragAndDropSettings.allowScroll && + appointmentPosition.dy - viewHeaderHeight - timeIntervalHeight <= 0 && + currentState._timelineViewVerticalScrollController!.position.pixels != + 0) { + if (_timer != null) { + return; + } + _timer = Timer(const Duration(milliseconds: 200), () async { + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy - + viewHeaderHeight - + timeIntervalHeight <= + 0 && + currentState + ._timelineViewVerticalScrollController!.position.pixels != + 0) { + Future _updateScrollPosition() async { + double scrollPosition = currentState + ._timelineViewVerticalScrollController!.position.pixels - + resourceItemHeight; + if (scrollPosition < 0) { + scrollPosition = 0; + } + await currentState._timelineViewVerticalScrollController!.position + .moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy - + viewHeaderHeight - + timeIntervalHeight <= + 0 && + currentState._timelineViewVerticalScrollController!.position + .pixels != + 0) { + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + } - /// Handle drag cancel related operations. - void _handleDragCancel() { - _isNeedTimelineScrollEnd = false; - _drag?.cancel(); - } + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); + } else if (widget.calendar.dragAndDropSettings.allowScroll && + appointmentPosition.dy + + _dragDetails.value.appointmentView!.appointmentRect!.height >= + widget.height && + currentState._timelineViewVerticalScrollController!.position.pixels != + currentState._timelineViewVerticalScrollController!.position + .maxScrollExtent) { + if (_timer != null) { + return; + } + _timer = Timer(const Duration(milliseconds: 200), () async { + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy + + _dragDetails + .value.appointmentView!.appointmentRect!.height >= + widget.height && + currentState + ._timelineViewVerticalScrollController!.position.pixels != + currentState._timelineViewVerticalScrollController!.position + .maxScrollExtent) { + Future _updateScrollPosition() async { + double scrollPosition = currentState + ._timelineViewVerticalScrollController!.position.pixels + + resourceItemHeight; + if (scrollPosition > + currentState._timelineViewVerticalScrollController!.position + .maxScrollExtent) { + scrollPosition = currentState + ._timelineViewVerticalScrollController! + .position + .maxScrollExtent; + } + + await currentState._timelineViewVerticalScrollController!.position + .moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + + if (_dragDetails.value.position.value != null && + _dragDetails.value.position.value!.dy + + _dragDetails + .value.appointmentView!.appointmentRect!.height >= + widget.height && + currentState._timelineViewVerticalScrollController!.position + .pixels != + currentState._timelineViewVerticalScrollController! + .position.maxScrollExtent) { + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + } - /// Remove the drag when the touch(drag) passed to custom scroll view. - void _disposeDrag() { - _drag = null; + _updateScrollPosition(); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); + } + } } - /// Handle the pointer scroll when a pointer signal occurs over this object. - /// eg., track pad scroll. - void _handlePointerSignal(PointerSignalEvent event) { - final GlobalKey<_CalendarViewState>? viewKey = - _getCurrentViewByVisibleDates(); - if (event is PointerScrollEvent && viewKey != null) { - final double scrolledPosition = - widget.isRTL ? -event.scrollDelta.dx : event.scrollDelta.dx; - final double targetScrollOffset = math.min( - math.max( - viewKey.currentState!._scrollController!.position.pixels + - scrolledPosition, - viewKey - .currentState!._scrollController!.position.minScrollExtent), - viewKey.currentState!._scrollController!.position.maxScrollExtent); - if (targetScrollOffset != - viewKey.currentState!._scrollController!.position.pixels) { - viewKey.currentState!._scrollController!.position - .jumpTo(targetScrollOffset); + void _updateAutoViewNavigationTimelineView( + _CalendarViewState currentState, + Offset appointmentPosition, + double viewHeaderHeight, + double timeIntervalHeight, + double resourceItemHeight, + bool isResourceEnabled, + dynamic details, + bool isMonthView, + double allDayHeight, + bool isTimelineView, + double timeLabelWidth, + double weekNumberPanelWidth, + double rtlValue) { + if (widget.calendar.dragAndDropSettings.allowNavigation && + (appointmentPosition.dx + + _dragDetails + .value.appointmentView!.appointmentRect!.width) - + rtlValue >= + widget.width && + ((!widget.isRTL && + currentState._scrollController!.offset == + currentState._scrollController!.position.maxScrollExtent) || + (widget.isRTL && currentState._scrollController!.offset == 0))) { + if (_timer != null) { + return; } + _timer = + Timer.periodic(widget.calendar.dragAndDropSettings.autoNavigateDelay, + (Timer timer) async { + if (_dragDetails.value.position.value != null && + (_dragDetails.value.position.value!.dx + + _dragDetails + .value.appointmentView!.appointmentRect!.width) - + rtlValue >= + widget.width && + ((!widget.isRTL && + currentState._scrollController!.offset == + currentState + ._scrollController!.position.maxScrollExtent) || + (widget.isRTL && + currentState._scrollController!.offset == 0))) { + if (widget.isRTL) { + _moveToPreviousViewWithAnimation(isScrollToEnd: true); + } else { + _moveToNextViewWithAnimation(); + } + currentState = _getCurrentViewByVisibleDates()!; + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); + } else if (widget.calendar.dragAndDropSettings.allowNavigation && + ((appointmentPosition.dx) - rtlValue).truncate() <= 0 && + ((widget.isRTL && + currentState._scrollController!.position.pixels == + currentState._scrollController!.position.maxScrollExtent) || + (!widget.isRTL && currentState._scrollController!.offset == 0))) { + if (_timer != null) { + return; + } + _timer = Timer.periodic(const Duration(seconds: 1), (Timer timer) async { + if (_dragDetails.value.position.value != null && + (_dragDetails.value.position.value!.dx - rtlValue).truncate() <= + 0 && + ((widget.isRTL && + currentState._scrollController!.position.pixels == + currentState + ._scrollController!.position.maxScrollExtent) || + (!widget.isRTL && + currentState._scrollController!.offset == 0))) { + if (widget.isRTL) { + _moveToNextViewWithAnimation(); + } else { + _moveToPreviousViewWithAnimation(isScrollToEnd: true); + } + currentState = _getCurrentViewByVisibleDates()!; + + _updateAppointmentDragUpdateCallback( + isTimelineView, + viewHeaderHeight, + timeLabelWidth, + allDayHeight, + appointmentPosition, + isMonthView, + timeIntervalHeight, + currentState, + details, + isResourceEnabled, + weekNumberPanelWidth); + } else if (_timer != null) { + _timer!.cancel(); + _timer = null; + } + }); } } - void _updateVisibleDates() { - widget.getCalendarState(_updateCalendarStateDetails); - final DateTime currentDate = DateTime( - _updateCalendarStateDetails.currentDate!.year, - _updateCalendarStateDetails.currentDate!.month, - _updateCalendarStateDetails.currentDate!.day); - final DateTime prevDate = DateTimeHelper.getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentDate); - final DateTime nextDate = DateTimeHelper.getNextViewStartDate(widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, currentDate); - final List? nonWorkingDays = (widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek) - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null; - final int visibleDatesCount = DateTimeHelper.getViewDatesCount( - widget.view, widget.calendar.monthViewSettings.numberOfWeeksInView); - - _visibleDates = getVisibleDates(currentDate, nonWorkingDays, - widget.calendar.firstDayOfWeek, visibleDatesCount) - .cast(); - _previousViewVisibleDates = getVisibleDates( - widget.isRTL ? nextDate : prevDate, - nonWorkingDays, - widget.calendar.firstDayOfWeek, - visibleDatesCount) - .cast(); - _nextViewVisibleDates = getVisibleDates(widget.isRTL ? prevDate : nextDate, - nonWorkingDays, widget.calendar.firstDayOfWeek, visibleDatesCount) - .cast(); - if (widget.view == CalendarView.timelineMonth) { - _visibleDates = DateTimeHelper.getCurrentMonthDates(_visibleDates); - _previousViewVisibleDates = - DateTimeHelper.getCurrentMonthDates(_previousViewVisibleDates); - _nextViewVisibleDates = - DateTimeHelper.getCurrentMonthDates(_nextViewVisibleDates); + void _updateAppointmentDragUpdateCallback( + bool isTimelineView, + double viewHeaderHeight, + double timeLabelWidth, + double allDayHeight, + Offset appointmentPosition, + bool isMonthView, + double timeIntervalHeight, + _CalendarViewState currentState, + Offset details, + bool isResourceEnabled, + double weekNumberPanelWidth) { + if (_dragDetails.value.appointmentView == null) { + return; } - _currentViewVisibleDates = _visibleDates; - _updateCalendarStateDetails.currentViewVisibleDates = - _currentViewVisibleDates; - widget.updateCalendarState(_updateCalendarStateDetails); + late DateTime draggingTime; + double xPosition = details.dx; + double yPosition = appointmentPosition.dy; + if (isTimelineView) { + xPosition = appointmentPosition.dx; + yPosition -= viewHeaderHeight + timeLabelWidth; + } else { + if (widget.view == CalendarView.month) { + if (yPosition < viewHeaderHeight) { + yPosition = viewHeaderHeight; + } else if (yPosition > widget.height - 1) { + yPosition = widget.height - 1; + } - if (_currentChildIndex == 0) { - _visibleDates = _nextViewVisibleDates; - _nextViewVisibleDates = _previousViewVisibleDates; - _previousViewVisibleDates = _currentViewVisibleDates; - } else if (_currentChildIndex == 1) { - _visibleDates = _currentViewVisibleDates; - } else if (_currentChildIndex == 2) { - _visibleDates = _previousViewVisibleDates; - _previousViewVisibleDates = _nextViewVisibleDates; - _nextViewVisibleDates = _currentViewVisibleDates; + yPosition -= viewHeaderHeight; + if (!widget.isRTL && xPosition <= weekNumberPanelWidth) { + xPosition = weekNumberPanelWidth; + } else if (widget.isRTL && + xPosition >= (widget.width - weekNumberPanelWidth)) { + xPosition = widget.width - weekNumberPanelWidth; + } + } else { + if (yPosition < viewHeaderHeight + allDayHeight) { + yPosition = viewHeaderHeight + allDayHeight; + } else if (yPosition > widget.height - 1) { + yPosition = widget.height - 1; + } + + yPosition -= viewHeaderHeight + allDayHeight; + if (!widget.isRTL) { + xPosition -= timeLabelWidth; + } + } } - } - void _updateNextViewVisibleDates() { - DateTime currentViewDate = _currentViewVisibleDates[0]; - if (widget.view == CalendarView.month && - widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / 2).truncate()]; + if (xPosition < 0) { + xPosition = 0; + } else if (xPosition > widget.width) { + xPosition = widget.width; } - if (widget.isRTL) { - currentViewDate = DateTimeHelper.getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); - } else { - currentViewDate = DateTimeHelper.getNextViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); + draggingTime = currentState._getDateFromPosition( + xPosition, yPosition, timeLabelWidth)!; + if (!isMonthView) { + if (isTimelineView) { + final DateTime time = _timeFromPosition( + draggingTime, + widget.calendar.timeSlotViewSettings, + xPosition, + currentState, + timeIntervalHeight, + isTimelineView)!; + + draggingTime = DateTime(draggingTime.year, draggingTime.month, + draggingTime.day, time.hour, time.minute); + } else { + if (yPosition < 0) { + draggingTime = DateTime( + draggingTime.year, draggingTime.month, draggingTime.day, 0, 0, 0); + } else { + draggingTime = _timeFromPosition( + draggingTime, + widget.calendar.timeSlotViewSettings, + yPosition, + currentState, + timeIntervalHeight, + isTimelineView)!; + } + } } - List dates = getVisibleDates( - currentViewDate, - widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null, - widget.calendar.firstDayOfWeek, - DateTimeHelper.getViewDatesCount(widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView)) - .cast(); + _dragDetails.value.position.value = Offset( + _dragDetails.value.position.value!.dx, + _dragDetails.value.position.value!.dy - 0.1); + _dragDetails.value.draggingTime = + yPosition <= 0 && widget.view != CalendarView.month && !isTimelineView + ? null + : draggingTime; + _dragDetails.value.position.value = Offset( + _dragDetails.value.position.value!.dx, + _dragDetails.value.position.value!.dy + 0.1); - if (widget.view == CalendarView.timelineMonth) { - dates = DateTimeHelper.getCurrentMonthDates(dates); + if (widget.calendar.onDragUpdate == null) { + return; } - if (_currentChildIndex == 0) { - _nextViewVisibleDates = dates; - } else if (_currentChildIndex == 1) { - _previousViewVisibleDates = dates; - } else { - _visibleDates = dates; + final dynamic draggingAppointment = _getCalendarAppointmentToObject( + _dragDetails.value.appointmentView!.appointment, widget.calendar); + + CalendarResource? selectedResource, previousResource; + int targetResourceIndex = -1; + int sourceSelectedResourceIndex = -1; + if (isResourceEnabled) { + targetResourceIndex = currentState._getSelectedResourceIndex( + appointmentPosition.dy + + currentState._timelineViewVerticalScrollController!.offset, + viewHeaderHeight, + timeLabelWidth); + if (targetResourceIndex > + widget.calendar.dataSource!.resources!.length - 1) { + targetResourceIndex = widget.calendar.dataSource!.resources!.length - 1; + } + selectedResource = + widget.calendar.dataSource!.resources![targetResourceIndex]; + sourceSelectedResourceIndex = currentState._getSelectedResourceIndex( + _dragDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth); + previousResource = + widget.calendar.dataSource!.resources![sourceSelectedResourceIndex]; + } + if (widget.calendar.onDragUpdate != null) { + widget.calendar.onDragUpdate!(AppointmentDragUpdateDetails( + draggingAppointment, + previousResource, + selectedResource, + appointmentPosition, + _dragDetails.value.draggingTime!)); + } + } + + void _handleLongPressEnd( + Offset details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double weekNumberPanelWidth) { + if (_dragDetails.value.appointmentView == null) { + return; } - } - void _updatePreviousViewVisibleDates() { - DateTime currentViewDate = _currentViewVisibleDates[0]; - if (widget.view == CalendarView.month && - widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - currentViewDate = _currentViewVisibleDates[ - (_currentViewVisibleDates.length / 2).truncate()]; + if (_timer != null) { + _timer!.cancel(); + _timer = null; } - if (widget.isRTL) { - currentViewDate = DateTimeHelper.getNextViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); + final Offset appointmentPosition = details + _dragDifferenceOffset!; + final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; + final double allDayHeight = currentState._isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : currentState._allDayHeight; + final double timeIntervalHeight = currentState._getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + currentState.widget.visibleDates.length, + currentState._allDayHeight, + widget.isMobilePlatform); + double xPosition = details.dx; + double yPosition = appointmentPosition.dy; + if (isTimelineView) { + xPosition = !isMonthView ? appointmentPosition.dx : xPosition; + yPosition -= viewHeaderHeight + timeLabelWidth; } else { - currentViewDate = DateTimeHelper.getPreviousViewStartDate( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - currentViewDate); + if (widget.view == CalendarView.month) { + if (yPosition < viewHeaderHeight) { + yPosition = viewHeaderHeight; + } else if (yPosition > widget.height - 1) { + yPosition = widget.height - 1; + } + + yPosition -= viewHeaderHeight; + if (!widget.isRTL && xPosition <= weekNumberPanelWidth) { + xPosition = weekNumberPanelWidth; + } else if (widget.isRTL && + xPosition >= (widget.width - weekNumberPanelWidth)) { + xPosition = widget.width - weekNumberPanelWidth; + } + } else { + yPosition -= viewHeaderHeight + allDayHeight; + if (!widget.isRTL) { + xPosition -= timeLabelWidth; + } + } } - List dates = getVisibleDates( - currentViewDate, - widget.view == CalendarView.workWeek || - widget.view == CalendarView.timelineWorkWeek - ? widget.calendar.timeSlotViewSettings.nonWorkingDays - : null, - widget.calendar.firstDayOfWeek, - DateTimeHelper.getViewDatesCount(widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView)) - .cast(); + if (xPosition < 0) { + xPosition = 0; + } else if (xPosition > widget.width) { + xPosition = widget.width; + } - if (widget.view == CalendarView.timelineMonth) { - dates = DateTimeHelper.getCurrentMonthDates(dates); + final CalendarAppointment? appointment = + _dragDetails.value.appointmentView!.appointment; + DateTime? dropTime = currentState._getDateFromPosition( + xPosition, yPosition, timeLabelWidth)!; + if (!isMonthView) { + if (isTimelineView) { + final DateTime time = _timeFromPosition( + dropTime, + widget.calendar.timeSlotViewSettings, + xPosition, + currentState, + timeIntervalHeight, + isTimelineView)!; + dropTime = DateTime(dropTime.year, dropTime.month, dropTime.day, + time.hour, time.minute); + } else { + dropTime = _timeFromPosition( + dropTime, + widget.calendar.timeSlotViewSettings, + yPosition, + currentState, + timeIntervalHeight, + isTimelineView)!; + } } - if (_currentChildIndex == 0) { - _visibleDates = dates; - } else if (_currentChildIndex == 1) { - _nextViewVisibleDates = dates; + CalendarResource? selectedResource, previousResource; + int targetResourceIndex = -1; + int sourceSelectedResourceIndex = -1; + if (isResourceEnabled) { + targetResourceIndex = currentState._getSelectedResourceIndex( + (details.dy - viewHeaderHeight - timeLabelWidth) + + currentState._timelineViewVerticalScrollController!.offset, + viewHeaderHeight, + timeLabelWidth); + if (targetResourceIndex > + widget.calendar.dataSource!.resources!.length - 1) { + targetResourceIndex = widget.calendar.dataSource!.resources!.length - 1; + } + selectedResource = + widget.calendar.dataSource!.resources![targetResourceIndex]; + sourceSelectedResourceIndex = currentState._getSelectedResourceIndex( + _dragDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth); + previousResource = + widget.calendar.dataSource!.resources![sourceSelectedResourceIndex]; + } + + final int currentMonth = currentState.widget + .visibleDates[currentState.widget.visibleDates.length ~/ 2].month; + + if (!_isSelectedDateEnabled(dropTime, targetResourceIndex) || + (widget.view == CalendarView.month && + !CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + dropTime))) { + if (widget.calendar.onDragEnd != null) { + widget.calendar.onDragEnd!(AppointmentDragEndDetails( + _getCalendarAppointmentToObject(appointment, widget.calendar), + previousResource, + previousResource, + appointment!.exactStartTime)); + } + _dragDetails.value.appointmentView = null; + _dragDetails.value.position.value = null; + _dragDifferenceOffset = null; + currentState._hoveringAppointmentView = null; + return; + } + bool _isAllDay = appointment!.isAllDay; + if (!isTimelineView && widget.view != CalendarView.month) { + _isAllDay = yPosition < 0; + if (_dragDetails.value.appointmentView!.appointment!.isSpanned && + !appointment.isAllDay) { + _isAllDay = appointment.isAllDay; + } } else { - _previousViewVisibleDates = dates; + _isAllDay = appointment.isAllDay; } - } - - void _getCalendarViewStateDetails(UpdateCalendarStateDetails details) { - widget.getCalendarState(_updateCalendarStateDetails); - details.currentDate = _updateCalendarStateDetails.currentDate; - details.currentViewVisibleDates = - _updateCalendarStateDetails.currentViewVisibleDates; - details.selectedDate = _updateCalendarStateDetails.selectedDate; - details.allDayPanelHeight = _updateCalendarStateDetails.allDayPanelHeight; - details.allDayAppointmentViewCollection = - _updateCalendarStateDetails.allDayAppointmentViewCollection; - details.appointments = _updateCalendarStateDetails.appointments; - details.visibleAppointments = - _updateCalendarStateDetails.visibleAppointments; - } - void _updateCalendarViewStateDetails(UpdateCalendarStateDetails details) { - _updateCalendarStateDetails.selectedDate = details.selectedDate; - widget.updateCalendarState(_updateCalendarStateDetails); - } + DateTime updateStartTime = _isAllDay + ? DateTime(dropTime.year, dropTime.month, dropTime.day, 0, 0, 0) + : dropTime; - CalendarTimeRegion _getCalendarTimeRegionFromTimeRegion(TimeRegion region) { - return CalendarTimeRegion( - startTime: region.startTime, - endTime: region.endTime, - color: region.color, - text: region.text, - textStyle: region.textStyle, - recurrenceExceptionDates: region.recurrenceExceptionDates, - recurrenceRule: region.recurrenceRule, - resourceIds: region.resourceIds, - timeZone: region.timeZone, - enablePointerInteraction: region.enablePointerInteraction, - iconData: region.iconData, - ); - } + final Duration appointmentDuration = appointment.isAllDay && + widget.view != CalendarView.month && + !isTimelineView + ? const Duration(hours: 1) + : appointment.endTime.difference(appointment.startTime); + DateTime updatedEndTime = + _isAllDay ? updateStartTime : updateStartTime.add(appointmentDuration); + + CalendarAppointment? parentAppointment; + if ((appointment.recurrenceRule != null && + appointment.recurrenceRule!.isNotEmpty) || + appointment.recurrenceId != null) { + for (int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++) { + final CalendarAppointment app = + _updateCalendarStateDetails.appointments[i]; + if (app.id == appointment.id || app.id == appointment.recurrenceId) { + parentAppointment = app; + break; + } + } - /// Return collection of time region, in between the visible dates. - List _getRegions(List visibleDates) { - final DateTime visibleStartDate = visibleDates[0]; - final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; - final List regionCollection = []; - if (_timeRegions == null) { - return regionCollection; - } + final List recurrenceDates = + RecurrenceHelper.getRecurrenceDateTimeCollection( + parentAppointment!.recurrenceRule ?? '', + parentAppointment.exactStartTime, + recurrenceDuration: AppointmentHelper.getDifference( + parentAppointment.exactStartTime, + parentAppointment.exactEndTime), + specificStartDate: currentState.widget.visibleDates[0], + specificEndDate: currentState.widget + .visibleDates[currentState.widget.visibleDates.length - 1]); + + for (int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++) { + final CalendarAppointment calendarApp = + _updateCalendarStateDetails.appointments[i]; + if (calendarApp.recurrenceId != null && + calendarApp.recurrenceId == parentAppointment.id) { + recurrenceDates.add( + AppointmentHelper.convertTimeToAppointmentTimeZone( + calendarApp.startTime, + calendarApp.startTimeZone, + widget.calendar.timeZone)); + } + } - final DateTime startDate = - AppointmentHelper.convertToStartTime(visibleStartDate); - final DateTime endDate = AppointmentHelper.convertToEndTime(visibleEndDate); - for (int j = 0; j < _timeRegions!.length; j++) { - final TimeRegion timeRegion = _timeRegions![j]; - final CalendarTimeRegion region = - _getCalendarTimeRegionFromTimeRegion(timeRegion); - region.actualStartTime = - AppointmentHelper.convertTimeToAppointmentTimeZone( - region.startTime, region.timeZone, widget.calendar.timeZone); - region.actualEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( - region.endTime, region.timeZone, widget.calendar.timeZone); - region.data = timeRegion; + if (parentAppointment.recurrenceExceptionDates != null) { + for (int i = 0; + i < parentAppointment.recurrenceExceptionDates!.length; + i++) { + recurrenceDates.remove( + AppointmentHelper.convertTimeToAppointmentTimeZone( + parentAppointment.recurrenceExceptionDates![i], + '', + widget.calendar.timeZone)); + } + } - if (region.recurrenceRule == null || region.recurrenceRule == '') { - if (AppointmentHelper.isDateRangeWithinVisibleDateRange( - region.actualStartTime, region.actualEndTime, startDate, endDate)) { - regionCollection.add(region); + recurrenceDates.sort(); + bool canAddRecurrence = + isSameDate(appointment.exactStartTime, updateStartTime); + if (!CalendarViewHelper.isDateInDateCollection( + recurrenceDates, updateStartTime)) { + final int currentRecurrenceIndex = + recurrenceDates.indexOf(appointment.exactStartTime); + if (currentRecurrenceIndex == 0 || + currentRecurrenceIndex == recurrenceDates.length - 1) { + canAddRecurrence = true; + } else if (currentRecurrenceIndex < 0) { + canAddRecurrence = false; + } else { + final DateTime previousRecurrence = + recurrenceDates[currentRecurrenceIndex - 1]; + final DateTime nextRecurrence = + recurrenceDates[currentRecurrenceIndex + 1]; + canAddRecurrence = (isDateWithInDateRange( + previousRecurrence, nextRecurrence, updateStartTime) && + !isSameDate(previousRecurrence, updateStartTime) && + !isSameDate(nextRecurrence, updateStartTime)) || + canAddRecurrence; } + } - continue; + if (!canAddRecurrence) { + if (widget.calendar.onDragEnd != null) { + widget.calendar.onDragEnd!(AppointmentDragEndDetails( + _getCalendarAppointmentToObject(appointment, widget.calendar), + previousResource, + previousResource, + appointment.exactStartTime)); + } + _dragDetails.value.appointmentView = null; + _dragDetails.value.position.value = null; + _dragDifferenceOffset = null; + return; } - getRecurrenceRegions(region, regionCollection, startDate, endDate, - widget.calendar.timeZone); + if (appointment.recurrenceId != null && + (appointment.recurrenceRule == null || + appointment.recurrenceRule!.isEmpty)) { + widget.calendar.dataSource!.appointments!.remove(appointment.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [appointment.data]); + } else { + widget.calendar.dataSource!.appointments! + .remove(parentAppointment.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [parentAppointment.data]); + final DateTime exceptionDate = + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.exactStartTime, widget.calendar.timeZone, ''); + parentAppointment.recurrenceExceptionDates != null + ? parentAppointment.recurrenceExceptionDates!.add(exceptionDate) + : parentAppointment.recurrenceExceptionDates = [ + exceptionDate + ]; + + appointment.id = + appointment.recurrenceId != null ? appointment.id : null; + appointment.recurrenceId = + appointment.recurrenceId ?? parentAppointment.id; + appointment.recurrenceRule = null; + final dynamic newParentAppointment = + _getCalendarAppointmentToObject(parentAppointment, widget.calendar); + widget.calendar.dataSource!.appointments!.add(newParentAppointment); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.add, [newParentAppointment]); + } + } else { + widget.calendar.dataSource!.appointments!.remove(appointment.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [appointment.data]); + } + + final DateTime callbackStartDate = updateStartTime; + updateStartTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + updateStartTime, widget.calendar.timeZone, appointment.startTimeZone); + updatedEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + updatedEndTime, widget.calendar.timeZone, appointment.endTimeZone); + appointment.startTime = updateStartTime; + appointment.endTime = updatedEndTime; + appointment.isAllDay = _isAllDay; + if (isResourceEnabled) { + if (appointment.resourceIds != null && + appointment.resourceIds!.isNotEmpty) { + if (previousResource!.id != selectedResource!.id && + !appointment.resourceIds!.contains(selectedResource.id)) { + appointment.resourceIds!.remove(previousResource.id); + appointment.resourceIds!.add(selectedResource.id); + } + } else { + appointment.resourceIds = [selectedResource!.id]; + } } - return regionCollection; - } + final dynamic newAppointment = + _getCalendarAppointmentToObject(appointment, widget.calendar); - /// Get the recurrence time regions in between the visible date range. - void getRecurrenceRegions( - CalendarTimeRegion region, - List regions, - DateTime visibleStartDate, - DateTime visibleEndDate, - String? calendarTimeZone) { - final DateTime regionStartDate = region.actualStartTime; - if (regionStartDate.isAfter(visibleEndDate)) { - return; + widget.calendar.dataSource!.appointments!.add(newAppointment); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.add, [newAppointment]); + + _dragDifferenceOffset = null; + _dragDetails.value.appointmentView = null; + _dragDetails.value.position.value = null; + currentState._hoveringAppointmentView = null; + if (widget.calendar.onDragEnd != null) { + widget.calendar.onDragEnd!(AppointmentDragEndDetails(newAppointment, + previousResource, selectedResource, callbackStartDate)); } + } - String rule = region.recurrenceRule!; - if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { - final DateFormat formatter = DateFormat('yyyyMMdd'); - final String newSubString = ';UNTIL=' + formatter.format(visibleEndDate); - rule = rule + newSubString; + /// Get the scroll layout current child view state based on its visible dates. + _CalendarViewState? _getCurrentViewByVisibleDates() { + _CalendarView? view; + for (int i = 0; i < _children.length; i++) { + final _CalendarView currentView = _children[i]; + if (currentView.visibleDates == _currentViewVisibleDates) { + view = currentView; + break; + } } - final List recursiveDates = - RecurrenceHelper.getRecurrenceDateTimeCollection( - rule, region.actualStartTime, - recurrenceDuration: - region.actualEndTime.difference(region.actualStartTime), - specificStartDate: visibleStartDate, - specificEndDate: visibleEndDate); + if (view == null) { + return null; + } - for (int j = 0; j < recursiveDates.length; j++) { - final DateTime recursiveDate = recursiveDates[j]; - if (region.recurrenceExceptionDates != null) { - bool isDateContains = false; - for (int i = 0; i < region.recurrenceExceptionDates!.length; i++) { - final DateTime date = - AppointmentHelper.convertTimeToAppointmentTimeZone( - region.recurrenceExceptionDates![i], '', calendarTimeZone); - if (isSameDate(date, recursiveDate)) { - isDateContains = true; - break; - } - } - if (isDateContains) { - continue; - } - } + return (view.key! as GlobalKey<_CalendarViewState>).currentState; + } - final CalendarTimeRegion occurrenceRegion = - cloneRecurrenceRegion(region, recursiveDate, calendarTimeZone); - regions.add(occurrenceRegion); + /// Handle start of the scroll, set the scroll start position and check + /// the start position as start or end of timeline scroll controller. + /// If the timeline view scroll starts at min or max scroll position then + /// move the previous view to end of the scroll or move the next view to + /// start of the scroll and set the drag as timeline scroll controller drag. + void _handleDragStart( + DragStartDetails details, + bool isNeedDragAndDrop, + bool isTimelineView, + bool isResourceEnabled, + double viewHeaderHeight, + double timeLabelWidth, + double resourceViewSize) { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; + } + final _CalendarViewState viewKey = _getCurrentViewByVisibleDates()!; + if (viewKey._hoveringAppointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleAppointmentDragStart( + viewKey._hoveringAppointmentView!.clone(), + isTimelineView, + Offset(details.localPosition.dx - widget.width, + details.localPosition.dy), + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth); + return; + } + _timelineScrollStartPosition = viewKey._scrollController!.position.pixels; + _timelineStartPosition = details.globalPosition.dx; + _isNeedTimelineScrollEnd = false; + + /// If the timeline view scroll starts at min or max scroll position then + /// move the previous view to end of the scroll or move the next view to + /// start of the scroll + if (_timelineScrollStartPosition >= + viewKey._scrollController!.position.maxScrollExtent) { + _positionTimelineView(); + } else if (_timelineScrollStartPosition <= + viewKey._scrollController!.position.minScrollExtent) { + _positionTimelineView(); + } + + /// Set the drag as timeline scroll controller drag. + if (viewKey._scrollController!.hasClients) { + _drag = viewKey._scrollController!.position.drag(details, _disposeDrag); } } - /// Used to clone the time region with new values. - CalendarTimeRegion cloneRecurrenceRegion(CalendarTimeRegion region, - DateTime recursiveDate, String? calendarTimeZone) { - final int minutes = - region.actualEndTime.difference(region.actualStartTime).inMinutes; - final DateTime actualEndTime = DateTimeHelper.getDateTimeValue( - addDuration(recursiveDate, Duration(minutes: minutes))); - final DateTime startDate = - AppointmentHelper.convertTimeToAppointmentTimeZone( - recursiveDate, region.timeZone, calendarTimeZone); + /// Handles the scroll update, if the scroll moves after the timeline max + /// scroll position or before the timeline min scroll position then check the + /// scroll start position if it is start or end of the timeline scroll view + /// then pass the touch to custom scroll view and set the timeline view + /// drag as null; + void _handleDragUpdate( + DragUpdateDetails details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double resourceItemHeight, + double weekNumberPanelWidth, + bool isNeedDragAndDrop, + double resourceViewSize) { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; + } + final _CalendarViewState viewKey = _getCurrentViewByVisibleDates()!; - final DateTime endDate = AppointmentHelper.convertTimeToAppointmentTimeZone( - actualEndTime, region.timeZone, calendarTimeZone); + if (_dragDetails.value.appointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleLongPressMove( + Offset(details.localPosition.dx - widget.width, + details.localPosition.dy), + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth); + return; + } - final TimeRegion occurrenceTimeRegion = - region.data.copyWith(startTime: startDate, endTime: endDate); - final CalendarTimeRegion occurrenceRegion = - _getCalendarTimeRegionFromTimeRegion(occurrenceTimeRegion); - occurrenceRegion.actualStartTime = recursiveDate; - occurrenceRegion.actualEndTime = actualEndTime; - occurrenceRegion.data = occurrenceTimeRegion; - return occurrenceRegion; + /// Calculate the scroll difference by current scroll position and start + /// scroll position. + final double difference = + details.globalPosition.dx - _timelineStartPosition; + if (_timelineScrollStartPosition >= + viewKey._scrollController!.position.maxScrollExtent && + ((difference < 0 && !widget.isRTL) || + (difference > 0 && widget.isRTL))) { + /// Set the scroll position as timeline scroll start position and the + /// value used on horizontal update method. + _scrollStartPosition = _timelineStartPosition; + _drag?.cancel(); + + /// Move the touch(drag) to custom scroll view. + _onHorizontalUpdate(details); + + /// Enable boolean value used to trigger the horizontal end animation on + /// drag end. + _isNeedTimelineScrollEnd = true; + + /// Remove the timeline view drag or scroll. + _disposeDrag(); + return; + } else if (_timelineScrollStartPosition <= + viewKey._scrollController!.position.minScrollExtent && + ((difference > 0 && !widget.isRTL) || + (difference < 0 && widget.isRTL))) { + /// Set the scroll position as timeline scroll start position and the + /// value used on horizontal update method. + _scrollStartPosition = _timelineStartPosition; + _drag?.cancel(); + + /// Move the touch(drag) to custom scroll view. + _onHorizontalUpdate(details); + + /// Enable boolean value used to trigger the horizontal end animation on + /// drag end. + _isNeedTimelineScrollEnd = true; + + /// Remove the timeline view drag or scroll. + _disposeDrag(); + return; + } + + _drag?.update(details); } - /// Return date collection which falls between the visible date range. - List _getDatesWithInVisibleDateRange( - List? dates, List visibleDates) { - final List visibleMonthDates = []; - if (dates == null) { - return visibleMonthDates; + /// Handle the scroll end to update the timeline view scroll or custom scroll + /// view scroll based on [_isNeedTimelineScrollEnd] value + void _handleDragEnd( + DragEndDetails details, + bool isTimelineView, + bool isResourceEnabled, + bool isMonthView, + double viewHeaderHeight, + double timeLabelWidth, + double weekNumberPanelWidth, + bool isNeedDragAndDrop) { + if (_dragDetails.value.appointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleLongPressEnd( + _dragDetails.value.position.value! - _dragDifferenceOffset!, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth); + return; } - final DateTime visibleStartDate = visibleDates[0]; - final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; - final int datesCount = dates.length; - final Map dateCollection = {}; - for (int i = 0; i < datesCount; i++) { - final DateTime currentDate = dates[i]; - if (!isDateWithInDateRange( - visibleStartDate, visibleEndDate, currentDate)) { - continue; - } + if (_isNeedTimelineScrollEnd) { + _isNeedTimelineScrollEnd = false; + _onHorizontalEnd(details); + return; + } - if (dateCollection.keys.contains( - currentDate.day.toString() + currentDate.month.toString())) { - continue; + _isNeedTimelineScrollEnd = false; + _drag?.end(details); + } + + /// Handle drag cancel related operations. + void _handleDragCancel() { + _isNeedTimelineScrollEnd = false; + _drag?.cancel(); + } + + /// Remove the drag when the touch(drag) passed to custom scroll view. + void _disposeDrag() { + _drag = null; + } + + /// Handle the pointer scroll when a pointer signal occurs over this object. + /// eg., track pad scroll. + void _handlePointerSignal(PointerSignalEvent event) { + final _CalendarViewState? viewKey = _getCurrentViewByVisibleDates(); + if (event is PointerScrollEvent && viewKey != null) { + final double scrolledPosition = + widget.isRTL ? -event.scrollDelta.dy : event.scrollDelta.dy; + final double targetScrollOffset = math.min( + math.max( + viewKey._scrollController!.position.pixels + scrolledPosition, + viewKey._scrollController!.position.minScrollExtent), + viewKey._scrollController!.position.maxScrollExtent); + if (targetScrollOffset != viewKey._scrollController!.position.pixels) { + viewKey._scrollController!.position.jumpTo(targetScrollOffset); } + } + } - dateCollection[currentDate.day.toString() + - currentDate.month.toString()] = currentDate; - visibleMonthDates.add(currentDate); + void _updateVisibleDates() { + widget.getCalendarState(_updateCalendarStateDetails); + final DateTime currentDate = DateTime( + _updateCalendarStateDetails.currentDate!.year, + _updateCalendarStateDetails.currentDate!.month, + _updateCalendarStateDetails.currentDate!.day); + final DateTime prevDate = DateTimeHelper.getPreviousViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentDate); + final DateTime nextDate = DateTimeHelper.getNextViewStartDate(widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, currentDate); + final List? nonWorkingDays = (widget.view == CalendarView.workWeek || + widget.view == CalendarView.timelineWorkWeek) + ? widget.calendar.timeSlotViewSettings.nonWorkingDays + : null; + final int visibleDatesCount = DateTimeHelper.getViewDatesCount( + widget.view, widget.calendar.monthViewSettings.numberOfWeeksInView); + + _visibleDates = getVisibleDates(currentDate, nonWorkingDays, + widget.calendar.firstDayOfWeek, visibleDatesCount) + .cast(); + _previousViewVisibleDates = getVisibleDates( + widget.isRTL ? nextDate : prevDate, + nonWorkingDays, + widget.calendar.firstDayOfWeek, + visibleDatesCount) + .cast(); + _nextViewVisibleDates = getVisibleDates(widget.isRTL ? prevDate : nextDate, + nonWorkingDays, widget.calendar.firstDayOfWeek, visibleDatesCount) + .cast(); + if (widget.view == CalendarView.timelineMonth) { + _visibleDates = DateTimeHelper.getCurrentMonthDates(_visibleDates); + _previousViewVisibleDates = + DateTimeHelper.getCurrentMonthDates(_previousViewVisibleDates); + _nextViewVisibleDates = + DateTimeHelper.getCurrentMonthDates(_nextViewVisibleDates); } - return visibleMonthDates; + _currentViewVisibleDates = _visibleDates; + _updateCalendarStateDetails.currentViewVisibleDates = + _currentViewVisibleDates; + widget.updateCalendarState(_updateCalendarStateDetails); + + if (_currentChildIndex == 0) { + _visibleDates = _nextViewVisibleDates; + _nextViewVisibleDates = _previousViewVisibleDates; + _previousViewVisibleDates = _currentViewVisibleDates; + } else if (_currentChildIndex == 1) { + _visibleDates = _currentViewVisibleDates; + } else if (_currentChildIndex == 2) { + _visibleDates = _previousViewVisibleDates; + _previousViewVisibleDates = _nextViewVisibleDates; + _nextViewVisibleDates = _currentViewVisibleDates; + } } - List _addViews() { - if (_children.isEmpty) { - _previousView = _CalendarView( - widget.calendar, - widget.view, - _previousViewVisibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(_previousViewVisibleDates), - _getDatesWithInVisibleDateRange( - widget.blackoutDates, _previousViewVisibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - widget.resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.minDate, - widget.maxDate, - widget.localizations, - (UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - (UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - key: _previousViewKey, - ); - _currentView = _CalendarView( - widget.calendar, - widget.view, - _visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(_visibleDates), - _getDatesWithInVisibleDateRange(widget.blackoutDates, _visibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - widget.resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.minDate, - widget.maxDate, - widget.localizations, - (UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - (UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - key: _currentViewKey, - ); - _nextView = _CalendarView( - widget.calendar, - widget.view, - _nextViewVisibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(_nextViewVisibleDates), - _getDatesWithInVisibleDateRange( - widget.blackoutDates, _nextViewVisibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - widget.resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.minDate, - widget.maxDate, - widget.localizations, - (UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - (UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - key: _nextViewKey, - ); + void _updateNextViewVisibleDates() { + DateTime currentViewDate = _currentViewVisibleDates[0]; + if (widget.view == CalendarView.month && + widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { + currentViewDate = _currentViewVisibleDates[ + (_currentViewVisibleDates.length / 2).truncate()]; + } - _children.add(_previousView); - _children.add(_currentView); - _children.add(_nextView); - return _children; + if (widget.isRTL) { + currentViewDate = DateTimeHelper.getPreviousViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); + } else { + currentViewDate = DateTimeHelper.getNextViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); } - widget.getCalendarState(_updateCalendarStateDetails); - final _CalendarView previousView = _updateViews( - _previousView, _previousViewKey, _previousViewVisibleDates); - final _CalendarView currentView = - _updateViews(_currentView, _currentViewKey, _visibleDates); - final _CalendarView nextView = - _updateViews(_nextView, _nextViewKey, _nextViewVisibleDates); + List dates = getVisibleDates( + currentViewDate, + widget.view == CalendarView.workWeek || + widget.view == CalendarView.timelineWorkWeek + ? widget.calendar.timeSlotViewSettings.nonWorkingDays + : null, + widget.calendar.firstDayOfWeek, + DateTimeHelper.getViewDatesCount(widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView)) + .cast(); - //// Update views while the all day view height differ from original height, - //// else repaint the appointment painter while current child visible appointment not equals calendar visible appointment - if (_previousView != previousView) { - _previousView = previousView; + if (widget.view == CalendarView.timelineMonth) { + dates = DateTimeHelper.getCurrentMonthDates(dates); } - if (_currentView != currentView) { - _currentView = currentView; + + if (_currentChildIndex == 0) { + _nextViewVisibleDates = dates; + } else if (_currentChildIndex == 1) { + _previousViewVisibleDates = dates; + } else { + _visibleDates = dates; } - if (_nextView != nextView) { - _nextView = nextView; + } + + void _updatePreviousViewVisibleDates() { + DateTime currentViewDate = _currentViewVisibleDates[0]; + if (widget.view == CalendarView.month && + widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { + currentViewDate = _currentViewVisibleDates[ + (_currentViewVisibleDates.length / 2).truncate()]; } - return _children; + if (widget.isRTL) { + currentViewDate = DateTimeHelper.getNextViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); + } else { + currentViewDate = DateTimeHelper.getPreviousViewStartDate( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + currentViewDate); + } + + List dates = getVisibleDates( + currentViewDate, + widget.view == CalendarView.workWeek || + widget.view == CalendarView.timelineWorkWeek + ? widget.calendar.timeSlotViewSettings.nonWorkingDays + : null, + widget.calendar.firstDayOfWeek, + DateTimeHelper.getViewDatesCount(widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView)) + .cast(); + + if (widget.view == CalendarView.timelineMonth) { + dates = DateTimeHelper.getCurrentMonthDates(dates); + } + + if (_currentChildIndex == 0) { + _visibleDates = dates; + } else if (_currentChildIndex == 1) { + _nextViewVisibleDates = dates; + } else { + _previousViewVisibleDates = dates; + } } - // method to check and update the views and appointments on the swiping end - _CalendarView _updateViews(_CalendarView view, - GlobalKey<_CalendarViewState> viewKey, List visibleDates) { - final int index = _children.indexOf(view); + void _getCalendarViewStateDetails(UpdateCalendarStateDetails details) { + widget.getCalendarState(_updateCalendarStateDetails); + details.currentDate = _updateCalendarStateDetails.currentDate; + details.currentViewVisibleDates = + _updateCalendarStateDetails.currentViewVisibleDates; + details.selectedDate = _updateCalendarStateDetails.selectedDate; + details.allDayPanelHeight = _updateCalendarStateDetails.allDayPanelHeight; + details.allDayAppointmentViewCollection = + _updateCalendarStateDetails.allDayAppointmentViewCollection; + details.appointments = _updateCalendarStateDetails.appointments; + details.visibleAppointments = + _updateCalendarStateDetails.visibleAppointments; + } - final AppointmentLayout appointmentLayout = - viewKey.currentState!._appointmentLayout; - // update the view with the visible dates on swiping end. - if (view.visibleDates != visibleDates) { - view = _CalendarView( - widget.calendar, - widget.view, - visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - _getRegions(visibleDates), - _getDatesWithInVisibleDateRange(widget.blackoutDates, visibleDates), - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - widget.resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.minDate, - widget.maxDate, - widget.localizations, - (UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - (UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - key: viewKey, - ); + void _updateCalendarViewStateDetails(UpdateCalendarStateDetails details) { + _updateCalendarStateDetails.selectedDate = details.selectedDate; + widget.updateCalendarState(_updateCalendarStateDetails); + } - _children[index] = view; - } // check and update the visible appointments in the view - else if (!CalendarViewHelper.isCollectionEqual( - appointmentLayout.visibleAppointments.value, - _updateCalendarStateDetails.visibleAppointments)) { - if (widget.view != CalendarView.month && - !CalendarViewHelper.isTimelineView(widget.view)) { - view = _CalendarView( - widget.calendar, - widget.view, - visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, + CalendarTimeRegion _getCalendarTimeRegionFromTimeRegion(TimeRegion region) { + return CalendarTimeRegion( + startTime: region.startTime, + endTime: region.endTime, + color: region.color, + text: region.text, + textStyle: region.textStyle, + recurrenceExceptionDates: region.recurrenceExceptionDates, + recurrenceRule: region.recurrenceRule, + resourceIds: region.resourceIds, + timeZone: region.timeZone, + enablePointerInteraction: region.enablePointerInteraction, + iconData: region.iconData, + ); + } + + /// Return collection of time region, in between the visible dates. + List _getRegions(List visibleDates) { + final DateTime visibleStartDate = visibleDates[0]; + final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; + final List regionCollection = []; + if (_timeRegions == null) { + return regionCollection; + } + + final DateTime startDate = + AppointmentHelper.convertToStartTime(visibleStartDate); + final DateTime endDate = AppointmentHelper.convertToEndTime(visibleEndDate); + for (int j = 0; j < _timeRegions!.length; j++) { + final TimeRegion timeRegion = _timeRegions![j]; + final CalendarTimeRegion region = + _getCalendarTimeRegionFromTimeRegion(timeRegion); + region.actualStartTime = + AppointmentHelper.convertTimeToAppointmentTimeZone( + region.startTime, region.timeZone, widget.calendar.timeZone); + region.actualEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + region.endTime, region.timeZone, widget.calendar.timeZone); + region.data = timeRegion; + + if (region.recurrenceRule == null || region.recurrenceRule == '') { + if (AppointmentHelper.isDateRangeWithinVisibleDateRange( + region.actualStartTime, region.actualEndTime, startDate, endDate)) { + regionCollection.add(region); + } + + continue; + } + + getRecurrenceRegions(region, regionCollection, startDate, endDate, + widget.calendar.timeZone); + } + + return regionCollection; + } + + /// Get the recurrence time regions in between the visible date range. + void getRecurrenceRegions( + CalendarTimeRegion region, + List regions, + DateTime visibleStartDate, + DateTime visibleEndDate, + String? calendarTimeZone) { + final DateTime regionStartDate = region.actualStartTime; + if (regionStartDate.isAfter(visibleEndDate)) { + return; + } + + String rule = region.recurrenceRule!; + if (!rule.contains('COUNT') && !rule.contains('UNTIL')) { + final DateFormat formatter = DateFormat('yyyyMMdd'); + final String newSubString = ';UNTIL=' + formatter.format(visibleEndDate); + rule = rule + newSubString; + } + + final List recursiveDates = + RecurrenceHelper.getRecurrenceDateTimeCollection( + rule, region.actualStartTime, + recurrenceDuration: AppointmentHelper.getDifference( + region.actualStartTime, region.actualEndTime), + specificStartDate: visibleStartDate, + specificEndDate: visibleEndDate); + + for (int j = 0; j < recursiveDates.length; j++) { + final DateTime recursiveDate = recursiveDates[j]; + if (region.recurrenceExceptionDates != null) { + bool isDateContains = false; + for (int i = 0; i < region.recurrenceExceptionDates!.length; i++) { + final DateTime date = + AppointmentHelper.convertTimeToAppointmentTimeZone( + region.recurrenceExceptionDates![i], '', calendarTimeZone); + if (isSameDate(date, recursiveDate)) { + isDateContains = true; + break; + } + } + if (isDateContains) { + continue; + } + } + + final CalendarTimeRegion occurrenceRegion = + cloneRecurrenceRegion(region, recursiveDate, calendarTimeZone); + regions.add(occurrenceRegion); + } + } + + /// Used to clone the time region with new values. + CalendarTimeRegion cloneRecurrenceRegion(CalendarTimeRegion region, + DateTime recursiveDate, String? calendarTimeZone) { + final int minutes = AppointmentHelper.getDifference( + region.actualStartTime, region.actualEndTime) + .inMinutes; + final DateTime actualEndTime = DateTimeHelper.getDateTimeValue( + addDuration(recursiveDate, Duration(minutes: minutes))); + final DateTime startDate = + AppointmentHelper.convertTimeToAppointmentTimeZone( + recursiveDate, calendarTimeZone, region.timeZone); + + final DateTime endDate = AppointmentHelper.convertTimeToAppointmentTimeZone( + actualEndTime, calendarTimeZone, region.timeZone); + + final TimeRegion occurrenceTimeRegion = + region.data.copyWith(startTime: startDate, endTime: endDate); + final CalendarTimeRegion occurrenceRegion = + _getCalendarTimeRegionFromTimeRegion(occurrenceTimeRegion); + occurrenceRegion.actualStartTime = recursiveDate; + occurrenceRegion.actualEndTime = actualEndTime; + occurrenceRegion.data = occurrenceTimeRegion; + return occurrenceRegion; + } + + /// Return date collection which falls between the visible date range. + List _getDatesWithInVisibleDateRange( + List? dates, List visibleDates) { + final List visibleMonthDates = []; + if (dates == null) { + return visibleMonthDates; + } + + final DateTime visibleStartDate = visibleDates[0]; + final DateTime visibleEndDate = visibleDates[visibleDates.length - 1]; + final int datesCount = dates.length; + final Map dateCollection = {}; + for (int i = 0; i < datesCount; i++) { + final DateTime currentDate = dates[i]; + if (!isDateWithInDateRange( + visibleStartDate, visibleEndDate, currentDate)) { + continue; + } + + if (dateCollection.keys.contains( + currentDate.day.toString() + currentDate.month.toString())) { + continue; + } + + dateCollection[currentDate.day.toString() + + currentDate.month.toString()] = currentDate; + visibleMonthDates.add(currentDate); + } + + return visibleMonthDates; + } + + List _addViews() { + if (_children.isEmpty) { + _previousView = _CalendarView( + widget.calendar, + widget.view, + _previousViewVisibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(_previousViewVisibleDates), + _getDatesWithInVisibleDateRange( + widget.blackoutDates, _previousViewVisibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: _previousViewKey, + ); + _currentView = _CalendarView( + widget.calendar, + widget.view, + _visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(_visibleDates), + _getDatesWithInVisibleDateRange(widget.blackoutDates, _visibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: _currentViewKey, + ); + _nextView = _CalendarView( + widget.calendar, + widget.view, + _nextViewVisibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(_nextViewVisibleDates), + _getDatesWithInVisibleDateRange( + widget.blackoutDates, _nextViewVisibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: _nextViewKey, + ); + + _children.add(_previousView); + _children.add(_currentView); + _children.add(_nextView); + return _children; + } + + widget.getCalendarState(_updateCalendarStateDetails); + final _CalendarView previousView = _updateViews( + _previousView, _previousViewKey, _previousViewVisibleDates); + final _CalendarView currentView = + _updateViews(_currentView, _currentViewKey, _visibleDates); + final _CalendarView nextView = + _updateViews(_nextView, _nextViewKey, _nextViewVisibleDates); + + //// Update views while the all day view height differ from original height, + //// else repaint the appointment painter while current child visible appointment not equals calendar visible appointment + if (_previousView != previousView) { + _previousView = previousView; + } + if (_currentView != currentView) { + _currentView = currentView; + } + if (_nextView != nextView) { + _nextView = nextView; + } + + return _children; + } + + // method to check and update the views and appointments on the swiping end + _CalendarView _updateViews(_CalendarView view, + GlobalKey<_CalendarViewState> viewKey, List visibleDates) { + final int index = _children.indexOf(view); + + final AppointmentLayout appointmentLayout = + viewKey.currentState!._appointmentLayout; + // update the view with the visible dates on swiping end. + if (view.visibleDates != visibleDates) { + view = _CalendarView( + widget.calendar, + widget.view, + visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + _getRegions(visibleDates), + _getDatesWithInVisibleDateRange(widget.blackoutDates, visibleDates), + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: viewKey, + ); + + _children[index] = view; + } // check and update the visible appointments in the view + else if (!CalendarViewHelper.isCollectionEqual( + appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails.visibleAppointments)) { + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + view = _CalendarView( + widget.calendar, + widget.view, + visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, view.regions, view.blackoutDates, _focusNode, @@ -1223,6 +2896,8 @@ class _CustomCalendarScrollViewState extends State widget.minDate, widget.maxDate, widget.localizations, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, (UpdateCalendarStateDetails details) { _updateCalendarViewStateDetails(details); }, @@ -1247,2259 +2922,3488 @@ class _CustomCalendarScrollViewState extends State !viewKey.currentState!._selectionPainter!.repaintNotifier.value; } - appointmentLayout.visibleAppointments.value = - _updateCalendarStateDetails.visibleAppointments; - if (widget.view == CalendarView.month && - widget.calendar.monthCellBuilder != null) { - viewKey.currentState!._monthView.visibleAppointmentNotifier.value = - _updateCalendarStateDetails.visibleAppointments; - } + appointmentLayout.visibleAppointments.value = + _updateCalendarStateDetails.visibleAppointments; + if (widget.view == CalendarView.month && + widget.calendar.monthCellBuilder != null) { + viewKey.currentState!._monthView.visibleAppointmentNotifier.value = + _updateCalendarStateDetails.visibleAppointments; + } + } + } + // When calendar state changed the state doesn't pass to the child of + // custom scroll view, hence to update the calendar state to the child we + // have added this. + else if (view.calendar != widget.calendar) { + /// Update the calendar view when calendar properties like blackout dates + /// dynamically changed. + view = _CalendarView( + widget.calendar, + widget.view, + visibleDates, + widget.width, + widget.height, + widget.agendaSelectedDate, + widget.locale, + widget.calendarTheme, + view.regions, + view.blackoutDates, + _focusNode, + widget.removePicker, + widget.calendar.allowViewNavigation, + widget.controller, + widget.resourcePanelScrollController, + widget.resourceCollection, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.minDate, + widget.maxDate, + widget.localizations, + widget.timelineMonthWeekNumberNotifier, + _dragDetails, + (UpdateCalendarStateDetails details) { + _updateCalendarViewStateDetails(details); + }, + (UpdateCalendarStateDetails details) { + _getCalendarViewStateDetails(details); + }, + key: viewKey, + ); + + _children[index] = view; + } + + return view; + } + + void animationListener() { + setState(() { + _position = _animation.value; + }); + } + + /// Check both the region collection as equal or not. + bool _isTimeRegionsEquals( + List? regions1, List? regions2) { + /// Check both instance as equal + /// eg., if both are null then its equal. + if (regions1 == regions2) { + return true; + } + + /// Check the collections are not equal based on its length + if (regions2 == null || + regions1 == null || + regions1.length != regions2.length) { + return false; + } + + /// Check each of the region is equal to another or not. + for (int i = 0; i < regions1.length; i++) { + if (regions1[i] != regions2[i]) { + return false; + } + } + + return true; + } + + /// Updates the selected date programmatically, when resource enables, in + /// this scenario the first resource cell will be selected + void _selectResourceProgrammatically() { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; + } + + for (int i = 0; i < _children.length; i++) { + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + _children[i].key! as GlobalKey<_CalendarViewState>; + if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + viewKey.currentState!._selectedResourceIndex = 0; + viewKey.currentState!._selectionPainter!.selectedResourceIndex = 0; + } else { + viewKey.currentState!._selectedResourceIndex = -1; + viewKey.currentState!._selectionPainter!.selectedResourceIndex = -1; + } + } + } + + /// Updates the selection, when the resource enabled and the resource + /// collection modified, moves or removes the selection based on the action + /// performed. + void _updateSelectedResourceIndex() { + for (int i = 0; i < _children.length; i++) { + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + _children[i].key! as GlobalKey<_CalendarViewState>; + final int selectedResourceIndex = + viewKey.currentState!._selectedResourceIndex; + if (selectedResourceIndex != -1) { + final Object selectedResourceId = + widget.resourceCollection![selectedResourceIndex].id; + final int newIndex = CalendarViewHelper.getResourceIndex( + widget.calendar.dataSource?.resources, selectedResourceId); + viewKey.currentState!._selectedResourceIndex = newIndex; + } + } + } + + void _updateSelection() { + widget.getCalendarState(_updateCalendarStateDetails); + final _CalendarViewState previousViewState = _previousViewKey.currentState!; + final _CalendarViewState currentViewState = _currentViewKey.currentState!; + final _CalendarViewState nextViewState = _nextViewKey.currentState!; + previousViewState._allDaySelectionNotifier.value = null; + currentViewState._allDaySelectionNotifier.value = null; + nextViewState._allDaySelectionNotifier.value = null; + previousViewState._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + nextViewState._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + currentViewState._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + previousViewState._selectionPainter!.appointmentView = null; + nextViewState._selectionPainter!.appointmentView = null; + currentViewState._selectionPainter!.appointmentView = null; + previousViewState._selectionNotifier.value = + !previousViewState._selectionNotifier.value; + currentViewState._selectionNotifier.value = + !currentViewState._selectionNotifier.value; + nextViewState._selectionNotifier.value = + !nextViewState._selectionNotifier.value; + } + + void _updateMoveToDate() { + if (widget.view == CalendarView.month) { + return; + } + + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (_currentChildIndex == 0) { + _previousViewKey.currentState?._scrollToPosition(); + } else if (_currentChildIndex == 1) { + _currentViewKey.currentState?._scrollToPosition(); + } else if (_currentChildIndex == 2) { + _nextViewKey.currentState?._scrollToPosition(); + } + }); + } + + /// Updates the current view visible dates for calendar in the swiping end + void _updateCurrentViewVisibleDates({bool isNextView = false}) { + if (isNextView) { + if (_currentChildIndex == 0) { + _currentViewVisibleDates = _visibleDates; + } else if (_currentChildIndex == 1) { + _currentViewVisibleDates = _nextViewVisibleDates; + } else { + _currentViewVisibleDates = _previousViewVisibleDates; + } + } else { + if (_currentChildIndex == 0) { + _currentViewVisibleDates = _nextViewVisibleDates; + } else if (_currentChildIndex == 1) { + _currentViewVisibleDates = _previousViewVisibleDates; + } else { + _currentViewVisibleDates = _visibleDates; + } + } + + _updateCalendarStateDetails.currentViewVisibleDates = + _currentViewVisibleDates; + if (widget.view == CalendarView.month && + widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { + final DateTime currentMonthDate = + _currentViewVisibleDates[_currentViewVisibleDates.length ~/ 2]; + _updateCalendarStateDetails.currentDate = + DateTime(currentMonthDate.year, currentMonthDate.month, 01); + } else { + _updateCalendarStateDetails.currentDate = _currentViewVisibleDates[0]; + } + + widget.updateCalendarState(_updateCalendarStateDetails); + } + + void _updateNextView() { + if (!_animationController.isCompleted) { + return; + } + + _updateSelection(); + _updateNextViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + _updateAllDayPanel(); + } + + setState(() { + /// Update the custom scroll layout current child index when the + /// animation ends. + if (_currentChildIndex == 0) { + _currentChildIndex = 1; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 0; + } + }); + + _resetPosition(); + _updateAppointmentPainter(); + } + + void _updatePreviousView() { + if (!_animationController.isCompleted) { + return; + } + + _updateSelection(); + _updatePreviousViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.view)) { + _updateAllDayPanel(); + } + + setState(() { + /// Update the custom scroll layout current child index when the + /// animation ends. + if (_currentChildIndex == 0) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 0; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 1; + } + }); + + _resetPosition(); + _updateAppointmentPainter(); + } + + void _moveToNextViewWithAnimation() { + if (!widget.isMobilePlatform) { + _moveToNextWebViewWithAnimation(); + return; + } + + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + // Resets the controller to forward it again, the animation will forward + // only from the dismissed state + if (_animationController.isCompleted || _animationController.isDismissed) { + _animationController.reset(); + } else { + return; + } + + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: false); + } + + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + widget.view == CalendarView.month) { + // update the bottom to top swiping + _tween.begin = 0; + _tween.end = -widget.height; + } else { + // update the right to left swiping + _tween.begin = 0; + _tween.end = -widget.width; + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped + _updateCurrentViewVisibleDates(isNextView: true); + } + + void _moveToPreviousViewWithAnimation({bool isScrollToEnd = false}) { + if (!widget.isMobilePlatform) { + _moveToPreviousWebViewWithAnimation(isScrollToEnd: isScrollToEnd); + return; + } + + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + // Resets the controller to backward it again, the animation will backward + // only from the dismissed state + if (_animationController.isCompleted || _animationController.isDismissed) { + _animationController.reset(); + } else { + return; + } + + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(isScrolledToEnd: isScrollToEnd); + } + + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + widget.view == CalendarView.month) { + // update the top to bottom swiping + _tween.begin = 0; + _tween.end = widget.height; + } else { + // update the left to right swiping + _tween.begin = 0; + _tween.end = widget.width; + } + + _animationController.duration = const Duration(milliseconds: 250); + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); + + /// updates the current view visible dates when the view swiped. + _updateCurrentViewVisibleDates(); + } + + void _moveToPreviousWebViewWithAnimation({bool isScrollToEnd = false}) { + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + /// Resets the animation from, we have added this without condition so that + /// the selection updates, when the cells selected through keyboard right + /// arrows with fast finger. + widget.fadeInController!.reset(); + + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (isTimelineView) { + _positionTimelineView(isScrolledToEnd: isScrollToEnd); + } else if (!isTimelineView && widget.view != CalendarView.month) { + _updateDayViewScrollPosition(); + } + + /// updates the current view visible dates when the view swiped. + _updateCurrentViewVisibleDates(); + _position = 0; + widget.fadeInController!.forward(); + _updateSelection(); + _updatePreviousViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && !isTimelineView) { + _updateAllDayPanel(); + } + + if (_currentChildIndex == 0) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 0; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 1; + } + + _updateAppointmentPainter(); + } + + void _moveToNextWebViewWithAnimation() { + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + return; + } + + /// Resets the animation from, we have added this without condition so that + /// the selection updates, when the cells selected through keyboard right + /// arrows with fast finger. + widget.fadeInController!.reset(); + + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + // Handled for time line view, to move the previous and next view to it's + // start and end position accordingly + if (isTimelineView) { + _positionTimelineView(isScrolledToEnd: false); + } else if (!isTimelineView && widget.view != CalendarView.month) { + _updateDayViewScrollPosition(); + } + + /// updates the current view visible dates when the view swiped + _updateCurrentViewVisibleDates(isNextView: true); + + _position = 0; + widget.fadeInController!.forward(); + _updateSelection(); + _updateNextViewVisibleDates(); + + /// Updates the all day panel of the view, when the all day panel expanded + /// and the view swiped with the expanded all day panel, and when we swipe + /// back to the view or swipes three times will render the all day panel as + /// expanded, to collapse the all day panel in day, week and work week view, + /// we have added this condition and called the method. + if (widget.view != CalendarView.month && !isTimelineView) { + _updateAllDayPanel(); + } + + if (_currentChildIndex == 0) { + _currentChildIndex = 1; + } else if (_currentChildIndex == 1) { + _currentChildIndex = 2; + } else if (_currentChildIndex == 2) { + _currentChildIndex = 0; + } + + _updateAppointmentPainter(); + } + + // resets position to zero on the swipe end to avoid the unwanted date updates + void _resetPosition() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (_position.abs() == widget.width || _position.abs() == widget.height) { + _position = 0; + } + }); + } + + void _updateScrollPosition() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (_previousViewKey.currentState == null || + _currentViewKey.currentState == null || + _nextViewKey.currentState == null || + _previousViewKey.currentState!._scrollController == null || + _currentViewKey.currentState!._scrollController == null || + _nextViewKey.currentState!._scrollController == null || + !_previousViewKey.currentState!._scrollController!.hasClients || + !_currentViewKey.currentState!._scrollController!.hasClients || + !_nextViewKey.currentState!._scrollController!.hasClients) { + return; + } + + _updateDayViewScrollPosition(); + }); + } + + /// Update the current day view view scroll position to other views. + void _updateDayViewScrollPosition() { + double scrolledPosition = 0; + if (_currentChildIndex == 0) { + scrolledPosition = + _previousViewKey.currentState!._scrollController!.offset; + } else if (_currentChildIndex == 1) { + scrolledPosition = + _currentViewKey.currentState!._scrollController!.offset; + } else if (_currentChildIndex == 2) { + scrolledPosition = _nextViewKey.currentState!._scrollController!.offset; + } + + if (_previousViewKey.currentState!._scrollController!.offset != + scrolledPosition && + _previousViewKey + .currentState!._scrollController!.position.maxScrollExtent >= + scrolledPosition) { + _previousViewKey.currentState!._scrollController! + .jumpTo(scrolledPosition); + } + + if (_currentViewKey.currentState!._scrollController!.offset != + scrolledPosition && + _currentViewKey + .currentState!._scrollController!.position.maxScrollExtent >= + scrolledPosition) { + _currentViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); + } + + if (_nextViewKey.currentState!._scrollController!.offset != + scrolledPosition && + _nextViewKey + .currentState!._scrollController!.position.maxScrollExtent >= + scrolledPosition) { + _nextViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); + } + } + + int _getRowOfDate(List visibleDates, DateTime selectedDate) { + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate(selectedDate, visibleDates[i])) { + switch (widget.view) { + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + case CalendarView.schedule: + return -1; + case CalendarView.month: + return i ~/ DateTime.daysPerWeek; + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return i; + } + } + } + + return -1; + } + + DateTime _updateSelectedDateForRightArrow(_CalendarView currentView, + _CalendarViewState currentViewState, DateTime? selectedDate) { + /// Condition added to move the view to next view when the selection reaches + /// the last horizontal cell of the view in day, week, workweek, month and + /// timeline month. + if (!CalendarViewHelper.isTimelineView(widget.view)) { + final int visibleDatesCount = currentView.visibleDates.length; + if (isSameDate( + currentView.visibleDates[visibleDatesCount - 1], selectedDate)) { + _moveToNextViewWithAnimation(); + } + + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, 1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + + /// Move to next view when the new selected date as next month date. + if (widget.view == CalendarView.month && + !CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[visibleDatesCount ~/ 2].month, + selectedDate)) { + _moveToNextViewWithAnimation(); + } else if (widget.view == CalendarView.workWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate!.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, 1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } + } else { + final double xPosition = widget.view == CalendarView.timelineMonth + ? 0 + : AppointmentHelper.timeToPosition(widget.calendar, selectedDate!, + currentViewState._timeIntervalHeight); + final int rowIndex = + _getRowOfDate(currentView.visibleDates, selectedDate!); + final double singleChildWidth = + _getSingleViewWidthForTimeLineView(currentViewState); + if ((rowIndex * singleChildWidth) + + xPosition + + currentViewState._timeIntervalHeight >= + currentViewState._scrollController!.offset + widget.width) { + currentViewState._scrollController!.jumpTo( + currentViewState._scrollController!.offset + + currentViewState._timeIntervalHeight); + } + if (widget.view == CalendarView.timelineDay && + selectedDate + .add(widget.calendar.timeSlotViewSettings.timeInterval) + .day != + currentView + .visibleDates[currentView.visibleDates.length - 1].day) { + _moveToNextViewWithAnimation(); + } + + if ((rowIndex * singleChildWidth) + + xPosition + + currentViewState._timeIntervalHeight == + currentViewState._scrollController!.position.maxScrollExtent + + currentViewState._scrollController!.position.viewportDimension) { + _moveToNextViewWithAnimation(); + SchedulerBinding.instance!.addPostFrameCallback((_) { + _moveToSelectedTimeSlot(); + }); + } + + /// For timeline month view each column represents a single day, and for + /// other timeline views each column represents a given time interval, + /// hence to update the selected date for timeline month we must add a day + /// and for other timeline views we must add the given time interval. + if (widget.view == CalendarView.timelineMonth) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, 1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + selectedDate = + selectedDate.add(widget.calendar.timeSlotViewSettings.timeInterval); + } + if (widget.view == CalendarView.timelineWorkWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate!.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, 1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } + } + + return selectedDate!; + } + + DateTime _updateSelectedDateForLeftArrow(_CalendarView currentView, + _CalendarViewState currentViewState, DateTime? selectedDate) { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + if (isSameDate(currentViewState.widget.visibleDates[0], selectedDate)) { + _moveToPreviousViewWithAnimation(); + } + + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, -1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + + /// Move to previous view when the selected date as previous month date. + if (widget.view == CalendarView.month && + !CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView + .visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate)) { + _moveToPreviousViewWithAnimation(); + } else if (widget.view == CalendarView.workWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate!.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } + } else { + final double xPosition = widget.view == CalendarView.timelineMonth + ? 0 + : AppointmentHelper.timeToPosition(widget.calendar, selectedDate!, + currentViewState._timeIntervalHeight); + final int rowIndex = + _getRowOfDate(currentView.visibleDates, selectedDate!); + final double singleChildWidth = + _getSingleViewWidthForTimeLineView(currentViewState); + + if ((rowIndex * singleChildWidth) + xPosition == 0) { + _moveToPreviousViewWithAnimation(isScrollToEnd: true); + SchedulerBinding.instance!.addPostFrameCallback((_) { + _moveToSelectedTimeSlot(); + }); } - } - // When calendar state changed the state doesn't pass to the child of - // custom scroll view, hence to update the calendar state to the child we - // have added this. - else if (view.calendar != widget.calendar) { - /// Update the calendar view when calendar properties like blackout dates - /// dynamically changed. - view = _CalendarView( - widget.calendar, - widget.view, - visibleDates, - widget.width, - widget.height, - widget.agendaSelectedDate, - widget.locale, - widget.calendarTheme, - view.regions, - view.blackoutDates, - _focusNode, - widget.removePicker, - widget.calendar.allowViewNavigation, - widget.controller, - widget.resourcePanelScrollController, - widget.resourceCollection, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.minDate, - widget.maxDate, - widget.localizations, - (UpdateCalendarStateDetails details) { - _updateCalendarViewStateDetails(details); - }, - (UpdateCalendarStateDetails details) { - _getCalendarViewStateDetails(details); - }, - key: viewKey, - ); - _children[index] = view; + if ((rowIndex * singleChildWidth) + xPosition <= + currentViewState._scrollController!.offset) { + currentViewState._scrollController!.jumpTo( + currentViewState._scrollController!.offset - + currentViewState._timeIntervalHeight); + } + + /// For timeline month view each column represents a single day, and for + /// other timeline views each column represents a given time interval, + /// hence to update the selected date for timeline month we must subtract + /// a day and for other timeline views we must subtract the given time + /// interval. + if (widget.view == CalendarView.timelineMonth) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + selectedDate = selectedDate + .subtract(widget.calendar.timeSlotViewSettings.timeInterval); + } + if (widget.view == CalendarView.timelineWorkWeek) { + for (int i = 0; + i < + DateTime.daysPerWeek - + widget.calendar.timeSlotViewSettings.nonWorkingDays.length; + i++) { + if (widget.calendar.timeSlotViewSettings.nonWorkingDays + .contains(selectedDate!.weekday)) { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } else { + break; + } + } + } } - return view; + return selectedDate!; } - void animationListener() { - setState(() { - _position = _animation.value; - }); - } + DateTime? _updateSelectedDateForUpArrow( + _CalendarView currentView, + _CalendarViewState currentViewState, + DateTime? selectedDate, + DateTime? appointmentSelectedDate) { + if (widget.view == CalendarView.month) { + final int rowIndex = + _getRowOfDate(currentView.visibleDates, selectedDate!); + if (rowIndex == 0) { + return selectedDate; + } + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate, + -DateTime.daysPerWeek, + selectedDate.hour, + selectedDate.minute, + selectedDate.second); - /// Check both the region collection as equal or not. - bool _isTimeRegionsEquals( - List? regions1, List? regions2) { - /// Check both instance as equal - /// eg., if both are null then its equal. - if (regions1 == regions2) { - return true; - } + /// Move to month start date when the new selected date as + /// previous month date. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate)) { + selectedDate = AppointmentHelper.getMonthStartDate( + currentViewState._selectionPainter!.selectedDate ?? + appointmentSelectedDate!); + + if (CalendarViewHelper.isDateInDateCollection( + currentView.blackoutDates, selectedDate)) { + do { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, 1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } while (CalendarViewHelper.isDateInDateCollection( + currentView.blackoutDates, selectedDate)); + } + } - /// Check the collections are not equal based on its length - if (regions2 == null || - regions1 == null || - regions1.length != regions2.length) { - return false; - } + return selectedDate; + } else if (!CalendarViewHelper.isTimelineView(widget.view)) { + final double yPosition = AppointmentHelper.timeToPosition( + widget.calendar, selectedDate!, currentViewState._timeIntervalHeight); + if (yPosition < 1) { + return selectedDate; + } + if (yPosition - 1 <= currentViewState._scrollController!.offset) { + currentViewState._scrollController! + .jumpTo(yPosition - currentViewState._timeIntervalHeight); + } + return selectedDate + .subtract(widget.calendar.timeSlotViewSettings.timeInterval); + } else if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + final double resourceItemHeight = + CalendarViewHelper.getResourceItemHeight( + widget.calendar.resourceViewSettings.size, + widget.height, + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length); - /// Check each of the region is equal to another or not. - for (int i = 0; i < regions1.length; i++) { - if (regions1[i] != regions2[i]) { - return false; + currentViewState._selectedResourceIndex -= 1; + + if (currentViewState._selectedResourceIndex == -1) { + currentViewState._selectedResourceIndex = 0; + return selectedDate; + } + + if (currentViewState._selectedResourceIndex * resourceItemHeight < + currentViewState._timelineViewVerticalScrollController!.offset) { + double scrollPosition = + currentViewState._timelineViewVerticalScrollController!.offset - + resourceItemHeight; + scrollPosition = scrollPosition > 0 ? scrollPosition : 0; + currentViewState._timelineViewVerticalScrollController! + .jumpTo(scrollPosition); } + + return selectedDate; } - return true; + return null; } - /// Updates the selected date programmatically, when resource enables, in - /// this scenario the first resource cell will be selected - void _selectResourceProgrammatically() { - if (!CalendarViewHelper.isTimelineView(widget.view)) { - return; - } + DateTime? _updateSelectedDateForDownArrow( + _CalendarView currentView, + _CalendarViewState currentViewState, + DateTime? selectedDate, + DateTime? selectedAppointmentDate) { + if (widget.view == CalendarView.month) { + final int rowIndex = + _getRowOfDate(currentView.visibleDates, selectedDate!); + if (rowIndex == + widget.calendar.monthViewSettings.numberOfWeeksInView - 1) { + return selectedDate; + } - for (int i = 0; i < _children.length; i++) { - final GlobalKey<_CalendarViewState> viewKey = - // ignore: avoid_as - _children[i].key! as GlobalKey<_CalendarViewState>; - if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { - viewKey.currentState!._selectedResourceIndex = 0; - viewKey.currentState!._selectionPainter!.selectedResourceIndex = 0; - } else { - viewKey.currentState!._selectedResourceIndex = -1; - viewKey.currentState!._selectionPainter!.selectedResourceIndex = -1; + selectedDate = AppointmentHelper.addDaysWithTime( + selectedDate, + DateTime.daysPerWeek, + selectedDate.hour, + selectedDate.minute, + selectedDate.second); + + /// Move to month end date when the new selected date as next month date. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, + selectedDate)) { + selectedDate = AppointmentHelper.getMonthEndDate( + currentViewState._selectionPainter!.selectedDate ?? + selectedAppointmentDate!); + + if (CalendarViewHelper.isDateInDateCollection( + currentView.blackoutDates, selectedDate)) { + do { + selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, -1, + selectedDate.hour, selectedDate.minute, selectedDate.second); + } while (CalendarViewHelper.isDateInDateCollection( + currentView.blackoutDates, selectedDate)); + } + } + return selectedDate; + } else if (!CalendarViewHelper.isTimelineView(widget.view)) { + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double yPosition = AppointmentHelper.timeToPosition( + widget.calendar, selectedDate!, currentViewState._timeIntervalHeight); + + if (selectedDate + .add(widget.calendar.timeSlotViewSettings.timeInterval) + .day != + selectedDate.day) { + return selectedDate; + } + + if (currentViewState._scrollController!.offset + + (widget.height - viewHeaderHeight) < + currentViewState._scrollController!.position.viewportDimension + + currentViewState + ._scrollController!.position.maxScrollExtent && + yPosition + + currentViewState._timeIntervalHeight + + widget.calendar.headerHeight + + viewHeaderHeight >= + currentViewState._scrollController!.offset + widget.height && + currentViewState._scrollController!.offset + + currentViewState + ._scrollController!.position.viewportDimension != + currentViewState._scrollController!.position.maxScrollExtent) { + currentViewState._scrollController!.jumpTo( + currentViewState._scrollController!.offset + + currentViewState._timeIntervalHeight); + } + return selectedDate + .add(widget.calendar.timeSlotViewSettings.timeInterval); + } else if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + final double resourceItemHeight = + CalendarViewHelper.getResourceItemHeight( + widget.calendar.resourceViewSettings.size, + widget.height, + widget.calendar.resourceViewSettings, + widget.calendar.dataSource!.resources!.length); + if (currentViewState._selectedResourceIndex == + widget.calendar.dataSource!.resources!.length - 1 || + currentViewState._selectedResourceIndex == -1) { + return selectedDate; } - } - } - /// Updates the selection, when the resource enabled and the resource - /// collection modified, moves or removes the selection based on the action - /// performed. - void _updateSelectedResourceIndex() { - for (int i = 0; i < _children.length; i++) { - final GlobalKey<_CalendarViewState> viewKey = - // ignore: avoid_as - _children[i].key! as GlobalKey<_CalendarViewState>; - final int selectedResourceIndex = - viewKey.currentState!._selectedResourceIndex; - if (selectedResourceIndex != -1) { - final Object selectedResourceId = - widget.resourceCollection![selectedResourceIndex].id; - final int newIndex = CalendarViewHelper.getResourceIndex( - widget.calendar.dataSource?.resources, selectedResourceId); - viewKey.currentState!._selectedResourceIndex = newIndex; - } - } - } + currentViewState._selectedResourceIndex += 1; - void _updateSelection() { - widget.getCalendarState(_updateCalendarStateDetails); - final _CalendarViewState previousViewState = _previousViewKey.currentState!; - final _CalendarViewState currentViewState = _currentViewKey.currentState!; - final _CalendarViewState nextViewState = _nextViewKey.currentState!; - previousViewState._allDaySelectionNotifier.value = null; - currentViewState._allDaySelectionNotifier.value = null; - nextViewState._allDaySelectionNotifier.value = null; - previousViewState._selectionPainter!.selectedDate = - _updateCalendarStateDetails.selectedDate; - nextViewState._selectionPainter!.selectedDate = - _updateCalendarStateDetails.selectedDate; - currentViewState._selectionPainter!.selectedDate = - _updateCalendarStateDetails.selectedDate; - previousViewState._selectionPainter!.appointmentView = null; - nextViewState._selectionPainter!.appointmentView = null; - currentViewState._selectionPainter!.appointmentView = null; - previousViewState._selectionNotifier.value = - !previousViewState._selectionNotifier.value; - currentViewState._selectionNotifier.value = - !currentViewState._selectionNotifier.value; - nextViewState._selectionNotifier.value = - !nextViewState._selectionNotifier.value; - } + if (currentViewState._selectedResourceIndex * resourceItemHeight >= + currentViewState._timelineViewVerticalScrollController!.offset + + currentViewState._timelineViewVerticalScrollController!.position + .viewportDimension) { + double scrollPosition = + currentViewState._timelineViewVerticalScrollController!.offset + + resourceItemHeight; + scrollPosition = scrollPosition > + currentViewState._timelineViewVerticalScrollController!.position + .maxScrollExtent + ? currentViewState + ._timelineViewVerticalScrollController!.position.maxScrollExtent + : scrollPosition; + currentViewState._timelineViewVerticalScrollController! + .jumpTo(scrollPosition); + } - void _updateMoveToDate() { - if (widget.view == CalendarView.month) { - return; + return selectedDate!; } - SchedulerBinding.instance!.addPostFrameCallback((_) { - if (_currentChildIndex == 0) { - _previousViewKey.currentState!._scrollToPosition(); - } else if (_currentChildIndex == 1) { - _currentViewKey.currentState!._scrollToPosition(); - } else if (_currentChildIndex == 2) { - _nextViewKey.currentState!._scrollToPosition(); - } - }); + return null; } - /// Updates the current view visible dates for calendar in the swiping end - void _updateCurrentViewVisibleDates({bool isNextView = false}) { - if (isNextView) { - if (_currentChildIndex == 0) { - _currentViewVisibleDates = _visibleDates; - } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _nextViewVisibleDates; - } else { - _currentViewVisibleDates = _previousViewVisibleDates; - } + /// Moves the view to the selected time slot. + void _moveToSelectedTimeSlot() { + _CalendarViewState currentViewState; + if (_currentChildIndex == 0) { + currentViewState = _previousViewKey.currentState!; + } else if (_currentChildIndex == 1) { + currentViewState = _currentViewKey.currentState!; } else { - if (_currentChildIndex == 0) { - _currentViewVisibleDates = _nextViewVisibleDates; - } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _previousViewVisibleDates; - } else { - _currentViewVisibleDates = _visibleDates; - } + currentViewState = _nextViewKey.currentState!; } - _updateCalendarStateDetails.currentViewVisibleDates = - _currentViewVisibleDates; - if (widget.view == CalendarView.month && - widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { - final DateTime currentMonthDate = - _currentViewVisibleDates[_currentViewVisibleDates.length ~/ 2]; - _updateCalendarStateDetails.currentDate = - DateTime(currentMonthDate.year, currentMonthDate.month, 01); - } else { - _updateCalendarStateDetails.currentDate = _currentViewVisibleDates[0]; + final double scrollPosition = + currentViewState._getScrollPositionForCurrentDate( + currentViewState._selectionPainter!.selectedDate!); + if (scrollPosition == -1 || + currentViewState._scrollController!.position.pixels == scrollPosition) { + return; } - widget.updateCalendarState(_updateCalendarStateDetails); + currentViewState._scrollController!.jumpTo( + currentViewState._scrollController!.position.maxScrollExtent > + scrollPosition + ? scrollPosition + : currentViewState._scrollController!.position.maxScrollExtent); } - void _updateNextView() { - if (!_animationController.isCompleted) { - return; - } + DateTime? _updateSelectedDate( + RawKeyEvent event, + _CalendarViewState currentViewState, + _CalendarView currentView, + int resourceIndex, + DateTime? selectedAppointmentDate, + bool isAllDayAppointment) { + DateTime? selectedDate = currentViewState._selectionPainter!.selectedDate ?? + selectedAppointmentDate; + if (event.logicalKey == LogicalKeyboardKey.arrowRight) { + do { + selectedDate = _updateSelectedDateForRightArrow( + currentView, currentViewState, selectedDate); + } while (!_isSelectedDateEnabled(selectedDate, resourceIndex, true)); - _updateSelection(); - _updateNextViewVisibleDates(); + return selectedDate; + } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { + do { + selectedDate = _updateSelectedDateForLeftArrow( + currentView, currentViewState, selectedDate); + } while (!_isSelectedDateEnabled(selectedDate, resourceIndex, true)); - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && - !CalendarViewHelper.isTimelineView(widget.view)) { - _updateAllDayPanel(); - } + return selectedDate; + } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + do { + selectedDate = _updateSelectedDateForUpArrow(currentView, + currentViewState, selectedDate, selectedAppointmentDate); + if (resourceIndex != -1 && + currentView.regions != null && + currentView.regions!.isNotEmpty) { + resourceIndex -= 1; + } - setState(() { - /// Update the custom scroll layout current child index when the - /// animation ends. - if (_currentChildIndex == 0) { - _currentChildIndex = 1; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 0; + if (widget.controller.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.calendar.view)) { + double yPosition = AppointmentHelper.timeToPosition(widget.calendar, + selectedDate!, currentViewState._timeIntervalHeight); + if (yPosition < 1 && + !_isSelectedDateEnabled(selectedDate, resourceIndex, true)) { + yPosition = AppointmentHelper.timeToPosition( + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight); + currentViewState._scrollController!.jumpTo(yPosition); + break; + } + } + } while (!_isSelectedDateEnabled(selectedDate!, resourceIndex, true) && + _getRowOfDate(currentView.visibleDates, selectedDate) != 0); + return selectedDate; + } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + if (isAllDayAppointment) { + return selectedDate; } - }); - - _resetPosition(); - _updateAppointmentPainter(); - } - - void _updatePreviousView() { - if (!_animationController.isCompleted) { - return; - } - _updateSelection(); - _updatePreviousViewVisibleDates(); + do { + selectedDate = _updateSelectedDateForDownArrow(currentView, + currentViewState, selectedDate, selectedAppointmentDate); + if (resourceIndex != -1 && + currentView.regions != null && + currentView.regions!.isNotEmpty) { + resourceIndex += 1; + } - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && - !CalendarViewHelper.isTimelineView(widget.view)) { - _updateAllDayPanel(); + if (widget.controller.view != CalendarView.month && + !CalendarViewHelper.isTimelineView(widget.calendar.view)) { + if (selectedDate! + .add(widget.calendar.timeSlotViewSettings.timeInterval) + .day != + selectedDate.day) { + final double yPosition = AppointmentHelper.timeToPosition( + widget.calendar, + currentViewState._selectionPainter!.selectedDate!, + currentViewState._timeIntervalHeight); + if (yPosition <= currentViewState._scrollController!.offset) { + currentViewState._scrollController!.jumpTo(yPosition); + } + break; + } + } + } while (!_isSelectedDateEnabled(selectedDate!, resourceIndex, true) && + _getRowOfDate(currentView.visibleDates, selectedDate) != + widget.calendar.monthViewSettings.numberOfWeeksInView - 1); + return selectedDate; } - setState(() { - /// Update the custom scroll layout current child index when the - /// animation ends. - if (_currentChildIndex == 0) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 0; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 1; - } - }); - - _resetPosition(); - _updateAppointmentPainter(); + return null; } - void _moveToNextViewWithAnimation() { - if (!widget.isMobilePlatform) { - _moveToNextWebViewWithAnimation(); - return; + /// Checks the selected date is enabled or not. + bool _isSelectedDateEnabled(DateTime date, int resourceIndex, + [bool isMinMaxDate = false]) { + final bool isMonthView = widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth; + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + if ((isMonthView && + !isDateWithInDateRange( + widget.calendar.minDate, widget.calendar.maxDate, date)) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + date, + timeInterval))) { + return isMinMaxDate; } - if (!DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; + final List blackoutDates = []; + if (_currentView.blackoutDates != null) { + blackoutDates.addAll(_currentView.blackoutDates!); + } + if (_previousView.blackoutDates != null) { + blackoutDates.addAll(_previousView.blackoutDates!); + } + if (_nextView.blackoutDates != null) { + blackoutDates.addAll(_nextView.blackoutDates!); } - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (_animationController.isCompleted || _animationController.isDismissed) { - _animationController.reset(); - } else { - return; - } + if (isMonthView && + CalendarViewHelper.isDateInDateCollection(blackoutDates, date)) { + return false; + } else if (!isMonthView) { + final List regions = []; + if (_currentView.regions != null) { + regions.addAll(_currentView.regions!); + } + if (_previousView.regions != null) { + regions.addAll(_previousView.regions!); + } + if (_nextView.regions != null) { + regions.addAll(_nextView.regions!); + } - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (CalendarViewHelper.isTimelineView(widget.view)) { - _positionTimelineView(isScrolledToEnd: false); - } + for (int i = 0; i < regions.length; i++) { + final CalendarTimeRegion region = regions[i]; + if (region.enablePointerInteraction || + (region.actualStartTime.isAfter(date) && + !CalendarViewHelper.isSameTimeSlot( + region.actualStartTime, date)) || + region.actualEndTime.isBefore(date) || + CalendarViewHelper.isSameTimeSlot(region.actualEndTime, date)) { + continue; + } - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - widget.view == CalendarView.month) { - // update the bottom to top swiping - _tween.begin = 0; - _tween.end = -widget.height; - } else { - // update the right to left swiping - _tween.begin = 0; - _tween.end = -widget.width; - } + /// Condition added ensure that the region is disabled only on the + /// specified resource slot, for other resources it must be enabled. + if (resourceIndex != -1 && + region.resourceIds != null && + region.resourceIds!.isNotEmpty && + !region.resourceIds! + .contains(widget.resourceCollection![resourceIndex].id)) { + continue; + } - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + return false; + } + } - /// updates the current view visible dates when the view swiped - _updateCurrentViewVisibleDates(isNextView: true); + return true; } - void _moveToPreviousViewWithAnimation({bool isScrollToEnd = false}) { - if (!widget.isMobilePlatform) { - _moveToPreviousWebViewWithAnimation(isScrollToEnd: isScrollToEnd); - return; + /// Method to handle the page up/down key for timeslot views in calendar. + KeyEventResult _updatePageUpAndDown(RawKeyEvent event, + _CalendarViewState currentViewState, bool isResourceEnabled) { + if (widget.controller.view != CalendarView.day && + widget.controller.view != CalendarView.week && + widget.controller.view != CalendarView.workWeek && + !isResourceEnabled) { + return KeyEventResult.ignored; } - if (!DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; - } + final ScrollController scrollController = isResourceEnabled + ? widget.resourcePanelScrollController! + : currentViewState._scrollController!; + final TargetPlatform platform = Theme.of(context).platform; - // Resets the controller to backward it again, the animation will backward - // only from the dismissed state - if (_animationController.isCompleted || _animationController.isDismissed) { - _animationController.reset(); - } else { - return; + double difference = 0; + final double scrollViewHeight = scrollController.position.maxScrollExtent + + scrollController.position.viewportDimension; + double divideValue = 0.25; + if (scrollController.position.pixels > scrollViewHeight / 2) { + divideValue = 0.5; } + if (event.logicalKey == LogicalKeyboardKey.pageUp || + (platform == TargetPlatform.windows && + event.logicalKey.keyId == 0x10700000021)) { + if (scrollController.position.pixels == 0) { + return KeyEventResult.ignored; + } + difference = scrollController.position.pixels * divideValue; + scrollController.jumpTo(difference); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.pageDown || + (platform == TargetPlatform.windows && + event.logicalKey.keyId == 0x10700000022)) { + double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.controller.view!); + double allDayHeight = 0; - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (CalendarViewHelper.isTimelineView(widget.view)) { - _positionTimelineView(isScrolledToEnd: isScrollToEnd); - } + if (widget.controller.view == CalendarView.day) { + allDayHeight = _kAllDayLayoutHeight; + viewHeaderHeight = 0; + } else { + allDayHeight = allDayHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : allDayHeight; + } - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - widget.view == CalendarView.month) { - // update the top to bottom swiping - _tween.begin = 0; - _tween.end = widget.height; - } else { - // update the left to right swiping - _tween.begin = 0; - _tween.end = widget.width; - } + final double timeRulerSize = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.controller.view!); - _animationController.duration = const Duration(milliseconds: 250); - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + final double viewPortHeight = isResourceEnabled + ? widget.height - viewHeaderHeight - timeRulerSize + : widget.height - allDayHeight - viewHeaderHeight; - /// updates the current view visible dates when the view swiped. - _updateCurrentViewVisibleDates(); + final double viewPortEndPosition = + scrollController.position.pixels + viewPortHeight; + if (viewPortEndPosition == scrollViewHeight) { + return KeyEventResult.ignored; + } + difference = + (scrollViewHeight - scrollController.position.pixels) * divideValue; + difference += scrollController.position.pixels; + if (difference + viewPortHeight >= scrollViewHeight) { + difference = scrollViewHeight - viewPortHeight; + } + scrollController.jumpTo(difference); + return KeyEventResult.handled; + } + + return KeyEventResult.ignored; } - void _moveToPreviousWebViewWithAnimation({bool isScrollToEnd = false}) { - if (!DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; + /// Updates the appointment selection based on keyboard navigation in calendar + KeyEventResult _updateAppointmentSelection( + RawKeyEvent event, + _CalendarViewState currentVisibleViewState, + bool isResourceEnabled, + AppointmentView? currentSelectedAppointment, + AppointmentView? currentAllDayAppointment) { + if (widget.controller.view == CalendarView.schedule) { + return KeyEventResult.ignored; } - // Resets the controller to backward it again, the animation will backward - // only from the dismissed state - if (widget.fadeInController!.isCompleted || - widget.fadeInController!.isDismissed) { - widget.fadeInController!.reset(); - } else { - return; - } + AppointmentView? selectedAppointment; + bool isAllDay = currentAllDayAppointment != null; + final List appointmentCollection = currentVisibleViewState + ._appointmentLayout + .getAppointmentViewCollection(); + final List allDayAppointmentCollection = + _updateCalendarStateDetails.allDayAppointmentViewCollection; + final List tempAppColl = + isAllDay ? allDayAppointmentCollection : appointmentCollection; + if (event.isShiftPressed) { + if (event.logicalKey == LogicalKeyboardKey.tab) { + if (currentAllDayAppointment != null || + currentSelectedAppointment != null) { + int index = tempAppColl.indexOf(isAllDay + ? currentAllDayAppointment + : currentSelectedAppointment!); + index -= 1; + if (tempAppColl.length > index && !index.isNegative) { + selectedAppointment = tempAppColl[index].appointment != null + ? tempAppColl[index] + : null; + } + } - final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (isTimelineView) { - _positionTimelineView(isScrolledToEnd: isScrollToEnd); - } else if (!isTimelineView && widget.view != CalendarView.month) { - _updateDayViewScrollPosition(); - } + if (currentSelectedAppointment != null && selectedAppointment == null) { + isAllDay = allDayAppointmentCollection.isNotEmpty; + selectedAppointment = isAllDay + ? allDayAppointmentCollection[ + allDayAppointmentCollection.length - 1] + : null; + } else if (currentSelectedAppointment == null && + currentAllDayAppointment == null && + selectedAppointment == null) { + if (currentVisibleViewState._selectionPainter!.selectedDate != null && + appointmentCollection.isNotEmpty) { + for (int i = 0; i < appointmentCollection.length; i++) { + if (AppointmentHelper.getDifference( + currentVisibleViewState._selectionPainter!.selectedDate!, + appointmentCollection[i].appointment!.actualStartTime) + .isNegative) { + continue; + } + + if (i != 0) { + selectedAppointment = appointmentCollection[i - 1]; + } + break; + } + } else { + selectedAppointment = appointmentCollection.isNotEmpty + ? appointmentCollection[appointmentCollection.length - 1] + : null; + } + } + + return _updateAppointmentSelectionOnView( + selectedAppointment, + currentVisibleViewState, + isAllDay, + isResourceEnabled, + !event.isShiftPressed); + } + } else if (event.logicalKey == LogicalKeyboardKey.tab) { + if (currentAllDayAppointment != null || + currentSelectedAppointment != null) { + int index = tempAppColl.indexOf( + isAllDay ? currentAllDayAppointment : currentSelectedAppointment!); + index += 1; + if (tempAppColl.length > index) { + selectedAppointment = tempAppColl[index].appointment != null + ? tempAppColl[index] + : null; + } + } - /// updates the current view visible dates when the view swiped. - _updateCurrentViewVisibleDates(); - _position = 0; - widget.fadeInController!.forward(); - _updateSelection(); - _updatePreviousViewVisibleDates(); + if (currentAllDayAppointment != null && selectedAppointment == null) { + isAllDay = false; + selectedAppointment = appointmentCollection[0]; + } else if (currentAllDayAppointment == null && + currentSelectedAppointment == null) { + if (currentVisibleViewState._selectionPainter!.selectedDate != null && + appointmentCollection.isNotEmpty) { + for (int i = 0; i < appointmentCollection.length; i++) { + if (AppointmentHelper.getDifference( + currentVisibleViewState._selectionPainter!.selectedDate!, + appointmentCollection[i].appointment!.actualStartTime) + .isNegative) { + continue; + } - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && !isTimelineView) { - _updateAllDayPanel(); - } + selectedAppointment = appointmentCollection[i]; + break; + } + } else { + isAllDay = allDayAppointmentCollection.isNotEmpty; + selectedAppointment = isAllDay + ? allDayAppointmentCollection[0] + : appointmentCollection.isNotEmpty + ? appointmentCollection[0] + : null; + } + } - if (_currentChildIndex == 0) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 0; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 1; + return _updateAppointmentSelectionOnView( + selectedAppointment, + currentVisibleViewState, + isAllDay, + isResourceEnabled, + !event.isShiftPressed); } - _updateAppointmentPainter(); + return KeyEventResult.ignored; } - void _moveToNextWebViewWithAnimation() { - if (!DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - return; - } + /// Updates the selection for appointment view based on keyboard navigation + /// in Calendar. + KeyEventResult _updateAppointmentSelectionOnView( + AppointmentView? selectedAppointment, + _CalendarViewState currentVisibleViewState, + bool isAllDay, + bool isResourceEnabled, + bool isForward) { + final DateTime visibleStartDate = AppointmentHelper.convertToStartTime( + currentVisibleViewState.widget.visibleDates[0]); + final DateTime visibleEndDate = AppointmentHelper.convertToEndTime( + currentVisibleViewState.widget.visibleDates[ + currentVisibleViewState.widget.visibleDates.length - 1]); - // Resets the controller to forward it again, the animation will forward - // only from the dismissed state - if (widget.fadeInController!.isCompleted || - widget.fadeInController!.isDismissed) { - widget.fadeInController!.reset(); - } else { - return; + if (isAllDay && selectedAppointment != null) { + currentVisibleViewState._updateAllDaySelection(selectedAppointment, null); + currentVisibleViewState._selectionPainter!.appointmentView = null; + currentVisibleViewState._selectionPainter!.selectedDate = null; + currentVisibleViewState._selectionNotifier.value = + !currentVisibleViewState._selectionNotifier.value; + _updateCalendarStateDetails.selectedDate = null; + widget.updateCalendarState(_updateCalendarStateDetails); + return KeyEventResult.handled; } - final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - // Handled for time line view, to move the previous and next view to it's - // start and end position accordingly - if (isTimelineView) { - _positionTimelineView(isScrolledToEnd: false); - } else if (!isTimelineView && widget.view != CalendarView.month) { - _updateDayViewScrollPosition(); - } + if (selectedAppointment != null && + AppointmentHelper.isAppointmentWithinVisibleDateRange( + selectedAppointment.appointment!, + visibleStartDate, + visibleEndDate)) { + currentVisibleViewState._allDaySelectionNotifier.value = null; + currentVisibleViewState._selectionPainter!.appointmentView = + selectedAppointment; + currentVisibleViewState._selectionPainter!.selectedDate = null; + currentVisibleViewState._selectionNotifier.value = + !currentVisibleViewState._selectionNotifier.value; - /// updates the current view visible dates when the view swiped - _updateCurrentViewVisibleDates(isNextView: true); + if (widget.controller.view != CalendarView.month) { + late double offset; + late double viewPortSize; + final double scrollViewHeight = currentVisibleViewState + ._scrollController!.position.maxScrollExtent + + currentVisibleViewState + ._scrollController!.position.viewportDimension; + final double resourceViewSize = + isResourceEnabled ? widget.calendar.resourceViewSettings.size : 0; + final bool isTimeline = + CalendarViewHelper.isTimelineView(widget.controller.view!); + double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.controller.view!); - _position = 0; - widget.fadeInController!.forward(); - _updateSelection(); - _updateNextViewVisibleDates(); + if (isTimeline) { + viewPortSize = widget.width - resourceViewSize; + offset = selectedAppointment.appointmentRect!.left; + } else { + double allDayHeight = 0; - /// Updates the all day panel of the view, when the all day panel expanded - /// and the view swiped with the expanded all day panel, and when we swipe - /// back to the view or swipes three times will render the all day panel as - /// expanded, to collapse the all day panel in day, week and work week view, - /// we have added this condition and called the method. - if (widget.view != CalendarView.month && !isTimelineView) { - _updateAllDayPanel(); - } + if (widget.controller.view == CalendarView.day) { + allDayHeight = _kAllDayLayoutHeight; + viewHeaderHeight = 0; + } else { + allDayHeight = allDayHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : allDayHeight; + } + viewPortSize = widget.height - allDayHeight - viewHeaderHeight; + offset = selectedAppointment.appointmentRect!.top; + } - if (_currentChildIndex == 0) { - _currentChildIndex = 1; - } else if (_currentChildIndex == 1) { - _currentChildIndex = 2; - } else if (_currentChildIndex == 2) { - _currentChildIndex = 0; - } + _updateScrollViewToAppointment( + offset, + currentVisibleViewState._scrollController!, + viewPortSize, + scrollViewHeight); - _updateAppointmentPainter(); + if (isResourceEnabled) { + final double resourcePanelHeight = widget + .resourcePanelScrollController!.position.viewportDimension + + widget.resourcePanelScrollController!.position.maxScrollExtent; + final double timeRulerSize = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.controller.view!), + viewPortSize = widget.height - viewHeaderHeight - timeRulerSize; + _updateScrollViewToAppointment( + selectedAppointment.appointmentRect!.top, + widget.resourcePanelScrollController!, + viewPortSize, + resourcePanelHeight); + } + } else if (widget.controller.view == CalendarView.month) { + widget.agendaSelectedDate.value = null; + } + + _updateCalendarStateDetails.selectedDate = null; + widget.updateCalendarState(_updateCalendarStateDetails); + return KeyEventResult.handled; + } else { + currentVisibleViewState._allDaySelectionNotifier.value = null; + currentVisibleViewState._selectionPainter!.appointmentView = null; + currentVisibleViewState._selectionPainter!.selectedDate = null; + currentVisibleViewState._selectionNotifier.value = + !currentVisibleViewState._selectionNotifier.value; + _updateCalendarStateDetails.selectedDate = null; + widget.updateCalendarState(_updateCalendarStateDetails); + isForward + ? FocusScope.of(context).nextFocus() + : FocusScope.of(context).previousFocus(); + return KeyEventResult.handled; + } } - // resets position to zero on the swipe end to avoid the unwanted date updates - void _resetPosition() { - SchedulerBinding.instance!.addPostFrameCallback((_) { - if (_position.abs() == widget.width || _position.abs() == widget.height) { - _position = 0; + /// Moves the scroll panel to the selected appointments position, if the + /// selected appointment doesn't falls on the view port. + void _updateScrollViewToAppointment( + double offset, + ScrollController scrollController, + double viewPortSize, + double panelHeight) { + if (offset < scrollController.position.pixels || + offset > (scrollController.position.pixels + viewPortSize)) { + if (offset + viewPortSize > panelHeight) { + offset = panelHeight - viewPortSize; } - }); + scrollController.jumpTo(offset); + } } - void _updateScrollPosition() { - SchedulerBinding.instance!.addPostFrameCallback((_) { - if (_previousViewKey.currentState == null || - _currentViewKey.currentState == null || - _nextViewKey.currentState == null || - _previousViewKey.currentState!._scrollController == null || - _currentViewKey.currentState!._scrollController == null || - _nextViewKey.currentState!._scrollController == null || - !_previousViewKey.currentState!._scrollController!.hasClients || - !_currentViewKey.currentState!._scrollController!.hasClients || - !_nextViewKey.currentState!._scrollController!.hasClients) { - return; + KeyEventResult _onKeyDown(FocusNode node, RawKeyEvent event) { + KeyEventResult result = KeyEventResult.ignored; + if (event.runtimeType != RawKeyDownEvent) { + return result; + } + + widget.removePicker(); + + if (event.isControlPressed && widget.view != CalendarView.schedule) { + final bool canMoveToNextView = DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL); + final bool canMoveToPreviousView = DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL); + if (event.logicalKey == LogicalKeyboardKey.arrowRight && + canMoveToNextView) { + widget.isRTL + ? _moveToPreviousViewWithAnimation() + : _moveToNextViewWithAnimation(); + result = KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft && + canMoveToPreviousView) { + widget.isRTL + ? _moveToNextViewWithAnimation() + : _moveToPreviousViewWithAnimation(); + result = KeyEventResult.handled; } + result = KeyEventResult.ignored; + } - _updateDayViewScrollPosition(); - }); - } + CalendarViewHelper.handleViewSwitchKeyBoardEvent( + event, widget.controller, widget.calendar.allowedViews); - /// Update the current day view view scroll position to other views. - void _updateDayViewScrollPosition() { - double scrolledPosition = 0; + _CalendarViewState currentVisibleViewState; + _CalendarView currentVisibleView; + final bool isResourcesEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); if (_currentChildIndex == 0) { - scrolledPosition = - _previousViewKey.currentState!._scrollController!.offset; + currentVisibleViewState = _previousViewKey.currentState!; + currentVisibleView = _previousView; } else if (_currentChildIndex == 1) { - scrolledPosition = - _currentViewKey.currentState!._scrollController!.offset; - } else if (_currentChildIndex == 2) { - scrolledPosition = _nextViewKey.currentState!._scrollController!.offset; + currentVisibleViewState = _currentViewKey.currentState!; + currentVisibleView = _currentView; + } else { + currentVisibleViewState = _nextViewKey.currentState!; + currentVisibleView = _nextView; } - if (_previousViewKey.currentState!._scrollController!.offset != - scrolledPosition && - _previousViewKey - .currentState!._scrollController!.position.maxScrollExtent >= - scrolledPosition) { - _previousViewKey.currentState!._scrollController! - .jumpTo(scrolledPosition); - } + result = _updatePageUpAndDown( + event, currentVisibleViewState, isResourcesEnabled); - if (_currentViewKey.currentState!._scrollController!.offset != - scrolledPosition && - _currentViewKey - .currentState!._scrollController!.position.maxScrollExtent >= - scrolledPosition) { - _currentViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); - } + AppointmentView? currentSelectedAppointment = + currentVisibleViewState._selectionPainter!.appointmentView; + AppointmentView? currentAllDayAppointment = + currentVisibleViewState._allDaySelectionNotifier.value?.appointmentView; - if (_nextViewKey.currentState!._scrollController!.offset != - scrolledPosition && - _nextViewKey - .currentState!._scrollController!.position.maxScrollExtent >= - scrolledPosition) { - _nextViewKey.currentState!._scrollController!.jumpTo(scrolledPosition); - } - } + result = _updateAppointmentSelection( + event, + currentVisibleViewState, + isResourcesEnabled, + currentSelectedAppointment, + currentAllDayAppointment); - int _getRowOfDate( - List visibleDates, _CalendarViewState currentViewState) { - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate( - currentViewState._selectionPainter!.selectedDate, visibleDates[i])) { - switch (widget.view) { - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - case CalendarView.schedule: - return -1; - case CalendarView.month: - return i ~/ DateTime.daysPerWeek; - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return i; - } - } + currentSelectedAppointment = + currentVisibleViewState._selectionPainter!.appointmentView; + currentAllDayAppointment = + currentVisibleViewState._allDaySelectionNotifier.value?.appointmentView; + + if (event.logicalKey == LogicalKeyboardKey.enter && + CalendarViewHelper.shouldRaiseCalendarTapCallback( + widget.calendar.onTap)) { + final AppointmentView? selectedAppointment = currentVisibleViewState + ._allDaySelectionNotifier.value?.appointmentView ?? + currentVisibleViewState._selectionPainter!.appointmentView; + final List? selectedAppointments = + widget.controller.view == CalendarView.month && + selectedAppointment == null + ? AppointmentHelper.getSelectedDateAppointments( + _updateCalendarStateDetails.appointments, + widget.calendar.timeZone, + _updateCalendarStateDetails.selectedDate) + : selectedAppointment != null + ? [selectedAppointment.appointment!] + : null; + final CalendarElement tappedElement = + _updateCalendarStateDetails.selectedDate != null + ? CalendarElement.calendarCell + : CalendarElement.appointment; + + CalendarViewHelper.raiseCalendarTapCallback( + widget.calendar, + tappedElement == CalendarElement.appointment + ? selectedAppointments![0].startTime + : _updateCalendarStateDetails.selectedDate, + CalendarViewHelper.getCustomAppointments( + selectedAppointments, widget.calendar.dataSource), + tappedElement, + isResourcesEnabled + ? widget.calendar.dataSource! + .resources![currentVisibleViewState._selectedResourceIndex] + : null); } - return -1; - } + final int previousResourceIndex = isResourcesEnabled + ? currentVisibleViewState._selectedResourceIndex + : -1; - DateTime _updateSelectedDateForRightArrow(_CalendarView currentView, - _CalendarViewState currentViewState, DateTime? selectedDate) { - /// Condition added to move the view to next view when the selection reaches - /// the last horizontal cell of the view in day, week, workweek, month and - /// timeline month. - if (!CalendarViewHelper.isTimelineView(widget.view)) { - final int visibleDatesCount = currentView.visibleDates.length; - if (isSameDate( - currentView.visibleDates[visibleDatesCount - 1], selectedDate)) { - _moveToNextViewWithAnimation(); - } + if ((currentVisibleViewState._selectionPainter!.selectedDate != null && + isDateWithInDateRange( + currentVisibleViewState.widget.visibleDates[0], + currentVisibleViewState.widget.visibleDates[ + currentVisibleViewState.widget.visibleDates.length - 1], + currentVisibleViewState._selectionPainter!.selectedDate)) || + (currentSelectedAppointment != null || + currentAllDayAppointment != null)) { + final int resourceIndex = isResourcesEnabled + ? currentVisibleViewState._selectedResourceIndex + : -1; - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, 1, - selectedDate.hour, selectedDate.minute, selectedDate.second); + final DateTime? selectedAppointmentDate = currentAllDayAppointment != null + ? AppointmentHelper.convertToStartTime( + currentAllDayAppointment.appointment!.actualStartTime) + : currentSelectedAppointment?.appointment!.actualStartTime; - /// Move to next view when the new selected date as next month date. - if (widget.view == CalendarView.month && - !CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView.visibleDates[visibleDatesCount ~/ 2].month, - selectedDate)) { - _moveToNextViewWithAnimation(); - } else if (widget.view == CalendarView.workWeek) { - for (int i = 0; - i < - DateTime.daysPerWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate!.weekday)) { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, 1, - selectedDate.hour, selectedDate.minute, selectedDate.second); - } else { - break; - } - } - } - } else { - final double xPosition = widget.view == CalendarView.timelineMonth - ? 0 - : AppointmentHelper.timeToPosition(widget.calendar, selectedDate!, - currentViewState._timeIntervalHeight); - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - final double singleChildWidth = - _getSingleViewWidthForTimeLineView(currentViewState); - if ((rowIndex * singleChildWidth) + - xPosition + - currentViewState._timeIntervalHeight >= - currentViewState._scrollController!.offset + widget.width) { - currentViewState._scrollController!.jumpTo( - currentViewState._scrollController!.offset + - currentViewState._timeIntervalHeight); + final bool isAllDayAppointment = currentAllDayAppointment != null; + + final DateTime? selectedDate = _updateSelectedDate( + event, + currentVisibleViewState, + currentVisibleView, + resourceIndex, + selectedAppointmentDate, + isAllDayAppointment); + + if (selectedDate == null) { + result = KeyEventResult.ignored; + return result; } - if (widget.view == CalendarView.timelineDay && - selectedDate! - .add(widget.calendar.timeSlotViewSettings.timeInterval) - .day != - currentView - .visibleDates[currentView.visibleDates.length - 1].day) { - _moveToNextViewWithAnimation(); + + if (!_isSelectedDateEnabled(selectedDate, resourceIndex)) { + currentVisibleViewState._selectedResourceIndex = previousResourceIndex; + return KeyEventResult.ignored; } - if ((rowIndex * singleChildWidth) + - xPosition + - currentViewState._timeIntervalHeight == - currentViewState._scrollController!.position.maxScrollExtent + - currentViewState._scrollController!.position.viewportDimension) { - _moveToNextViewWithAnimation(); + if (widget.view == CalendarView.month) { + widget.agendaSelectedDate.value = selectedDate; } - /// For timeline month view each column represents a single day, and for - /// other timeline views each column represents a given time interval, - /// hence to update the selected date for timeline month we must add a day - /// and for other timeline views we must add the given time interval. - if (widget.view == CalendarView.timelineMonth) { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, 1, - selectedDate.hour, selectedDate.minute, selectedDate.second); - } else { - selectedDate = selectedDate! - .add(widget.calendar.timeSlotViewSettings.timeInterval); + _updateCalendarStateDetails.selectedDate = selectedDate; + if (widget.calendar.onSelectionChanged != null && + (!CalendarViewHelper.isSameTimeSlot( + currentVisibleViewState._selectionPainter!.selectedDate, + selectedDate) || + (isResourcesEnabled && + currentVisibleViewState + ._selectionPainter!.selectedResourceIndex != + currentVisibleViewState._selectedResourceIndex))) { + CalendarViewHelper.raiseCalendarSelectionChangedCallback( + widget.calendar, + selectedDate, + isResourcesEnabled + ? widget.resourceCollection![ + currentVisibleViewState._selectedResourceIndex] + : null); } - if (widget.view == CalendarView.timelineWorkWeek) { - for (int i = 0; - i < - DateTime.daysPerWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate!.weekday)) { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, 1, - selectedDate.hour, selectedDate.minute, selectedDate.second); - } else { - break; - } - } + currentVisibleViewState._selectionPainter!.selectedDate = selectedDate; + currentVisibleViewState._updateAllDaySelection(null, null); + currentVisibleViewState._selectionPainter!.appointmentView = null; + currentVisibleViewState._selectionPainter!.selectedResourceIndex = + currentVisibleViewState._selectedResourceIndex; + currentVisibleViewState._selectionNotifier.value = + !currentVisibleViewState._selectionNotifier.value; + + widget.updateCalendarState(_updateCalendarStateDetails); + result = KeyEventResult.handled; + } + + return result; + } + + void _positionTimelineView({bool isScrolledToEnd = true}) { + final _CalendarViewState previousViewState = _previousViewKey.currentState!; + final _CalendarViewState currentViewState = _currentViewKey.currentState!; + final _CalendarViewState nextViewState = _nextViewKey.currentState!; + if (widget.isRTL) { + if (_currentChildIndex == 0) { + currentViewState._scrollController!.jumpTo(isScrolledToEnd + ? currentViewState._scrollController!.position.maxScrollExtent + : 0); + nextViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 1) { + nextViewState._scrollController!.jumpTo(isScrolledToEnd + ? nextViewState._scrollController!.position.maxScrollExtent + : 0); + previousViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 2) { + previousViewState._scrollController!.jumpTo(isScrolledToEnd + ? previousViewState._scrollController!.position.maxScrollExtent + : 0); + currentViewState._scrollController!.jumpTo(0); + } + } else { + if (_currentChildIndex == 0) { + nextViewState._scrollController!.jumpTo(isScrolledToEnd + ? nextViewState._scrollController!.position.maxScrollExtent + : 0); + currentViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 1) { + previousViewState._scrollController!.jumpTo(isScrolledToEnd + ? previousViewState._scrollController!.position.maxScrollExtent + : 0); + nextViewState._scrollController!.jumpTo(0); + } else if (_currentChildIndex == 2) { + currentViewState._scrollController!.jumpTo(isScrolledToEnd + ? currentViewState._scrollController!.position.maxScrollExtent + : 0); + previousViewState._scrollController!.jumpTo(0); } } - - return selectedDate!; } - DateTime _updateSelectedDateForLeftArrow(_CalendarView currentView, - _CalendarViewState currentViewState, DateTime? selectedDate) { - if (!CalendarViewHelper.isTimelineView(widget.view)) { - if (isSameDate(currentViewState.widget.visibleDates[0], - currentViewState._selectionPainter!.selectedDate)) { - _moveToPreviousViewWithAnimation(); - } - - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, -1, - selectedDate.hour, selectedDate.minute, selectedDate.second); - - /// Move to previous view when the selected date as previous month date. - if (widget.view == CalendarView.month && - !CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView - .visibleDates[currentView.visibleDates.length ~/ 2].month, - selectedDate)) { - _moveToPreviousViewWithAnimation(); - } else if (widget.view == CalendarView.workWeek) { - for (int i = 0; - i < - DateTime.daysPerWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate!.weekday)) { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, - selectedDate.hour, selectedDate.minute, selectedDate.second); - } else { - break; - } + void _onHorizontalStart( + DragStartDetails dragStartDetails, + bool isResourceEnabled, + bool isTimelineView, + double viewHeaderHeight, + double timeLabelWidth, + bool isNeedDragAndDrop) { + final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; + if (currentState._hoveringAppointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleAppointmentDragStart( + currentState._hoveringAppointmentView!.clone(), + isTimelineView, + Offset(dragStartDetails.localPosition.dx - widget.width, + dragStartDetails.localPosition.dy), + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth); + return; + } + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month) { + _scrollStartPosition = dragStartDetails.globalPosition.dx; } - } - } else { - final double xPosition = widget.view == CalendarView.timelineMonth - ? 0 - : AppointmentHelper.timeToPosition(widget.calendar, selectedDate!, - currentViewState._timeIntervalHeight); - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - final double singleChildWidth = - _getSingleViewWidthForTimeLineView(currentViewState); - if ((rowIndex * singleChildWidth) + xPosition == 0) { - _moveToPreviousViewWithAnimation(isScrollToEnd: true); - } - - if ((rowIndex * singleChildWidth) + xPosition <= - currentViewState._scrollController!.offset) { - currentViewState._scrollController!.jumpTo( - currentViewState._scrollController!.offset - - currentViewState._timeIntervalHeight); - } + // Handled for time line view, to move the previous and + // next view to it's start and end position accordingly + if (CalendarViewHelper.isTimelineView(widget.view)) { + _positionTimelineView(); + } + } + } - /// For timeline month view each column represents a single day, and for - /// other timeline views each column represents a given time interval, - /// hence to update the selected date for timeline month we must subtract - /// a day and for other timeline views we must subtract the given time - /// interval. - if (widget.view == CalendarView.timelineMonth) { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate!, -1, - selectedDate.hour, selectedDate.minute, selectedDate.second); - } else { - selectedDate = selectedDate! - .subtract(widget.calendar.timeSlotViewSettings.timeInterval); - } - if (widget.view == CalendarView.timelineWorkWeek) { - for (int i = 0; - i < - DateTime.daysPerWeek - - widget.calendar.timeSlotViewSettings.nonWorkingDays.length; - i++) { - if (widget.calendar.timeSlotViewSettings.nonWorkingDays - .contains(selectedDate!.weekday)) { - selectedDate = AppointmentHelper.addDaysWithTime(selectedDate, -1, - selectedDate.hour, selectedDate.minute, selectedDate.second); - } else { - break; + void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails, + [bool isResourceEnabled = false, + bool isMonthView = false, + bool isTimelineView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double resourceItemHeight = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false]) { + if (_dragDetails.value.appointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleLongPressMove( + Offset(dragUpdateDetails.localPosition.dx - widget.width, + dragUpdateDetails.localPosition.dy), + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth); + return; + } + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month) { + final double difference = + dragUpdateDetails.globalPosition.dx - _scrollStartPosition; + if (difference < 0 && + !DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + return; + } else if (difference > 0 && + !DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + return; } + _position = difference; + _clearSelection(); + setState(() { + /* Updates the widget navigated distance and moves the widget + in the custom scroll view */ + }); } - } } - - return selectedDate!; } - DateTime? _updateSelectedDateForUpArrow(_CalendarView currentView, - _CalendarViewState currentViewState, DateTime? selectedDate) { - if (widget.view == CalendarView.month) { - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - if (rowIndex == 0) { - return selectedDate; - } - selectedDate = AppointmentHelper.addDaysWithTime( - selectedDate!, - -DateTime.daysPerWeek, - selectedDate.hour, - selectedDate.minute, - selectedDate.second); + void _onHorizontalEnd(DragEndDetails dragEndDetails, + [bool isResourceEnabled = false, + bool isTimelineView = false, + bool isMonthView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false]) { + if (_dragDetails.value.appointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleLongPressEnd( + _dragDetails.value.position.value! - _dragDifferenceOffset!, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth); + return; + } + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.horizontal || + widget.view != CalendarView.month) { + // condition to check and update the right to left swiping + if (-_position >= widget.width / 2) { + _tween.begin = _position; + _tween.end = -widget.width; - /// Move to month start date when the new selected date as - /// previous month date. - if (!CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, - selectedDate)) { - selectedDate = AppointmentHelper.getMonthStartDate(selectedDate); - } + // Resets the controller to forward it again, + // the animation will forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } - return selectedDate; - } else if (!CalendarViewHelper.isTimelineView(widget.view)) { - final double yPosition = AppointmentHelper.timeToPosition( - widget.calendar, selectedDate!, currentViewState._timeIntervalHeight); - if (yPosition == 0) { - return selectedDate; - } - if (yPosition <= currentViewState._scrollController!.offset) { - currentViewState._scrollController! - .jumpTo(yPosition - currentViewState._timeIntervalHeight); - } - return selectedDate - .subtract(widget.calendar.timeSlotViewSettings.timeInterval); - } else if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { - final double resourceItemHeight = - CalendarViewHelper.getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height, - widget.calendar.resourceViewSettings, - widget.calendar.dataSource!.resources!.length); + _animationController + .forward() + .then((dynamic value) => _updateNextView()); - currentViewState._selectedResourceIndex -= 1; + /// updates the current view visible dates when the view swiped in + /// right to left direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // fling the view from right to left + else if (-dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position + in the custom scroll view */ + }); + return; + } - if (currentViewState._selectedResourceIndex == -1) { - currentViewState._selectedResourceIndex = 0; - return selectedDate; - } + _tween.begin = _position; + _tween.end = -widget.width; + + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } - if (currentViewState._selectedResourceIndex * resourceItemHeight < - currentViewState._timelineViewVerticalScrollController!.offset) { - double scrollPosition = - currentViewState._timelineViewVerticalScrollController!.offset - - resourceItemHeight; - scrollPosition = scrollPosition > 0 ? scrollPosition : 0; - currentViewState._timelineViewVerticalScrollController! - .jumpTo(scrollPosition); - } + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updateNextView()); - return selectedDate; - } + /// updates the current view visible dates when fling the view in + /// right to left direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // condition to check and update the left to right swiping + else if (_position >= widget.width / 2) { + _tween.begin = _position; + _tween.end = widget.width; - return null; - } + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } - DateTime? _updateSelectedDateForDownArrow(_CalendarView currentView, - _CalendarViewState currentViewState, DateTime? selectedDate) { - if (widget.view == CalendarView.month) { - final int rowIndex = - _getRowOfDate(currentView.visibleDates, currentViewState); - if (rowIndex == - widget.calendar.monthViewSettings.numberOfWeeksInView - 1) { - return selectedDate!; - } + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); - selectedDate = AppointmentHelper.addDaysWithTime( - selectedDate!, - DateTime.daysPerWeek, - selectedDate.hour, - selectedDate.minute, - selectedDate.second); + /// updates the current view visible dates when the view swiped in + /// left to right direction + _updateCurrentViewVisibleDates(); + } + // fling the view from left to right + else if (dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays, + widget.isRTL)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position + in the custom scroll view */ + }); + return; + } - /// Move to month end date when the new selected date as next month date. - if (!CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentView.visibleDates[currentView.visibleDates.length ~/ 2].month, - selectedDate)) { - selectedDate = AppointmentHelper.getMonthEndDate(selectedDate); - } - return selectedDate; - } else if (!CalendarViewHelper.isTimelineView(widget.view)) { - final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - final double yPosition = AppointmentHelper.timeToPosition( - widget.calendar, selectedDate!, currentViewState._timeIntervalHeight); + _tween.begin = _position; + _tween.end = widget.width; - if (selectedDate - .add(widget.calendar.timeSlotViewSettings.timeInterval) - .day != - selectedDate.day) { - return selectedDate; - } + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } - if (yPosition + - currentViewState._timeIntervalHeight + - widget.calendar.headerHeight + - viewHeaderHeight >= - currentViewState._scrollController!.offset + widget.height && - currentViewState._scrollController!.offset + - currentViewState - ._scrollController!.position.viewportDimension != - currentViewState._scrollController!.position.maxScrollExtent) { - currentViewState._scrollController!.jumpTo( - currentViewState._scrollController!.offset + - currentViewState._timeIntervalHeight); - } - return selectedDate - .add(widget.calendar.timeSlotViewSettings.timeInterval); - } else if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { - final double resourceItemHeight = - CalendarViewHelper.getResourceItemHeight( - widget.calendar.resourceViewSettings.size, - widget.height, - widget.calendar.resourceViewSettings, - widget.calendar.dataSource!.resources!.length); - if (currentViewState._selectedResourceIndex == - widget.calendar.dataSource!.resources!.length - 1 || - currentViewState._selectedResourceIndex == -1) { - return selectedDate; - } + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updatePreviousView()); - currentViewState._selectedResourceIndex += 1; + /// updates the current view visible dates when fling the view in + /// left to right direction + _updateCurrentViewVisibleDates(); + } + // condition to check and revert the right to left swiping + else if (_position.abs() <= widget.width / 2) { + _tween.begin = _position; + _tween.end = 0.0; - if (currentViewState._selectedResourceIndex * resourceItemHeight >= - currentViewState._timelineViewVerticalScrollController!.offset + - currentViewState._timelineViewVerticalScrollController!.position - .viewportDimension) { - double scrollPosition = - currentViewState._timelineViewVerticalScrollController!.offset + - resourceItemHeight; - scrollPosition = scrollPosition > - currentViewState._timelineViewVerticalScrollController!.position - .maxScrollExtent - ? currentViewState - ._timelineViewVerticalScrollController!.position.maxScrollExtent - : scrollPosition; - currentViewState._timelineViewVerticalScrollController! - .jumpTo(scrollPosition); - } + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted && _position != _tween.end) { + _animationController.reset(); + } - return selectedDate!; + _animationController.forward(); + } + } } - - return null; } - DateTime? _updateSelectedDate( - RawKeyEvent event, - _CalendarViewState currentViewState, - _CalendarView currentView, - int resourceIndex) { - DateTime? selectedDate = currentViewState._selectionPainter!.selectedDate; - if (event.logicalKey == LogicalKeyboardKey.arrowRight) { - do { - selectedDate = _updateSelectedDateForRightArrow( - currentView, currentViewState, selectedDate); - } while (!_isSelectedDateEnabled(selectedDate, resourceIndex, true)); - return selectedDate; - } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) { - do { - selectedDate = _updateSelectedDateForLeftArrow( - currentView, currentViewState, selectedDate); - } while (!_isSelectedDateEnabled(selectedDate, resourceIndex, true)); - return selectedDate; - } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - do { - selectedDate = _updateSelectedDateForUpArrow( - currentView, currentViewState, selectedDate); - if (resourceIndex != -1 && - currentView.regions != null && - currentView.regions!.isNotEmpty) { - resourceIndex -= 1; + void _onVerticalStart( + DragStartDetails dragStartDetails, + bool isResourceEnabled, + bool isTimelineView, + double viewHeaderHeight, + double timeLabelWidth, + bool isNeedDragAndDrop) { + final _CalendarViewState currentState = _getCurrentViewByVisibleDates()!; + if (currentState._hoveringAppointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleAppointmentDragStart( + currentState._hoveringAppointmentView!.clone(), + isTimelineView, + Offset(dragStartDetails.localPosition.dx, + dragStartDetails.localPosition.dy - widget.height), + isResourceEnabled, + viewHeaderHeight, + timeLabelWidth); + return; + } + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + !CalendarViewHelper.isTimelineView(widget.view)) { + _scrollStartPosition = dragStartDetails.globalPosition.dy; } - } while (!_isSelectedDateEnabled(selectedDate!, resourceIndex, true)); - return selectedDate; - } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - do { - selectedDate = _updateSelectedDateForDownArrow( - currentView, currentViewState, selectedDate); - if (resourceIndex != -1 && - currentView.regions != null && - currentView.regions!.isNotEmpty) { - resourceIndex += 1; + } + } + + void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails, + [bool isResourceEnabled = false, + bool isMonthView = false, + bool isTimelineView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double resourceItemHeight = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false]) { + if (_dragDetails.value.appointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleLongPressMove( + Offset(dragUpdateDetails.localPosition.dx, + dragUpdateDetails.localPosition.dy - widget.height), + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + weekNumberPanelWidth); + return; + } + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + !CalendarViewHelper.isTimelineView(widget.view)) { + final double difference = + dragUpdateDetails.globalPosition.dy - _scrollStartPosition; + if (difference < 0 && + !DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + return; + } else if (difference > 0 && + !DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + return; + } + _position = difference; + setState(() { + /* Updates the widget navigated distance and moves the widget + in the custom scroll view */ + }); } - } while (!_isSelectedDateEnabled(selectedDate!, resourceIndex, true)); - return selectedDate; } + } + + void _onVerticalEnd(DragEndDetails dragEndDetails, + [bool isResourceEnabled = false, + bool isTimelineView = false, + bool isMonthView = false, + double viewHeaderHeight = 0, + double timeLabelWidth = 0, + double weekNumberPanelWidth = 0, + bool isNeedDragAndDrop = false]) { + if (_dragDetails.value.appointmentView != null && + !widget.isMobilePlatform && + isNeedDragAndDrop) { + _handleLongPressEnd( + _dragDetails.value.position.value! - _dragDifferenceOffset!, + isTimelineView, + isResourceEnabled, + isMonthView, + viewHeaderHeight, + timeLabelWidth, + weekNumberPanelWidth); + return; + } + switch (widget.calendar.viewNavigationMode) { + case ViewNavigationMode.none: + return; + case ViewNavigationMode.snap: + widget.removePicker(); + if (widget.calendar.monthViewSettings.navigationDirection == + MonthNavigationDirection.vertical && + !CalendarViewHelper.isTimelineView(widget.view)) { + // condition to check and update the bottom to top swiping + if (-_position >= widget.height / 2) { + _tween.begin = _position; + _tween.end = -widget.height; - return null; - } + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } - /// Checks the selected date is enabled or not. - bool _isSelectedDateEnabled(DateTime date, int resourceIndex, - [bool isMinMaxDate = false]) { - final bool isMonthView = widget.view == CalendarView.month || - widget.view == CalendarView.timelineMonth; - final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); - if ((isMonthView && - !isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, date)) || - (!isMonthView && - !CalendarViewHelper.isDateTimeWithInDateTimeRange( + _animationController + .forward() + .then((dynamic value) => _updateNextView()); + + /// updates the current view visible dates when the view swiped in + /// bottom to top direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // fling the view to bottom to top + else if (-dragEndDetails.velocity.pixelsPerSecond.dy > + widget.height) { + if (!DateTimeHelper.canMoveToNextView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, widget.calendar.minDate, widget.calendar.maxDate, - date, - timeInterval))) { - return isMinMaxDate; - } + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position in + the custom scroll view */ + }); + return; + } - final List blackoutDates = []; - if (_currentView.blackoutDates != null) { - blackoutDates.addAll(_currentView.blackoutDates!); - } - if (_previousView.blackoutDates != null) { - blackoutDates.addAll(_previousView.blackoutDates!); - } - if (_nextView.blackoutDates != null) { - blackoutDates.addAll(_nextView.blackoutDates!); - } + _tween.begin = _position; + _tween.end = -widget.height; - if (isMonthView && - CalendarViewHelper.isDateInDateCollection(blackoutDates, date)) { - return false; - } else if (!isMonthView) { - final List regions = []; - if (_currentView.regions != null) { - regions.addAll(_currentView.regions!); - } - if (_previousView.regions != null) { - regions.addAll(_previousView.regions!); - } - if (_nextView.regions != null) { - regions.addAll(_nextView.regions!); - } + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } - for (int i = 0; i < regions.length; i++) { - final CalendarTimeRegion region = regions[i]; - if (region.enablePointerInteraction || - (region.actualStartTime.isAfter(date) && - !CalendarViewHelper.isSameTimeSlot( - region.actualStartTime, date)) || - region.actualEndTime.isBefore(date) || - CalendarViewHelper.isSameTimeSlot(region.actualEndTime, date)) { - continue; - } + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updateNextView()); - /// Condition added ensure that the region is disabled only on the - /// specified resource slot, for other resources it must be enabled. - if (resourceIndex != -1 && - region.resourceIds != null && - region.resourceIds!.isNotEmpty && - !region.resourceIds! - .contains(widget.resourceCollection![resourceIndex].id)) { - continue; - } + /// updates the current view visible dates when fling the view in + /// bottom to top direction + _updateCurrentViewVisibleDates(isNextView: true); + } + // condition to check and update the top to bottom swiping + else if (_position >= widget.height / 2) { + _tween.begin = _position; + _tween.end = widget.height; - return false; - } - } + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } - return true; - } + _animationController + .forward() + .then((dynamic value) => _updatePreviousView()); - /// Method to handle the page up/down key for timeslot views in calendar. - KeyEventResult _updatePageUpAndDown(RawKeyEvent event, - _CalendarViewState currentViewState, bool isResourceEnabled) { - if (widget.controller.view != CalendarView.day && - widget.controller.view != CalendarView.week && - widget.controller.view != CalendarView.workWeek && - !isResourceEnabled) { - return KeyEventResult.ignored; - } + /// updates the current view visible dates when the view swiped in + /// top to bottom direction + _updateCurrentViewVisibleDates(); + } + // fling the view to top to bottom + else if (dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { + if (!DateTimeHelper.canMoveToPreviousView( + widget.view, + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.minDate, + widget.calendar.maxDate, + _currentViewVisibleDates, + widget.calendar.timeSlotViewSettings.nonWorkingDays)) { + _position = 0; + setState(() { + /* Completes the swiping and rearrange the children position in + the custom scroll view */ + }); + return; + } - final ScrollController scrollController = isResourceEnabled - ? widget.resourcePanelScrollController! - : currentViewState._scrollController!; - final TargetPlatform platform = Theme.of(context).platform; + _tween.begin = _position; + _tween.end = widget.height; - double difference = 0; - final double scrollViewHeight = scrollController.position.maxScrollExtent + - scrollController.position.viewportDimension; - double divideValue = 0.25; - if (scrollController.position.pixels > scrollViewHeight / 2) { - divideValue = 0.5; - } - if (event.logicalKey == LogicalKeyboardKey.pageUp || - (platform == TargetPlatform.windows && - event.logicalKey.keyId == 0x10700000021)) { - if (scrollController.position.pixels == 0) { - return KeyEventResult.ignored; - } - difference = scrollController.position.pixels * divideValue; - scrollController.jumpTo(difference); - return KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.pageDown || - (platform == TargetPlatform.windows && - event.logicalKey.keyId == 0x10700000022)) { - double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.controller.view!); - double allDayHeight = 0; + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } - if (widget.controller.view == CalendarView.day) { - allDayHeight = _kAllDayLayoutHeight; - viewHeaderHeight = 0; - } else { - allDayHeight = allDayHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : allDayHeight; - } + _animationController + .fling( + velocity: 5.0, animationBehavior: AnimationBehavior.normal) + .then((dynamic value) => _updatePreviousView()); - final double timeRulerSize = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, - widget.controller.view!); + /// updates the current view visible dates when fling the view in + /// top to bottom direction + _updateCurrentViewVisibleDates(); + } + // condition to check and revert the bottom to top swiping + else if (_position.abs() <= widget.height / 2) { + _tween.begin = _position; + _tween.end = 0.0; - final double viewPortHeight = isResourceEnabled - ? widget.height - viewHeaderHeight - timeRulerSize - : widget.height - allDayHeight - viewHeaderHeight; + // Resets the controller to forward it again, the animation will + // forward only from the dismissed state + if (_animationController.isCompleted || _position != _tween.end) { + _animationController.reset(); + } - final double viewPortEndPosition = - scrollController.position.pixels + viewPortHeight; - if (viewPortEndPosition == scrollViewHeight) { - return KeyEventResult.ignored; - } - difference = - (scrollViewHeight - scrollController.position.pixels) * divideValue; - difference += scrollController.position.pixels; - if (difference + viewPortHeight >= scrollViewHeight) { - difference = scrollViewHeight - viewPortHeight; + _animationController.forward(); + } + } + } + } + + void _clearSelection() { + widget.getCalendarState(_updateCalendarStateDetails); + for (int i = 0; i < _children.length; i++) { + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + _children[i].key! as GlobalKey<_CalendarViewState>; + if (viewKey.currentState!._selectionPainter!.selectedDate != + _updateCalendarStateDetails.selectedDate) { + viewKey.currentState!._selectionPainter!.selectedDate = + _updateCalendarStateDetails.selectedDate; + viewKey.currentState!._selectionNotifier.value = + !viewKey.currentState!._selectionNotifier.value; } - scrollController.jumpTo(difference); - return KeyEventResult.handled; } - - return KeyEventResult.ignored; } - /// Updates the appointment selection based on keyboard navigation in calendar - KeyEventResult _updateAppointmentSelection(RawKeyEvent event, - _CalendarViewState currentVisibleViewState, bool isResourceEnabled) { - if (widget.controller.view == CalendarView.schedule) { - return KeyEventResult.ignored; + /// Updates the all day panel of the view, when the all day panel expanded and + /// the view swiped to next or previous view with the expanded all day panel, + /// it will be collapsed. + void _updateAllDayPanel() { + GlobalKey<_CalendarViewState> viewKey; + if (_currentChildIndex == 0) { + viewKey = _previousViewKey; + } else if (_currentChildIndex == 1) { + viewKey = _currentViewKey; + } else { + viewKey = _nextViewKey; + } + if (viewKey.currentState!._expanderAnimationController?.status == + AnimationStatus.completed) { + viewKey.currentState!._expanderAnimationController?.reset(); } + viewKey.currentState!._isExpanded = false; + } - AppointmentView? selectedAppointment; - final AppointmentView? currentSelectedAppointment = - currentVisibleViewState._selectionPainter!.appointmentView; - final AppointmentView? currentAllDayAppointment = - currentVisibleViewState._allDaySelectionNotifier.value?.appointmentView; - bool isAllDay = currentAllDayAppointment != null; - final List appointmentCollection = currentVisibleViewState - ._appointmentLayout - .getAppointmentViewCollection(); - final List allDayAppointmentCollection = - _updateCalendarStateDetails.allDayAppointmentViewCollection; - final List tempAppColl = - isAllDay ? allDayAppointmentCollection : appointmentCollection; - if (event.isShiftPressed) { - if (event.logicalKey == LogicalKeyboardKey.tab) { - if (currentAllDayAppointment != null || - currentSelectedAppointment != null) { - int index = tempAppColl.indexOf(isAllDay - ? currentAllDayAppointment - : currentSelectedAppointment!); - index -= 1; - if (tempAppColl.length > index && !index.isNegative) { - selectedAppointment = tempAppColl[index].appointment != null - ? tempAppColl[index] - : null; + /// Method to clear the appointments in the previous/next view + void _updateAppointmentPainter() { + for (int i = 0; i < _children.length; i++) { + final _CalendarView view = _children[i]; + final GlobalKey<_CalendarViewState> viewKey = + // ignore: avoid_as + view.key! as GlobalKey<_CalendarViewState>; + if (widget.view == CalendarView.month && + widget.calendar.monthCellBuilder != null) { + if (view.visibleDates == _currentViewVisibleDates) { + widget.getCalendarState(_updateCalendarStateDetails); + if (!CalendarViewHelper.isCollectionEqual( + viewKey.currentState!._monthView.visibleAppointmentNotifier.value, + _updateCalendarStateDetails.visibleAppointments)) { + viewKey.currentState!._monthView.visibleAppointmentNotifier.value = + _updateCalendarStateDetails.visibleAppointments; + } + } else { + if (!CalendarViewHelper.isEmptyList(viewKey + .currentState!._monthView.visibleAppointmentNotifier.value)) { + viewKey.currentState!._monthView.visibleAppointmentNotifier.value = + null; } } - - if (currentSelectedAppointment != null && selectedAppointment == null) { - isAllDay = allDayAppointmentCollection.isNotEmpty; - selectedAppointment = isAllDay - ? allDayAppointmentCollection[ - allDayAppointmentCollection.length - 1] - : null; - } else if (currentSelectedAppointment == null && - currentAllDayAppointment == null && - selectedAppointment == null) { - selectedAppointment = - appointmentCollection[appointmentCollection.length - 1]; - } - - return _updateAppointmentSelectionOnView( - selectedAppointment, - currentVisibleViewState, - isAllDay, - isResourceEnabled, - !event.isShiftPressed); - } - } else if (event.logicalKey == LogicalKeyboardKey.tab) { - if (currentAllDayAppointment != null || - currentSelectedAppointment != null) { - int index = tempAppColl.indexOf( - isAllDay ? currentAllDayAppointment : currentSelectedAppointment!); - index += 1; - if (tempAppColl.length > index) { - selectedAppointment = tempAppColl[index].appointment != null - ? tempAppColl[index] - : null; + } else { + final AppointmentLayout appointmentLayout = + viewKey.currentState!._appointmentLayout; + if (view.visibleDates == _currentViewVisibleDates) { + widget.getCalendarState(_updateCalendarStateDetails); + if (!CalendarViewHelper.isCollectionEqual( + appointmentLayout.visibleAppointments.value, + _updateCalendarStateDetails.visibleAppointments)) { + appointmentLayout.visibleAppointments.value = + _updateCalendarStateDetails.visibleAppointments; + } + } else { + if (!CalendarViewHelper.isEmptyList( + appointmentLayout.visibleAppointments.value)) { + appointmentLayout.visibleAppointments.value = null; + } } } - - if (currentAllDayAppointment != null && selectedAppointment == null) { - isAllDay = false; - selectedAppointment = appointmentCollection[0]; - } else if (currentAllDayAppointment == null && - currentSelectedAppointment == null) { - isAllDay = allDayAppointmentCollection.isNotEmpty; - selectedAppointment = isAllDay - ? allDayAppointmentCollection[0] - : appointmentCollection[0]; - } - - return _updateAppointmentSelectionOnView( - selectedAppointment, - currentVisibleViewState, - isAllDay, - isResourceEnabled, - !event.isShiftPressed); } - - return KeyEventResult.ignored; } +} - /// Updates the selection for appointment view based on keyboard navigation - /// in Calendar. - KeyEventResult _updateAppointmentSelectionOnView( - AppointmentView? selectedAppointment, - _CalendarViewState currentVisibleViewState, - bool isAllDay, - bool isResourceEnabled, - bool isForward) { - final DateTime visibleStartDate = AppointmentHelper.convertToStartTime( - currentVisibleViewState.widget.visibleDates[0]); - final DateTime visibleEndDate = AppointmentHelper.convertToEndTime( - currentVisibleViewState.widget.visibleDates[ - currentVisibleViewState.widget.visibleDates.length - 1]); +@immutable +class _CalendarView extends StatefulWidget { + const _CalendarView( + this.calendar, + this.view, + this.visibleDates, + this.width, + this.height, + this.agendaSelectedDate, + this.locale, + this.calendarTheme, + this.regions, + this.blackoutDates, + this.focusNode, + this.removePicker, + this.allowViewNavigation, + this.controller, + this.resourcePanelScrollController, + this.resourceCollection, + this.textScaleFactor, + this.isMobilePlatform, + this.minDate, + this.maxDate, + this.localizations, + this.timelineMonthWeekNumberNotifier, + this.dragDetails, + this.updateCalendarState, + this.getCalendarState, + {Key? key}) + : super(key: key); - if (isAllDay && selectedAppointment != null) { - currentVisibleViewState._updateAllDaySelection(selectedAppointment, null); - currentVisibleViewState._selectionPainter!.appointmentView = null; - currentVisibleViewState._selectionPainter!.selectedDate = null; - currentVisibleViewState._selectionNotifier.value = - !currentVisibleViewState._selectionNotifier.value; - return KeyEventResult.handled; - } + final List visibleDates; + final List? regions; + final List? blackoutDates; + final SfCalendar calendar; + final CalendarView view; + final double width; + final SfCalendarThemeData calendarTheme; + final double height; + final String locale; + final ValueNotifier agendaSelectedDate, + timelineMonthWeekNumberNotifier; + final CalendarController controller; + final VoidCallback removePicker; + final UpdateCalendarState updateCalendarState; + final UpdateCalendarState getCalendarState; + final bool allowViewNavigation; + final FocusNode focusNode; + final ScrollController? resourcePanelScrollController; + final List? resourceCollection; + final double textScaleFactor; + final bool isMobilePlatform; + final DateTime minDate; + final DateTime maxDate; + final SfLocalizations localizations; + final ValueNotifier<_DragPaintDetails> dragDetails; - if (selectedAppointment != null && - AppointmentHelper.isAppointmentWithinVisibleDateRange( - selectedAppointment.appointment!, - visibleStartDate, - visibleEndDate)) { - currentVisibleViewState._allDaySelectionNotifier.value = null; - currentVisibleViewState._selectionPainter!.appointmentView = - selectedAppointment; - currentVisibleViewState._selectionPainter!.selectedDate = null; - currentVisibleViewState._selectionNotifier.value = - !currentVisibleViewState._selectionNotifier.value; + @override + _CalendarViewState createState() => _CalendarViewState(); +} - if (widget.controller.view != CalendarView.month) { - late double offset; - late double viewPortSize; - final double scrollViewHeight = currentVisibleViewState - ._scrollController!.position.maxScrollExtent + - currentVisibleViewState - ._scrollController!.position.viewportDimension; - final double resourceViewSize = - isResourceEnabled ? widget.calendar.resourceViewSettings.size : 0; - final bool isTimeline = - CalendarViewHelper.isTimelineView(widget.controller.view!); - double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.controller.view!); +class _CalendarViewState extends State<_CalendarView> + with TickerProviderStateMixin { + // line count is the total time slot lines to be drawn in the view + // line count per view is for time line view which contains the time slot + // count for per view + double? _horizontalLinesCount; - if (isTimeline) { - viewPortSize = widget.width - resourceViewSize; - offset = selectedAppointment.appointmentRect!.left; - } else { - double allDayHeight = 0; + // all day scroll controller is used to identify the scroll position for draw + // all day selection. + ScrollController? _scrollController; + ScrollController? _timelineViewHeaderScrollController, + _timelineViewVerticalScrollController, + _timelineRulerController; + + late AppointmentLayout _appointmentLayout; + AnimationController? _timelineViewAnimationController; + Animation? _timelineViewAnimation; + final Tween _timelineViewTween = Tween(begin: 0.0, end: 0.1); + + //// timeline header is used to implement the sticky view header in horizontal calendar view mode. + late TimelineViewHeaderView _timelineViewHeader; + _SelectionPainter? _selectionPainter; + double _allDayHeight = 0; + late double _timeIntervalHeight; + final UpdateCalendarStateDetails _updateCalendarStateDetails = + UpdateCalendarStateDetails(); + ValueNotifier _allDaySelectionNotifier = + ValueNotifier(null); + late ValueNotifier _viewHeaderNotifier; + final ValueNotifier _calendarCellNotifier = + ValueNotifier(null), + _allDayNotifier = ValueNotifier(null), + _appointmentHoverNotifier = ValueNotifier(null); + final ValueNotifier _selectionNotifier = ValueNotifier(false), + _timelineViewHeaderNotifier = ValueNotifier(false); + late bool _isRTL; - if (widget.controller.view == CalendarView.day) { - allDayHeight = _kAllDayLayoutHeight; - viewHeaderHeight = 0; - } else { - allDayHeight = allDayHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : allDayHeight; - } - viewPortSize = widget.height - allDayHeight - viewHeaderHeight; - offset = selectedAppointment.appointmentRect!.top; - } + bool _isExpanded = false; + DateTime? _hoveringDate; + SystemMouseCursor _mouseCursor = SystemMouseCursors.basic; + AppointmentView? _hoveringAppointmentView; - _updateScrollViewToAppointment( - offset, - currentVisibleViewState._scrollController!, - viewPortSize, - scrollViewHeight); + /// The property to hold the resource value associated with the selected + /// calendar cell. + int _selectedResourceIndex = -1; + AnimationController? _animationController; + Animation? _heightAnimation; + Animation? _allDayExpanderAnimation; + AnimationController? _expanderAnimationController; - if (isResourceEnabled) { - final double resourcePanelHeight = widget - .resourcePanelScrollController!.position.viewportDimension + - widget.resourcePanelScrollController!.position.maxScrollExtent; - final double timeRulerSize = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, - widget.controller.view!), - viewPortSize = widget.height - viewHeaderHeight - timeRulerSize; - _updateScrollViewToAppointment( - selectedAppointment.appointmentRect!.top, - widget.resourcePanelScrollController!, - viewPortSize, - resourcePanelHeight); - } - } else if (widget.controller.view == CalendarView.month) { - widget.agendaSelectedDate.value = null; - } + /// Store the month widget instance used to update the month view + /// when the visible appointment updated. + late MonthViewWidget _monthView; - _updateCalendarStateDetails.selectedDate = null; - widget.updateCalendarState(_updateCalendarStateDetails); - return KeyEventResult.handled; - } else { - currentVisibleViewState._allDaySelectionNotifier.value = null; - currentVisibleViewState._selectionPainter!.appointmentView = null; - currentVisibleViewState._selectionPainter!.selectedDate = null; - currentVisibleViewState._selectionNotifier.value = - !currentVisibleViewState._selectionNotifier.value; - _updateCalendarStateDetails.selectedDate = null; - widget.updateCalendarState(_updateCalendarStateDetails); - isForward - ? FocusScope.of(context).nextFocus() - : FocusScope.of(context).previousFocus(); - return KeyEventResult.handled; - } - } + /// Used to hold the global key for restrict the new appointment layout + /// creation. + /// if set the appointment layout key property as new Global key when create + /// the appointment layout then each of the time it creates new appointment + /// layout rather than update the existing appointment layout. + final GlobalKey _appointmentLayoutKey = GlobalKey(); - /// Moves the scroll panel to the selected appointments position, if the - /// selected appointment doesn't falls on the view port. - void _updateScrollViewToAppointment( - double offset, - ScrollController scrollController, - double viewPortSize, - double panelHeight) { - if (offset < scrollController.position.pixels || - offset > (scrollController.position.pixels + viewPortSize)) { - if (offset + viewPortSize > panelHeight) { - offset = panelHeight - viewPortSize; - } - scrollController.jumpTo(offset); - } - } + Timer? _timer, _autoScrollTimer; + late ValueNotifier _currentTimeNotifier; - KeyEventResult _onKeyDown(FocusNode node, RawKeyEvent event) { - KeyEventResult result = KeyEventResult.ignored; - if (event.runtimeType != RawKeyDownEvent) { - return result; - } + late ValueNotifier<_ResizingPaintDetails> _resizingDetails; + double? _maximumResizingPosition; - widget.removePicker(); + @override + void initState() { + _resizingDetails = ValueNotifier<_ResizingPaintDetails>( + _ResizingPaintDetails(position: ValueNotifier(null))); + _viewHeaderNotifier = ValueNotifier(null) + ..addListener(_timelineViewHoveringUpdate); + if (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _animationController = AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); + _heightAnimation = + CurveTween(curve: Curves.easeIn).animate(_animationController!) + ..addListener(() { + setState(() { + /* Animates the all day panel height when + expanding or collapsing */ + }); + }); - if (event.isControlPressed && widget.view != CalendarView.schedule) { - final bool canMoveToNextView = DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL); - final bool canMoveToPreviousView = DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL); - if (event.logicalKey == LogicalKeyboardKey.arrowRight && - canMoveToNextView) { - widget.isRTL - ? _moveToPreviousViewWithAnimation() - : _moveToNextViewWithAnimation(); - result = KeyEventResult.handled; - } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft && - canMoveToPreviousView) { - widget.isRTL - ? _moveToNextViewWithAnimation() - : _moveToPreviousViewWithAnimation(); - result = KeyEventResult.handled; - } - result = KeyEventResult.ignored; + _expanderAnimationController = AnimationController( + duration: const Duration(milliseconds: 100), vsync: this); + _allDayExpanderAnimation = CurveTween(curve: Curves.easeIn) + .animate(_expanderAnimationController!) + ..addListener(() { + setState(() { + /* Animates the all day panel height when + expanding or collapsing */ + }); + }); } - CalendarViewHelper.handleViewSwitchKeyBoardEvent( - event, widget.controller, widget.calendar.allowedViews); + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + if (widget.view != CalendarView.month) { + _horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view); + _scrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_scrollListener); + if (CalendarViewHelper.isTimelineView(widget.view)) { + _timelineRulerController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_timeRulerListener); + _timelineViewHeaderScrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _timelineViewAnimationController = AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + animationBehavior: AnimationBehavior.normal); + _timelineViewAnimation = _timelineViewTween + .animate(_timelineViewAnimationController!) + ..addListener(_scrollAnimationListener); + _timelineViewVerticalScrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_updateResourceScroll); + widget.resourcePanelScrollController + ?.addListener(_updateResourcePanelScroll); + } - _CalendarViewState currentVisibleViewState; - _CalendarView currentVisibleView; - final bool isResourcesEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); - if (_currentChildIndex == 0) { - currentVisibleViewState = _previousViewKey.currentState!; - currentVisibleView = _previousView; - } else if (_currentChildIndex == 1) { - currentVisibleViewState = _currentViewKey.currentState!; - currentVisibleView = _currentView; - } else { - currentVisibleViewState = _nextViewKey.currentState!; - currentVisibleView = _nextView; + _scrollToPosition(); } - result = _updatePageUpAndDown( - event, currentVisibleViewState, isResourcesEnabled); + final DateTime today = DateTime.now(); + _currentTimeNotifier = ValueNotifier( + (today.day * 24 * 60) + (today.hour * 60) + today.minute); + _timer = _createTimer(); + super.initState(); + } - result = _updateAppointmentSelection( - event, currentVisibleViewState, isResourcesEnabled); + @override + void didUpdateWidget(_CalendarView oldWidget) { + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + if (widget.view != CalendarView.month) { + if (!isTimelineView) { + _updateTimeSlotView(oldWidget); + } - if (event.logicalKey == LogicalKeyboardKey.enter && - CalendarViewHelper.shouldRaiseCalendarTapCallback( - widget.calendar.onTap)) { - final AppointmentView? selectedAppointment = currentVisibleViewState - ._allDaySelectionNotifier.value?.appointmentView ?? - currentVisibleViewState._selectionPainter!.appointmentView; - final List? selectedAppointments = - widget.controller.view == CalendarView.month && - selectedAppointment == null - ? AppointmentHelper.getSelectedDateAppointments( - _updateCalendarStateDetails.appointments, - widget.calendar.timeZone, - _updateCalendarStateDetails.selectedDate) - : selectedAppointment != null - ? [selectedAppointment.appointment!] - : null; - final CalendarElement tappedElement = - _updateCalendarStateDetails.selectedDate != null - ? CalendarElement.calendarCell - : CalendarElement.appointment; + _updateHorizontalLineCount(oldWidget); - CalendarViewHelper.raiseCalendarTapCallback( - widget.calendar, - tappedElement == CalendarElement.appointment - ? selectedAppointments![0].startTime - : _updateCalendarStateDetails.selectedDate, - CalendarViewHelper.getCustomAppointments(selectedAppointments), - tappedElement, - isResourcesEnabled - ? widget.calendar.dataSource! - .resources![currentVisibleViewState._selectedResourceIndex] - : null); + _scrollController ??= + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_scrollListener); + + if (isTimelineView) { + _updateTimelineViews(oldWidget); + } } - final int previousResourceIndex = isResourcesEnabled - ? currentVisibleViewState._selectedResourceIndex - : -1; - if (currentVisibleViewState._selectionPainter!.selectedDate != null && - isDateWithInDateRange( - currentVisibleViewState.widget.visibleDates[0], - currentVisibleViewState.widget.visibleDates[ - currentVisibleViewState.widget.visibleDates.length - 1], - currentVisibleViewState._selectionPainter!.selectedDate)) { - final int resourceIndex = isResourcesEnabled - ? currentVisibleViewState._selectedResourceIndex - : -1; + /// Update the scroll position with following scenarios + /// 1. View changed from month or schedule view. + /// 2. View changed from timeline view(timeline day, timeline week, + /// timeline work week) to timeslot view(day, week, work week). + /// 3. View changed from timeslot view(day, week, work week) to + /// timeline view(timeline day, timeline week, timeline work week). + /// + /// This condition used to restrict the following scenarios + /// 1. View changed to month view. + /// 2. View changed with in the day, week, work week + /// (eg., view changed to week from day). + /// 3. View changed with in the timeline day, timeline week, timeline + /// work week(eg., view changed to timeline week from timeline day). + if ((oldWidget.view == CalendarView.month || + oldWidget.view == CalendarView.schedule || + (oldWidget.view != widget.view && isTimelineView) || + (CalendarViewHelper.isTimelineView(oldWidget.view) && + !isTimelineView)) && + widget.view != CalendarView.month) { + _scrollToPosition(); + } - final DateTime? selectedDate = _updateSelectedDate( - event, currentVisibleViewState, currentVisibleView, resourceIndex); + /// Method called to update all day height, when the view changed from + /// day to week views to avoid the blank space at the bottom of the view. + final bool isCurrentView = + _updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates; + _updateAllDayHeight(isCurrentView); - if (selectedDate == null) { - result = KeyEventResult.ignored; - return result; - } + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); - if (!_isSelectedDateEnabled(selectedDate, resourceIndex)) { - currentVisibleViewState._selectedResourceIndex = previousResourceIndex; - return KeyEventResult.ignored; - } + /// Clear the all day panel selection when the calendar view changed + /// Eg., if select the all day panel and switch to month view and again + /// select the same month cell and move to day view then the view show + /// calendar cell selection and all day panel selection. + if (oldWidget.view != widget.view) { + _allDaySelectionNotifier = ValueNotifier(null); + final DateTime today = DateTime.now(); + _currentTimeNotifier = ValueNotifier( + (today.day * 24 * 60) + (today.hour * 60) + today.minute); + _timer?.cancel(); + _timer = null; + } - if (widget.view == CalendarView.month) { - widget.agendaSelectedDate.value = selectedDate; - } + if (oldWidget.calendar.showCurrentTimeIndicator != + widget.calendar.showCurrentTimeIndicator) { + _timer?.cancel(); + _timer = _createTimer(); + } - _updateCalendarStateDetails.selectedDate = selectedDate; - if (widget.calendar.onSelectionChanged != null && - (!CalendarViewHelper.isSameTimeSlot( - currentVisibleViewState._selectionPainter!.selectedDate, - selectedDate) || - (isResourcesEnabled && - currentVisibleViewState - ._selectionPainter!.selectedResourceIndex != - currentVisibleViewState._selectedResourceIndex))) { - CalendarViewHelper.raiseCalendarSelectionChangedCallback( - widget.calendar, - selectedDate, - isResourcesEnabled - ? widget.resourceCollection![ - currentVisibleViewState._selectedResourceIndex] - : null); - } - currentVisibleViewState._selectionPainter!.selectedDate = selectedDate; - currentVisibleViewState._selectionPainter!.appointmentView = null; - currentVisibleViewState._selectionPainter!.selectedResourceIndex = - currentVisibleViewState._selectedResourceIndex; - currentVisibleViewState._selectionNotifier.value = - !currentVisibleViewState._selectionNotifier.value; + if ((oldWidget.view != widget.view || + oldWidget.width != widget.width || + oldWidget.height != widget.height) && + _selectionPainter!.appointmentView != null) { + _selectionPainter!.appointmentView = null; + } - widget.updateCalendarState(_updateCalendarStateDetails); - result = KeyEventResult.handled; + /// When view switched from any other view to timeline view, and resource + /// enabled the selection must render the first resource view. + widget.getCalendarState(_updateCalendarStateDetails); + if (!CalendarViewHelper.isTimelineView(oldWidget.view) && + _updateCalendarStateDetails.selectedDate != null && + CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view) && + _selectedResourceIndex == -1) { + _selectedResourceIndex = 0; } - return result; + if (!CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + _selectedResourceIndex = -1; + } + + _timer ??= _createTimer(); + super.didUpdateWidget(oldWidget); } - void _positionTimelineView({bool isScrolledToEnd = true}) { - final _CalendarViewState previousViewState = _previousViewKey.currentState!; - final _CalendarViewState currentViewState = _currentViewKey.currentState!; - final _CalendarViewState nextViewState = _nextViewKey.currentState!; - if (widget.isRTL) { - if (_currentChildIndex == 0) { - currentViewState._scrollController!.jumpTo(isScrolledToEnd - ? currentViewState._scrollController!.position.maxScrollExtent - : 0); - nextViewState._scrollController!.jumpTo(0); - } else if (_currentChildIndex == 1) { - nextViewState._scrollController!.jumpTo(isScrolledToEnd - ? nextViewState._scrollController!.position.maxScrollExtent - : 0); - previousViewState._scrollController!.jumpTo(0); - } else if (_currentChildIndex == 2) { - previousViewState._scrollController!.jumpTo(isScrolledToEnd - ? previousViewState._scrollController!.position.maxScrollExtent - : 0); - currentViewState._scrollController!.jumpTo(0); - } - } else { - if (_currentChildIndex == 0) { - nextViewState._scrollController!.jumpTo(isScrolledToEnd - ? nextViewState._scrollController!.position.maxScrollExtent - : 0); - currentViewState._scrollController!.jumpTo(0); - } else if (_currentChildIndex == 1) { - previousViewState._scrollController!.jumpTo(isScrolledToEnd - ? previousViewState._scrollController!.position.maxScrollExtent - : 0); - nextViewState._scrollController!.jumpTo(0); - } else if (_currentChildIndex == 2) { - currentViewState._scrollController!.jumpTo(isScrolledToEnd - ? currentViewState._scrollController!.position.maxScrollExtent - : 0); - previousViewState._scrollController!.jumpTo(0); - } + @override + Widget build(BuildContext context) { + _isRTL = CalendarViewHelper.isRTLLayout(context); + widget.getCalendarState(_updateCalendarStateDetails); + switch (widget.view) { + case CalendarView.schedule: + return Container(); + case CalendarView.month: + return _getMonthView(); + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + return _getDayView(); + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return _getTimelineView(); } } - void _onHorizontalStart(DragStartDetails dragStartDetails) { - switch (widget.calendar.viewNavigationMode) { - case ViewNavigationMode.none: - return; - case ViewNavigationMode.snap: - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month) { - _scrollStartPosition = dragStartDetails.globalPosition.dx; - } + @override + void dispose() { + _viewHeaderNotifier.removeListener(_timelineViewHoveringUpdate); - // Handled for time line view, to move the previous and - // next view to it's start and end position accordingly - if (CalendarViewHelper.isTimelineView(widget.view)) { - _positionTimelineView(); - } + _calendarCellNotifier.removeListener(_timelineViewHoveringUpdate); + + if (_timelineViewAnimation != null) { + _timelineViewAnimation!.removeListener(_scrollAnimationListener); } - } - void _onHorizontalUpdate(DragUpdateDetails dragUpdateDetails) { - switch (widget.calendar.viewNavigationMode) { - case ViewNavigationMode.none: - return; - case ViewNavigationMode.snap: - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month) { - final double difference = - dragUpdateDetails.globalPosition.dx - _scrollStartPosition; - if (difference < 0 && - !DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - return; - } else if (difference > 0 && - !DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - return; - } - _position = difference; - _clearSelection(); - setState(() { - /* Updates the widget navigated distance and moves the widget - in the custom scroll view */ - }); - } + if (widget.resourcePanelScrollController != null) { + widget.resourcePanelScrollController! + .removeListener(_updateResourcePanelScroll); + } + + if (CalendarViewHelper.isTimelineView(widget.view) && + _timelineViewAnimationController != null) { + _timelineViewAnimationController!.dispose(); + _timelineViewAnimationController = null; + } + if (_scrollController != null) { + _scrollController!.removeListener(_scrollListener); + _scrollController!.dispose(); + _scrollController = null; + } + if (_timelineViewHeaderScrollController != null) { + _timelineViewHeaderScrollController!.dispose(); + _timelineViewHeaderScrollController = null; + } + if (_animationController != null) { + _animationController!.dispose(); + _animationController = null; + } + if (_timelineRulerController != null) { + _timelineRulerController!.dispose(); + _timelineRulerController = null; } - } - void _onHorizontalEnd(DragEndDetails dragEndDetails) { - switch (widget.calendar.viewNavigationMode) { - case ViewNavigationMode.none: - return; - case ViewNavigationMode.snap: - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.horizontal || - widget.view != CalendarView.month) { - // condition to check and update the right to left swiping - if (-_position >= widget.width / 2) { - _tween.begin = _position; - _tween.end = -widget.width; + if (_expanderAnimationController != null) { + _expanderAnimationController!.dispose(); + _expanderAnimationController = null; + } - // Resets the controller to forward it again, - // the animation will forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } + if (_timer != null) { + _timer!.cancel(); + _timer = null; + } - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + super.dispose(); + } - /// updates the current view visible dates when the view swiped in - /// right to left direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // fling the view from right to left - else if (-dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { - if (!DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position - in the custom scroll view */ - }); + Timer? _createTimer() { + return widget.calendar.showCurrentTimeIndicator && + widget.view != CalendarView.month && + widget.view != CalendarView.timelineMonth + ? Timer.periodic(const Duration(seconds: 1), (Timer t) { + final DateTime today = DateTime.now(); + final DateTime viewEndDate = + widget.visibleDates[widget.visibleDates.length - 1]; + + /// Check the today date is in between visible date range and + /// today date hour and minute is 0(12 AM) because in day view + /// current time as Feb 16, 23.59 and changed to Feb 17 then view + /// will update both Feb 16 and 17 views. + if (!isDateWithInDateRange( + widget.visibleDates[0], viewEndDate, today) && + !(today.hour == 0 && + today.minute == 0 && + isSameDate(addDays(today, -1), viewEndDate))) { return; } - _tween.begin = _position; - _tween.end = -widget.width; - - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } + _currentTimeNotifier.value = + (today.day * 24 * 60) + (today.hour * 60) + today.minute; + }) + : null; + } - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updateNextView()); + /// Updates the resource panel scroll based on timeline scroll in vertical + /// direction. + void _updateResourcePanelScroll() { + if (_updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates) { + widget.removePicker(); + } - /// updates the current view visible dates when fling the view in - /// right to left direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // condition to check and update the left to right swiping - else if (_position >= widget.width / 2) { - _tween.begin = _position; - _tween.end = widget.width; + if (widget.resourcePanelScrollController == null || + !CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + return; + } - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } + if (widget.resourcePanelScrollController!.offset != + _timelineViewVerticalScrollController!.offset) { + _timelineViewVerticalScrollController! + .jumpTo(widget.resourcePanelScrollController!.offset); + } + } - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + /// Updates the timeline view scroll in vertical direction based on resource + /// panel scroll. + void _updateResourceScroll() { + if (_updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates) { + widget.removePicker(); + } - /// updates the current view visible dates when the view swiped in - /// left to right direction - _updateCurrentViewVisibleDates(); - } - // fling the view from left to right - else if (dragEndDetails.velocity.pixelsPerSecond.dx > widget.width) { - if (!DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays, - widget.isRTL)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position - in the custom scroll view */ - }); - return; - } + if (widget.resourcePanelScrollController == null || + !CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + return; + } - _tween.begin = _position; - _tween.end = widget.width; + if (widget.resourcePanelScrollController!.offset != + _timelineViewVerticalScrollController!.offset) { + widget.resourcePanelScrollController! + .jumpTo(_timelineViewVerticalScrollController!.offset); + } + } - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } + Widget _getMonthView() { + return MouseRegion( + cursor: _mouseCursor, + onEnter: _pointerEnterEvent, + onExit: _pointerExitEvent, + onHover: _pointerHoverEvent, + child: Stack(children: [ + GestureDetector( + child: Container( + width: widget.width, + height: widget.height, + child: _addMonthView(_isRTL, widget.locale)), + onTapUp: _handleOnTapForMonth, + onLongPressStart: null, + ), + _getResizeShadowView() + ]), + ); + } - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updatePreviousView()); + Widget _getDayView() { + final bool isCurrentView = + _updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates; + _updateAllDayHeight(isCurrentView); - /// updates the current view visible dates when fling the view in - /// left to right direction - _updateCurrentViewVisibleDates(); - } - // condition to check and revert the right to left swiping - else if (_position.abs() <= widget.width / 2) { - _tween.begin = _position; - _tween.end = 0.0; + return MouseRegion( + cursor: _mouseCursor, + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: Stack( + children: [ + GestureDetector( + child: Container( + height: widget.height, + width: widget.width, + child: _addDayView( + widget.width, + _timeIntervalHeight * _horizontalLinesCount!, + _isRTL, + widget.locale, + isCurrentView)), + onTapUp: _handleOnTapForDay, + onLongPressStart: null, + ), + _getResizeShadowView() + ], + ), + ); + } - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted && _position != _tween.end) { - _animationController.reset(); - } + /// Method to update alldayHeight calculation for day, week and work week + /// view, based on the view also based on the timeintervalheight. + void _updateAllDayHeight(bool isCurrentView) { + if (widget.view != CalendarView.day && + widget.view != CalendarView.week && + widget.view != CalendarView.workWeek) { + return; + } - _animationController.forward(); - } + _allDayHeight = 0; + if (widget.view == CalendarView.day) { + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + if (isCurrentView) { + _allDayHeight = _kAllDayLayoutHeight > viewHeaderHeight && + _updateCalendarStateDetails.allDayPanelHeight > viewHeaderHeight + ? _updateCalendarStateDetails.allDayPanelHeight > + _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : _updateCalendarStateDetails.allDayPanelHeight + : viewHeaderHeight; + if (_allDayHeight < _updateCalendarStateDetails.allDayPanelHeight) { + _allDayHeight += kAllDayAppointmentHeight; } + } else { + _allDayHeight = viewHeaderHeight; + } + } else if (isCurrentView) { + _allDayHeight = + _updateCalendarStateDetails.allDayPanelHeight > _kAllDayLayoutHeight + ? _kAllDayLayoutHeight + : _updateCalendarStateDetails.allDayPanelHeight; + _allDayHeight = _allDayHeight * _heightAnimation!.value; } } - void _onVerticalStart(DragStartDetails dragStartDetails) { - switch (widget.calendar.viewNavigationMode) { - case ViewNavigationMode.none: - return; - case ViewNavigationMode.snap: - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - !CalendarViewHelper.isTimelineView(widget.view)) { - _scrollStartPosition = dragStartDetails.globalPosition.dy; - } + Widget _getTimelineView() { + return MouseRegion( + cursor: _mouseCursor, + onEnter: _pointerEnterEvent, + onHover: _pointerHoverEvent, + onExit: _pointerExitEvent, + child: Stack(children: [ + GestureDetector( + child: Container( + width: widget.width, + height: widget.height, + child: _addTimelineView( + _timeIntervalHeight * + (_horizontalLinesCount! * widget.visibleDates.length), + widget.height, + widget.locale), + ), + onTapUp: _handleOnTapForTimeline, + onLongPressStart: null, + ), + _getResizeShadowView() + ])); + } + + void _timelineViewHoveringUpdate() { + if (!CalendarViewHelper.isTimelineView(widget.view) && mounted) { + return; } + + // Updates the timeline views based on mouse hovering position. + _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; } - void _onVerticalUpdate(DragUpdateDetails dragUpdateDetails) { - switch (widget.calendar.viewNavigationMode) { - case ViewNavigationMode.none: - return; - case ViewNavigationMode.snap: - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - !CalendarViewHelper.isTimelineView(widget.view)) { - final double difference = - dragUpdateDetails.globalPosition.dy - _scrollStartPosition; - if (difference < 0 && - !DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - return; - } else if (difference > 0 && - !DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - return; - } - _position = difference; - setState(() { - /* Updates the widget navigated distance and moves the widget - in the custom scroll view */ - }); - } - } + void _scrollAnimationListener() { + _scrollController!.jumpTo(_timelineViewAnimation!.value); } - void _onVerticalEnd(DragEndDetails dragEndDetails) { - switch (widget.calendar.viewNavigationMode) { - case ViewNavigationMode.none: + void _scrollToPosition() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + if (widget.view == CalendarView.month) { return; - case ViewNavigationMode.snap: - widget.removePicker(); - if (widget.calendar.monthViewSettings.navigationDirection == - MonthNavigationDirection.vertical && - !CalendarViewHelper.isTimelineView(widget.view)) { - // condition to check and update the bottom to top swiping - if (-_position >= widget.height / 2) { - _tween.begin = _position; - _tween.end = -widget.height; + } - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } + widget.getCalendarState(_updateCalendarStateDetails); + final double scrollPosition = _getScrollPositionForCurrentDate( + _updateCalendarStateDetails.currentDate!); + if (scrollPosition == -1 || + _scrollController!.position.pixels == scrollPosition) { + return; + } - _animationController - .forward() - .then((dynamic value) => _updateNextView()); + _scrollController!.jumpTo( + _scrollController!.position.maxScrollExtent > scrollPosition + ? scrollPosition + : _scrollController!.position.maxScrollExtent); + }); + } - /// updates the current view visible dates when the view swiped in - /// bottom to top direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // fling the view to bottom to top - else if (-dragEndDetails.velocity.pixelsPerSecond.dy > - widget.height) { - if (!DateTimeHelper.canMoveToNextView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in - the custom scroll view */ - }); - return; - } + double _getScrollPositionForCurrentDate(DateTime date) { + final int visibleDatesCount = widget.visibleDates.length; + if (!isDateWithInDateRange(widget.visibleDates[0], + widget.visibleDates[visibleDatesCount - 1], date)) { + return -1; + } - _tween.begin = _position; - _tween.end = -widget.height; + double timeToPosition = 0; + if (!CalendarViewHelper.isTimelineView(widget.view)) { + timeToPosition = AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } else { + for (int i = 0; i < visibleDatesCount; i++) { + if (!isSameDate(date, widget.visibleDates[i])) { + continue; + } - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } + if (widget.view == CalendarView.timelineMonth) { + timeToPosition = _timeIntervalHeight * i; + } else { + timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + + AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updateNextView()); + break; + } + } - /// updates the current view visible dates when fling the view in - /// bottom to top direction - _updateCurrentViewVisibleDates(isNextView: true); - } - // condition to check and update the top to bottom swiping - else if (_position >= widget.height / 2) { - _tween.begin = _position; - _tween.end = widget.height; + if (_scrollController!.hasClients) { + if (timeToPosition > _scrollController!.position.maxScrollExtent) { + timeToPosition = _scrollController!.position.maxScrollExtent; + } else if (timeToPosition < _scrollController!.position.minScrollExtent) { + timeToPosition = _scrollController!.position.minScrollExtent; + } + } - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } + return timeToPosition; + } - _animationController - .forward() - .then((dynamic value) => _updatePreviousView()); + /// Used to retain the scrolled date time. + void _retainScrolledDateTime() { + if (widget.view == CalendarView.month) { + return; + } - /// updates the current view visible dates when the view swiped in - /// top to bottom direction - _updateCurrentViewVisibleDates(); - } - // fling the view to top to bottom - else if (dragEndDetails.velocity.pixelsPerSecond.dy > widget.height) { - if (!DateTimeHelper.canMoveToPreviousView( - widget.view, - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.minDate, - widget.calendar.maxDate, - _currentViewVisibleDates, - widget.calendar.timeSlotViewSettings.nonWorkingDays)) { - _position = 0; - setState(() { - /* Completes the swiping and rearrange the children position in - the custom scroll view */ - }); - return; - } + DateTime scrolledDate = widget.visibleDates[0]; + double scrolledPosition = 0; + if (CalendarViewHelper.isTimelineView(widget.view)) { + final double singleViewWidth = _getSingleViewWidthForTimeLineView(this); - _tween.begin = _position; - _tween.end = widget.height; + /// Calculate the scrolled position date. + scrolledDate = widget + .visibleDates[_scrollController!.position.pixels ~/ singleViewWidth]; - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } + /// Calculate the scrolled hour position without visible date position. + scrolledPosition = _scrollController!.position.pixels % singleViewWidth; + } else { + /// Calculate the scrolled hour position. + scrolledPosition = _scrollController!.position.pixels; + } - _animationController - .fling( - velocity: 5.0, animationBehavior: AnimationBehavior.normal) - .then((dynamic value) => _updatePreviousView()); + /// Calculate the current horizontal line based on time interval height. + final double columnIndex = scrolledPosition / _timeIntervalHeight; + + /// Calculate the time based on calculated horizontal position. + final double time = ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + columnIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + scrolledDate = DateTime( + scrolledDate.year, scrolledDate.month, scrolledDate.day, hour, minute); + + /// Update the scrolled position after the widget generated. + SchedulerBinding.instance!.addPostFrameCallback((_) { + _scrollController!.jumpTo(_getPositionFromDate(scrolledDate)); + }); + } + + /// Calculate the position from date. + double _getPositionFromDate(DateTime date) { + final int visibleDatesCount = widget.visibleDates.length; + _timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + visibleDatesCount, + _allDayHeight, + widget.isMobilePlatform); + double timeToPosition = 0; + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + if (!isTimelineView) { + timeToPosition = AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } else { + for (int i = 0; i < visibleDatesCount; i++) { + if (!isSameDate(date, widget.visibleDates[i])) { + continue; + } - /// updates the current view visible dates when fling the view in - /// top to bottom direction - _updateCurrentViewVisibleDates(); - } - // condition to check and revert the bottom to top swiping - else if (_position.abs() <= widget.height / 2) { - _tween.begin = _position; - _tween.end = 0.0; + if (widget.view == CalendarView.timelineMonth) { + timeToPosition = _timeIntervalHeight * i; + } else { + timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + + AppointmentHelper.timeToPosition( + widget.calendar, date, _timeIntervalHeight); + } - // Resets the controller to forward it again, the animation will - // forward only from the dismissed state - if (_animationController.isCompleted || _position != _tween.end) { - _animationController.reset(); - } + break; + } + } - _animationController.forward(); - } - } + double maxScrollPosition = 0; + if (!isTimelineView) { + final double scrollViewHeight = widget.height - + _allDayHeight - + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double scrollViewContentHeight = + CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view) * + _timeIntervalHeight; + maxScrollPosition = scrollViewContentHeight - scrollViewHeight; + } else { + final double scrollViewContentWidth = + CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view) * + _timeIntervalHeight * + visibleDatesCount; + maxScrollPosition = scrollViewContentWidth - widget.width; } + + return maxScrollPosition > timeToPosition + ? timeToPosition + : maxScrollPosition; } - void _clearSelection() { - widget.getCalendarState(_updateCalendarStateDetails); - for (int i = 0; i < _children.length; i++) { - final GlobalKey<_CalendarViewState> viewKey = - // ignore: avoid_as - _children[i].key! as GlobalKey<_CalendarViewState>; - if (viewKey.currentState!._selectionPainter!.selectedDate != - _updateCalendarStateDetails.selectedDate) { - viewKey.currentState!._selectionPainter!.selectedDate = - _updateCalendarStateDetails.selectedDate; - viewKey.currentState!._selectionNotifier.value = - !viewKey.currentState!._selectionNotifier.value; - } + void _expandOrCollapseAllDay() { + _isExpanded = !_isExpanded; + if (_isExpanded) { + _expanderAnimationController!.forward(); + } else { + _expanderAnimationController!.reverse(); } } - /// Updates the all day panel of the view, when the all day panel expanded and - /// the view swiped to next or previous view with the expanded all day panel, - /// it will be collapsed. - void _updateAllDayPanel() { - GlobalKey<_CalendarViewState> viewKey; - if (_currentChildIndex == 0) { - viewKey = _previousViewKey; - } else if (_currentChildIndex == 1) { - viewKey = _currentViewKey; - } else { - viewKey = _nextViewKey; + /// Update the time slot view scroll based on time ruler view scroll in + /// timeslot views. + void _timeRulerListener() { + if (!CalendarViewHelper.isTimelineView(widget.view)) { + return; } - if (viewKey.currentState!._expanderAnimationController?.status == - AnimationStatus.completed) { - viewKey.currentState!._expanderAnimationController?.reset(); + + if (_timelineRulerController!.offset != _scrollController!.offset) { + _scrollController!.jumpTo(_timelineRulerController!.offset); } - viewKey.currentState!._isExpanded = false; } - /// Method to clear the appointments in the previous/next view - void _updateAppointmentPainter() { - for (int i = 0; i < _children.length; i++) { - final _CalendarView view = _children[i]; - final GlobalKey<_CalendarViewState> viewKey = - // ignore: avoid_as - view.key! as GlobalKey<_CalendarViewState>; - if (widget.view == CalendarView.month && - widget.calendar.monthCellBuilder != null) { - if (view.visibleDates == _currentViewVisibleDates) { - widget.getCalendarState(_updateCalendarStateDetails); - if (!CalendarViewHelper.isCollectionEqual( - viewKey.currentState!._monthView.visibleAppointmentNotifier.value, - _updateCalendarStateDetails.visibleAppointments)) { - viewKey.currentState!._monthView.visibleAppointmentNotifier.value = - _updateCalendarStateDetails.visibleAppointments; - } - } else { - if (!CalendarViewHelper.isEmptyList(viewKey - .currentState!._monthView.visibleAppointmentNotifier.value)) { - viewKey.currentState!._monthView.visibleAppointmentNotifier.value = - null; - } - } - } else { - final AppointmentLayout appointmentLayout = - viewKey.currentState!._appointmentLayout; - if (view.visibleDates == _currentViewVisibleDates) { - widget.getCalendarState(_updateCalendarStateDetails); - if (!CalendarViewHelper.isCollectionEqual( - appointmentLayout.visibleAppointments.value, - _updateCalendarStateDetails.visibleAppointments)) { - appointmentLayout.visibleAppointments.value = - _updateCalendarStateDetails.visibleAppointments; - } - } else { - if (!CalendarViewHelper.isEmptyList( - appointmentLayout.visibleAppointments.value)) { - appointmentLayout.visibleAppointments.value = null; - } + void _scrollListener() { + if (_updateCalendarStateDetails.currentViewVisibleDates == + widget.visibleDates) { + widget.removePicker(); + } + + if (CalendarViewHelper.isTimelineView(widget.view)) { + widget.getCalendarState(_updateCalendarStateDetails); + if (widget.view != CalendarView.timelineMonth) { + _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; + } + + if (_timelineRulerController!.offset != _scrollController!.offset) { + _timelineRulerController!.jumpTo(_scrollController!.offset); + } + + if (widget.view == CalendarView.timelineMonth && + widget.calendar.showWeekNumber) { + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + final DateTime? date = + _getDateFromPosition(_scrollController!.offset, 0, timeLabelWidth); + if (date != null) { + widget.timelineMonthWeekNumberNotifier.value = date; } } + + _timelineViewHeaderScrollController!.jumpTo(_scrollController!.offset); } } -} -@immutable -class _CalendarView extends StatefulWidget { - const _CalendarView( - this.calendar, - this.view, - this.visibleDates, - this.width, - this.height, - this.agendaSelectedDate, - this.locale, - this.calendarTheme, - this.regions, - this.blackoutDates, - this.focusNode, - this.removePicker, - this.allowViewNavigation, - this.controller, - this.resourcePanelScrollController, - this.resourceCollection, - this.textScaleFactor, - this.isMobilePlatform, - this.minDate, - this.maxDate, - this.localizations, - this.updateCalendarState, - this.getCalendarState, - {Key? key}) - : super(key: key); + void _updateTimeSlotView(_CalendarView oldWidget) { + _animationController ??= AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); + _heightAnimation ??= + CurveTween(curve: Curves.easeIn).animate(_animationController!) + ..addListener(() { + setState(() { + /*Animates the all day panel when it's expanding or + collapsing*/ + }); + }); - final List visibleDates; - final List? regions; - final List? blackoutDates; - final SfCalendar calendar; - final CalendarView view; - final double width; - final SfCalendarThemeData calendarTheme; - final double height; - final String locale; - final ValueNotifier agendaSelectedDate; - final CalendarController controller; - final VoidCallback removePicker; - final UpdateCalendarState updateCalendarState; - final UpdateCalendarState getCalendarState; - final bool allowViewNavigation; - final FocusNode focusNode; - final ScrollController? resourcePanelScrollController; - final List? resourceCollection; - final double textScaleFactor; - final bool isMobilePlatform; - final DateTime minDate; - final DateTime maxDate; - final SfLocalizations localizations; + _expanderAnimationController ??= AnimationController( + duration: const Duration(milliseconds: 100), vsync: this); + _allDayExpanderAnimation ??= + CurveTween(curve: Curves.easeIn).animate(_expanderAnimationController!) + ..addListener(() { + setState(() { + /*Animates the all day panel when it's expanding or + collapsing*/ + }); + }); - @override - _CalendarViewState createState() => _CalendarViewState(); -} + if (widget.view != CalendarView.day && _allDayHeight == 0) { + if (_animationController!.status == AnimationStatus.completed) { + _animationController!.reset(); + } + + _animationController!.forward(); + } + } + + void _updateHorizontalLineCount(_CalendarView oldWidget) { + if (widget.calendar.timeSlotViewSettings.startHour != + oldWidget.calendar.timeSlotViewSettings.startHour || + widget.calendar.timeSlotViewSettings.endHour != + oldWidget.calendar.timeSlotViewSettings.endHour || + CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) != + CalendarViewHelper.getTimeInterval( + oldWidget.calendar.timeSlotViewSettings) || + oldWidget.view == CalendarView.month || + oldWidget.view == CalendarView.timelineMonth || + oldWidget.view != CalendarView.timelineMonth && + widget.view == CalendarView.timelineMonth) { + _horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view); + } else { + _horizontalLinesCount = _horizontalLinesCount ?? + CalendarViewHelper.getHorizontalLinesCount( + widget.calendar.timeSlotViewSettings, widget.view); + } + } -class _CalendarViewState extends State<_CalendarView> - with TickerProviderStateMixin { - // line count is the total time slot lines to be drawn in the view - // line count per view is for time line view which contains the time slot - // count for per view - double? _horizontalLinesCount; + void _updateTimelineViews(_CalendarView oldWidget) { + _timelineRulerController ??= + ScrollController(initialScrollOffset: 0, keepScrollOffset: true) + ..addListener(_timeRulerListener); - // all day scroll controller is used to identify the scroll position for draw - // all day selection. - ScrollController? _scrollController; - ScrollController? _timelineViewHeaderScrollController, - _timelineViewVerticalScrollController, - _timelineRulerController; + _timelineViewAnimationController ??= AnimationController( + duration: const Duration(milliseconds: 300), + vsync: this, + animationBehavior: AnimationBehavior.normal); - late AppointmentLayout _appointmentLayout; - AnimationController? _timelineViewAnimationController; - Animation? _timelineViewAnimation; - final Tween _timelineViewTween = Tween(begin: 0.0, end: 0.1); + _timelineViewAnimation ??= _timelineViewTween + .animate(_timelineViewAnimationController!) + ..addListener(_scrollAnimationListener); - //// timeline header is used to implement the sticky view header in horizontal calendar view mode. - late TimelineViewHeaderView _timelineViewHeader; - _SelectionPainter? _selectionPainter; - double _allDayHeight = 0; - late double _timeIntervalHeight; - final UpdateCalendarStateDetails _updateCalendarStateDetails = - UpdateCalendarStateDetails(); - ValueNotifier _allDaySelectionNotifier = - ValueNotifier(null); - late ValueNotifier _viewHeaderNotifier; - final ValueNotifier _calendarCellNotifier = - ValueNotifier(null), - _allDayNotifier = ValueNotifier(null), - _appointmentHoverNotifier = ValueNotifier(null); - final ValueNotifier _selectionNotifier = ValueNotifier(false), - _timelineViewHeaderNotifier = ValueNotifier(false); - late bool _isRTL; + _timelineViewHeaderScrollController ??= + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _timelineViewVerticalScrollController = + ScrollController(initialScrollOffset: 0, keepScrollOffset: true); + _timelineViewVerticalScrollController!.addListener(_updateResourceScroll); + widget.resourcePanelScrollController + ?.addListener(_updateResourcePanelScroll); + } - bool _isExpanded = false; - DateTime? _hoveringDate; + void _getPainterProperties(UpdateCalendarStateDetails details) { + widget.getCalendarState(_updateCalendarStateDetails); + details.allDayAppointmentViewCollection = + _updateCalendarStateDetails.allDayAppointmentViewCollection; + details.currentViewVisibleDates = + _updateCalendarStateDetails.currentViewVisibleDates; + details.visibleAppointments = + _updateCalendarStateDetails.visibleAppointments; + details.selectedDate = _updateCalendarStateDetails.selectedDate; + } - /// The property to hold the resource value associated with the selected - /// calendar cell. - int _selectedResourceIndex = -1; - AnimationController? _animationController; - Animation? _heightAnimation; - Animation? _allDayExpanderAnimation; - AnimationController? _expanderAnimationController; + Widget _addAllDayAppointmentPanel( + SfCalendarThemeData calendarTheme, bool isCurrentView) { + final Color borderColor = + widget.calendar.cellBorderColor ?? calendarTheme.cellBorderColor; + final Widget shadowView = Divider( + height: 1, + thickness: 1, + color: borderColor.withOpacity(borderColor.opacity * 0.5), + ); - /// Store the month widget instance used to update the month view - /// when the visible appointment updated. - late MonthViewWidget _monthView; + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + double topPosition = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + if (widget.view == CalendarView.day) { + topPosition = _allDayHeight; + } - /// Used to hold the global key for restrict the new appointment layout - /// creation. - /// if set the appointment layout key property as new Global key when create - /// the appointment layout then each of the time it creates new appointment - /// layout rather than update the existing appointment layout. - final GlobalKey _appointmentLayoutKey = GlobalKey(); + if (_allDayHeight == 0 || + (widget.view != CalendarView.day && + widget.visibleDates != + _updateCalendarStateDetails.currentViewVisibleDates)) { + return Positioned( + left: 0, right: 0, top: topPosition, height: 1, child: shadowView); + } - Timer? _timer; - late ValueNotifier _currentTimeNotifier; + if (widget.view == CalendarView.day) { + //// Default minimum view header width in day view as 50,so set 50 + //// when view header width less than 50. + topPosition = 0; + } - @override - void initState() { - _viewHeaderNotifier = ValueNotifier(null) - ..addListener(_timelineViewHoveringUpdate); - if (!CalendarViewHelper.isTimelineView(widget.view) && - widget.view != CalendarView.month) { - _animationController = AnimationController( - duration: const Duration(milliseconds: 200), vsync: this); - _heightAnimation = - CurveTween(curve: Curves.easeIn).animate(_animationController!) - ..addListener(() { - setState(() { - /* Animates the all day panel height when - expanding or collapsing */ - }); - }); + double panelHeight = isCurrentView + ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight + : 0; + if (panelHeight < 0) { + panelHeight = 0; + } - _expanderAnimationController = AnimationController( - duration: const Duration(milliseconds: 100), vsync: this); - _allDayExpanderAnimation = CurveTween(curve: Curves.easeIn) - .animate(_expanderAnimationController!) - ..addListener(() { - setState(() { - /* Animates the all day panel height when - expanding or collapsing */ - }); - }); + /// Remove the all day appointment selection when the selected all + /// day appointment removed. + if (_allDaySelectionNotifier.value != null && + _allDaySelectionNotifier.value!.appointmentView != null && + (!_updateCalendarStateDetails.visibleAppointments.contains( + _allDaySelectionNotifier.value!.appointmentView!.appointment))) { + _allDaySelectionNotifier.value = null; } - _timeIntervalHeight = _getTimeIntervalHeight( + final double allDayExpanderHeight = + _allDayHeight + (panelHeight * _allDayExpanderAnimation!.value); + return Positioned( + left: 0, + top: topPosition, + right: 0, + height: allDayExpanderHeight, + child: Stack( + children: [ + Positioned( + left: 0, + top: 0, + right: 0, + height: _isExpanded ? allDayExpanderHeight : _allDayHeight, + child: ListView( + physics: const NeverScrollableScrollPhysics(), + padding: const EdgeInsets.all(0.0), + children: [ + _getAllDayLayout(timeLabelWidth, panelHeight, + allDayExpanderHeight, isCurrentView) + ], + ), + ), + Positioned( + left: 0, + top: allDayExpanderHeight - 1, + right: 0, + height: 1, + child: shadowView), + ], + ), + ); + } + + Widget _getAllDayLayout(double timeLabelWidth, double panelHeight, + double allDayExpanderHeight, bool isCurrentView) { + final Widget _allDayLayout = AllDayAppointmentLayout( widget.calendar, widget.view, + widget.visibleDates, + widget.visibleDates == + _updateCalendarStateDetails.currentViewVisibleDates + ? _updateCalendarStateDetails.visibleAppointments + : null, + timeLabelWidth, + allDayExpanderHeight, + panelHeight > 0 && + (_heightAnimation!.value == 1 || widget.view == CalendarView.day), + _allDayExpanderAnimation!.value != 0.0 && + _allDayExpanderAnimation!.value != 1, + _isRTL, + widget.calendarTheme, + _allDaySelectionNotifier, + _allDayNotifier, + widget.textScaleFactor, + widget.isMobilePlatform, widget.width, - widget.height, - widget.visibleDates.length, - _allDayHeight, - widget.isMobilePlatform); - if (widget.view != CalendarView.month) { - _horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); - _scrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_scrollListener); - if (CalendarViewHelper.isTimelineView(widget.view)) { - _timelineRulerController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_timeRulerListener); - _timelineViewHeaderScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewAnimationController = AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - animationBehavior: AnimationBehavior.normal); - _timelineViewAnimation = _timelineViewTween - .animate(_timelineViewAnimationController!) - ..addListener(_scrollAnimationListener); - _timelineViewVerticalScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_updateResourceScroll); - widget.resourcePanelScrollController - ?.addListener(_updateResourcePanelScroll); + (widget.view == CalendarView.day && + _updateCalendarStateDetails.allDayPanelHeight < + _allDayHeight) || + !isCurrentView + ? _allDayHeight + : _updateCalendarStateDetails.allDayPanelHeight, + widget.localizations, + _getPainterProperties); + + if (_mouseCursor == SystemMouseCursors.basic || + !widget.calendar.allowAppointmentResize) { + return _allDayLayout; + } else { + return GestureDetector( + child: _allDayLayout, + onHorizontalDragStart: _onHorizontalStart, + onHorizontalDragUpdate: _onHorizontalUpdate, + onHorizontalDragEnd: _onHorizontalEnd, + ); + } + } + + Widget _addAppointmentPainter(double width, double height, + [double? resourceItemHeight]) { + final List? visibleAppointments = + widget.visibleDates == + _updateCalendarStateDetails.currentViewVisibleDates + ? _updateCalendarStateDetails.visibleAppointments + : null; + _appointmentLayout = AppointmentLayout( + widget.calendar, + widget.view, + widget.visibleDates, + ValueNotifier?>(visibleAppointments), + _timeIntervalHeight, + widget.calendarTheme, + _isRTL, + _appointmentHoverNotifier, + widget.resourceCollection, + resourceItemHeight, + widget.textScaleFactor, + widget.isMobilePlatform, + width, + height, + widget.localizations, + _getPainterProperties, + key: _appointmentLayoutKey, + ); + + return _appointmentLayout; + } + + void _onVerticalStart(DragStartDetails details) { + final double xPosition = details.localPosition.dx; + double yPosition = details.localPosition.dy; + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + AppointmentView? appointmentView; + const double padding = 10; + final bool isForwardResize = _mouseCursor == SystemMouseCursors.resizeDown; + final bool isBackwardResize = _mouseCursor == SystemMouseCursors.resizeUp; + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + if (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month) { + if (xPosition < timeLabelWidth) { + return; } - _scrollToPosition(); + final double allDayPanelHeight = _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; + + yPosition = yPosition - + viewHeaderHeight - + allDayPanelHeight + + _scrollController!.offset; + + if (isBackwardResize) { + yPosition += padding; + } else if (isForwardResize) { + yPosition -= padding; + } + appointmentView = + _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + if (appointmentView == null) { + return; + } + + _resizingDetails.value.isAllDayPanel = false; + yPosition = details.localPosition.dy - + viewHeaderHeight - + allDayPanelHeight + + _scrollController!.offset; + + if (_mouseCursor != SystemMouseCursors.basic) { + _resizingDetails.value.appointmentView = appointmentView.clone(); + } else { + appointmentView = null; + return; + } + + _updateMaximumResizingPosition(isForwardResize, isBackwardResize, + appointmentView, allDayPanelHeight, viewHeaderHeight); + _resizingDetails.value.position.value = Offset( + appointmentView.appointmentRect!.left, details.localPosition.dy); } - final DateTime today = DateTime.now(); - _currentTimeNotifier = ValueNotifier( - (today.day * 24 * 60) + (today.hour * 60) + today.minute); - _timer = _createTimer(); - super.initState(); + _resizingDetails.value.scrollPosition = null; + if (widget.calendar.appointmentBuilder == null) { + _resizingDetails.value.appointmentColor = + appointmentView!.appointment!.color; + } + if (CalendarViewHelper.shouldRaiseAppointmentResizeStartCallback( + widget.calendar.onAppointmentResizeStart)) { + CalendarViewHelper.raiseAppointmentResizeStartCallback( + widget.calendar, + _getCalendarAppointmentToObject( + appointmentView!.appointment, widget.calendar), + null); + } } - @override - void didUpdateWidget(_CalendarView oldWidget) { - final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - if (widget.view != CalendarView.month) { - if (!isTimelineView) { - _updateTimeSlotView(oldWidget); + void _onVerticalUpdate(DragUpdateDetails details) { + if (_resizingDetails.value.appointmentView == null) { + return; + } + + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + double yPosition = details.localPosition.dy; + final bool isForwardResize = _mouseCursor == SystemMouseCursors.resizeDown; + final bool isBackwardResize = _mouseCursor == SystemMouseCursors.resizeUp; + final double allDayPanelHeight = _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; + if (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month) { + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + allDayPanelHeight, + viewHeaderHeight); + if ((isForwardResize && yPosition < _maximumResizingPosition!) || + (isBackwardResize && yPosition > _maximumResizingPosition!)) { + yPosition = _maximumResizingPosition!; } - _updateHorizontalLineCount(oldWidget); + _updateAutoScrollDay(details, viewHeaderHeight, allDayPanelHeight, + isForwardResize, isBackwardResize, yPosition); + } - _scrollController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_scrollListener); + _resizingDetails.value.scrollPosition = null; + _resizingDetails.value.position.value = Offset( + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition); + _updateAppointmentResizingUpdateCallback(isForwardResize, isBackwardResize, + yPosition, viewHeaderHeight, allDayPanelHeight); + } - if (isTimelineView) { - _updateTimelineViews(oldWidget); - } + void _onVerticalEnd(DragEndDetails details) { + if (_resizingDetails.value.appointmentView == null) { + _resizingDetails.value.position.value = null; + return; } - /// Update the scroll position with following scenarios - /// 1. View changed from month or schedule view. - /// 2. View changed from timeline view(timeline day, timeline week, - /// timeline work week) to timeslot view(day, week, work week). - /// 3. View changed from timeslot view(day, week, work week) to - /// timeline view(timeline day, timeline week, timeline work week). - /// - /// This condition used to restrict the following scenarios - /// 1. View changed to month view. - /// 2. View changed with in the day, week, work week - /// (eg., view changed to week from day). - /// 3. View changed with in the timeline day, timeline week, timeline - /// work week(eg., view changed to timeline week from timeline day). - if ((oldWidget.view == CalendarView.month || - oldWidget.view == CalendarView.schedule || - (oldWidget.view != widget.view && isTimelineView) || - (CalendarViewHelper.isTimelineView(oldWidget.view) && - !isTimelineView)) && - widget.view != CalendarView.month) { - _scrollToPosition(); + if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; } - /// Method called to update all day height, when the view changed from - /// day to week views to avoid the blank space at the bottom of the view. - final bool isCurrentView = - _updateCalendarStateDetails.currentViewVisibleDates == - widget.visibleDates; - _updateAllDayHeight(isCurrentView); + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); - _timeIntervalHeight = _getTimeIntervalHeight( + final double allDayPanelHeight = _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; + + final double currentYPosition = + _resizingDetails.value.position.value!.dy > widget.height - 1 + ? widget.height - 1 + : _resizingDetails.value.position.value!.dy; + final double yPosition = currentYPosition - + viewHeaderHeight - + allDayPanelHeight + + _scrollController!.offset; + + final CalendarAppointment appointment = + _resizingDetails.value.appointmentView!.appointment!; + final double timeIntervalHeight = _getTimeIntervalHeight( widget.calendar, widget.view, widget.width, @@ -3508,745 +6412,1474 @@ class _CalendarViewState extends State<_CalendarView> _allDayHeight, widget.isMobilePlatform); - /// Clear the all day panel selection when the calendar view changed - /// Eg., if select the all day panel and switch to month view and again - /// select the same month cell and move to day view then the view show - /// calendar cell selection and all day panel selection. - if (oldWidget.view != widget.view) { - _allDaySelectionNotifier = ValueNotifier(null); - final DateTime today = DateTime.now(); - _currentTimeNotifier = ValueNotifier( - (today.day * 24 * 60) + (today.hour * 60) + today.minute); - _timer?.cancel(); - _timer = null; - } + final DateTime resizingTime = _timeFromPosition( + appointment.actualStartTime, + widget.calendar.timeSlotViewSettings, + yPosition, + null, + timeIntervalHeight, + false)!; - if (oldWidget.calendar.showCurrentTimeIndicator != - widget.calendar.showCurrentTimeIndicator) { - _timer?.cancel(); - _timer = _createTimer(); + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + if (!_isEnabledRegion(yPosition, resizingTime, -1) || + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + resizingTime, + timeInterval)) { + if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( + widget.calendar.onAppointmentResizeEnd)) { + CalendarViewHelper.raiseAppointmentResizeEndCallback( + widget.calendar, + appointment.data, + null, + appointment.exactStartTime, + appointment.exactEndTime); + } + + _resetResizingPainter(); + return; } - if ((oldWidget.view != widget.view || - oldWidget.width != widget.width || - oldWidget.height != widget.height) && - _selectionPainter!.appointmentView != null) { - _selectionPainter!.appointmentView = null; + CalendarAppointment? parentAppointment; + if (appointment.recurrenceRule != null && + appointment.recurrenceRule!.isNotEmpty) { + for (int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++) { + final CalendarAppointment app = + _updateCalendarStateDetails.appointments[i]; + if (app.id == appointment.id) { + parentAppointment = app; + break; + } + } + + widget.calendar.dataSource!.appointments!.remove(parentAppointment!.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [parentAppointment.data]); + + final DateTime exceptionDate = + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.exactStartTime, widget.calendar.timeZone, ''); + parentAppointment.recurrenceExceptionDates != null + ? parentAppointment.recurrenceExceptionDates!.add(exceptionDate) + : parentAppointment.recurrenceExceptionDates = [ + exceptionDate + ]; + + final dynamic newParentAppointment = + _getCalendarAppointmentToObject(parentAppointment, widget.calendar); + widget.calendar.dataSource!.appointments!.add(newParentAppointment); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.add, [newParentAppointment]); + } else { + widget.calendar.dataSource!.appointments!.remove(appointment.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [appointment.data]); } - /// When view switched from any other view to timeline view, and resource - /// enabled the selection must render the first resource view. - widget.getCalendarState(_updateCalendarStateDetails); - if (!CalendarViewHelper.isTimelineView(oldWidget.view) && - _updateCalendarStateDetails.selectedDate != null && - CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view) && - _selectedResourceIndex == -1) { - _selectedResourceIndex = 0; + DateTime updatedStartTime = appointment.actualStartTime, + updatedEndTime = appointment.actualEndTime; + + if (AppointmentHelper.canAddSpanIcon( + widget.visibleDates, appointment, widget.view)) { + updatedStartTime = appointment.exactStartTime; + updatedEndTime = appointment.exactEndTime; } - if (!CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { - _selectedResourceIndex = -1; + if (_mouseCursor == SystemMouseCursors.resizeDown) { + updatedEndTime = resizingTime; + } else if (_mouseCursor == SystemMouseCursors.resizeUp) { + updatedStartTime = resizingTime; } - _timer ??= _createTimer(); - super.didUpdateWidget(oldWidget); - } + final DateTime callbackStartDate = updatedStartTime; + final DateTime callbackEndDate = updatedEndTime; + updatedStartTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + updatedStartTime, widget.calendar.timeZone, appointment.startTimeZone); + updatedEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + updatedEndTime, widget.calendar.timeZone, appointment.endTimeZone); + appointment.startTime = updatedStartTime; + appointment.endTime = updatedEndTime; + appointment.recurrenceId = parentAppointment != null + ? parentAppointment.id + : appointment.recurrenceId; + appointment.recurrenceRule = + appointment.recurrenceId != null ? null : appointment.recurrenceRule; + appointment.id = parentAppointment != null ? null : appointment.id; + final dynamic newAppointment = + _getCalendarAppointmentToObject(appointment, widget.calendar); + widget.calendar.dataSource!.appointments!.add(newAppointment); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.add, [newAppointment]); - @override - Widget build(BuildContext context) { - _isRTL = CalendarViewHelper.isRTLLayout(context); - widget.getCalendarState(_updateCalendarStateDetails); - switch (widget.view) { - case CalendarView.schedule: - return Container(); - case CalendarView.month: - return _getMonthView(); - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - return _getDayView(); - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return _getTimelineView(); + if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( + widget.calendar.onAppointmentResizeEnd)) { + CalendarViewHelper.raiseAppointmentResizeEndCallback(widget.calendar, + newAppointment, null, callbackStartDate, callbackEndDate); } + + _resetResizingPainter(); } - @override - void dispose() { - _viewHeaderNotifier.removeListener(_timelineViewHoveringUpdate); + void _onHorizontalStart(DragStartDetails details) { + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + double xPosition = details.localPosition.dx; + CalendarResource? resource; + double yPosition = details.localPosition.dy; + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + AppointmentView? appointmentView; + const double padding = 10; + final bool isForwardResize = _mouseCursor == SystemMouseCursors.resizeRight; + final bool isBackwardResize = _mouseCursor == SystemMouseCursors.resizeLeft; + if (!isTimelineView && widget.view != CalendarView.month) { + if ((!_isRTL && xPosition < timeLabelWidth) || + (_isRTL && xPosition > (widget.width - timeLabelWidth))) { + return; + } - _calendarCellNotifier.removeListener(_timelineViewHoveringUpdate); + if (isBackwardResize) { + xPosition += padding; + } else if (isForwardResize) { + xPosition -= padding; + } - if (_timelineViewAnimation != null) { - _timelineViewAnimation!.removeListener(_scrollAnimationListener); - } + appointmentView = _getAllDayAppointmentOnPoint( + _updateCalendarStateDetails.allDayAppointmentViewCollection, + xPosition, + yPosition); + if (appointmentView == null) { + return; + } - if (widget.resourcePanelScrollController != null) { - widget.resourcePanelScrollController! - .removeListener(_updateResourcePanelScroll); - } + xPosition = details.localPosition.dx; + yPosition = appointmentView.appointmentRect!.top + viewHeaderHeight; + _resizingDetails.value.isAllDayPanel = true; + _updateMaximumResizingPosition(isForwardResize, isBackwardResize, + appointmentView, null, viewHeaderHeight); + } else if (isTimelineView) { + yPosition -= viewHeaderHeight + timeLabelWidth; + xPosition = _scrollController!.offset + details.localPosition.dx; + if (_isRTL) { + xPosition = _scrollController!.offset + + (_scrollController!.position.viewportDimension - + details.localPosition.dx); + xPosition = (_scrollController!.position.viewportDimension + + _scrollController!.position.maxScrollExtent) - + xPosition; + } - if (CalendarViewHelper.isTimelineView(widget.view) && - _timelineViewAnimationController != null) { - _timelineViewAnimationController!.dispose(); - _timelineViewAnimationController = null; - } - if (_scrollController != null) { - _scrollController!.removeListener(_scrollListener); - _scrollController!.dispose(); - _scrollController = null; - } - if (_timelineViewHeaderScrollController != null) { - _timelineViewHeaderScrollController!.dispose(); - _timelineViewHeaderScrollController = null; - } - if (_animationController != null) { - _animationController!.dispose(); - _animationController = null; - } - if (_timelineRulerController != null) { - _timelineRulerController!.dispose(); - _timelineRulerController = null; - } + if (isBackwardResize) { + xPosition += padding; + } else if (isForwardResize) { + xPosition -= padding; + } - if (_expanderAnimationController != null) { - _expanderAnimationController!.dispose(); - _expanderAnimationController = null; - } + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); - if (_timer != null) { - _timer!.cancel(); - _timer = null; - } + if (isResourceEnabled) { + yPosition += _timelineViewVerticalScrollController!.offset; + } - super.dispose(); - } + appointmentView = + _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + _resizingDetails.value.isAllDayPanel = false; + if (appointmentView == null) { + return; + } + if (isResourceEnabled) { + resource = widget.calendar.dataSource!.resources![ + _getSelectedResourceIndex(appointmentView.appointmentRect!.top, + viewHeaderHeight, timeLabelWidth)]; + } - Timer? _createTimer() { - return widget.calendar.showCurrentTimeIndicator && - widget.view != CalendarView.month && - widget.view != CalendarView.timelineMonth - ? Timer.periodic(const Duration(seconds: 1), (Timer t) { - final DateTime today = DateTime.now(); - final DateTime viewEndDate = - widget.visibleDates[widget.visibleDates.length - 1]; + yPosition = appointmentView.appointmentRect!.top + + viewHeaderHeight + + timeLabelWidth; + if (isResourceEnabled) { + yPosition -= _timelineViewVerticalScrollController!.offset; + } + _updateMaximumResizingPosition(isForwardResize, isBackwardResize, + appointmentView, null, viewHeaderHeight); + } else if (widget.view == CalendarView.month) { + _resizingDetails.value.monthRowCount = 0; + yPosition -= viewHeaderHeight; + xPosition = details.localPosition.dx; + if (isBackwardResize) { + xPosition += padding; + } else if (isForwardResize) { + xPosition -= padding; + } - /// Check the today date is in between visible date range and - /// today date hour and minute is 0(12 AM) because in day view - /// current time as Feb 16, 23.59 and changed to Feb 17 then view - /// will update both Feb 16 and 17 views. - if (!isDateWithInDateRange( - widget.visibleDates[0], viewEndDate, today) && - !(today.hour == 0 && - today.minute == 0 && - isSameDate(addDays(today, -1), viewEndDate))) { - return; - } + appointmentView = + _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + _resizingDetails.value.isAllDayPanel = false; + if (appointmentView == null) { + return; + } - _currentTimeNotifier.value = - (today.day * 24 * 60) + (today.hour * 60) + today.minute; - }) - : null; - } + xPosition = details.localPosition.dx; + yPosition = appointmentView.appointmentRect!.top + viewHeaderHeight; - /// Updates the resource panel scroll based on timeline scroll in vertical - /// direction. - void _updateResourcePanelScroll() { - if (_updateCalendarStateDetails.currentViewVisibleDates == - widget.visibleDates) { - widget.removePicker(); + _updateMaximumResizingPosition(isForwardResize, isBackwardResize, + appointmentView, null, viewHeaderHeight); } - if (widget.resourcePanelScrollController == null || - !CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + if (_mouseCursor != SystemMouseCursors.basic) { + _resizingDetails.value.appointmentView = appointmentView!.clone(); + } else { + appointmentView = null; return; } - if (widget.resourcePanelScrollController!.offset != - _timelineViewVerticalScrollController!.offset) { - _timelineViewVerticalScrollController! - .jumpTo(widget.resourcePanelScrollController!.offset); + _resizingDetails.value.scrollPosition = null; + if (widget.calendar.appointmentBuilder == null) { + _resizingDetails.value.appointmentColor = + appointmentView.appointment!.color; } - } - - /// Updates the timeline view scroll in vertical direction based on resource - /// panel scroll. - void _updateResourceScroll() { - if (_updateCalendarStateDetails.currentViewVisibleDates == - widget.visibleDates) { - widget.removePicker(); + _resizingDetails.value.position.value = + Offset(details.localPosition.dx, yPosition); + if (CalendarViewHelper.shouldRaiseAppointmentResizeStartCallback( + widget.calendar.onAppointmentResizeStart)) { + CalendarViewHelper.raiseAppointmentResizeStartCallback( + widget.calendar, + _getCalendarAppointmentToObject( + appointmentView.appointment, widget.calendar), + resource); } + } - if (widget.resourcePanelScrollController == null || - !CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { + void _onHorizontalUpdate(DragUpdateDetails details) { + if (_resizingDetails.value.appointmentView == null) { return; } - if (widget.resourcePanelScrollController!.offset != - _timelineViewVerticalScrollController!.offset) { - widget.resourcePanelScrollController! - .jumpTo(_timelineViewVerticalScrollController!.offset); - } - } + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); + final bool isForwardResize = _mouseCursor == SystemMouseCursors.resizeRight; + final bool isBackwardResize = _mouseCursor == SystemMouseCursors.resizeLeft; + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); + double xPosition = details.localPosition.dx; + double yPosition = _resizingDetails.value.position.value!.dy; + late DateTime resizingTime; + final double timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); - Widget _getMonthView() { - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onExit: _pointerExitEvent, - onHover: _pointerHoverEvent, - child: Container( - width: widget.width, - height: widget.height, - child: _addMonthView(_isRTL, widget.locale)), - ), - onTapUp: (TapUpDetails details) { - _handleOnTapForMonth(details); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForMonth(details); - }, - ); - } + if (isTimelineView) { + _updateMaximumResizingPosition(isForwardResize, isBackwardResize, + _resizingDetails.value.appointmentView!, null, null); + if ((isForwardResize && xPosition < _maximumResizingPosition!) || + (isBackwardResize && xPosition > _maximumResizingPosition!)) { + xPosition = _maximumResizingPosition!; + } + + _updateAutoScrollTimeline( + details, + timeIntervalHeight, + isForwardResize, + isBackwardResize, + xPosition, + yPosition, + timeLabelWidth, + isResourceEnabled); + } else if (widget.view == CalendarView.month) { + final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + double resizingPosition = details.localPosition.dy - viewHeaderHeight; + if (resizingPosition < 0) { + resizingPosition = 0; + } else if (resizingPosition > widget.height - viewHeaderHeight - 1) { + resizingPosition = widget.height - viewHeaderHeight - 1; + } + + final double cellHeight = (widget.height - viewHeaderHeight) / + widget.calendar.monthViewSettings.numberOfWeeksInView; + final int appointmentRowIndex = + (_resizingDetails.value.appointmentView!.appointmentRect!.top / + cellHeight) + .truncate(); + int resizingRowIndex = (resizingPosition / cellHeight).truncate(); + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform); + if (!_isRTL) { + if (xPosition < weekNumberPanelWidth) { + xPosition = weekNumberPanelWidth; + } else if (xPosition > widget.width - 1) { + xPosition = widget.width - 1; + } + } else { + if (xPosition > widget.width - weekNumberPanelWidth - 1) { + xPosition = widget.width - weekNumberPanelWidth - 1; + } else if (xPosition < 0) { + xPosition = 0; + } + } - Widget _getDayView() { - final bool isCurrentView = - _updateCalendarStateDetails.currentViewVisibleDates == - widget.visibleDates; - _updateAllDayHeight(isCurrentView); + /// Handle the appointment resize after and before the current month + /// dates when hide trailing and leading dates enabled. + if (!widget.calendar.monthViewSettings.showTrailingAndLeadingDates && + widget.calendar.monthViewSettings.numberOfWeeksInView == 6) { + final DateTime currentMonthDate = + widget.visibleDates[widget.visibleDates.length ~/ 2]; + final int startIndex = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, + AppointmentHelper.getMonthStartDate(currentMonthDate)); + final int endIndex = DateTimeHelper.getVisibleDateIndex( + widget.visibleDates, + AppointmentHelper.getMonthEndDate(currentMonthDate)); + final int startRowCount = startIndex ~/ DateTime.daysPerWeek; + final int startColumnCount = startIndex % DateTime.daysPerWeek; + final int endRowCount = endIndex ~/ DateTime.daysPerWeek; + final int endColumnCount = endIndex % DateTime.daysPerWeek; + if (resizingRowIndex >= endRowCount) { + resizingRowIndex = endRowCount; + resizingPosition = resizingRowIndex * cellHeight; + final double cellWidth = + (widget.width - weekNumberPanelWidth) / DateTime.daysPerWeek; + if (_isRTL) { + final double currentXPosition = + (DateTime.daysPerWeek - endColumnCount - 1) * cellWidth; + xPosition = + xPosition > currentXPosition ? xPosition : currentXPosition; + } else { + final double currentXPosition = + ((endColumnCount + 1) * cellWidth) + weekNumberPanelWidth - 1; + xPosition = + xPosition > currentXPosition ? currentXPosition : xPosition; + } + } else if (resizingRowIndex <= startRowCount) { + resizingRowIndex = startRowCount; + resizingPosition = resizingRowIndex * cellHeight; + final double cellWidth = + (widget.width - weekNumberPanelWidth) / DateTime.daysPerWeek; + if (_isRTL) { + double currentXPosition = + (DateTime.daysPerWeek - startColumnCount) * cellWidth; + if (currentXPosition != 0) { + currentXPosition -= 1; + } - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: Container( - height: widget.height, - width: widget.width, - child: _addDayView( - widget.width, - _timeIntervalHeight * _horizontalLinesCount!, - _isRTL, - widget.locale, - isCurrentView)), - ), - onTapUp: (TapUpDetails details) { - _handleOnTapForDay(details); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForDay(details); - }, - ); - } + xPosition = + xPosition < currentXPosition ? xPosition : currentXPosition; + } else { + final double currentXPosition = + (startColumnCount * cellWidth) + weekNumberPanelWidth; + xPosition = + xPosition < currentXPosition ? currentXPosition : xPosition; + } + } + } - /// Method to update alldayHeight calculation for day, week and work week - /// view, based on the view also based on the timeintervalheight. - void _updateAllDayHeight(bool isCurrentView) { - if (widget.view != CalendarView.day && - widget.view != CalendarView.week && - widget.view != CalendarView.workWeek) { - return; - } + /// Restrict by max resize position only restrict the appointment resize + /// on previous and next row also so check the row index also to resolve + /// the issue with both RTL and LTR scenarios. + if (_isRTL) { + if (isForwardResize && + ((appointmentRowIndex == resizingRowIndex && + xPosition < _maximumResizingPosition!) || + appointmentRowIndex < resizingRowIndex)) { + xPosition = _maximumResizingPosition!; + resizingRowIndex = appointmentRowIndex; + resizingPosition = + _resizingDetails.value.appointmentView!.appointmentRect!.top; + } else if (isBackwardResize && + ((appointmentRowIndex == resizingRowIndex && + xPosition > _maximumResizingPosition!) || + appointmentRowIndex > resizingRowIndex)) { + xPosition = _maximumResizingPosition!; + resizingRowIndex = appointmentRowIndex; + resizingPosition = + _resizingDetails.value.appointmentView!.appointmentRect!.top; + } + } else { + if (isForwardResize && + ((appointmentRowIndex == resizingRowIndex && + xPosition < _maximumResizingPosition!) || + appointmentRowIndex > resizingRowIndex)) { + xPosition = _maximumResizingPosition!; + resizingRowIndex = appointmentRowIndex; + resizingPosition = + _resizingDetails.value.appointmentView!.appointmentRect!.top; + } else if (isBackwardResize && + ((appointmentRowIndex == resizingRowIndex && + xPosition > _maximumResizingPosition!) || + appointmentRowIndex < resizingRowIndex)) { + xPosition = _maximumResizingPosition!; + resizingRowIndex = appointmentRowIndex; + resizingPosition = + _resizingDetails.value.appointmentView!.appointmentRect!.top; + } + } - _allDayHeight = 0; - if (widget.view == CalendarView.day) { - final double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - if (isCurrentView) { - _allDayHeight = _kAllDayLayoutHeight > viewHeaderHeight && - _updateCalendarStateDetails.allDayPanelHeight > viewHeaderHeight - ? _updateCalendarStateDetails.allDayPanelHeight > - _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : _updateCalendarStateDetails.allDayPanelHeight - : viewHeaderHeight; - if (_allDayHeight < _updateCalendarStateDetails.allDayPanelHeight) { - _allDayHeight += kAllDayAppointmentHeight; + resizingTime = + _getDateFromPosition(xPosition, resizingPosition, timeLabelWidth)!; + final int rowDifference = isBackwardResize + ? _isRTL + ? (appointmentRowIndex - resizingRowIndex).abs() + : appointmentRowIndex - resizingRowIndex + : _isRTL + ? appointmentRowIndex - resizingRowIndex + : (appointmentRowIndex - resizingRowIndex).abs(); + if (((!_isRTL && + ((isBackwardResize && + appointmentRowIndex > resizingRowIndex) || + (isForwardResize && + appointmentRowIndex < resizingRowIndex))) || + (_isRTL && + ((isBackwardResize && + appointmentRowIndex < resizingRowIndex) || + (isForwardResize && + appointmentRowIndex > resizingRowIndex)))) && + resizingRowIndex != appointmentRowIndex && + rowDifference != _resizingDetails.value.monthRowCount) { + if (isForwardResize) { + if (_isRTL) { + if (_resizingDetails.value.monthRowCount > rowDifference) { + yPosition += cellHeight; + } else { + yPosition -= cellHeight; + } + } else { + if (_resizingDetails.value.monthRowCount > rowDifference) { + yPosition -= cellHeight; + } else { + yPosition += cellHeight; + } + } + } else { + if (_isRTL) { + if (_resizingDetails.value.monthRowCount > rowDifference) { + yPosition -= cellHeight; + } else { + yPosition += cellHeight; + } + } else { + if (_resizingDetails.value.monthRowCount > rowDifference) { + yPosition += cellHeight; + } else { + yPosition -= cellHeight; + } + } + } + _resizingDetails.value.monthRowCount = rowDifference; + _resizingDetails.value.monthCellHeight = cellHeight; + } else if (resizingRowIndex == appointmentRowIndex && + rowDifference == 0) { + _resizingDetails.value.monthRowCount = rowDifference; + _resizingDetails.value.monthCellHeight = cellHeight; + yPosition = + _resizingDetails.value.appointmentView!.appointmentRect!.top + + viewHeaderHeight; + } + } else { + if ((isForwardResize && xPosition < _maximumResizingPosition!) || + (isBackwardResize && xPosition > _maximumResizingPosition!)) { + xPosition = _maximumResizingPosition!; + } + + double currentXPosition = xPosition; + if (_isRTL) { + if (currentXPosition > widget.width - timeLabelWidth - 1) { + currentXPosition = widget.width - timeLabelWidth - 1; + } else if (currentXPosition < 0) { + currentXPosition = 0; } } else { - _allDayHeight = viewHeaderHeight; + if (currentXPosition < timeLabelWidth) { + currentXPosition = timeLabelWidth; + } else if (currentXPosition > widget.width - 1) { + currentXPosition = widget.width - 1; + } + + currentXPosition -= timeLabelWidth; } - } else if (isCurrentView) { - _allDayHeight = - _updateCalendarStateDetails.allDayPanelHeight > _kAllDayLayoutHeight - ? _kAllDayLayoutHeight - : _updateCalendarStateDetails.allDayPanelHeight; - _allDayHeight = _allDayHeight * _heightAnimation!.value; + resizingTime = + _getDateFromPosition(currentXPosition, yPosition, timeLabelWidth)!; } - } - Widget _getTimelineView() { - return GestureDetector( - child: MouseRegion( - onEnter: _pointerEnterEvent, - onHover: _pointerHoverEvent, - onExit: _pointerExitEvent, - child: Container( - width: widget.width, - height: widget.height, - child: _addTimelineView( - _timeIntervalHeight * - (_horizontalLinesCount! * widget.visibleDates.length), - widget.height, - widget.locale), - )), - onTapUp: (TapUpDetails details) { - _handleOnTapForTimeline(details); - }, - onLongPressStart: (LongPressStartDetails details) { - _handleOnLongPressForTimeline(details); - }, - ); - } + if (_resizingDetails.value.isAllDayPanel || + widget.view == CalendarView.month) { + resizingTime = DateTime( + resizingTime.year, resizingTime.month, resizingTime.day, 0, 0, 0); + } - void _timelineViewHoveringUpdate() { - if (!CalendarViewHelper.isTimelineView(widget.view) && mounted) { + _resizingDetails.value.position.value = Offset(xPosition, yPosition); + + if (isTimelineView) { + _updateAppointmentResizingUpdateCallback( + isForwardResize, isBackwardResize, yPosition, null, null, + xPosition: xPosition, + timeLabelWidth: timeLabelWidth, + isResourceEnabled: isResourceEnabled, + details: details); return; } - // Updates the timeline views based on mouse hovering position. - _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; + if (CalendarViewHelper.shouldRaiseAppointmentResizeUpdateCallback( + widget.calendar.onAppointmentResizeUpdate)) { + CalendarViewHelper.raiseAppointmentResizeUpdateCallback( + widget.calendar, + _getCalendarAppointmentToObject( + _resizingDetails.value.appointmentView!.appointment, + widget.calendar), + null, + resizingTime, + _resizingDetails.value.position.value!); + } } - void _scrollAnimationListener() { - _scrollController!.jumpTo(_timelineViewAnimation!.value); - } + void _onHorizontalEnd(DragEndDetails details) { + if (_resizingDetails.value.appointmentView == null) { + _resizingDetails.value.position.value = null; + return; + } - void _scrollToPosition() { - SchedulerBinding.instance!.addPostFrameCallback((_) { - if (widget.view == CalendarView.month) { - return; - } + if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } - widget.getCalendarState(_updateCalendarStateDetails); - final double scrollPosition = _getScrollPositionForCurrentDate( - _updateCalendarStateDetails.currentDate!); - if (scrollPosition == -1 || - _scrollController!.position.pixels == scrollPosition) { - return; - } + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); - _scrollController!.jumpTo( - _scrollController!.position.maxScrollExtent > scrollPosition - ? scrollPosition - : _scrollController!.position.maxScrollExtent); - }); - } + final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - double _getScrollPositionForCurrentDate(DateTime date) { - final int visibleDatesCount = widget.visibleDates.length; - if (!isDateWithInDateRange(widget.visibleDates[0], - widget.visibleDates[visibleDatesCount - 1], date)) { - return -1; - } + double xPosition = _resizingDetails.value.position.value!.dx; + double yPosition = _resizingDetails.value.position.value!.dy; - double timeToPosition = 0; - if (!CalendarViewHelper.isTimelineView(widget.view)) { - timeToPosition = AppointmentHelper.timeToPosition( - widget.calendar, date, _timeIntervalHeight); - } else { - for (int i = 0; i < visibleDatesCount; i++) { - if (!isSameDate(date, widget.visibleDates[i])) { - continue; - } + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); - if (widget.view == CalendarView.timelineMonth) { - timeToPosition = _timeIntervalHeight * i; - } else { - timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + - AppointmentHelper.timeToPosition( - widget.calendar, date, _timeIntervalHeight); + final CalendarAppointment appointment = + _resizingDetails.value.appointmentView!.appointment!; + final double timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + if (!isTimelineView && widget.view != CalendarView.month) { + if (_isRTL) { + if (xPosition > widget.width - timeLabelWidth - 1) { + xPosition = widget.width - timeLabelWidth - 1; + } else if (xPosition < 0) { + xPosition = 0; + } + } else { + if (xPosition < timeLabelWidth) { + xPosition = timeLabelWidth; + } else if (xPosition > widget.width - 1) { + xPosition = widget.width - 1; } - break; + xPosition -= timeLabelWidth; + } + } else if (widget.view == CalendarView.month) { + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform); + _resizingDetails.value.monthRowCount = 0; + if (!_isRTL) { + if (xPosition < weekNumberPanelWidth) { + xPosition = weekNumberPanelWidth; + } + } else { + if (xPosition > widget.width - weekNumberPanelWidth) { + xPosition = widget.width - weekNumberPanelWidth; + } + } + yPosition -= viewHeaderHeight; + } else if (isTimelineView) { + if (xPosition < 0) { + xPosition = 0; + } else if (xPosition > widget.width - 1) { + xPosition = widget.width - 1; } } - if (_scrollController!.hasClients) { - if (timeToPosition > _scrollController!.position.maxScrollExtent) { - timeToPosition = _scrollController!.position.maxScrollExtent; - } else if (timeToPosition < _scrollController!.position.minScrollExtent) { - timeToPosition = _scrollController!.position.minScrollExtent; + DateTime resizingTime = + _getDateFromPosition(xPosition, yPosition, timeLabelWidth)!; + if (_resizingDetails.value.isAllDayPanel || + widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth) { + resizingTime = DateTime( + resizingTime.year, resizingTime.month, resizingTime.day, 0, 0, 0); + } else if (isTimelineView) { + final DateTime time = _timeFromPosition( + resizingTime, + widget.calendar.timeSlotViewSettings, + xPosition, + this, + timeIntervalHeight, + isTimelineView)!; + + resizingTime = DateTime(resizingTime.year, resizingTime.month, + resizingTime.day, time.hour, time.minute, time.second); + } + + CalendarResource? resource; + int selectedResourceIndex = -1; + if (isResourceEnabled) { + selectedResourceIndex = _getSelectedResourceIndex( + _resizingDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth); + resource = widget.calendar.dataSource!.resources![selectedResourceIndex]; + } + + final bool isMonthView = widget.view == CalendarView.timelineMonth || + widget.view == CalendarView.month; + + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + + if (!_isEnabledRegion(yPosition, resizingTime, selectedResourceIndex)) { + if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( + widget.calendar.onAppointmentResizeEnd)) { + CalendarViewHelper.raiseAppointmentResizeEndCallback( + widget.calendar, + appointment.data, + resource, + appointment.exactStartTime, + appointment.exactEndTime); } + + _resetResizingPainter(); + return; } - return timeToPosition; - } + if ((isMonthView && + (!isDateWithInDateRange(widget.calendar.minDate, + widget.calendar.maxDate, resizingTime) || + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, resizingTime))) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + resizingTime, + timeInterval))) { + if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( + widget.calendar.onAppointmentResizeEnd)) { + CalendarViewHelper.raiseAppointmentResizeEndCallback( + widget.calendar, + appointment.data, + resource, + appointment.exactStartTime, + appointment.exactEndTime); + } - /// Used to retain the scrolled date time. - void _retainScrolledDateTime() { - if (widget.view == CalendarView.month) { + _resetResizingPainter(); return; } - DateTime scrolledDate = widget.visibleDates[0]; - double scrolledPosition = 0; - if (CalendarViewHelper.isTimelineView(widget.view)) { - final double singleViewWidth = _getSingleViewWidthForTimeLineView(this); + CalendarAppointment? parentAppointment; + widget.getCalendarState(_updateCalendarStateDetails); + if ((appointment.recurrenceRule != null && + appointment.recurrenceRule!.isNotEmpty) || + appointment.recurrenceId != null) { + for (int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++) { + final CalendarAppointment app = + _updateCalendarStateDetails.appointments[i]; + if (app.id == appointment.id || app.id == appointment.recurrenceId) { + parentAppointment = app; + break; + } + } - /// Calculate the scrolled position date. - scrolledDate = widget - .visibleDates[_scrollController!.position.pixels ~/ singleViewWidth]; + final List recurrenceDates = + RecurrenceHelper.getRecurrenceDateTimeCollection( + parentAppointment!.recurrenceRule ?? '', + parentAppointment.exactStartTime, + recurrenceDuration: AppointmentHelper.getDifference( + parentAppointment.exactStartTime, + parentAppointment.exactEndTime), + specificStartDate: widget.visibleDates[0], + specificEndDate: + widget.visibleDates[widget.visibleDates.length - 1]); + + for (int i = 0; + i < _updateCalendarStateDetails.appointments.length; + i++) { + final CalendarAppointment calendarApp = + _updateCalendarStateDetails.appointments[i]; + if (calendarApp.recurrenceId != null && + calendarApp.recurrenceId == parentAppointment.id) { + recurrenceDates.add( + AppointmentHelper.convertTimeToAppointmentTimeZone( + calendarApp.startTime, + calendarApp.startTimeZone, + widget.calendar.timeZone)); + } + } - /// Calculate the scrolled hour position without visible date position. - scrolledPosition = _scrollController!.position.pixels % singleViewWidth; - } else { - /// Calculate the scrolled hour position. - scrolledPosition = _scrollController!.position.pixels; - } + if (parentAppointment.recurrenceExceptionDates != null) { + for (int i = 0; + i < parentAppointment.recurrenceExceptionDates!.length; + i++) { + recurrenceDates.remove( + AppointmentHelper.convertTimeToAppointmentTimeZone( + parentAppointment.recurrenceExceptionDates![i], + '', + widget.calendar.timeZone)); + } + } - /// Calculate the current horizontal line based on time interval height. - final double columnIndex = scrolledPosition / _timeIntervalHeight; + recurrenceDates.sort(); + final int currentRecurrenceIndex = + recurrenceDates.indexOf(appointment.exactStartTime); + if (currentRecurrenceIndex != -1) { + final DateTime? previousRecurrence = currentRecurrenceIndex <= 0 + ? null + : recurrenceDates[currentRecurrenceIndex - 1]; + final DateTime? nextRecurrence = + currentRecurrenceIndex >= recurrenceDates.length - 1 + ? null + : recurrenceDates[currentRecurrenceIndex + 1]; + + /// Check the resizing time is in between previous and next recurrence + /// date. If previous recurrence is null(means resized appointment + /// is first occurrence) then it does not have a limit for previous + /// occurrence. + if (!((nextRecurrence == null || + isSameOrBeforeDate(nextRecurrence, resizingTime)) && + (previousRecurrence == null || + isSameOrAfterDate(previousRecurrence, resizingTime)) && + !isSameDate(previousRecurrence, resizingTime) && + !isSameDate(nextRecurrence, resizingTime)) && + !isSameDate(appointment.exactStartTime, resizingTime)) { + if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( + widget.calendar.onAppointmentResizeEnd)) { + CalendarViewHelper.raiseAppointmentResizeEndCallback( + widget.calendar, + appointment.data, + resource, + appointment.exactStartTime, + appointment.exactEndTime); + } - /// Calculate the time based on calculated horizontal position. - final double time = ((CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) / - 60) * - columnIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - scrolledDate = DateTime( - scrolledDate.year, scrolledDate.month, scrolledDate.day, hour, minute); + _resetResizingPainter(); + return; + } + } - /// Update the scrolled position after the widget generated. - SchedulerBinding.instance!.addPostFrameCallback((_) { - _scrollController!.jumpTo(_getPositionFromDate(scrolledDate)); - }); - } + if (appointment.recurrenceId != null && + (appointment.recurrenceRule == null || + appointment.recurrenceRule!.isEmpty)) { + widget.calendar.dataSource!.appointments!.remove(appointment.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [appointment.data]); + } else { + widget.calendar.dataSource!.appointments! + .remove(parentAppointment.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [parentAppointment.data]); + + final DateTime exceptionDate = + AppointmentHelper.convertTimeToAppointmentTimeZone( + appointment.exactStartTime, widget.calendar.timeZone, ''); + parentAppointment.recurrenceExceptionDates != null + ? parentAppointment.recurrenceExceptionDates!.add(exceptionDate) + : parentAppointment.recurrenceExceptionDates = [ + exceptionDate + ]; + + final dynamic newParentAppointment = + _getCalendarAppointmentToObject(parentAppointment, widget.calendar); + widget.calendar.dataSource!.appointments!.add(newParentAppointment); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.add, [newParentAppointment]); + } + } else { + widget.calendar.dataSource!.appointments!.remove(appointment.data); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.remove, [appointment.data]); + } + + DateTime updatedStartTime = appointment.actualStartTime, + updatedEndTime = appointment.actualEndTime; + if ((_isRTL && _mouseCursor == SystemMouseCursors.resizeLeft) || + (!_isRTL && _mouseCursor == SystemMouseCursors.resizeRight)) { + if (isMonthView) { + updatedEndTime = DateTime(resizingTime.year, resizingTime.month, + resizingTime.day, updatedEndTime.hour, updatedEndTime.minute); + } else { + updatedEndTime = resizingTime; + } + } else if ((_isRTL && _mouseCursor == SystemMouseCursors.resizeRight) || + (!_isRTL && _mouseCursor == SystemMouseCursors.resizeLeft)) { + if (isMonthView) { + updatedStartTime = DateTime(resizingTime.year, resizingTime.month, + resizingTime.day, updatedStartTime.hour, updatedStartTime.minute); + } else { + updatedStartTime = resizingTime; + } + } + + final DateTime callbackStartDate = updatedStartTime; + final DateTime callbackEndDate = updatedEndTime; + updatedStartTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + updatedStartTime, widget.calendar.timeZone, appointment.startTimeZone); + updatedEndTime = AppointmentHelper.convertTimeToAppointmentTimeZone( + updatedEndTime, widget.calendar.timeZone, appointment.endTimeZone); + appointment.startTime = updatedStartTime; + appointment.endTime = updatedEndTime; + appointment.recurrenceId = parentAppointment != null + ? parentAppointment.id + : appointment.recurrenceId; + appointment.recurrenceRule = + appointment.recurrenceId != null ? null : appointment.recurrenceRule; + appointment.id = parentAppointment != null ? null : appointment.id; + final dynamic newAppointment = + _getCalendarAppointmentToObject(appointment, widget.calendar); + + widget.calendar.dataSource!.appointments!.add(newAppointment); + widget.calendar.dataSource!.notifyListeners( + CalendarDataSourceAction.add, [newAppointment]); + + if (CalendarViewHelper.shouldRaiseAppointmentResizeEndCallback( + widget.calendar.onAppointmentResizeEnd)) { + CalendarViewHelper.raiseAppointmentResizeEndCallback(widget.calendar, + newAppointment, resource, callbackStartDate, callbackEndDate); + } + + _resetResizingPainter(); + } + + Future _updateAutoScrollDay( + DragUpdateDetails details, + double viewHeaderHeight, + double allDayPanelHeight, + bool isForwardResize, + bool isBackwardResize, + double? yPosition) async { + if (_resizingDetails.value.appointmentView == null) { + return; + } - /// Calculate the position from date. - double _getPositionFromDate(DateTime date) { - final int visibleDatesCount = widget.visibleDates.length; - _timeIntervalHeight = _getTimeIntervalHeight( + final double timeIntervalHeight = _getTimeIntervalHeight( widget.calendar, widget.view, widget.width, widget.height, - visibleDatesCount, + widget.visibleDates.length, _allDayHeight, widget.isMobilePlatform); - double timeToPosition = 0; - final bool isTimelineView = CalendarViewHelper.isTimelineView(widget.view); - if (!isTimelineView) { - timeToPosition = AppointmentHelper.timeToPosition( - widget.calendar, date, _timeIntervalHeight); - } else { - for (int i = 0; i < visibleDatesCount; i++) { - if (!isSameDate(date, widget.visibleDates[i])) { - continue; - } - if (widget.view == CalendarView.timelineMonth) { - timeToPosition = _timeIntervalHeight * i; - } else { - timeToPosition = (_getSingleViewWidthForTimeLineView(this) * i) + - AppointmentHelper.timeToPosition( - widget.calendar, date, _timeIntervalHeight); - } + if (yPosition! <= viewHeaderHeight + allDayPanelHeight && + _scrollController!.position.pixels != 0) { + if (_autoScrollTimer != null) { + return; + } + _autoScrollTimer = Timer(const Duration(milliseconds: 200), () async { + yPosition = _resizingDetails.value.position.value?.dy; + if (yPosition != null && + yPosition! <= viewHeaderHeight + allDayPanelHeight && + _scrollController!.offset != 0) { + Future _updateScrollPosition() async { + double scrollPosition = + _scrollController!.position.pixels - timeIntervalHeight; + if (scrollPosition < 0) { + scrollPosition = 0; + } - break; + _resizingDetails.value.scrollPosition = scrollPosition; + + _resizingDetails.value.position.value = Offset( + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition! - 0.1); + + await _scrollController!.position.animateTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + if (_resizingDetails.value.appointmentView == null) { + if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + + return; + } + + yPosition = _resizingDetails.value.position.value?.dy; + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + allDayPanelHeight, + viewHeaderHeight); + if ((isForwardResize && yPosition! < _maximumResizingPosition!) || + (isBackwardResize && yPosition! > _maximumResizingPosition!)) { + yPosition = _maximumResizingPosition!; + } + _updateAppointmentResizingUpdateCallback( + isForwardResize, + isBackwardResize, + yPosition!, + viewHeaderHeight, + allDayPanelHeight); + + _resizingDetails.value.position.value = Offset( + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition!); + + if (yPosition != null && + yPosition! <= viewHeaderHeight + allDayPanelHeight && + _scrollController!.offset != 0) { + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + } + + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + }); + } else if (yPosition >= widget.height && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) { + if (_autoScrollTimer != null) { + return; } - } + _autoScrollTimer = Timer(const Duration(milliseconds: 200), () async { + yPosition = _resizingDetails.value.position.value?.dy; + if (yPosition != null && + yPosition! >= widget.height && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) { + Future _updateScrollPosition() async { + double scrollPosition = + _scrollController!.position.pixels + timeIntervalHeight; + if (scrollPosition > _scrollController!.position.maxScrollExtent) { + scrollPosition = _scrollController!.position.maxScrollExtent; + } - double maxScrollPosition = 0; - if (!isTimelineView) { - final double scrollViewHeight = widget.height - - _allDayHeight - - CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - final double scrollViewContentHeight = - CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view) * - _timeIntervalHeight; - maxScrollPosition = scrollViewContentHeight - scrollViewHeight; - } else { - final double scrollViewContentWidth = - CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view) * - _timeIntervalHeight * - visibleDatesCount; - maxScrollPosition = scrollViewContentWidth - widget.width; - } + _resizingDetails.value.scrollPosition = scrollPosition; - return maxScrollPosition > timeToPosition - ? timeToPosition - : maxScrollPosition; - } + _resizingDetails.value.position.value = Offset( + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition! - 0.1); - void _expandOrCollapseAllDay() { - _isExpanded = !_isExpanded; - if (_isExpanded) { - _expanderAnimationController!.forward(); - } else { - _expanderAnimationController!.reverse(); - } - } + await _scrollController!.position.moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + if (_resizingDetails.value.appointmentView == null) { + if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } - /// Update the time slot view scroll based on time ruler view scroll in - /// timeslot views. - void _timeRulerListener() { - if (!CalendarViewHelper.isTimelineView(widget.view)) { - return; - } + return; + } - if (_timelineRulerController!.offset != _scrollController!.offset) { - _scrollController!.jumpTo(_timelineRulerController!.offset); + yPosition = _resizingDetails.value.position.value?.dy; + + _updateMaximumResizingPosition( + isForwardResize, + isBackwardResize, + _resizingDetails.value.appointmentView!, + allDayPanelHeight, + viewHeaderHeight); + if ((isForwardResize && yPosition! < _maximumResizingPosition!) || + (isBackwardResize && yPosition! > _maximumResizingPosition!)) { + yPosition = _maximumResizingPosition!; + } + _updateAppointmentResizingUpdateCallback( + isForwardResize, + isBackwardResize, + yPosition!, + viewHeaderHeight, + allDayPanelHeight); + + _resizingDetails.value.position.value = Offset( + _resizingDetails.value.appointmentView!.appointmentRect!.left, + yPosition!); + + if (yPosition != null && + yPosition! >= widget.height && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) { + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + } + + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + }); } } - void _scrollListener() { - if (_updateCalendarStateDetails.currentViewVisibleDates == - widget.visibleDates) { - widget.removePicker(); + Future _updateAutoScrollTimeline( + DragUpdateDetails details, + double timeIntervalHeight, + bool isForwardResize, + bool isBackwardResize, + double? xPosition, + double yPosition, + double timeLabelWidth, + bool isResourceEnabled) async { + if (_resizingDetails.value.appointmentView == null) { + return; } - if (CalendarViewHelper.isTimelineView(widget.view)) { - widget.getCalendarState(_updateCalendarStateDetails); - if (widget.view != CalendarView.timelineMonth) { - _timelineViewHeaderNotifier.value = !_timelineViewHeaderNotifier.value; - } + const int padding = 5; - if (_timelineRulerController!.offset != _scrollController!.offset) { - _timelineRulerController!.jumpTo(_scrollController!.offset); + if (xPosition! <= 0 && + ((_isRTL && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) || + (!_isRTL && _scrollController!.position.pixels != 0))) { + if (_autoScrollTimer != null) { + return; } + _autoScrollTimer = Timer(const Duration(milliseconds: 200), () async { + xPosition = _resizingDetails.value.position.value?.dx; + if (xPosition != null && + xPosition! <= 0 && + ((_isRTL && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) || + (!_isRTL && _scrollController!.position.pixels != 0))) { + Future _updateScrollPosition() async { + double scrollPosition = + _scrollController!.position.pixels - timeIntervalHeight; + if (_isRTL) { + scrollPosition = + _scrollController!.position.pixels + timeIntervalHeight; + } + if (scrollPosition < 0 && !_isRTL) { + scrollPosition = 0; + } else if (_isRTL && + scrollPosition > _scrollController!.position.maxScrollExtent) { + scrollPosition = _scrollController!.position.maxScrollExtent; + } - _timelineViewHeaderScrollController!.jumpTo(_scrollController!.offset); - } - } + _resizingDetails.value.scrollPosition = scrollPosition; - void _updateTimeSlotView(_CalendarView oldWidget) { - _animationController ??= AnimationController( - duration: const Duration(milliseconds: 200), vsync: this); - _heightAnimation ??= - CurveTween(curve: Curves.easeIn).animate(_animationController!) - ..addListener(() { - setState(() { - /*Animates the all day panel when it's expanding or - collapsing*/ - }); - }); + _resizingDetails.value.position.value = Offset( + xPosition! - 0.1, _resizingDetails.value.position.value!.dy); - _expanderAnimationController ??= AnimationController( - duration: const Duration(milliseconds: 100), vsync: this); - _allDayExpanderAnimation ??= - CurveTween(curve: Curves.easeIn).animate(_expanderAnimationController!) - ..addListener(() { - setState(() { - /*Animates the all day panel when it's expanding or - collapsing*/ - }); - }); + await _scrollController!.position.animateTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + if (_resizingDetails.value.appointmentView == null) { + if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } - if (widget.view != CalendarView.day && _allDayHeight == 0) { - if (_animationController!.status == AnimationStatus.completed) { - _animationController!.reset(); - } + return; + } - _animationController!.forward(); - } - } + xPosition = _resizingDetails.value.position.value?.dx; + _updateMaximumResizingPosition(isForwardResize, isBackwardResize, + _resizingDetails.value.appointmentView!, null, null); + if ((isForwardResize && xPosition! < _maximumResizingPosition!) || + (isBackwardResize && xPosition! > _maximumResizingPosition!)) { + xPosition = _maximumResizingPosition!; + } - void _updateHorizontalLineCount(_CalendarView oldWidget) { - if (widget.calendar.timeSlotViewSettings.startHour != - oldWidget.calendar.timeSlotViewSettings.startHour || - widget.calendar.timeSlotViewSettings.endHour != - oldWidget.calendar.timeSlotViewSettings.endHour || - CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) != - CalendarViewHelper.getTimeInterval( - oldWidget.calendar.timeSlotViewSettings) || - oldWidget.view == CalendarView.month || - oldWidget.view == CalendarView.timelineMonth || - oldWidget.view != CalendarView.timelineMonth && - widget.view == CalendarView.timelineMonth) { - _horizontalLinesCount = CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); - } else { - _horizontalLinesCount = _horizontalLinesCount ?? - CalendarViewHelper.getHorizontalLinesCount( - widget.calendar.timeSlotViewSettings, widget.view); - } - } + _updateAppointmentResizingUpdateCallback( + isForwardResize, isBackwardResize, yPosition, null, null, + xPosition: xPosition, + timeLabelWidth: timeLabelWidth, + isResourceEnabled: isResourceEnabled, + details: details); + + _resizingDetails.value.position.value = + Offset(xPosition!, _resizingDetails.value.position.value!.dy); + + if (xPosition != null && + xPosition! <= 0 && + ((_isRTL && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) || + (!_isRTL && _scrollController!.position.pixels != 0))) { + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + } + + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + }); + } else if (xPosition + padding >= widget.width && + ((!_isRTL && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) || + (_isRTL && _scrollController!.position.pixels != 0))) { + if (_autoScrollTimer != null) { + return; + } + _autoScrollTimer = Timer(const Duration(milliseconds: 200), () async { + xPosition = _resizingDetails.value.position.value?.dx; + if (_resizingDetails.value.position.value != null && + xPosition! + padding >= widget.width && + ((!_isRTL && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) || + (_isRTL && _scrollController!.position.pixels != 0))) { + Future _updateScrollPosition() async { + double scrollPosition = + _scrollController!.position.pixels + timeIntervalHeight; + if (_isRTL) { + scrollPosition = + _scrollController!.position.pixels - timeIntervalHeight; + } + if (scrollPosition > _scrollController!.position.maxScrollExtent && + !_isRTL) { + scrollPosition = _scrollController!.position.maxScrollExtent; + } else if (_isRTL && scrollPosition < 0) { + scrollPosition = 0; + } - void _updateTimelineViews(_CalendarView oldWidget) { - _timelineRulerController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true) - ..addListener(_timeRulerListener); + _resizingDetails.value.scrollPosition = scrollPosition; - _timelineViewAnimationController ??= AnimationController( - duration: const Duration(milliseconds: 300), - vsync: this, - animationBehavior: AnimationBehavior.normal); + _resizingDetails.value.position.value = Offset( + xPosition! + 0.1, _resizingDetails.value.position.value!.dy); - _timelineViewAnimation ??= _timelineViewTween - .animate(_timelineViewAnimationController!) - ..addListener(_scrollAnimationListener); + await _scrollController!.position.moveTo( + scrollPosition, + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + ); + if (_resizingDetails.value.appointmentView == null) { + if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } - _timelineViewHeaderScrollController ??= - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewVerticalScrollController = - ScrollController(initialScrollOffset: 0, keepScrollOffset: true); - _timelineViewVerticalScrollController!.addListener(_updateResourceScroll); - widget.resourcePanelScrollController - ?.addListener(_updateResourcePanelScroll); - } + return; + } - void _getPainterProperties(UpdateCalendarStateDetails details) { - widget.getCalendarState(_updateCalendarStateDetails); - details.allDayAppointmentViewCollection = - _updateCalendarStateDetails.allDayAppointmentViewCollection; - details.currentViewVisibleDates = - _updateCalendarStateDetails.currentViewVisibleDates; - details.visibleAppointments = - _updateCalendarStateDetails.visibleAppointments; - details.selectedDate = _updateCalendarStateDetails.selectedDate; - } + xPosition = _resizingDetails.value.position.value?.dx; + _updateMaximumResizingPosition(isForwardResize, isBackwardResize, + _resizingDetails.value.appointmentView!, null, null); + if ((isForwardResize && xPosition! < _maximumResizingPosition!) || + (isBackwardResize && xPosition! > _maximumResizingPosition!)) { + xPosition = _maximumResizingPosition!; + } - Widget _addAllDayAppointmentPanel( - SfCalendarThemeData calendarTheme, bool isCurrentView) { - final Color borderColor = - widget.calendar.cellBorderColor ?? calendarTheme.cellBorderColor; - final Widget shadowView = Divider( - height: 1, - thickness: 1, - color: borderColor.withOpacity(borderColor.opacity * 0.5), - ); + _updateAppointmentResizingUpdateCallback( + isForwardResize, isBackwardResize, yPosition, null, null, + xPosition: xPosition, + timeLabelWidth: timeLabelWidth, + isResourceEnabled: isResourceEnabled, + details: details); + + _resizingDetails.value.position.value = + Offset(xPosition!, _resizingDetails.value.position.value!.dy); + + if (xPosition != null && + xPosition! + padding >= widget.width && + ((!_isRTL && + _scrollController!.position.pixels != + _scrollController!.position.maxScrollExtent) || + (_isRTL && _scrollController!.position.pixels != 0))) { + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + } - final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - double topPosition = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - if (widget.view == CalendarView.day) { - topPosition = _allDayHeight; + _updateScrollPosition(); + } else if (_autoScrollTimer != null) { + _autoScrollTimer!.cancel(); + _autoScrollTimer = null; + } + }); } + } - if (_allDayHeight == 0 || - (widget.view != CalendarView.day && - widget.visibleDates != - _updateCalendarStateDetails.currentViewVisibleDates)) { - return Positioned( - left: 0, right: 0, top: topPosition, height: 1, child: shadowView); - } + void _updateMaximumResizingPosition( + bool isForwardResize, + bool isBackwardResize, + AppointmentView appointmentView, + double? allDayPanelHeight, + double? viewHeaderHeight) { + switch (widget.view) { + case CalendarView.schedule: + break; + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + { + if (_resizingDetails.value.isAllDayPanel) { + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, + widget.view); + final double minimumCellWidth = + ((widget.width - timeLabelWidth) / widget.visibleDates.length) / + 2; + if (isForwardResize) { + _maximumResizingPosition = appointmentView.appointmentRect!.left + + (appointmentView.appointmentRect!.width > minimumCellWidth + ? minimumCellWidth + : appointmentView.appointmentRect!.width); + } else if (isBackwardResize) { + _maximumResizingPosition = + appointmentView.appointmentRect!.right - + (appointmentView.appointmentRect!.width > minimumCellWidth + ? minimumCellWidth + : appointmentView.appointmentRect!.width); + } + } else { + final double timeIntervalSize = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + double minimumTimeIntervalSize = timeIntervalSize / 4; + if (minimumTimeIntervalSize < 20) { + minimumTimeIntervalSize = 20; + } - if (widget.view == CalendarView.day) { - //// Default minimum view header width in day view as 50,so set 50 - //// when view header width less than 50. - topPosition = 0; + if (isForwardResize) { + _maximumResizingPosition = (appointmentView.appointmentRect!.top - + _scrollController!.offset + + allDayPanelHeight! + + viewHeaderHeight!) + + (appointmentView.appointmentRect!.height / 2 > + minimumTimeIntervalSize + ? minimumTimeIntervalSize + : appointmentView.appointmentRect!.height / 2); + } else if (isBackwardResize) { + _maximumResizingPosition = + (appointmentView.appointmentRect!.bottom - + _scrollController!.offset + + allDayPanelHeight! + + viewHeaderHeight!) - + (appointmentView.appointmentRect!.height / 2 > + minimumTimeIntervalSize + ? minimumTimeIntervalSize + : appointmentView.appointmentRect!.height / 2); + } + } + } + break; + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + { + final double timeIntervalSize = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + double minimumTimeIntervalSize = timeIntervalSize / + (widget.view == CalendarView.timelineMonth ? 2 : 4); + if (minimumTimeIntervalSize < 20) { + minimumTimeIntervalSize = 20; + } + if (isForwardResize) { + _maximumResizingPosition = appointmentView.appointmentRect!.left - + _scrollController!.offset; + if (_isRTL) { + _maximumResizingPosition = _scrollController!.offset - + _scrollController!.position.maxScrollExtent + + appointmentView.appointmentRect!.left; + } + _maximumResizingPosition = _maximumResizingPosition! + + (appointmentView.appointmentRect!.width / 2 > + minimumTimeIntervalSize + ? minimumTimeIntervalSize + : appointmentView.appointmentRect!.width / 2); + } else if (isBackwardResize) { + _maximumResizingPosition = appointmentView.appointmentRect!.right - + _scrollController!.offset; + if (_isRTL) { + _maximumResizingPosition = _scrollController!.offset - + _scrollController!.position.maxScrollExtent + + appointmentView.appointmentRect!.right; + } + _maximumResizingPosition = _maximumResizingPosition! - + (appointmentView.appointmentRect!.width / 2 > + minimumTimeIntervalSize + ? minimumTimeIntervalSize + : appointmentView.appointmentRect!.width / 2); + } + } + break; + case CalendarView.month: + { + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform); + final double minimumCellWidth = + ((widget.width - weekNumberPanelWidth) / DateTime.daysPerWeek) / + 2; + if (isForwardResize) { + _maximumResizingPosition = appointmentView.appointmentRect!.left + + (appointmentView.appointmentRect!.width / 2 > minimumCellWidth + ? minimumCellWidth + : appointmentView.appointmentRect!.width / 2); + } else if (isBackwardResize) { + _maximumResizingPosition = appointmentView.appointmentRect!.right - + (appointmentView.appointmentRect!.width / 2 > minimumCellWidth + ? minimumCellWidth + : appointmentView.appointmentRect!.width / 2); + } + } } + } - double panelHeight = isCurrentView - ? _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight - : 0; - if (panelHeight < 0) { - panelHeight = 0; + void _updateAppointmentResizingUpdateCallback( + bool isForwardResize, + bool isBackwardResize, + double yPosition, + double? viewHeaderHeight, + double? allDayPanelHeight, + {bool isResourceEnabled = false, + double? timeLabelWidth, + double? xPosition, + DragUpdateDetails? details}) { + final double timeIntervalHeight = _getTimeIntervalHeight( + widget.calendar, + widget.view, + widget.width, + widget.height, + widget.visibleDates.length, + _allDayHeight, + widget.isMobilePlatform); + late DateTime resizingTime; + CalendarResource? resource; + if (isResourceEnabled) { + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + resource = widget.calendar.dataSource!.resources![ + _getSelectedResourceIndex( + _resizingDetails.value.appointmentView!.appointmentRect!.top, + viewHeaderHeight, + timeLabelWidth!)]; } - /// Remove the all day appointment selection when the selected all - /// day appointment removed. - if (_allDaySelectionNotifier.value != null && - _allDaySelectionNotifier.value!.appointmentView != null && - (!_updateCalendarStateDetails.visibleAppointments.contains( - _allDaySelectionNotifier.value!.appointmentView!.appointment))) { - _allDaySelectionNotifier.value = null; + if (CalendarViewHelper.isTimelineView(widget.view)) { + double updatedXPosition = details!.localPosition.dx; + if (updatedXPosition > widget.width - 1) { + updatedXPosition = widget.width - 1; + } else if (updatedXPosition < 0) { + updatedXPosition = 0; + } + + resizingTime = _getDateFromPosition( + updatedXPosition, details.localPosition.dy, timeLabelWidth!)!; + final DateTime time = _timeFromPosition( + resizingTime, + widget.calendar.timeSlotViewSettings, + xPosition! > widget.width - 1 + ? widget.width - 1 + : (xPosition < 0 ? 0 : xPosition), + this, + timeIntervalHeight, + true)!; + + if (widget.view == CalendarView.timelineMonth) { + resizingTime = DateTime( + resizingTime.year, resizingTime.month, resizingTime.day, 0, 0, 0); + } else { + resizingTime = DateTime(resizingTime.year, resizingTime.month, + resizingTime.day, time.hour, time.minute, time.second); + } + } else { + final double updatedYPosition = + yPosition > widget.height - 1 ? widget.height - 1 : yPosition; + final double currentYPosition = + updatedYPosition - viewHeaderHeight! - allDayPanelHeight!; + resizingTime = _timeFromPosition( + _resizingDetails.value.appointmentView!.appointment!.actualStartTime, + widget.calendar.timeSlotViewSettings, + currentYPosition > 0 ? currentYPosition : 0, + this, + timeIntervalHeight, + false)!; } - final double allDayExpanderHeight = - _allDayHeight + (panelHeight * _allDayExpanderAnimation!.value); - return Positioned( - left: 0, - top: topPosition, - right: 0, - height: allDayExpanderHeight, - child: Stack( - children: [ - Positioned( - left: 0, - top: 0, - right: 0, - height: _isExpanded ? allDayExpanderHeight : _allDayHeight, - child: ListView( - physics: const NeverScrollableScrollPhysics(), - padding: const EdgeInsets.all(0.0), - children: [ - AllDayAppointmentLayout( - widget.calendar, - widget.view, - widget.visibleDates, - widget.visibleDates == - _updateCalendarStateDetails.currentViewVisibleDates - ? _updateCalendarStateDetails.visibleAppointments - : null, - timeLabelWidth, - allDayExpanderHeight, - panelHeight > 0 && - (_heightAnimation!.value == 1 || - widget.view == CalendarView.day), - _allDayExpanderAnimation!.value != 0.0 && - _allDayExpanderAnimation!.value != 1, - _isRTL, - widget.calendarTheme, - _allDaySelectionNotifier, - _allDayNotifier, - widget.textScaleFactor, - widget.isMobilePlatform, - widget.width, - (widget.view == CalendarView.day && - _updateCalendarStateDetails.allDayPanelHeight < - _allDayHeight) || - !isCurrentView - ? _allDayHeight - : _updateCalendarStateDetails.allDayPanelHeight, - widget.localizations, - _getPainterProperties), - ], - ), - ), - Positioned( - left: 0, - top: allDayExpanderHeight - 1, - right: 0, - height: 1, - child: shadowView), - ], - ), - ); + if (CalendarViewHelper.shouldRaiseAppointmentResizeUpdateCallback( + widget.calendar.onAppointmentResizeUpdate)) { + CalendarViewHelper.raiseAppointmentResizeUpdateCallback( + widget.calendar, + _getCalendarAppointmentToObject( + _resizingDetails.value.appointmentView!.appointment, + widget.calendar), + resource, + resizingTime, + _resizingDetails.value.position.value!); + } } - AppointmentLayout _addAppointmentPainter(double width, double height, - [double? resourceItemHeight]) { - final List? visibleAppointments = - widget.visibleDates == - _updateCalendarStateDetails.currentViewVisibleDates - ? _updateCalendarStateDetails.visibleAppointments - : null; - _appointmentLayout = AppointmentLayout( - widget.calendar, - widget.view, - widget.visibleDates, - ValueNotifier?>(visibleAppointments), - _timeIntervalHeight, - widget.calendarTheme, - _isRTL, - _appointmentHoverNotifier, - widget.resourceCollection, - resourceItemHeight, - widget.textScaleFactor, - widget.isMobilePlatform, - width, - height, - widget.localizations, - _getPainterProperties, - key: _appointmentLayoutKey, - ); - - return _appointmentLayout; + void _resetResizingPainter() { + SchedulerBinding.instance!.addPostFrameCallback((_) { + _resizingDetails.value.position.value = null; + }); + _resizingDetails.value.isAllDayPanel = false; + _resizingDetails.value.scrollPosition = null; + _resizingDetails.value.monthRowCount = 0; + _resizingDetails.value.monthCellHeight = null; + _resizingDetails.value.appointmentView = null; + _resizingDetails.value.appointmentColor = Colors.transparent; } // Returns the month view as a child for the calendar view. @@ -4290,7 +7923,8 @@ class _CalendarViewState extends State<_CalendarView> widget.textScaleFactor, widget.calendar.showWeekNumber, widget.isMobilePlatform, - widget.calendar.weekNumberStyle), + widget.calendar.weekNumberStyle, + widget.localizations), ), ), ), @@ -4360,6 +7994,64 @@ class _CalendarViewState extends State<_CalendarView> return _monthView; } + Widget _getResizeShadowView() { + if (widget.isMobilePlatform || !widget.calendar.allowAppointmentResize) { + return Container(width: 0, height: 0); + } + + final double viewHeaderHeight = widget.view == CalendarView.day + ? 0 + : CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double allDayPanelHeight = _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; + final bool isVerticalResize = _mouseCursor == SystemMouseCursors.resizeUp || + _mouseCursor == SystemMouseCursors.resizeDown; + final bool isAllDayPanel = !isVerticalResize && + (!CalendarViewHelper.isTimelineView(widget.view) && + widget.view != CalendarView.month); + + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform); + + return Positioned( + left: 0, + right: 0, + bottom: 0, + top: 0, + child: GestureDetector( + child: IgnorePointer( + ignoring: + _mouseCursor == SystemMouseCursors.basic || isAllDayPanel, + child: RepaintBoundary( + child: CustomPaint( + painter: _ResizingAppointmentPainter( + _resizingDetails, + _isRTL, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.calendar.appointmentTextStyle, + allDayPanelHeight, + viewHeaderHeight, + _scrollController, + widget.view, + _mouseCursor, + weekNumberPanelWidth, + widget.calendarTheme), + ))), + onVerticalDragStart: isVerticalResize ? _onVerticalStart : null, + onVerticalDragUpdate: isVerticalResize ? _onVerticalUpdate : null, + onVerticalDragEnd: isVerticalResize ? _onVerticalEnd : null, + onHorizontalDragStart: isVerticalResize ? null : _onHorizontalStart, + onHorizontalDragUpdate: isVerticalResize ? null : _onHorizontalUpdate, + onHorizontalDragEnd: isVerticalResize ? null : _onHorizontalEnd, + )); + } + // Returns the day view as a child for the calendar view. Widget _addDayView(double width, double height, bool isRTL, String locale, bool isCurrentView) { @@ -4421,7 +8113,8 @@ class _CalendarViewState extends State<_CalendarView> widget.textScaleFactor, widget.calendar.showWeekNumber, widget.isMobilePlatform, - widget.calendar.weekNumberStyle), + widget.calendar.weekNumberStyle, + widget.localizations), ), ), ), @@ -4696,7 +8389,7 @@ class _CalendarViewState extends State<_CalendarView> } /// Handles the tap and long press related functions for month view. - void _handleTouchOnMonthView( + AppointmentView? _handleTouchOnMonthView( TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { widget.removePicker(); final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; @@ -4720,7 +8413,7 @@ class _CalendarViewState extends State<_CalendarView> widget.isMobilePlatform); if ((!_isRTL && xDetails < weekNumberPanelWidth) || (_isRTL && xDetails > widget.width - weekNumberPanelWidth)) { - return; + return null; } if (yDetails < viewHeaderHeight) { if (isTapCallback) { @@ -4765,7 +8458,7 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.maxDate, selectedDate) || CalendarViewHelper.isDateInDateCollection( widget.blackoutDates, selectedDate)) { - return; + return null; } final int currentMonth = @@ -4778,7 +8471,7 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.monthViewSettings.showTrailingAndLeadingDates, currentMonth, selectedDate)) { - return; + return null; } _handleMonthCellTapNavigation(selectedDate); @@ -4797,13 +8490,13 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.onSelectionChanged); if (canRaiseLongPress || canRaiseTap || canRaiseSelectionChanged) { - final List selectedAppointments = - appointmentView == null || isMoreTapped - ? _getSelectedAppointments(selectedDate) - : [ - CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!) - ]; + final List selectedAppointments = appointmentView == null || + isMoreTapped + ? _getSelectedAppointments(selectedDate) + : [ + CalendarViewHelper.getAppointmentDetail( + appointmentView.appointment!, widget.calendar.dataSource) + ]; final CalendarElement selectedElement = appointmentView == null ? CalendarElement.calendarCell : isMoreTapped @@ -4820,6 +8513,7 @@ class _CalendarViewState extends State<_CalendarView> _updatedSelectionChangedCallback( canRaiseSelectionChanged, previousSelectedDate); } + return appointmentView; } } @@ -4857,11 +8551,6 @@ class _CalendarViewState extends State<_CalendarView> widget.controller.displayDate = date; } - //// Handles the onLongPress callback for month cells, and view header of month. - void _handleOnLongPressForMonth(LongPressStartDetails details) { - _handleTouchOnMonthView(null, details); - } - //// Handles the onTap callback for timeline view cells, and view header of timeline. void _handleOnTapForTimeline(TapUpDetails details) { _handleTouchOnTimeline(details, null); @@ -4884,7 +8573,7 @@ class _CalendarViewState extends State<_CalendarView> } /// Handles the tap and long press related functions for timeline view. - void _handleTouchOnTimeline( + AppointmentView? _handleTouchOnTimeline( TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { widget.removePicker(); final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; @@ -4922,7 +8611,7 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); if (yPosition < timeLabelWidth) { - return; + return null; } yPosition -= timeLabelWidth; @@ -4993,14 +8682,14 @@ class _CalendarViewState extends State<_CalendarView> (widget.view == CalendarView.timelineMonth && CalendarViewHelper.isDateInDateCollection( widget.calendar.blackoutDates, selectedDate))) { - return; + return null; } /// Restrict the callback, while selected region as disabled /// [TimeRegion]. if (!_isEnabledRegion( xDetails, selectedDate, _selectedResourceIndex)) { - return; + return null; } if (canRaiseTap) { @@ -5030,7 +8719,7 @@ class _CalendarViewState extends State<_CalendarView> selectedDate, [ CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!) + appointmentView.appointment!, widget.calendar.dataSource) ], CalendarElement.appointment, selectedResource); @@ -5040,7 +8729,7 @@ class _CalendarViewState extends State<_CalendarView> selectedDate, [ CalendarViewHelper.getAppointmentDetail( - appointmentView.appointment!) + appointmentView.appointment!, widget.calendar.dataSource) ], CalendarElement.appointment, selectedResource); @@ -5052,13 +8741,9 @@ class _CalendarViewState extends State<_CalendarView> previousSelectedResourceIndex); } } - } - } - //// Handles the onLongPress callback for timeline view cells, and view header - //// of timeline. - void _handleOnLongPressForTimeline(LongPressStartDetails details) { - _handleTouchOnTimeline(null, details); + return appointmentView; + } } void _updateAllDaySelection(AppointmentView? view, DateTime? date) { @@ -5079,7 +8764,7 @@ class _CalendarViewState extends State<_CalendarView> /// Handles the tap and long press related functions for day, week /// work week views. - void _handleTouchOnDayView( + AppointmentView? _handleTouchOnDayView( TapUpDetails? tapDetails, LongPressStartDetails? longPressDetails) { widget.removePicker(); final DateTime? previousSelectedDate = _selectionPainter!.selectedDate; @@ -5100,6 +8785,7 @@ class _CalendarViewState extends State<_CalendarView> } widget.getCalendarState(_updateCalendarStateDetails); + AppointmentView? selectedAppointmentView; dynamic selectedAppointment; List? selectedAppointments; CalendarElement targetElement = CalendarElement.viewHeader; @@ -5117,13 +8803,13 @@ class _CalendarViewState extends State<_CalendarView> if (!_isRTL && xDetails <= timeLabelWidth && yDetails > viewHeaderHeight + allDayHeight) { - return; + return null; } if (_isRTL && xDetails >= widget.width - timeLabelWidth && yDetails > viewHeaderHeight + allDayHeight) { - return; + return null; } if (yDetails < viewHeaderHeight) { @@ -5132,7 +8818,7 @@ class _CalendarViewState extends State<_CalendarView> /// else time ruler placed at left side. if ((!_isRTL && xDetails <= timeLabelWidth) || (_isRTL && widget.width - xDetails <= timeLabelWidth)) { - return; + return null; } if (isTappedCallback) { @@ -5141,7 +8827,7 @@ class _CalendarViewState extends State<_CalendarView> _handleOnLongPressForViewHeader(longPressDetails!, widget.width); } - return; + return null; } else if (yDetails < viewHeaderHeight + allDayHeight) { /// Check the touch position in view header when [CalendarView] is day /// If RTL, view header placed at right side, @@ -5158,13 +8844,13 @@ class _CalendarViewState extends State<_CalendarView> _handleOnLongPressForViewHeader(longPressDetails!, widget.width); } - return; + return null; } else if ((!_isRTL && timeLabelWidth >= xDetails) || (_isRTL && xDetails > widget.width - timeLabelWidth)) { /// Perform expand or collapse when the touch position on /// expander icon in all day panel. _expandOrCollapseAllDay(); - return; + return null; } final double yPosition = yDetails - viewHeaderHeight; @@ -5256,7 +8942,7 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.maxDate, appointmentView.appointment!.actualEndTime, timeInterval)) { - return; + return null; } if (selectedDate != null) { selectedDate = null; @@ -5272,7 +8958,7 @@ class _CalendarViewState extends State<_CalendarView> _updateAllDaySelection(appointmentView, null); } else if (isTappedOnCount) { _expandOrCollapseAllDay(); - return; + return null; } else if (appointmentView == null) { _updateAllDaySelection(null, selectedDate); _selectionPainter!.selectedDate = null; @@ -5280,6 +8966,8 @@ class _CalendarViewState extends State<_CalendarView> _selectionNotifier.value = !_selectionNotifier.value; _updateCalendarStateDetails.selectedDate = null; } + + selectedAppointmentView = appointmentView; } else { final double yPosition = yDetails - viewHeaderHeight - @@ -5306,6 +8994,7 @@ class _CalendarViewState extends State<_CalendarView> _selectionPainter!.appointmentView = appointmentView; _selectionNotifier.value = !_selectionNotifier.value; + selectedAppointmentView = appointmentView; selectedAppointment = appointmentView.appointment; targetElement = CalendarElement.appointment; } @@ -5340,7 +9029,7 @@ class _CalendarViewState extends State<_CalendarView> widget.calendar.maxDate, selectedDate!, timeInterval)) { - return; + return null; } /// Restrict the callback, while selected region as disabled @@ -5348,7 +9037,7 @@ class _CalendarViewState extends State<_CalendarView> if (targetElement == CalendarElement.calendarCell && !_isEnabledRegion( yPosition, selectedDate, _selectedResourceIndex)) { - return; + return null; } if (canRaiseTap) { @@ -5370,7 +9059,8 @@ class _CalendarViewState extends State<_CalendarView> canRaiseSelectionChanged, previousSelectedDate); } else if (selectedAppointment != null) { selectedAppointments = [ - CalendarViewHelper.getAppointmentDetail(selectedAppointment) + CalendarViewHelper.getAppointmentDetail( + selectedAppointment, widget.calendar.dataSource) ]; /// In LTR, remove the time ruler width value from the @@ -5410,12 +9100,15 @@ class _CalendarViewState extends State<_CalendarView> canRaiseSelectionChanged, previousSelectedDate); } } + + return selectedAppointmentView; } /// Check the selected date region as enabled time region or not. bool _isEnabledRegion(double y, DateTime? selectedDate, int resourceIndex) { if (widget.regions == null || widget.regions!.isEmpty || + widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth || selectedDate == null) { return true; @@ -5588,12 +9281,6 @@ class _CalendarViewState extends State<_CalendarView> return false; } - //// Handles the onLongPress callback for day view cells, all day panel and - //// view header of day. - void _handleOnLongPressForDay(LongPressStartDetails details) { - _handleTouchOnDayView(null, details); - } - //// Handles the on tap callback for view header void _handleOnTapForViewHeader(TapUpDetails details, double width) { final DateTime tappedDate = @@ -5714,6 +9401,11 @@ class _CalendarViewState extends State<_CalendarView> if (_allDayNotifier.value != null) { _allDayNotifier.value = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } } if (_hoveringDate != null) { @@ -5735,6 +9427,11 @@ class _CalendarViewState extends State<_CalendarView> if (_appointmentHoverNotifier.value != null) { _appointmentHoverNotifier.value = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } } if (_hoveringDate != null) { @@ -5773,10 +9470,22 @@ class _CalendarViewState extends State<_CalendarView> if (_allDayNotifier.value != null) { _allDayNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } } if (_appointmentHoverNotifier.value != null) { _appointmentHoverNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } } } @@ -5799,2232 +9508,3667 @@ class _CalendarViewState extends State<_CalendarView> _hoveringDate = null; } - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; + } + + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } + + if (_allDayNotifier.value != null) { + _allDayNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + } + + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + } + } + + final DateTime? hoverDate = _getTappedViewHeaderDate( + Offset( + CalendarViewHelper.isTimelineView(widget.view) + ? localPosition.dx + : xPosition, + yPosition), + widget.width); + + // Remove the hovering when the position not in cell regions. + if (hoverDate == null) { + _removeViewHeaderHovering(); + + return; + } + + if (!isDateWithInDateRange( + widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) { + _removeViewHeaderHovering(); + + return; + } + + if (widget.view == CalendarView.timelineMonth && + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, hoverDate)) { + _removeViewHeaderHovering(); + + return; + } + + _hoveringDate = hoverDate; + + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } + + if (_allDayNotifier.value != null) { + _allDayNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + } + + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + } + + _viewHeaderNotifier.value = Offset(xPosition, yPosition); + } + + void _updateMouseCursorForAppointment(AppointmentView? appointmentView, + double xPosition, double yPosition, bool isTimelineViews, + {bool isAllDayPanel = false}) { + _hoveringAppointmentView = appointmentView; + if (!widget.calendar.allowAppointmentResize || + (widget.view == CalendarView.month && + widget.calendar.monthViewSettings.appointmentDisplayMode != + MonthAppointmentDisplayMode.appointment)) { + return; + } + + if (appointmentView == null || appointmentView.appointment == null) { + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + + return; + } + + const double padding = 5; + + if (isAllDayPanel || + (widget.view == CalendarView.month || isTimelineViews)) { + final bool isMonthView = widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth; + final DateTime viewStartDate = + AppointmentHelper.convertToStartTime(widget.visibleDates[0]); + final DateTime viewEndDate = AppointmentHelper.convertToEndTime( + widget.visibleDates[widget.visibleDates.length - 1]); + final DateTime appStartTime = appointmentView.appointment!.exactStartTime; + final DateTime appEndTime = appointmentView.appointment!.exactEndTime; + + final bool canAddForwardSpanIcon = + AppointmentHelper.canAddForwardSpanIcon( + appStartTime, appEndTime, viewStartDate, viewEndDate); + final bool canAddBackwardSpanIcon = + AppointmentHelper.canAddBackwardSpanIcon( + appStartTime, appEndTime, viewStartDate, viewEndDate); + + final DateTime appointmentStartTime = + appointmentView.appointment!.isAllDay + ? AppointmentHelper.convertToStartTime( + appointmentView.appointment!.actualStartTime) + : appointmentView.appointment!.actualStartTime; + final DateTime appointmentEndTime = appointmentView.appointment!.isAllDay + ? AppointmentHelper.convertToEndTime( + appointmentView.appointment!.actualEndTime) + : appointmentView.appointment!.actualEndTime; + final DateTime appointmentExactStartTime = + appointmentView.appointment!.isAllDay + ? AppointmentHelper.convertToStartTime( + appointmentView.appointment!.exactStartTime) + : appointmentView.appointment!.exactStartTime; + final DateTime appointmentExactEndTime = + appointmentView.appointment!.isAllDay + ? AppointmentHelper.convertToEndTime( + appointmentView.appointment!.exactEndTime) + : appointmentView.appointment!.exactEndTime; + + if (xPosition >= appointmentView.appointmentRect!.left && + xPosition <= appointmentView.appointmentRect!.left + padding && + ((isMonthView && + isSameDate( + _isRTL ? appointmentEndTime : appointmentStartTime, + _isRTL + ? appointmentExactEndTime + : appointmentExactStartTime)) || + (!isMonthView && + CalendarViewHelper.isSameTimeSlot( + _isRTL ? appointmentEndTime : appointmentStartTime, + _isRTL + ? appointmentExactEndTime + : appointmentExactStartTime))) && + ((_isRTL && !canAddForwardSpanIcon) || + (!_isRTL && !canAddBackwardSpanIcon))) { + setState(() { + _mouseCursor = SystemMouseCursors.resizeLeft; + }); + } else if (xPosition <= appointmentView.appointmentRect!.right && + xPosition >= appointmentView.appointmentRect!.right - padding && + ((isMonthView && + isSameDate( + _isRTL ? appointmentStartTime : appointmentEndTime, + _isRTL + ? appointmentExactStartTime + : appointmentExactEndTime)) || + (!isMonthView && + CalendarViewHelper.isSameTimeSlot( + _isRTL ? appointmentStartTime : appointmentEndTime, + _isRTL + ? appointmentExactStartTime + : appointmentExactEndTime))) && + ((_isRTL && !canAddBackwardSpanIcon) || + (!_isRTL && !canAddForwardSpanIcon))) { + setState(() { + _mouseCursor = SystemMouseCursors.resizeRight; + }); + } else if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + } else { + if (yPosition >= appointmentView.appointmentRect!.top && + yPosition <= appointmentView.appointmentRect!.top + padding && + CalendarViewHelper.isSameTimeSlot( + appointmentView.appointment!.actualStartTime, + appointmentView.appointment!.exactStartTime)) { + setState(() { + _mouseCursor = SystemMouseCursors.resizeUp; + }); + } else if (yPosition <= appointmentView.appointmentRect!.bottom && + yPosition >= appointmentView.appointmentRect!.bottom - padding && + CalendarViewHelper.isSameTimeSlot( + appointmentView.appointment!.actualEndTime, + appointmentView.appointment!.exactEndTime)) { + setState(() { + _mouseCursor = SystemMouseCursors.resizeDown; + }); + } else if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + } + } + + void _updatePointerHover(Offset globalPosition) { + if (widget.isMobilePlatform || + _resizingDetails.value.appointmentView != null || + widget.dragDetails.value.appointmentView != null && + widget.calendar.appointmentBuilder == null) { + return; + } + + // ignore: avoid_as + final RenderBox box = context.findRenderObject()! as RenderBox; + final Offset localPosition = box.globalToLocal(globalPosition); + double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view); + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); + double allDayHeight = _isExpanded + ? _updateCalendarStateDetails.allDayPanelHeight + : _allDayHeight; + + /// All day panel and view header are arranged horizontally, + /// so get the maximum value from all day height and view header height and + /// use the value instead of adding of view header height and all day + /// height. + if (widget.view == CalendarView.day) { + if (allDayHeight > viewHeaderHeight) { + viewHeaderHeight = allDayHeight; + } + + allDayHeight = 0; + } + + double xPosition; + double yPosition; + final bool isTimelineViews = CalendarViewHelper.isTimelineView(widget.view); + if (widget.view != CalendarView.month && !isTimelineViews) { + /// In LTR, remove the time ruler width value from the + /// touch x position while calculate the selected date from position. + xPosition = _isRTL ? localPosition.dx : localPosition.dx - timeLabelWidth; + + if (localPosition.dy < viewHeaderHeight) { + if (widget.view == CalendarView.day) { + if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || + (!_isRTL && localPosition.dx > timeLabelWidth)) { + _updateHoveringForAllDayPanel(localPosition.dx, localPosition.dy); + + final AppointmentView? appointment = _getAllDayAppointmentOnPoint( + _updateCalendarStateDetails.allDayAppointmentViewCollection, + localPosition.dx, + localPosition.dy); + _updateMouseCursorForAppointment(appointment, localPosition.dx, + localPosition.dy, isTimelineViews, + isAllDayPanel: true); + return; + } + + _updateHoveringForViewHeader( + localPosition, + _isRTL ? widget.width - localPosition.dx : localPosition.dx, + localPosition.dy, + viewHeaderHeight); + return; + } + + _updateHoveringForViewHeader(localPosition, localPosition.dx, + localPosition.dy, viewHeaderHeight); + return; + } + + double panelHeight = + _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight; + if (panelHeight < 0) { + panelHeight = 0; + } + + final double allDayExpanderHeight = + panelHeight * _allDayExpanderAnimation!.value; + final double allDayBottom = widget.view == CalendarView.day + ? viewHeaderHeight + : viewHeaderHeight + _allDayHeight + allDayExpanderHeight; + if (localPosition.dy > viewHeaderHeight && + localPosition.dy < allDayBottom) { + if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || + (!_isRTL && localPosition.dx > timeLabelWidth)) { + _updateHoveringForAllDayPanel( + localPosition.dx, localPosition.dy - viewHeaderHeight); + final AppointmentView? appointment = _getAllDayAppointmentOnPoint( + _updateCalendarStateDetails.allDayAppointmentViewCollection, + localPosition.dx, + localPosition.dy - viewHeaderHeight); + _updateMouseCursorForAppointment(appointment, localPosition.dx, + localPosition.dy - viewHeaderHeight, isTimelineViews, + isAllDayPanel: true); + } else { + _removeAllWidgetHovering(); + } + + return; + } + + yPosition = localPosition.dy - (viewHeaderHeight + allDayHeight); + + final AppointmentView? appointment = + _appointmentLayout.getAppointmentViewOnPoint( + localPosition.dx, yPosition + _scrollController!.offset); + _hoveringAppointmentView = appointment; + if (appointment != null) { + _updateHoveringForAppointment( + localPosition.dx, yPosition + _scrollController!.offset); + _updateMouseCursorForAppointment(appointment, localPosition.dx, + yPosition + _scrollController!.offset, isTimelineViews); + _hoveringDate = null; + return; + } + } else { + xPosition = localPosition.dx; + + /// Remove the hovering when the position not in week number panel. + if (widget.calendar.showWeekNumber && widget.view == CalendarView.month) { + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform); + if ((!_isRTL && xPosition < weekNumberPanelWidth) || + (_isRTL && xPosition > widget.width - weekNumberPanelWidth)) { + _hoveringDate = null; + _calendarCellNotifier.value = null; + _viewHeaderNotifier.value = null; + _appointmentHoverNotifier.value = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + _allDayNotifier.value = null; + _hoveringAppointmentView = null; + return; + } + } + + /// Update the x position value with scroller offset and the value + /// assigned to mouse hover position. + /// mouse hover position value used for highlight the position + /// on all the calendar views. + if (isTimelineViews) { + if (_isRTL) { + xPosition = (_getSingleViewWidthForTimeLineView(this) * + widget.visibleDates.length) - + (_scrollController!.offset + + (_scrollController!.position.viewportDimension - + localPosition.dx)); + } else { + xPosition = localPosition.dx + _scrollController!.offset; + } + } + + if (localPosition.dy < viewHeaderHeight) { + _updateHoveringForViewHeader( + localPosition, xPosition, localPosition.dy, viewHeaderHeight); + return; + } + + yPosition = localPosition.dy - viewHeaderHeight - timeLabelWidth; + if (CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view)) { + yPosition += _timelineViewVerticalScrollController!.offset; + } + + final AppointmentView? appointment = + _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); + _hoveringAppointmentView = appointment; + if (appointment != null) { + _updateHoveringForAppointment(xPosition, yPosition); + _updateMouseCursorForAppointment( + appointment, xPosition, yPosition, isTimelineViews); + _hoveringDate = null; + return; + } + } + + /// Remove the hovering when the position not in cell regions. + if (yPosition < 0) { + if (_hoveringDate != null) { + _hoveringDate = null; + } + + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } + + return; + } + + final DateTime? hoverDate = _getDateFromPosition( + isTimelineViews ? localPosition.dx : xPosition, + yPosition, + timeLabelWidth); + + /// Remove the hovering when the position not in cell regions or non active + /// cell regions. + final bool isMonthView = widget.view == CalendarView.month || + widget.view == CalendarView.timelineMonth; + final int timeInterval = CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings); + if (hoverDate == null || + (isMonthView && + !isDateWithInDateRange( + widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + widget.calendar.minDate, + widget.calendar.maxDate, + hoverDate, + timeInterval))) { + if (_hoveringDate != null) { + _hoveringDate = null; } if (_calendarCellNotifier.value != null) { _calendarCellNotifier.value = null; } - if (_allDayNotifier.value != null) { - _allDayNotifier.value = null; + return; + } + + /// Check the hovering month cell date is blackout date. + if (isMonthView && + CalendarViewHelper.isDateInDateCollection( + widget.blackoutDates, hoverDate)) { + if (_hoveringDate != null) { + _hoveringDate = null; + } + + /// Remove the existing cell hovering. + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; } + /// Remove the existing appointment hovering. if (_appointmentHoverNotifier.value != null) { _appointmentHoverNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } } + + return; } - final DateTime? hoverDate = _getTappedViewHeaderDate( - Offset( - CalendarViewHelper.isTimelineView(widget.view) - ? localPosition.dx - : xPosition, - yPosition), - widget.width); + final int hoveringResourceIndex = + _getSelectedResourceIndex(yPosition, viewHeaderHeight, timeLabelWidth); - // Remove the hovering when the position not in cell regions. - if (hoverDate == null) { - _removeViewHeaderHovering(); + /// Restrict the hovering, while selected region as disabled [TimeRegion]. + if (((widget.view == CalendarView.day || + widget.view == CalendarView.week || + widget.view == CalendarView.workWeek) && + !_isEnabledRegion(yPosition, hoverDate, hoveringResourceIndex)) || + (isTimelineViews && + !_isEnabledRegion( + localPosition.dx, hoverDate, hoveringResourceIndex))) { + if (_hoveringDate != null) { + _hoveringDate = null; + } + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } return; } - if (!isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) { - _removeViewHeaderHovering(); + final int currentMonth = + widget.visibleDates[widget.visibleDates.length ~/ 2].month; + + /// Check the selected cell date as trailing or leading date when + /// [SfCalendar] month not shown leading and trailing dates. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + hoverDate)) { + if (_hoveringDate != null) { + _hoveringDate = null; + } + + /// Remove the existing cell hovering. + if (_calendarCellNotifier.value != null) { + _calendarCellNotifier.value = null; + } + + /// Remove the existing appointment hovering. + if (_appointmentHoverNotifier.value != null) { + _appointmentHoverNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } + } return; } - if (widget.view == CalendarView.timelineMonth && - CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, hoverDate)) { - _removeViewHeaderHovering(); + final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( + widget.calendar.dataSource, widget.view); - return; + /// If resource enabled the selected date or time slot can be same but the + /// resource value differs hence to handle this scenario we are excluding + /// the following conditions, if resource enabled. + if (!isResourceEnabled) { + if ((widget.view == CalendarView.month && + isSameDate(_hoveringDate, hoverDate) && + _viewHeaderNotifier.value == null) || + (widget.view != CalendarView.month && + CalendarViewHelper.isSameTimeSlot(_hoveringDate, hoverDate) && + _viewHeaderNotifier.value == null)) { + return; + } } _hoveringDate = hoverDate; - if (_calendarCellNotifier.value != null) { + if (widget.view == CalendarView.month && + isSameDate(_selectionPainter!.selectedDate, _hoveringDate)) { + _calendarCellNotifier.value = null; + return; + } else if (widget.view != CalendarView.month && + CalendarViewHelper.isSameTimeSlot( + _selectionPainter!.selectedDate, _hoveringDate) && + hoveringResourceIndex == _selectedResourceIndex) { _calendarCellNotifier.value = null; + return; + } + + if (widget.view != CalendarView.month && !isTimelineViews) { + yPosition += _scrollController!.offset; + } + + if (_viewHeaderNotifier.value != null) { + _viewHeaderNotifier.value = null; } if (_allDayNotifier.value != null) { _allDayNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } } if (_appointmentHoverNotifier.value != null) { _appointmentHoverNotifier.value = null; + _hoveringAppointmentView = null; + if (_mouseCursor != SystemMouseCursors.basic) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); + } } - _viewHeaderNotifier.value = Offset(xPosition, yPosition); + _calendarCellNotifier.value = Offset(xPosition, yPosition); } - void _updatePointerHover(Offset globalPosition) { - if (widget.isMobilePlatform) { - return; + void _pointerEnterEvent(PointerEnterEvent event) { + _updatePointerHover(event.position); + } + + void _pointerHoverEvent(PointerHoverEvent event) { + _updatePointerHover(event.position); + } + + void _pointerExitEvent(PointerExitEvent event) { + _hoveringDate = null; + _calendarCellNotifier.value = null; + _viewHeaderNotifier.value = null; + _appointmentHoverNotifier.value = null; + if (_mouseCursor != SystemMouseCursors.basic && + _resizingDetails.value.appointmentView == null) { + setState(() { + _mouseCursor = SystemMouseCursors.basic; + }); } + _allDayNotifier.value = null; + _hoveringAppointmentView = null; + } - // ignore: avoid_as - final RenderBox box = context.findRenderObject()! as RenderBox; - final Offset localPosition = box.globalToLocal(globalPosition); - double viewHeaderHeight = CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view); - final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - widget.calendar.timeSlotViewSettings.timeRulerSize, widget.view); - double allDayHeight = _isExpanded - ? _updateCalendarStateDetails.allDayPanelHeight - : _allDayHeight; + AppointmentView? _getAllDayAppointmentOnPoint( + List? appointmentCollection, double x, double y) { + if (appointmentCollection == null) { + return null; + } - /// All day panel and view header are arranged horizontally, - /// so get the maximum value from all day height and view header height and - /// use the value instead of adding of view header height and all day - /// height. - if (widget.view == CalendarView.day) { - if (allDayHeight > viewHeaderHeight) { - viewHeaderHeight = allDayHeight; + AppointmentView? selectedAppointmentView; + for (int i = 0; i < appointmentCollection.length; i++) { + final AppointmentView appointmentView = appointmentCollection[i]; + if (appointmentView.appointment != null && + appointmentView.appointmentRect != null && + appointmentView.appointmentRect!.left <= x && + appointmentView.appointmentRect!.right >= x && + appointmentView.appointmentRect!.top <= y && + appointmentView.appointmentRect!.bottom >= y) { + selectedAppointmentView = appointmentView; + break; } - - allDayHeight = 0; } - double xPosition; - double yPosition; - final bool isTimelineViews = CalendarViewHelper.isTimelineView(widget.view); - if (widget.view != CalendarView.month && !isTimelineViews) { - /// In LTR, remove the time ruler width value from the - /// touch x position while calculate the selected date from position. - xPosition = _isRTL ? localPosition.dx : localPosition.dx - timeLabelWidth; + return selectedAppointmentView; + } - if (localPosition.dy < viewHeaderHeight) { - if (widget.view == CalendarView.day) { - if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || - (!_isRTL && localPosition.dx > timeLabelWidth)) { - _updateHoveringForAllDayPanel(localPosition.dx, localPosition.dy); - return; - } + List _getSelectedAppointments(DateTime selectedDate) { + return (widget.calendar.dataSource != null && + !AppointmentHelper.isCalendarAppointment( + widget.calendar.dataSource!)) + ? CalendarViewHelper.getCustomAppointments( + AppointmentHelper.getSelectedDateAppointments( + _updateCalendarStateDetails.appointments, + widget.calendar.timeZone, + selectedDate), + widget.calendar.dataSource) + : (AppointmentHelper.getSelectedDateAppointments( + _updateCalendarStateDetails.appointments, + widget.calendar.timeZone, + selectedDate)); + } - _updateHoveringForViewHeader( - localPosition, - _isRTL ? widget.width - localPosition.dx : localPosition.dx, - localPosition.dy, - viewHeaderHeight); - return; - } + DateTime? _getDateFromPositionForMonth( + double cellWidth, double cellHeight, double x, double y) { + final int rowIndex = (x / cellWidth).truncate(); + final int columnIndex = (y / cellHeight).truncate(); + int index = 0; + if (_isRTL) { + index = (columnIndex * DateTime.daysPerWeek) + + (DateTime.daysPerWeek - rowIndex) - + 1; + } else { + index = (columnIndex * DateTime.daysPerWeek) + rowIndex; + } - _updateHoveringForViewHeader(localPosition, localPosition.dx, - localPosition.dy, viewHeaderHeight); - return; - } + if (index < 0 || index >= widget.visibleDates.length) { + return null; + } - double panelHeight = - _updateCalendarStateDetails.allDayPanelHeight - _allDayHeight; - if (panelHeight < 0) { - panelHeight = 0; - } + return widget.visibleDates[index]; + } - final double allDayExpanderHeight = - panelHeight * _allDayExpanderAnimation!.value; - final double allDayBottom = widget.view == CalendarView.day - ? viewHeaderHeight - : viewHeaderHeight + _allDayHeight + allDayExpanderHeight; - if (localPosition.dy > viewHeaderHeight && - localPosition.dy < allDayBottom) { - if ((_isRTL && localPosition.dx < widget.width - timeLabelWidth) || - (!_isRTL && localPosition.dx > timeLabelWidth)) { - _updateHoveringForAllDayPanel( - localPosition.dx, localPosition.dy - viewHeaderHeight); - } else { - _removeAllWidgetHovering(); - } + DateTime _getDateFromPositionForDay( + double cellWidth, double cellHeight, double x, double y) { + final int columnIndex = + ((_scrollController!.offset + y) / cellHeight).truncate(); + final double time = columnIndex == -1 + ? 0 + : ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + columnIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + return DateTime(widget.visibleDates[0].year, widget.visibleDates[0].month, + widget.visibleDates[0].day, hour, minute); + } - return; - } + DateTime? _getDateFromPositionForWeek( + double cellWidth, double cellHeight, double x, double y) { + final int columnIndex = + ((_scrollController!.offset + y) / cellHeight).truncate(); + final double time = columnIndex == -1 + ? 0 + : ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + columnIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + int rowIndex = (x / cellWidth).truncate(); + if (_isRTL) { + rowIndex = (widget.visibleDates.length - rowIndex) - 1; + } - yPosition = localPosition.dy - (viewHeaderHeight + allDayHeight); + if (rowIndex < 0 || rowIndex >= widget.visibleDates.length) { + return null; + } - final AppointmentView? appointment = - _appointmentLayout.getAppointmentViewOnPoint( - localPosition.dx, yPosition + _scrollController!.offset); - if (appointment != null) { - _updateHoveringForAppointment( - localPosition.dx, yPosition + _scrollController!.offset); - _hoveringDate = null; - return; - } - } else { - xPosition = localPosition.dx; + final DateTime date = widget.visibleDates[rowIndex]; - /// Remove the hovering when the position not in week number panel. - if (widget.calendar.showWeekNumber && widget.view == CalendarView.month) { - final double weekNumberPanelWidth = - CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); - if ((!_isRTL && xPosition < weekNumberPanelWidth) || - (_isRTL && xPosition > widget.width - weekNumberPanelWidth)) { - _hoveringDate = null; - _calendarCellNotifier.value = null; - _viewHeaderNotifier.value = null; - _appointmentHoverNotifier.value = null; - _allDayNotifier.value = null; - return; - } - } + return DateTime(date.year, date.month, date.day, hour, minute); + } - /// Update the x position value with scroller offset and the value - /// assigned to mouse hover position. - /// mouse hover position value used for highlight the position - /// on all the calendar views. - if (isTimelineViews) { - if (_isRTL) { - xPosition = (_getSingleViewWidthForTimeLineView(this) * - widget.visibleDates.length) - - (_scrollController!.offset + - (_scrollController!.position.viewportDimension - - localPosition.dx)); - } else { - xPosition = localPosition.dx + _scrollController!.offset; - } - } + DateTime? _getDateFromPositionForTimeline( + double cellWidth, double cellHeight, double x, double y) { + int rowIndex, columnIndex; + if (_isRTL) { + rowIndex = (((_scrollController!.offset % + _getSingleViewWidthForTimeLineView(this)) + + (_scrollController!.position.viewportDimension - x)) / + cellWidth) + .truncate(); + } else { + rowIndex = (((_scrollController!.offset % + _getSingleViewWidthForTimeLineView(this)) + + x) / + cellWidth) + .truncate(); + } + columnIndex = + (_scrollController!.offset / _getSingleViewWidthForTimeLineView(this)) + .truncate(); + if (rowIndex >= _horizontalLinesCount!) { + columnIndex += rowIndex ~/ _horizontalLinesCount!; + rowIndex = (rowIndex % _horizontalLinesCount!).toInt(); + } + final double time = ((CalendarViewHelper.getTimeInterval( + widget.calendar.timeSlotViewSettings) / + 60) * + rowIndex) + + widget.calendar.timeSlotViewSettings.startHour; + final int hour = time.toInt(); + final int minute = ((time - hour) * 60).round(); + if (columnIndex < 0) { + columnIndex = 0; + } else if (columnIndex > widget.visibleDates.length) { + columnIndex = widget.visibleDates.length - 1; + } - if (localPosition.dy < viewHeaderHeight) { - _updateHoveringForViewHeader( - localPosition, xPosition, localPosition.dy, viewHeaderHeight); - return; - } + if (columnIndex < 0 || columnIndex >= widget.visibleDates.length) { + return null; + } - yPosition = localPosition.dy - viewHeaderHeight - timeLabelWidth; - if (CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view)) { - yPosition += _timelineViewVerticalScrollController!.offset; - } + final DateTime date = widget.visibleDates[columnIndex]; - final AppointmentView? appointment = - _appointmentLayout.getAppointmentViewOnPoint(xPosition, yPosition); - if (appointment != null) { - _updateHoveringForAppointment(xPosition, yPosition); - _hoveringDate = null; - return; - } - } + return DateTime(date.year, date.month, date.day, hour, minute); + } - /// Remove the hovering when the position not in cell regions. - if (yPosition < 0) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + DateTime? _getDateFromPosition(double x, double y, double timeLabelWidth) { + double cellWidth = 0; + double cellHeight = 0; + final double width = widget.width - timeLabelWidth; + switch (widget.view) { + case CalendarView.schedule: + return null; + case CalendarView.month: + { + /// Remove the selection when the position is to week number panel. + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + widget.calendar.showWeekNumber, + widget.width, + widget.isMobilePlatform); + if (x > widget.width || + (!_isRTL && x < weekNumberPanelWidth) || + (_isRTL && x > widget.width - weekNumberPanelWidth)) { + return null; + } - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; - } + /// In RTL the week number panel will render on the right side hence, + /// we didn't consider the week number panel width in rtl. + if (!_isRTL) { + x -= weekNumberPanelWidth; + } - return; + cellWidth = + (widget.width - weekNumberPanelWidth) / DateTime.daysPerWeek; + cellHeight = (widget.height - + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view)) / + widget.calendar.monthViewSettings.numberOfWeeksInView; + return _getDateFromPositionForMonth(cellWidth, cellHeight, x, y); + } + case CalendarView.day: + { + if (y >= _timeIntervalHeight * _horizontalLinesCount! || + x > width || + x < 0) { + return null; + } + cellWidth = width; + cellHeight = _timeIntervalHeight; + return _getDateFromPositionForDay(cellWidth, cellHeight, x, y); + } + case CalendarView.week: + case CalendarView.workWeek: + { + if (y >= _timeIntervalHeight * _horizontalLinesCount! || + x > width || + x < 0) { + return null; + } + cellWidth = width / widget.visibleDates.length; + cellHeight = _timeIntervalHeight; + return _getDateFromPositionForWeek(cellWidth, cellHeight, x, y); + } + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + { + final double viewWidth = _timeIntervalHeight * + (_horizontalLinesCount! * widget.visibleDates.length); + if ((!_isRTL && x >= viewWidth) || + (_isRTL && x < (widget.width - viewWidth))) { + return null; + } + cellWidth = _timeIntervalHeight; + cellHeight = widget.height; + return _getDateFromPositionForTimeline(cellWidth, cellHeight, x, y); + } } + } - final DateTime? hoverDate = _getDateFromPosition( - isTimelineViews ? localPosition.dx : xPosition, - yPosition, - timeLabelWidth); - - /// Remove the hovering when the position not in cell regions or non active - /// cell regions. + void _drawSelection(double x, double y, double timeLabelWidth) { + final DateTime? selectedDate = _getDateFromPosition(x, y, timeLabelWidth); final bool isMonthView = widget.view == CalendarView.month || widget.view == CalendarView.timelineMonth; final int timeInterval = CalendarViewHelper.getTimeInterval( widget.calendar.timeSlotViewSettings); - if (hoverDate == null || + if (selectedDate == null || (isMonthView && - !isDateWithInDateRange( - widget.calendar.minDate, widget.calendar.maxDate, hoverDate)) || + !isDateWithInDateRange(widget.calendar.minDate, + widget.calendar.maxDate, selectedDate)) || (!isMonthView && !CalendarViewHelper.isDateTimeWithInDateTimeRange( widget.calendar.minDate, widget.calendar.maxDate, - hoverDate, + selectedDate, timeInterval))) { - if (_hoveringDate != null) { - _hoveringDate = null; - } - - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; - } + return; + } + /// Restrict the selection update, while selected region as disabled + /// [TimeRegion]. + if (((widget.view == CalendarView.day || + widget.view == CalendarView.week || + widget.view == CalendarView.workWeek) && + !_isEnabledRegion(y, selectedDate, _selectedResourceIndex)) || + (CalendarViewHelper.isTimelineView(widget.view) && + !_isEnabledRegion(x, selectedDate, _selectedResourceIndex))) { return; } - /// Check the hovering month cell date is blackout date. if (isMonthView && CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, hoverDate)) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + widget.blackoutDates, selectedDate)) { + return; + } - /// Remove the existing cell hovering. - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; - } + if (widget.view == CalendarView.month) { + final int currentMonth = + widget.visibleDates[widget.visibleDates.length ~/ 2].month; - /// Remove the existing appointment hovering. - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + /// Check the selected cell date as trailing or leading date when + /// [SfCalendar] month not shown leading and trailing dates. + if (!CalendarViewHelper.isCurrentMonthDate( + widget.calendar.monthViewSettings.numberOfWeeksInView, + widget.calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + selectedDate)) { + return; } - return; + widget.agendaSelectedDate.value = selectedDate; } - final int hoveringResourceIndex = - _getSelectedResourceIndex(yPosition, viewHeaderHeight, timeLabelWidth); + _updateCalendarStateDetails.selectedDate = selectedDate; + _selectionPainter!.selectedDate = selectedDate; + _selectionPainter!.appointmentView = null; + _selectionNotifier.value = !_selectionNotifier.value; + } - /// Restrict the hovering, while selected region as disabled [TimeRegion]. - if (((widget.view == CalendarView.day || - widget.view == CalendarView.week || - widget.view == CalendarView.workWeek) && - !_isEnabledRegion(yPosition, hoverDate, hoveringResourceIndex)) || - (isTimelineViews && - !_isEnabledRegion( - localPosition.dx, hoverDate, hoveringResourceIndex))) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + _SelectionPainter _addSelectionView([double? resourceItemHeight]) { + AppointmentView? appointmentView; + if (_selectionPainter?.appointmentView != null) { + appointmentView = _selectionPainter!.appointmentView; + } - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; - } - return; + _selectionPainter = _SelectionPainter( + widget.calendar, + widget.view, + widget.visibleDates, + _updateCalendarStateDetails.selectedDate, + widget.calendar.selectionDecoration, + _timeIntervalHeight, + widget.calendarTheme, + _selectionNotifier, + _isRTL, + _selectedResourceIndex, + resourceItemHeight, + widget.calendar.showWeekNumber, + widget.isMobilePlatform, + (UpdateCalendarStateDetails details) { + _getPainterProperties(details); + }, + ); + + if (appointmentView != null && + _updateCalendarStateDetails.visibleAppointments + .contains(appointmentView.appointment)) { + _selectionPainter!.appointmentView = appointmentView; } - final int currentMonth = - widget.visibleDates[widget.visibleDates.length ~/ 2].month; + return _selectionPainter!; + } - /// Check the selected cell date as trailing or leading date when - /// [SfCalendar] month not shown leading and trailing dates. - if (!CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - hoverDate)) { - if (_hoveringDate != null) { - _hoveringDate = null; - } + Widget _getTimelineViewHeader(double width, double height, String locale) { + _timelineViewHeader = TimelineViewHeaderView( + widget.visibleDates, + _timelineViewHeaderScrollController!, + _timelineViewHeaderNotifier, + widget.calendar.viewHeaderStyle, + widget.calendar.timeSlotViewSettings, + CalendarViewHelper.getViewHeaderHeight( + widget.calendar.viewHeaderHeight, widget.view), + _isRTL, + widget.calendar.todayHighlightColor ?? + widget.calendarTheme.todayHighlightColor, + widget.calendar.todayTextStyle, + widget.locale, + widget.calendarTheme, + widget.calendar.minDate, + widget.calendar.maxDate, + _viewHeaderNotifier, + widget.calendar.cellBorderColor, + widget.blackoutDates, + widget.calendar.blackoutDatesTextStyle, + widget.textScaleFactor); + return ListView( + padding: const EdgeInsets.all(0.0), + controller: _timelineViewHeaderScrollController, + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + children: [ + CustomPaint( + painter: _timelineViewHeader, + size: Size(width, height), + ) + ]); + } +} + +class _ViewHeaderViewPainter extends CustomPainter { + _ViewHeaderViewPainter( + this.visibleDates, + this.view, + this.viewHeaderStyle, + this.timeSlotViewSettings, + this.timeLabelWidth, + this.viewHeaderHeight, + this.monthViewSettings, + this.isRTL, + this.locale, + this.calendarTheme, + this.todayHighlightColor, + this.todayTextStyle, + this.cellBorderColor, + this.minDate, + this.maxDate, + this.viewHeaderNotifier, + this.textScaleFactor, + this.showWeekNumber, + this.isMobilePlatform, + this.weekNumberStyle, + this.localizations) + : super(repaint: viewHeaderNotifier); + + final CalendarView view; + final ViewHeaderStyle viewHeaderStyle; + final TimeSlotViewSettings timeSlotViewSettings; + final MonthViewSettings monthViewSettings; + final List visibleDates; + final double timeLabelWidth; + final double viewHeaderHeight; + final SfCalendarThemeData calendarTheme; + final bool isRTL; + final String locale; + final Color? todayHighlightColor; + final TextStyle? todayTextStyle; + final Color? cellBorderColor; + final DateTime minDate; + final DateTime maxDate; + final ValueNotifier viewHeaderNotifier; + final double textScaleFactor; + final Paint _circlePainter = Paint(); + final TextPainter _dayTextPainter = TextPainter(), + _dateTextPainter = TextPainter(); + final bool showWeekNumber; + final bool isMobilePlatform; + final WeekNumberStyle weekNumberStyle; + final SfLocalizations localizations; + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + showWeekNumber, size.width, isMobilePlatform); + double width = view == CalendarView.month + ? size.width - weekNumberPanelWidth + : size.width; + width = _getViewHeaderWidth(width); + + /// Initializes the default text style for the texts in view header of + /// calendar. + final TextStyle viewHeaderDayStyle = + viewHeaderStyle.dayTextStyle ?? calendarTheme.viewHeaderDayTextStyle; + final TextStyle viewHeaderDateStyle = + viewHeaderStyle.dateTextStyle ?? calendarTheme.viewHeaderDateTextStyle; + + final DateTime today = DateTime.now(); + if (view != CalendarView.month) { + _addViewHeaderForTimeSlotViews( + canvas, size, viewHeaderDayStyle, viewHeaderDateStyle, width, today); + } else { + _addViewHeaderForMonthView( + canvas, size, viewHeaderDayStyle, width, today, weekNumberPanelWidth); + } + } + + void _addViewHeaderForMonthView( + Canvas canvas, + Size size, + TextStyle viewHeaderDayStyle, + double width, + DateTime today, + double weekNumberPanelWidth) { + TextStyle dayTextStyle = viewHeaderDayStyle; + double xPosition = isRTL + ? size.width - width - weekNumberPanelWidth + : weekNumberPanelWidth; + double yPosition = 0; + final int visibleDatesLength = visibleDates.length; + bool hasToday = monthViewSettings.numberOfWeeksInView > 0 && + monthViewSettings.numberOfWeeksInView < 6 || + visibleDates[visibleDatesLength ~/ 2].month == today.month; + if (hasToday) { + hasToday = isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDatesLength - 1], today); + } + + for (int i = 0; i < DateTime.daysPerWeek; i++) { + final DateTime currentDate = visibleDates[i]; + String dayText = DateFormat(monthViewSettings.dayFormat, locale) + .format(currentDate) + .toString() + .toUpperCase(); + + dayText = _updateViewHeaderFormat(monthViewSettings.dayFormat, dayText); + + if (hasToday && currentDate.weekday == today.weekday) { + final Color? todayTextColor = + CalendarViewHelper.getTodayHighlightTextColor( + todayHighlightColor, todayTextStyle, calendarTheme); - /// Remove the existing cell hovering. - if (_calendarCellNotifier.value != null) { - _calendarCellNotifier.value = null; + dayTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith( + fontSize: viewHeaderDayStyle.fontSize, color: todayTextColor) + : viewHeaderDayStyle.copyWith(color: todayTextColor); + } else { + dayTextStyle = viewHeaderDayStyle; } - /// Remove the existing appointment hovering. - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + _updateDayTextPainter(dayTextStyle, width, dayText); + + if (yPosition == 0) { + yPosition = (viewHeaderHeight - _dayTextPainter.height) / 2; } - return; - } + if (viewHeaderNotifier.value != null) { + _addMouseHoverForMonth(canvas, size, xPosition, yPosition, width); + } - final bool isResourceEnabled = CalendarViewHelper.isResourceEnabled( - widget.calendar.dataSource, widget.view); + _dayTextPainter.paint( + canvas, + Offset( + xPosition + (width / 2 - _dayTextPainter.width / 2), yPosition)); - /// If resource enabled the selected date or time slot can be same but the - /// resource value differs hence to handle this scenario we are excluding - /// the following conditions, if resource enabled. - if (!isResourceEnabled) { - if ((widget.view == CalendarView.month && - isSameDate(_hoveringDate, hoverDate) && - _viewHeaderNotifier.value == null) || - (widget.view != CalendarView.month && - CalendarViewHelper.isSameTimeSlot(_hoveringDate, hoverDate) && - _viewHeaderNotifier.value == null)) { - return; + if (isRTL) { + xPosition -= width; + } else { + xPosition += width; } } + if (weekNumberPanelWidth != 0 && showWeekNumber) { + const double defaultFontSize = 14; + final TextStyle weekNumberTextStyle = + weekNumberStyle.textStyle ?? calendarTheme.weekNumberTextStyle; + final double xPosition = isRTL ? (size.width - weekNumberPanelWidth) : 0; - _hoveringDate = hoverDate; - - if (widget.view == CalendarView.month && - isSameDate(_selectionPainter!.selectedDate, _hoveringDate)) { - _calendarCellNotifier.value = null; - return; - } else if (widget.view != CalendarView.month && - CalendarViewHelper.isSameTimeSlot( - _selectionPainter!.selectedDate, _hoveringDate) && - hoveringResourceIndex == _selectedResourceIndex) { - _calendarCellNotifier.value = null; - return; - } + _updateDayTextPainter(weekNumberTextStyle, weekNumberPanelWidth, + localizations.weeknumberLabel); - if (widget.view != CalendarView.month && !isTimelineViews) { - yPosition += _scrollController!.offset; - } + /// Condition added to remove the ellipsis, when the width is too small + /// the ellipsis alone displayed, hence to resolve this removed ecclipsis + /// when the width is too small, in this scenario the remaining letters + /// were clipped. + if (_dayTextPainter.didExceedMaxLines && + (_dayTextPainter.width <= + (weekNumberTextStyle.fontSize ?? defaultFontSize) * 1.5)) { + _dayTextPainter.ellipsis = null; + _dayTextPainter.layout(minWidth: 0, maxWidth: weekNumberPanelWidth); + } - if (_viewHeaderNotifier.value != null) { - _viewHeaderNotifier.value = null; + _dayTextPainter.paint( + canvas, + Offset( + xPosition + + (weekNumberPanelWidth / 2 - _dayTextPainter.width / 2), + yPosition)); } + } - if (_allDayNotifier.value != null) { - _allDayNotifier.value = null; + void _addViewHeaderForTimeSlotViews( + Canvas canvas, + Size size, + TextStyle viewHeaderDayStyle, + TextStyle viewHeaderDateStyle, + double width, + DateTime today) { + double xPosition, yPosition; + final double labelWidth = + view == CalendarView.day && timeLabelWidth < 50 ? 50 : timeLabelWidth; + TextStyle dayTextStyle = viewHeaderDayStyle; + TextStyle dateTextStyle = viewHeaderDateStyle; + const double topPadding = 5; + if (view == CalendarView.day) { + width = labelWidth; } - if (_appointmentHoverNotifier.value != null) { - _appointmentHoverNotifier.value = null; + final Paint _linePainter = Paint(); + xPosition = view == CalendarView.day ? 0 : timeLabelWidth; + yPosition = 2; + final int visibleDatesLength = visibleDates.length; + final double cellWidth = width / visibleDatesLength; + if (isRTL && view != CalendarView.day) { + xPosition = size.width - timeLabelWidth - cellWidth; } + for (int i = 0; i < visibleDatesLength; i++) { + final DateTime currentDate = visibleDates[i]; - _calendarCellNotifier.value = Offset(xPosition, yPosition); - } + String dayText = DateFormat(timeSlotViewSettings.dayFormat, locale) + .format(currentDate) + .toString() + .toUpperCase(); - void _pointerEnterEvent(PointerEnterEvent event) { - _updatePointerHover(event.position); - } + dayText = + _updateViewHeaderFormat(timeSlotViewSettings.dayFormat, dayText); - void _pointerHoverEvent(PointerHoverEvent event) { - _updatePointerHover(event.position); - } + final String dateText = DateFormat(timeSlotViewSettings.dateFormat) + .format(currentDate) + .toString(); + final bool isToday = isSameDate(currentDate, today); + if (isToday) { + final Color? todayTextStyleColor = todayTextStyle != null + ? todayTextStyle!.color + : calendarTheme.todayTextStyle.color; + final Color? todayTextColor = + CalendarViewHelper.getTodayHighlightTextColor( + todayHighlightColor, todayTextStyle, calendarTheme); + dayTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith( + fontSize: viewHeaderDayStyle.fontSize, color: todayTextColor) + : viewHeaderDayStyle.copyWith(color: todayTextColor); + dateTextStyle = todayTextStyle != null + ? todayTextStyle!.copyWith(fontSize: viewHeaderDateStyle.fontSize) + : viewHeaderDateStyle.copyWith(color: todayTextStyleColor); + } else { + dayTextStyle = viewHeaderDayStyle; + dateTextStyle = viewHeaderDateStyle; + } - void _pointerExitEvent(PointerExitEvent event) { - _hoveringDate = null; - _calendarCellNotifier.value = null; - _viewHeaderNotifier.value = null; - _appointmentHoverNotifier.value = null; - _allDayNotifier.value = null; - } + if (!isDateWithInDateRange(minDate, maxDate, currentDate)) { + if (calendarTheme.brightness == Brightness.light) { + dayTextStyle = dayTextStyle.copyWith(color: Colors.black26); + dateTextStyle = dateTextStyle.copyWith(color: Colors.black26); + } else { + dayTextStyle = dayTextStyle.copyWith(color: Colors.white38); + dateTextStyle = dateTextStyle.copyWith(color: Colors.white38); + } + } - AppointmentView? _getAllDayAppointmentOnPoint( - List? appointmentCollection, double x, double y) { - if (appointmentCollection == null) { - return null; - } + _updateDayTextPainter(dayTextStyle, width, dayText); - AppointmentView? selectedAppointmentView; - for (int i = 0; i < appointmentCollection.length; i++) { - final AppointmentView appointmentView = appointmentCollection[i]; - if (appointmentView.appointment != null && - appointmentView.appointmentRect != null && - appointmentView.appointmentRect!.left <= x && - appointmentView.appointmentRect!.right >= x && - appointmentView.appointmentRect!.top <= y && - appointmentView.appointmentRect!.bottom >= y) { - selectedAppointmentView = appointmentView; - break; - } - } + final TextSpan dateTextSpan = TextSpan( + text: dateText, + style: dateTextStyle, + ); - return selectedAppointmentView; - } + _dateTextPainter.text = dateTextSpan; + _dateTextPainter.textDirection = TextDirection.ltr; + _dateTextPainter.textAlign = TextAlign.left; + _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; + _dateTextPainter.textScaleFactor = textScaleFactor; - List _getSelectedAppointments(DateTime selectedDate) { - return (widget.calendar.dataSource != null && - !AppointmentHelper.isCalendarAppointment( - widget.calendar.dataSource!)) - ? CalendarViewHelper.getCustomAppointments( - AppointmentHelper.getSelectedDateAppointments( - _updateCalendarStateDetails.appointments, - widget.calendar.timeZone, - selectedDate)) - : (AppointmentHelper.getSelectedDateAppointments( - _updateCalendarStateDetails.appointments, - widget.calendar.timeZone, - selectedDate)); - } + _dateTextPainter.layout(minWidth: 0, maxWidth: width); - DateTime? _getDateFromPositionForMonth( - double cellWidth, double cellHeight, double x, double y) { - final int rowIndex = (x / cellWidth).truncate(); - final int columnIndex = (y / cellHeight).truncate(); - int index = 0; - if (_isRTL) { - index = (columnIndex * DateTime.daysPerWeek) + - (DateTime.daysPerWeek - rowIndex) - - 1; - } else { - index = (columnIndex * DateTime.daysPerWeek) + rowIndex; - } + /// To calculate the day start position by width and day painter + final double dayXPosition = (cellWidth - _dayTextPainter.width) / 2; - if (index < 0 || index >= widget.visibleDates.length) { - return null; - } + /// To calculate the date start position by width and date painter + final double dateXPosition = (cellWidth - _dateTextPainter.width) / 2; - return widget.visibleDates[index]; - } + const int inBetweenPadding = 2; + yPosition = size.height / 2 - + (_dayTextPainter.height + + topPadding + + _dateTextPainter.height + + inBetweenPadding) / + 2; - DateTime _getDateFromPositionForDay( - double cellWidth, double cellHeight, double x, double y) { - final int columnIndex = - ((_scrollController!.offset + y) / cellHeight).truncate(); - final double time = ((CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) / - 60) * - columnIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - return DateTime(widget.visibleDates[0].year, widget.visibleDates[0].month, - widget.visibleDates[0].day, hour, minute); - } + _dayTextPainter.paint( + canvas, Offset(xPosition + dayXPosition, yPosition)); - DateTime? _getDateFromPositionForWeek( - double cellWidth, double cellHeight, double x, double y) { - final int columnIndex = - ((_scrollController!.offset + y) / cellHeight).truncate(); - final double time = ((CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) / - 60) * - columnIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - int rowIndex = (x / cellWidth).truncate(); - if (_isRTL) { - rowIndex = (widget.visibleDates.length - rowIndex) - 1; - } + if (isToday) { + _drawTodayCircle( + canvas, + xPosition + dateXPosition, + yPosition + topPadding + _dayTextPainter.height + inBetweenPadding, + _dateTextPainter); + } - if (rowIndex < 0 || rowIndex >= widget.visibleDates.length) { - return null; - } + if (viewHeaderNotifier.value != null) { + _addMouseHoverForTimeSlotView(canvas, size, xPosition, yPosition, + dateXPosition, topPadding, isToday, inBetweenPadding); + } - final DateTime date = widget.visibleDates[rowIndex]; + _dateTextPainter.paint( + canvas, + Offset( + xPosition + dateXPosition, + yPosition + + topPadding + + _dayTextPainter.height + + inBetweenPadding)); + if (view != CalendarView.day && + showWeekNumber && + ((currentDate.weekday == DateTime.monday) || + (view == CalendarView.workWeek && + timeSlotViewSettings.nonWorkingDays + .contains(DateTime.monday) && + i == visibleDatesLength ~/ 2))) { + final String weekNumber = + DateTimeHelper.getWeekNumberOfYear(currentDate).toString(); + final TextStyle weekNumberTextStyle = + weekNumberStyle.textStyle ?? calendarTheme.weekNumberTextStyle; + final TextSpan dayTextSpan = TextSpan( + text: weekNumber, + style: weekNumberTextStyle, + ); + _dateTextPainter.text = dayTextSpan; + _dateTextPainter.textDirection = TextDirection.ltr; + _dateTextPainter.textAlign = TextAlign.left; + _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; + _dateTextPainter.textScaleFactor = textScaleFactor; + _dateTextPainter.layout(minWidth: 0, maxWidth: timeLabelWidth); + final double weekNumberPosition = isRTL + ? (size.width - timeLabelWidth) + + ((timeLabelWidth - _dateTextPainter.width) / 2) + : (timeLabelWidth - _dateTextPainter.width) / 2; + final double weekNumberYPosition = size.height / 2 - + (_dayTextPainter.height + + topPadding + + _dateTextPainter.height + + inBetweenPadding) / + 2 + + topPadding + + _dayTextPainter.height + + inBetweenPadding; + const double padding = 10; + final Rect rect = Rect.fromLTRB( + weekNumberPosition - padding, + weekNumberYPosition - (padding / 2), + weekNumberPosition + _dateTextPainter.width + padding, + weekNumberYPosition + _dateTextPainter.height + (padding / 2)); + _linePainter.style = PaintingStyle.fill; + _linePainter.color = weekNumberStyle.backgroundColor ?? + calendarTheme.weekNumberBackgroundColor!; + final RRect roundedRect = + RRect.fromRectAndRadius(rect, const Radius.circular(padding / 2)); + canvas.drawRRect(roundedRect, _linePainter); + _dateTextPainter.paint( + canvas, Offset(weekNumberPosition, weekNumberYPosition)); + final double xPosition = isRTL ? (size.width - timeLabelWidth) : 0; + _updateDayTextPainter( + weekNumberTextStyle, timeLabelWidth, localizations.weeknumberLabel); + _dayTextPainter.paint( + canvas, + Offset(xPosition + (timeLabelWidth / 2 - _dayTextPainter.width / 2), + yPosition)); + } - return DateTime(date.year, date.month, date.day, hour, minute); + if (isRTL) { + xPosition -= cellWidth; + } else { + xPosition += cellWidth; + } + } } - DateTime? _getDateFromPositionForTimeline( - double cellWidth, double cellHeight, double x, double y) { - int rowIndex, columnIndex; - if (_isRTL) { - rowIndex = (((_scrollController!.offset % - _getSingleViewWidthForTimeLineView(this)) + - (_scrollController!.position.viewportDimension - x)) / - cellWidth) - .truncate(); - } else { - rowIndex = (((_scrollController!.offset % - _getSingleViewWidthForTimeLineView(this)) + - x) / - cellWidth) - .truncate(); - } - columnIndex = - (_scrollController!.offset / _getSingleViewWidthForTimeLineView(this)) - .truncate(); - if (rowIndex >= _horizontalLinesCount!) { - columnIndex += rowIndex ~/ _horizontalLinesCount!; - rowIndex = (rowIndex % _horizontalLinesCount!).toInt(); - } - final double time = ((CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings) / - 60) * - rowIndex) + - widget.calendar.timeSlotViewSettings.startHour; - final int hour = time.toInt(); - final int minute = ((time - hour) * 60).round(); - if (columnIndex < 0) { - columnIndex = 0; - } else if (columnIndex > widget.visibleDates.length) { - columnIndex = widget.visibleDates.length - 1; + void _addMouseHoverForMonth(Canvas canvas, Size size, double xPosition, + double yPosition, double width) { + if (xPosition + (width / 2 - _dayTextPainter.width / 2) <= + viewHeaderNotifier.value!.dx && + xPosition + + (width / 2 - _dayTextPainter.width / 2) + + _dayTextPainter.width >= + viewHeaderNotifier.value!.dx && + yPosition - 5 <= viewHeaderNotifier.value!.dy && + (yPosition + size.height) - 5 >= viewHeaderNotifier.value!.dy) { + _drawTodayCircle( + canvas, + xPosition + (width / 2 - _dayTextPainter.width / 2), + yPosition, + _dayTextPainter, + hoveringColor: (calendarTheme.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04)); } + } - if (columnIndex < 0 || columnIndex >= widget.visibleDates.length) { - return null; + void _addMouseHoverForTimeSlotView( + Canvas canvas, + Size size, + double xPosition, + double yPosition, + double dateXPosition, + double topPadding, + bool isToday, + int padding) { + if (xPosition + dateXPosition <= viewHeaderNotifier.value!.dx && + xPosition + dateXPosition + _dateTextPainter.width >= + viewHeaderNotifier.value!.dx) { + final Color hoveringColor = isToday + ? Colors.black.withOpacity(0.12) + : (calendarTheme.brightness == Brightness.dark + ? Colors.white + : Colors.black87) + .withOpacity(0.04); + _drawTodayCircle( + canvas, + xPosition + dateXPosition, + yPosition + topPadding + _dayTextPainter.height + padding, + _dateTextPainter, + hoveringColor: hoveringColor); } - - final DateTime date = widget.visibleDates[columnIndex]; - - return DateTime(date.year, date.month, date.day, hour, minute); } - DateTime? _getDateFromPosition(double x, double y, double timeLabelWidth) { - double cellWidth = 0; - double cellHeight = 0; - final double width = widget.width - timeLabelWidth; - switch (widget.view) { + String _updateViewHeaderFormat(String dayFormat, String dayText) { + switch (view) { + case CalendarView.day: case CalendarView.schedule: - return null; + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + break; case CalendarView.month: - { - /// Remove the selection when the position is to week number panel. - final double weekNumberPanelWidth = - CalendarViewHelper.getWeekNumberPanelWidth( - widget.calendar.showWeekNumber, - widget.width, - widget.isMobilePlatform); - if (x > widget.width || - (!_isRTL && x < weekNumberPanelWidth) || - (_isRTL && x > widget.width - weekNumberPanelWidth)) { - return null; - } - - /// In RTL the week number panel will render on the right side hence, - /// we didn't consider the week number panel width in rtl. - if (!_isRTL) { - x -= weekNumberPanelWidth; - } - - cellWidth = - (widget.width - weekNumberPanelWidth) / DateTime.daysPerWeek; - cellHeight = (widget.height - - CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view)) / - widget.calendar.monthViewSettings.numberOfWeeksInView; - return _getDateFromPositionForMonth(cellWidth, cellHeight, x, y); - } - case CalendarView.day: - { - if (y >= _timeIntervalHeight * _horizontalLinesCount! || - x > width || - x < 0) { - return null; - } - cellWidth = width; - cellHeight = _timeIntervalHeight; - return _getDateFromPositionForDay(cellWidth, cellHeight, x, y); - } case CalendarView.week: case CalendarView.workWeek: { - if (y >= _timeIntervalHeight * _horizontalLinesCount! || - x > width || - x < 0) { - return null; + //// EE format value shows the week days as S, M, T, W, T, F, S. + if (dayFormat == 'EE' && (locale.contains('en'))) { + return dayText[0]; } - cellWidth = width / widget.visibleDates.length; - cellHeight = _timeIntervalHeight; - return _getDateFromPositionForWeek(cellWidth, cellHeight, x, y); } + } + + return dayText; + } + + void _updateDayTextPainter( + TextStyle dayTextStyle, double width, String dayText) { + final TextSpan dayTextSpan = TextSpan( + text: dayText, + style: dayTextStyle, + ); + + _dayTextPainter.text = dayTextSpan; + _dayTextPainter.textDirection = TextDirection.ltr; + _dayTextPainter.textAlign = TextAlign.left; + _dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; + _dayTextPainter.textScaleFactor = textScaleFactor; + _dayTextPainter.ellipsis = '...'; + _dayTextPainter.maxLines = 1; + + _dayTextPainter.layout(minWidth: 0, maxWidth: width); + } + + double _getViewHeaderWidth(double width) { + switch (view) { case CalendarView.timelineDay: case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - { - final double viewWidth = _timeIntervalHeight * - (_horizontalLinesCount! * widget.visibleDates.length); - if ((!_isRTL && x >= viewWidth) || - (_isRTL && x < (widget.width - viewWidth))) { - return null; - } - cellWidth = _timeIntervalHeight; - cellHeight = widget.height; - return _getDateFromPositionForTimeline(cellWidth, cellHeight, x, y); - } + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + case CalendarView.schedule: + return 0; + case CalendarView.month: + return width / DateTime.daysPerWeek; + case CalendarView.day: + return timeLabelWidth; + case CalendarView.week: + case CalendarView.workWeek: + return width - timeLabelWidth; } } - void _drawSelection(double x, double y, double timeLabelWidth) { - final DateTime? selectedDate = _getDateFromPosition(x, y, timeLabelWidth); - final bool isMonthView = widget.view == CalendarView.month || - widget.view == CalendarView.timelineMonth; - final int timeInterval = CalendarViewHelper.getTimeInterval( - widget.calendar.timeSlotViewSettings); - if (selectedDate == null || - (isMonthView && - !isDateWithInDateRange(widget.calendar.minDate, - widget.calendar.maxDate, selectedDate)) || - (!isMonthView && - !CalendarViewHelper.isDateTimeWithInDateTimeRange( - widget.calendar.minDate, - widget.calendar.maxDate, - selectedDate, - timeInterval))) { - return; - } - - /// Restrict the selection update, while selected region as disabled - /// [TimeRegion]. - if (((widget.view == CalendarView.day || - widget.view == CalendarView.week || - widget.view == CalendarView.workWeek) && - !_isEnabledRegion(y, selectedDate, _selectedResourceIndex)) || - (CalendarViewHelper.isTimelineView(widget.view) && - !_isEnabledRegion(x, selectedDate, _selectedResourceIndex))) { - return; - } + @override + bool shouldRepaint(_ViewHeaderViewPainter oldDelegate) { + final _ViewHeaderViewPainter oldWidget = oldDelegate; + return oldWidget.visibleDates != visibleDates || + oldWidget.viewHeaderStyle != viewHeaderStyle || + oldWidget.viewHeaderHeight != viewHeaderHeight || + oldWidget.todayHighlightColor != todayHighlightColor || + oldWidget.timeSlotViewSettings != timeSlotViewSettings || + oldWidget.monthViewSettings != monthViewSettings || + oldWidget.cellBorderColor != cellBorderColor || + oldWidget.calendarTheme != calendarTheme || + oldWidget.isRTL != isRTL || + oldWidget.locale != locale || + oldWidget.todayTextStyle != todayTextStyle || + oldWidget.textScaleFactor != textScaleFactor || + oldWidget.weekNumberStyle != weekNumberStyle || + oldWidget.showWeekNumber != showWeekNumber; + } - if (isMonthView && - CalendarViewHelper.isDateInDateCollection( - widget.blackoutDates, selectedDate)) { - return; - } + //// draw today highlight circle in view header. + void _drawTodayCircle( + Canvas canvas, double x, double y, TextPainter dateTextPainter, + {Color? hoveringColor}) { + _circlePainter.color = (hoveringColor ?? todayHighlightColor)!; + const double circlePadding = 5; + final double painterWidth = dateTextPainter.width / 2; + final double painterHeight = dateTextPainter.height / 2; + final double radius = + painterHeight > painterWidth ? painterHeight : painterWidth; + canvas.drawCircle(Offset(x + painterWidth, y + painterHeight), + radius + circlePadding, _circlePainter); + } - if (widget.view == CalendarView.month) { - final int currentMonth = - widget.visibleDates[widget.visibleDates.length ~/ 2].month; + /// overrides this property to build the semantics information which uses to + /// return the required information for accessibility, need to return the list + /// of custom painter semantics which contains the rect area and the semantics + /// properties for accessibility + @override + SemanticsBuilderCallback get semanticsBuilder { + return (Size size) { + return _getSemanticsBuilder(size); + }; + } - /// Check the selected cell date as trailing or leading date when - /// [SfCalendar] month not shown leading and trailing dates. - if (!CalendarViewHelper.isCurrentMonthDate( - widget.calendar.monthViewSettings.numberOfWeeksInView, - widget.calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - selectedDate)) { - return; - } + @override + bool shouldRebuildSemantics(_ViewHeaderViewPainter oldDelegate) { + final _ViewHeaderViewPainter oldWidget = oldDelegate; + return oldWidget.visibleDates != visibleDates; + } - widget.agendaSelectedDate.value = selectedDate; + String _getAccessibilityText(DateTime date) { + if (!isDateWithInDateRange(minDate, maxDate, date)) { + return DateFormat('EEEEE').format(date).toString() + + DateFormat('dd/MMMM/yyyy').format(date).toString() + + ', Disabled date'; } - _updateCalendarStateDetails.selectedDate = selectedDate; - _selectionPainter!.selectedDate = selectedDate; - _selectionPainter!.appointmentView = null; - _selectionNotifier.value = !_selectionNotifier.value; + return DateFormat('EEEEE').format(date).toString() + + DateFormat('dd/MMMM/yyyy').format(date).toString(); } - _SelectionPainter _addSelectionView([double? resourceItemHeight]) { - AppointmentView? appointmentView; - if (_selectionPainter?.appointmentView != null) { - appointmentView = _selectionPainter!.appointmentView; + List _getSemanticsForMonthViewHeader(Size size) { + final List semanticsBuilder = + []; + final double cellWidth = size.width / DateTime.daysPerWeek; + double left = isRTL ? size.width - cellWidth : 0; + const double top = 0; + for (int i = 0; i < DateTime.daysPerWeek; i++) { + semanticsBuilder.add(CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, cellWidth, size.height), + properties: SemanticsProperties( + label: DateFormat('EEEEE') + .format(visibleDates[i]) + .toString() + .toUpperCase(), + textDirection: TextDirection.ltr, + ), + )); + if (isRTL) { + left -= cellWidth; + } else { + left += cellWidth; + } } - _selectionPainter = _SelectionPainter( - widget.calendar, - widget.view, - widget.visibleDates, - _updateCalendarStateDetails.selectedDate, - widget.calendar.selectionDecoration, - _timeIntervalHeight, - widget.calendarTheme, - _selectionNotifier, - _isRTL, - _selectedResourceIndex, - resourceItemHeight, - widget.calendar.showWeekNumber, - widget.isMobilePlatform, - (UpdateCalendarStateDetails details) { - _getPainterProperties(details); - }, - ); + return semanticsBuilder; + } - if (appointmentView != null && - _updateCalendarStateDetails.visibleAppointments - .contains(appointmentView.appointment)) { - _selectionPainter!.appointmentView = appointmentView; + List _getSemanticsForDayHeader(Size size) { + final List semanticsBuilder = + []; + const double top = 0; + double left; + final double cellWidth = view == CalendarView.day + ? size.width + : (size.width - timeLabelWidth) / visibleDates.length; + if (isRTL) { + left = view == CalendarView.day + ? size.width - timeLabelWidth + : (size.width - timeLabelWidth) - cellWidth; + } else { + left = view == CalendarView.day ? 0 : timeLabelWidth; + } + for (int i = 0; i < visibleDates.length; i++) { + final DateTime visibleDate = visibleDates[i]; + if (showWeekNumber && + ((visibleDate.weekday == DateTime.monday && + view != CalendarView.day) || + (view == CalendarView.workWeek && + timeSlotViewSettings.nonWorkingDays + .contains(DateTime.monday) && + i == visibleDates.length ~/ 2))) { + final int weekNumber = DateTimeHelper.getWeekNumberOfYear(visibleDate); + semanticsBuilder.add(CustomPainterSemantics( + rect: Rect.fromLTWH(isRTL ? (size.width - timeLabelWidth) : 0, 0, + isRTL ? size.width : timeLabelWidth, viewHeaderHeight), + properties: SemanticsProperties( + label: 'week' + weekNumber.toString(), + textDirection: TextDirection.ltr, + ))); + } + semanticsBuilder.add(CustomPainterSemantics( + rect: Rect.fromLTWH(left, top, cellWidth, size.height), + properties: SemanticsProperties( + label: _getAccessibilityText(visibleDates[i]), + textDirection: TextDirection.ltr, + ), + )); + if (isRTL) { + left -= cellWidth; + } else { + left += cellWidth; + } } - return _selectionPainter!; + return semanticsBuilder; } - Widget _getTimelineViewHeader(double width, double height, String locale) { - _timelineViewHeader = TimelineViewHeaderView( - widget.visibleDates, - _timelineViewHeaderScrollController!, - _timelineViewHeaderNotifier, - widget.calendar.viewHeaderStyle, - widget.calendar.timeSlotViewSettings, - CalendarViewHelper.getViewHeaderHeight( - widget.calendar.viewHeaderHeight, widget.view), - _isRTL, - widget.calendar.todayHighlightColor ?? - widget.calendarTheme.todayHighlightColor, - widget.calendar.todayTextStyle, - widget.locale, - widget.calendarTheme, - widget.calendar.minDate, - widget.calendar.maxDate, - _viewHeaderNotifier, - widget.calendar.cellBorderColor, - widget.blackoutDates, - widget.calendar.blackoutDatesTextStyle, - widget.textScaleFactor); - return ListView( - padding: const EdgeInsets.all(0.0), - controller: _timelineViewHeaderScrollController, - scrollDirection: Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - children: [ - CustomPaint( - painter: _timelineViewHeader, - size: Size(width, height), - ) - ]); + List _getSemanticsBuilder(Size size) { + switch (view) { + case CalendarView.schedule: + case CalendarView.timelineDay: + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + case CalendarView.timelineMonth: + return []; + case CalendarView.month: + return _getSemanticsForMonthViewHeader(size); + case CalendarView.day: + case CalendarView.week: + case CalendarView.workWeek: + return _getSemanticsForDayHeader(size); + } } } -class _ViewHeaderViewPainter extends CustomPainter { - _ViewHeaderViewPainter( - this.visibleDates, +class _SelectionPainter extends CustomPainter { + _SelectionPainter( + this.calendar, this.view, - this.viewHeaderStyle, - this.timeSlotViewSettings, - this.timeLabelWidth, - this.viewHeaderHeight, - this.monthViewSettings, - this.isRTL, - this.locale, + this.visibleDates, + this.selectedDate, + this.selectionDecoration, + this.timeIntervalHeight, this.calendarTheme, - this.todayHighlightColor, - this.todayTextStyle, - this.cellBorderColor, - this.minDate, - this.maxDate, - this.viewHeaderNotifier, - this.textScaleFactor, + this.repaintNotifier, + this.isRTL, + this.selectedResourceIndex, + this.resourceItemHeight, this.showWeekNumber, this.isMobilePlatform, - this.weekNumberStyle) - : super(repaint: viewHeaderNotifier); + this.getCalendarState) + : super(repaint: repaintNotifier); + final SfCalendar calendar; final CalendarView view; - final ViewHeaderStyle viewHeaderStyle; - final TimeSlotViewSettings timeSlotViewSettings; - final MonthViewSettings monthViewSettings; - final List visibleDates; - final double timeLabelWidth; - final double viewHeaderHeight; final SfCalendarThemeData calendarTheme; + final List visibleDates; + Decoration? selectionDecoration; + DateTime? selectedDate; + final double timeIntervalHeight; final bool isRTL; - final String locale; - final Color? todayHighlightColor; - final TextStyle? todayTextStyle; - final Color? cellBorderColor; - final DateTime minDate; - final DateTime maxDate; - final ValueNotifier viewHeaderNotifier; - final double textScaleFactor; - final Paint _circlePainter = Paint(); - final TextPainter _dayTextPainter = TextPainter(), - _dateTextPainter = TextPainter(); + final UpdateCalendarState getCalendarState; + int selectedResourceIndex; + final double? resourceItemHeight; + + late BoxPainter _boxPainter; + AppointmentView? appointmentView; + double _cellWidth = 0, _cellHeight = 0, _xPosition = 0, _yPosition = 0; + final ValueNotifier repaintNotifier; + final UpdateCalendarStateDetails _updateCalendarStateDetails = + UpdateCalendarStateDetails(); final bool showWeekNumber; final bool isMobilePlatform; - final WeekNumberStyle weekNumberStyle; @override void paint(Canvas canvas, Size size) { + selectionDecoration ??= BoxDecoration( + color: Colors.transparent, + border: Border.all(color: calendarTheme.selectionBorderColor!, width: 2), + borderRadius: const BorderRadius.all(Radius.circular(2)), + shape: BoxShape.rectangle, + ); + + getCalendarState(_updateCalendarStateDetails); + selectedDate = _updateCalendarStateDetails.selectedDate; + final bool isMonthView = + view == CalendarView.month || view == CalendarView.timelineMonth; + final int timeInterval = + CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); + if (selectedDate != null && + ((isMonthView && + !isDateWithInDateRange( + calendar.minDate, calendar.maxDate, selectedDate)) || + (!isMonthView && + !CalendarViewHelper.isDateTimeWithInDateTimeRange( + calendar.minDate, + calendar.maxDate, + selectedDate!, + timeInterval)))) { + return; + } canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - final double weekNumberPanelWidth = - CalendarViewHelper.getWeekNumberPanelWidth( - showWeekNumber, size.width, isMobilePlatform); - double width = view == CalendarView.month - ? size.width - weekNumberPanelWidth - : size.width; - width = _getViewHeaderWidth(width); + final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( + calendar.timeSlotViewSettings.timeRulerSize, view); + double width = size.width; + final bool isTimeline = CalendarViewHelper.isTimelineView(view); + if (view != CalendarView.month && !isTimeline) { + width -= timeLabelWidth; + } - /// Initializes the default text style for the texts in view header of - /// calendar. - final TextStyle viewHeaderDayStyle = - viewHeaderStyle.dayTextStyle ?? calendarTheme.viewHeaderDayTextStyle; - final TextStyle viewHeaderDateStyle = - viewHeaderStyle.dateTextStyle ?? calendarTheme.viewHeaderDateTextStyle; + final bool isResourceEnabled = isTimeline && + CalendarViewHelper.isResourceEnabled(calendar.dataSource, view); + if ((selectedDate == null && appointmentView == null) || + visibleDates != _updateCalendarStateDetails.currentViewVisibleDates || + (isResourceEnabled && selectedResourceIndex == -1)) { + return; + } - final DateTime today = DateTime.now(); - if (view != CalendarView.month) { - _addViewHeaderForTimeSlotViews( - canvas, size, viewHeaderDayStyle, viewHeaderDateStyle, width, today); + if (!isTimeline) { + if (view == CalendarView.month) { + _cellWidth = width / DateTime.daysPerWeek; + _cellHeight = + size.height / calendar.monthViewSettings.numberOfWeeksInView; + } else { + _cellWidth = width / visibleDates.length; + _cellHeight = timeIntervalHeight; + } } else { - _addViewHeaderForMonthView( - canvas, size, viewHeaderDayStyle, width, today, weekNumberPanelWidth); + _cellWidth = timeIntervalHeight; + _cellHeight = size.height; + + /// The selection view must render on the resource area alone, when the + /// resource enabled. + if (isResourceEnabled && selectedResourceIndex >= 0) { + _cellHeight = resourceItemHeight!; + } + } + + if (appointmentView != null && appointmentView!.appointment != null) { + _drawAppointmentSelection(canvas); + } + + switch (view) { + case CalendarView.schedule: + return; + case CalendarView.month: + { + if (selectedDate != null) { + _drawMonthSelection(canvas, size, width); + } + } + break; + case CalendarView.day: + { + if (selectedDate != null) { + _drawDaySelection(canvas, size, width, timeLabelWidth); + } + } + break; + case CalendarView.week: + case CalendarView.workWeek: + { + if (selectedDate != null) { + _drawWeekSelection(canvas, size, timeLabelWidth, width); + } + } + break; + case CalendarView.timelineDay: + { + if (selectedDate != null) { + _drawTimelineDaySelection(canvas, size, width); + } + } + break; + case CalendarView.timelineWeek: + case CalendarView.timelineWorkWeek: + { + if (selectedDate != null) { + _drawTimelineWeekSelection(canvas, size, width); + } + } + break; + case CalendarView.timelineMonth: + { + if (selectedDate != null) { + _drawTimelineMonthSelection(canvas, size, width); + } + } } } - void _addViewHeaderForMonthView( - Canvas canvas, - Size size, - TextStyle viewHeaderDayStyle, - double width, - DateTime today, - double weekNumberPanelWidth) { - TextStyle dayTextStyle = viewHeaderDayStyle; - double xPosition = isRTL - ? size.width - width - weekNumberPanelWidth - : weekNumberPanelWidth; - double yPosition = 0; + @override + bool? hitTest(Offset position) { + return false; + } + + void _drawMonthSelection(Canvas canvas, Size size, double width) { final int visibleDatesLength = visibleDates.length; - bool hasToday = monthViewSettings.numberOfWeeksInView > 0 && - monthViewSettings.numberOfWeeksInView < 6 || - visibleDates[visibleDatesLength ~/ 2].month == today.month; - if (hasToday) { - hasToday = isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDatesLength - 1], today); + if (!isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDatesLength - 1], selectedDate)) { + return; + } + + final int currentMonth = visibleDates[visibleDatesLength ~/ 2].month; + + /// Check the selected cell date as trailing or leading date when + /// [SfCalendar] month not shown leading and trailing dates. + if (!CalendarViewHelper.isCurrentMonthDate( + calendar.monthViewSettings.numberOfWeeksInView, + calendar.monthViewSettings.showTrailingAndLeadingDates, + currentMonth, + selectedDate!)) { + return; + } + + if (CalendarViewHelper.isDateInDateCollection( + calendar.blackoutDates, selectedDate!)) { + return; + } + + for (int i = 0; i < visibleDatesLength; i++) { + if (isSameDate(visibleDates[i], selectedDate)) { + final double weekNumberPanelWidth = + CalendarViewHelper.getWeekNumberPanelWidth( + showWeekNumber, width, isMobilePlatform); + _cellWidth = (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek; + final int columnIndex = (i / DateTime.daysPerWeek).truncate(); + _yPosition = columnIndex * _cellHeight; + final int rowIndex = i % DateTime.daysPerWeek; + if (isRTL) { + _xPosition = (DateTime.daysPerWeek - 1 - rowIndex) * _cellWidth; + } else { + _xPosition = rowIndex * _cellWidth + weekNumberPanelWidth; + } + _drawSlotSelection(width, size.height, canvas); + break; + } } + } - for (int i = 0; i < DateTime.daysPerWeek; i++) { - final DateTime currentDate = visibleDates[i]; - String dayText = DateFormat(monthViewSettings.dayFormat, locale) - .format(currentDate) - .toString() - .toUpperCase(); + void _drawDaySelection( + Canvas canvas, Size size, double width, double timeLabelWidth) { + if (isSameDate(visibleDates[0], selectedDate)) { + if (isRTL) { + _xPosition = 0; + } else { + _xPosition = timeLabelWidth; + } - dayText = _updateViewHeaderFormat(monthViewSettings.dayFormat, dayText); + selectedDate = _updateSelectedDate(); - if (hasToday && currentDate.weekday == today.weekday) { - final Color? todayTextColor = - CalendarViewHelper.getTodayHighlightTextColor( - todayHighlightColor, todayTextStyle, calendarTheme); + _yPosition = AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + _drawSlotSelection(width + timeLabelWidth, size.height, canvas); + } + } - dayTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith( - fontSize: viewHeaderDayStyle.fontSize, color: todayTextColor) - : viewHeaderDayStyle.copyWith(color: todayTextColor); + /// Method to update the selected date, when the selected date not fill the + /// exact time slot, and render the mid of time slot, on this scenario we + /// have updated the selected date to update the exact time slot. + /// + /// Eg: If the time interval is 60min, and the selected date is 12.45 PM the + /// selection renders on the center of 12 to 1 PM slot, to avoid this we have + /// modified the selected date to 1 PM so that the selection will render the + /// exact time slot. + DateTime _updateSelectedDate() { + final int timeInterval = + CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); + final int startHour = calendar.timeSlotViewSettings.startHour.toInt(); + final double startMinute = (calendar.timeSlotViewSettings.startHour - + calendar.timeSlotViewSettings.startHour.toInt()) * + 60; + final int selectedMinutes = ((selectedDate!.hour - startHour) * 60) + + (selectedDate!.minute - startMinute.toInt()); + if (selectedMinutes % timeInterval != 0) { + final int diff = selectedMinutes % timeInterval; + if (diff < (timeInterval / 2)) { + return selectedDate!.subtract(Duration(minutes: diff)); } else { - dayTextStyle = viewHeaderDayStyle; + return selectedDate!.add(Duration(minutes: timeInterval - diff)); } + } - _updateDayTextPainter(dayTextStyle, width, dayText); + return selectedDate!; + } - if (yPosition == 0) { - yPosition = (viewHeaderHeight - _dayTextPainter.height) / 2; - } + void _drawWeekSelection( + Canvas canvas, Size size, double timeLabelWidth, double width) { + final int visibleDatesLength = visibleDates.length; + if (isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDatesLength - 1], selectedDate)) { + for (int i = 0; i < visibleDatesLength; i++) { + if (isSameDate(selectedDate, visibleDates[i])) { + final int rowIndex = i; + if (isRTL) { + _xPosition = _cellWidth * (visibleDatesLength - 1 - rowIndex); + } else { + _xPosition = timeLabelWidth + _cellWidth * rowIndex; + } - if (viewHeaderNotifier.value != null) { - _addMouseHoverForMonth(canvas, size, xPosition, yPosition, width); + selectedDate = _updateSelectedDate(); + _yPosition = AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + _drawSlotSelection(width + timeLabelWidth, size.height, canvas); + break; + } } + } + } - _dayTextPainter.paint( - canvas, - Offset( - xPosition + (width / 2 - _dayTextPainter.width / 2), yPosition)); + /// Returns the yPosition for selection view based on resource associated with + /// the selected cell in timeline views when resource enabled. + double _getTimelineYPosition() { + if (selectedResourceIndex == -1) { + return 0; + } + + return selectedResourceIndex * resourceItemHeight!; + } + void _drawTimelineDaySelection(Canvas canvas, Size size, double width) { + if (isSameDate(visibleDates[0], selectedDate)) { + selectedDate = _updateSelectedDate(); + _xPosition = AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + _yPosition = _getTimelineYPosition(); + final double height = selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; if (isRTL) { - xPosition -= width; - } else { - xPosition += width; + _xPosition = size.width - _xPosition - _cellWidth; } + _drawSlotSelection(width, height, canvas); } } - void _addViewHeaderForTimeSlotViews( - Canvas canvas, - Size size, - TextStyle viewHeaderDayStyle, - TextStyle viewHeaderDateStyle, - double width, - DateTime today) { - double xPosition, yPosition; - final double labelWidth = - view == CalendarView.day && timeLabelWidth < 50 ? 50 : timeLabelWidth; - TextStyle dayTextStyle = viewHeaderDayStyle; - TextStyle dateTextStyle = viewHeaderDateStyle; - const double topPadding = 5; - if (view == CalendarView.day) { - width = labelWidth; + void _drawTimelineMonthSelection(Canvas canvas, Size size, double width) { + if (!isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + return; } - final Paint _linePainter = Paint(); - xPosition = view == CalendarView.day ? 0 : timeLabelWidth; - yPosition = 2; - final int visibleDatesLength = visibleDates.length; - final double cellWidth = width / visibleDatesLength; - if (isRTL && view != CalendarView.day) { - xPosition = size.width - timeLabelWidth - cellWidth; + if (CalendarViewHelper.isDateInDateCollection( + calendar.blackoutDates, selectedDate!)) { + return; } - for (int i = 0; i < visibleDatesLength; i++) { - final DateTime currentDate = visibleDates[i]; - String dayText = DateFormat(timeSlotViewSettings.dayFormat, locale) - .format(currentDate) - .toString() - .toUpperCase(); + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate(visibleDates[i], selectedDate)) { + _yPosition = _getTimelineYPosition(); + _xPosition = + isRTL ? size.width - ((i + 1) * _cellWidth) : i * _cellWidth; + final double height = selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; + _drawSlotSelection(width, height, canvas); + break; + } + } + } + + void _drawTimelineWeekSelection(Canvas canvas, Size size, double width) { + if (isDateWithInDateRange( + visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + selectedDate = _updateSelectedDate(); + for (int i = 0; i < visibleDates.length; i++) { + if (isSameDate(selectedDate, visibleDates[i])) { + final double singleViewWidth = width / visibleDates.length; + _xPosition = (i * singleViewWidth) + + AppointmentHelper.timeToPosition( + calendar, selectedDate!, timeIntervalHeight); + if (isRTL) { + _xPosition = size.width - _xPosition - _cellWidth; + } + _yPosition = _getTimelineYPosition(); + final double height = selectedResourceIndex == -1 + ? size.height + : _yPosition + resourceItemHeight!; + _drawSlotSelection(width, height, canvas); + break; + } + } + } + } + + void _drawAppointmentSelection(Canvas canvas) { + Rect rect = appointmentView!.appointmentRect!.outerRect; + rect = Rect.fromLTRB(rect.left, rect.top, rect.right, rect.bottom); + _boxPainter = selectionDecoration! + .createBoxPainter(_updateSelectionDecorationPainter); + _boxPainter.paint(canvas, Offset(rect.left, rect.top), + ImageConfiguration(size: rect.size)); + } + + /// Used to pass the argument of create box painter and it is called when + /// decoration have asynchronous data like image. + void _updateSelectionDecorationPainter() { + repaintNotifier.value = !repaintNotifier.value; + } + + void _drawSlotSelection(double width, double height, Canvas canvas) { + //// padding used to avoid first, last row and column selection clipping. + const double padding = 0.5; + final Rect rect = Rect.fromLTRB( + _xPosition == 0 ? _xPosition + padding : _xPosition, + _yPosition == 0 ? _yPosition + padding : _yPosition, + _xPosition + _cellWidth == width + ? _xPosition + _cellWidth - padding + : _xPosition + _cellWidth, + _yPosition + _cellHeight == height + ? _yPosition + _cellHeight - padding + : _yPosition + _cellHeight); + + _boxPainter = selectionDecoration! + .createBoxPainter(_updateSelectionDecorationPainter); + _boxPainter.paint(canvas, Offset(rect.left, rect.top), + ImageConfiguration(size: rect.size, textDirection: TextDirection.ltr)); + } + + @override + bool shouldRepaint(_SelectionPainter oldDelegate) { + final _SelectionPainter oldWidget = oldDelegate; + return oldWidget.selectionDecoration != selectionDecoration || + oldWidget.selectedDate != selectedDate || + oldWidget.view != view || + oldWidget.visibleDates != visibleDates || + oldWidget.selectedResourceIndex != selectedResourceIndex || + oldWidget.isRTL != isRTL; + } +} + +class _TimeRulerView extends CustomPainter { + _TimeRulerView( + this.horizontalLinesCount, + this.timeIntervalHeight, + this.timeSlotViewSettings, + this.cellBorderColor, + this.isRTL, + this.locale, + this.calendarTheme, + this.isTimelineView, + this.visibleDates, + this.textScaleFactor); + + final double horizontalLinesCount; + final double timeIntervalHeight; + final TimeSlotViewSettings timeSlotViewSettings; + final bool isRTL; + final String locale; + final SfCalendarThemeData calendarTheme; + final Color? cellBorderColor; + final bool isTimelineView; + final List visibleDates; + final double textScaleFactor; + final Paint _linePainter = Paint(); + final TextPainter _textPainter = TextPainter(); + + @override + void paint(Canvas canvas, Size size) { + canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); + const double offset = 0.5; + double xPosition, yPosition; + final DateTime date = DateTime.now(); + xPosition = isRTL && isTimelineView ? size.width : 0; + yPosition = timeIntervalHeight; + _linePainter.strokeWidth = offset; + _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; + + if (!isTimelineView) { + final double lineXPosition = isRTL ? offset : size.width - offset; + // Draw vertical time label line + canvas.drawLine(Offset(lineXPosition, 0), + Offset(lineXPosition, size.height), _linePainter); + } - dayText = - _updateViewHeaderFormat(timeSlotViewSettings.dayFormat, dayText); + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaleFactor = textScaleFactor; - final String dateText = DateFormat(timeSlotViewSettings.dateFormat) - .format(currentDate) - .toString(); - final bool isToday = isSameDate(currentDate, today); - if (isToday) { - final Color? todayTextStyleColor = todayTextStyle != null - ? todayTextStyle!.color - : calendarTheme.todayTextStyle.color; - final Color? todayTextColor = - CalendarViewHelper.getTodayHighlightTextColor( - todayHighlightColor, todayTextStyle, calendarTheme); - dayTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith( - fontSize: viewHeaderDayStyle.fontSize, color: todayTextColor) - : viewHeaderDayStyle.copyWith(color: todayTextColor); - dateTextStyle = todayTextStyle != null - ? todayTextStyle!.copyWith(fontSize: viewHeaderDateStyle.fontSize) - : viewHeaderDateStyle.copyWith(color: todayTextStyleColor); - } else { - dayTextStyle = viewHeaderDayStyle; - dateTextStyle = viewHeaderDateStyle; - } + final TextStyle timeTextStyle = + timeSlotViewSettings.timeTextStyle ?? calendarTheme.timeTextStyle; - if (!isDateWithInDateRange(minDate, maxDate, currentDate)) { - if (calendarTheme.brightness == Brightness.light) { - dayTextStyle = dayTextStyle.copyWith(color: Colors.black26); - dateTextStyle = dateTextStyle.copyWith(color: Colors.black26); + final double hour = (timeSlotViewSettings.startHour - + timeSlotViewSettings.startHour.toInt()) * + 60; + if (isTimelineView) { + canvas.drawLine(const Offset(0, 0), Offset(size.width, 0), _linePainter); + final double timelineViewWidth = + timeIntervalHeight * horizontalLinesCount; + for (int i = 0; i < visibleDates.length; i++) { + _drawTimeLabels( + canvas, size, date, hour, xPosition, yPosition, timeTextStyle); + if (isRTL) { + xPosition -= timelineViewWidth; } else { - dayTextStyle = dayTextStyle.copyWith(color: Colors.white38); - dateTextStyle = dateTextStyle.copyWith(color: Colors.white38); + xPosition += timelineViewWidth; } } + } else { + _drawTimeLabels( + canvas, size, date, hour, xPosition, yPosition, timeTextStyle); + } + } - _updateDayTextPainter(dayTextStyle, width, dayText); + /// Draws the time labels in the time label view for timeslot views in + /// calendar. + void _drawTimeLabels(Canvas canvas, Size size, DateTime date, double hour, + double xPosition, double yPosition, TextStyle timeTextStyle) { + const int padding = 5; + final int timeInterval = + CalendarViewHelper.getTimeInterval(timeSlotViewSettings); - final TextSpan dateTextSpan = TextSpan( - text: dateText, - style: dateTextStyle, - ); + /// For timeline view we will draw 24 lines where as in day, week and work + /// week view we will draw 23 lines excluding the 12 AM, hence to rectify + /// this the i value handled accordingly. + for (int i = isTimelineView ? 0 : 1; + i <= (isTimelineView ? horizontalLinesCount - 1 : horizontalLinesCount); + i++) { + if (isTimelineView) { + canvas.save(); + canvas.clipRect( + Rect.fromLTWH(xPosition, 0, timeIntervalHeight, size.height)); + canvas.restore(); + canvas.drawLine( + Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); + } - _dateTextPainter.text = dateTextSpan; - _dateTextPainter.textDirection = TextDirection.ltr; - _dateTextPainter.textAlign = TextAlign.left; - _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dateTextPainter.textScaleFactor = textScaleFactor; + final double minute = (i * timeInterval) + hour; + date = DateTime(date.day, date.month, date.year, + timeSlotViewSettings.startHour.toInt(), minute.toInt()); + final String time = DateFormat(timeSlotViewSettings.timeFormat, locale) + .format(date) + .toString(); + final TextSpan span = TextSpan( + text: time, + style: timeTextStyle, + ); - _dateTextPainter.layout(minWidth: 0, maxWidth: width); + final double cellWidth = isTimelineView ? timeIntervalHeight : size.width; - /// To calculate the day start position by width and day painter - final double dayXPosition = (cellWidth - _dayTextPainter.width) / 2; + _textPainter.text = span; + _textPainter.layout(minWidth: 0, maxWidth: cellWidth); + if (isTimelineView && _textPainter.height > size.height) { + return; + } - /// To calculate the date start position by width and date painter - final double dateXPosition = (cellWidth - _dateTextPainter.width) / 2; + double startXPosition = (cellWidth - _textPainter.width) / 2; + if (startXPosition < 0) { + startXPosition = 0; + } - const int inBetweenPadding = 2; - yPosition = size.height / 2 - - (_dayTextPainter.height + - topPadding + - _dateTextPainter.height + - inBetweenPadding) / - 2; + if (isTimelineView) { + startXPosition = isRTL ? xPosition - _textPainter.width : xPosition; + } - _dayTextPainter.paint( - canvas, Offset(xPosition + dayXPosition, yPosition)); + double startYPosition = yPosition - (_textPainter.height / 2); - if (isToday) { - _drawTodayCircle( - canvas, - xPosition + dateXPosition, - yPosition + topPadding + _dayTextPainter.height + inBetweenPadding, - _dateTextPainter); + if (isTimelineView) { + startYPosition = (size.height - _textPainter.height) / 2; + startXPosition = + isRTL ? startXPosition - padding : startXPosition + padding; } - if (viewHeaderNotifier.value != null) { - _addMouseHoverForTimeSlotView(canvas, size, xPosition, yPosition, - dateXPosition, topPadding, isToday, inBetweenPadding); - } + _textPainter.paint(canvas, Offset(startXPosition, startYPosition)); - _dateTextPainter.paint( - canvas, - Offset( - xPosition + dateXPosition, - yPosition + - topPadding + - _dayTextPainter.height + - inBetweenPadding)); - if (showWeekNumber && - ((currentDate.weekday == DateTime.monday) || - (view == CalendarView.workWeek && - timeSlotViewSettings.nonWorkingDays - .contains(DateTime.monday) && - i == visibleDatesLength ~/ 2))) { - final String weekNumber = - DateTimeHelper.getWeekNumberOfYear(currentDate).toString(); - final TextStyle weekNumberTextStyle = - weekNumberStyle.textStyle ?? calendarTheme.weekNumberTextStyle; - final TextSpan dayTextSpan = TextSpan( - text: weekNumber, - style: weekNumberTextStyle, - ); - _dateTextPainter.text = dayTextSpan; - _dateTextPainter.textDirection = TextDirection.ltr; - _dateTextPainter.textAlign = TextAlign.left; - _dateTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dateTextPainter.textScaleFactor = textScaleFactor; - _dateTextPainter.layout(minWidth: 0, maxWidth: timeLabelWidth); - final double weekNumberPosition = isRTL - ? (size.width - timeLabelWidth) + - ((timeLabelWidth - _dateTextPainter.width) / 2) - : (timeLabelWidth - _dateTextPainter.width) / 2; - final double weekNumberYPosition = size.height / 2 - - (_dayTextPainter.height + - topPadding + - _dateTextPainter.height + - inBetweenPadding) / - 2 + - topPadding + - _dayTextPainter.height + - inBetweenPadding; - const double padding = 10; - final Rect rect = Rect.fromLTRB( - weekNumberPosition - padding, - weekNumberYPosition - (padding / 2), - weekNumberPosition + _dateTextPainter.width + padding, - weekNumberYPosition + _dateTextPainter.height + (padding / 2)); - _linePainter.style = PaintingStyle.fill; - _linePainter.color = weekNumberStyle.backgroundColor ?? - calendarTheme.weekNumberBackgroundColor!; - final RRect roundedRect = - RRect.fromRectAndRadius(rect, const Radius.circular(padding / 2)); - canvas.drawRRect(roundedRect, _linePainter); - _dateTextPainter.paint( - canvas, Offset(weekNumberPosition, weekNumberYPosition)); + if (!isTimelineView) { + final Offset start = + Offset(isRTL ? 0 : size.width - (startXPosition / 2), yPosition); + final Offset end = + Offset(isRTL ? startXPosition / 2 : size.width, yPosition); + canvas.drawLine(start, end, _linePainter); + yPosition += timeIntervalHeight; + if (yPosition.round() == size.height.round()) { + break; + } + } else { + if (isRTL) { + xPosition -= timeIntervalHeight; + } else { + xPosition += timeIntervalHeight; + } } + } + } + + @override + bool shouldRepaint(_TimeRulerView oldDelegate) { + final _TimeRulerView oldWidget = oldDelegate; + return oldWidget.timeSlotViewSettings != timeSlotViewSettings || + oldWidget.cellBorderColor != cellBorderColor || + oldWidget.calendarTheme != calendarTheme || + oldWidget.isRTL != isRTL || + oldWidget.locale != locale || + oldWidget.visibleDates != visibleDates || + oldWidget.isTimelineView != isTimelineView || + oldWidget.textScaleFactor != textScaleFactor; + } +} + +class _CalendarMultiChildContainer extends Stack { + _CalendarMultiChildContainer( + {this.painter, + List children = const [], + required this.width, + required this.height}) + : super(children: children); + final CustomPainter? painter; + final double width; + final double height; + + @override + RenderStack createRenderObject(BuildContext context) { + final Directionality? widget = + context.dependOnInheritedWidgetOfExactType(); + return _MultiChildContainerRenderObject(width, height, + painter: painter, + direction: widget != null ? widget.textDirection : null); + } - if (isRTL) { - xPosition -= cellWidth; - } else { - xPosition += cellWidth; - } + @override + void updateRenderObject(BuildContext context, RenderStack renderObject) { + super.updateRenderObject(context, renderObject); + if (renderObject is _MultiChildContainerRenderObject) { + final Directionality? widget = + context.dependOnInheritedWidgetOfExactType(); + renderObject + ..width = width + ..height = height + ..painter = painter + ..textDirection = widget != null ? widget.textDirection : null; } } +} - void _addMouseHoverForMonth(Canvas canvas, Size size, double xPosition, - double yPosition, double width) { - if (xPosition + (width / 2 - _dayTextPainter.width / 2) <= - viewHeaderNotifier.value!.dx && - xPosition + - (width / 2 - _dayTextPainter.width / 2) + - _dayTextPainter.width >= - viewHeaderNotifier.value!.dx && - yPosition - 5 <= viewHeaderNotifier.value!.dy && - (yPosition + size.height) - 5 >= viewHeaderNotifier.value!.dy) { - _drawTodayCircle( - canvas, - xPosition + (width / 2 - _dayTextPainter.width / 2), - yPosition, - _dayTextPainter, - hoveringColor: (calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04)); +class _MultiChildContainerRenderObject extends RenderStack { + _MultiChildContainerRenderObject(this._width, this._height, + {CustomPainter? painter, TextDirection? direction}) + : _painter = painter, + super(textDirection: direction); + + CustomPainter? get painter => _painter; + CustomPainter? _painter; + + set painter(CustomPainter? value) { + if (_painter == value) { + return; + } + + final CustomPainter? oldPainter = _painter; + _painter = value; + _updatePainter(_painter, oldPainter); + if (attached) { + oldPainter?.removeListener(markNeedsPaint); + _painter?.addListener(markNeedsPaint); } } - void _addMouseHoverForTimeSlotView( - Canvas canvas, - Size size, - double xPosition, - double yPosition, - double dateXPosition, - double topPadding, - bool isToday, - int padding) { - if (xPosition + dateXPosition <= viewHeaderNotifier.value!.dx && - xPosition + dateXPosition + _dateTextPainter.width >= - viewHeaderNotifier.value!.dx) { - final Color hoveringColor = isToday - ? Colors.black.withOpacity(0.12) - : (calendarTheme.brightness == Brightness.dark - ? Colors.white - : Colors.black87) - .withOpacity(0.04); - _drawTodayCircle( - canvas, - xPosition + dateXPosition, - yPosition + topPadding + _dayTextPainter.height + padding, - _dateTextPainter, - hoveringColor: hoveringColor); + double get width => _width; + + set width(double value) { + if (_width == value) { + return; } + + _width = value; + markNeedsLayout(); } - String _updateViewHeaderFormat(String dayFormat, String dayText) { - switch (view) { - case CalendarView.day: - case CalendarView.schedule: - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - break; - case CalendarView.month: - case CalendarView.week: - case CalendarView.workWeek: - { - //// EE format value shows the week days as S, M, T, W, T, F, S. - if (dayFormat == 'EE' && (locale.contains('en'))) { - return dayText[0]; - } - } + double _width; + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; } - return dayText; + _height = value; + markNeedsLayout(); } - void _updateDayTextPainter( - TextStyle dayTextStyle, double width, String dayText) { - final TextSpan dayTextSpan = TextSpan( - text: dayText, - style: dayTextStyle, - ); + /// Caches [SemanticsNode]s created during [assembleSemanticsNode] so they + /// can be re-used when [assembleSemanticsNode] is called again. This ensures + /// stable ids for the [SemanticsNode]s of children across + /// [assembleSemanticsNode] invocations. + /// Ref: assembleSemanticsNode method in RenderParagraph class + /// (https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/paragraph.dart) + List? _cacheNodes; - _dayTextPainter.text = dayTextSpan; - _dayTextPainter.textDirection = TextDirection.ltr; - _dayTextPainter.textAlign = TextAlign.left; - _dayTextPainter.textWidthBasis = TextWidthBasis.longestLine; - _dayTextPainter.textScaleFactor = textScaleFactor; + void _updatePainter(CustomPainter? newPainter, CustomPainter? oldPainter) { + if (newPainter == null) { + markNeedsPaint(); + } else if (oldPainter == null || + newPainter.runtimeType != oldPainter.runtimeType || + newPainter.shouldRepaint(oldPainter)) { + markNeedsPaint(); + } - _dayTextPainter.layout(minWidth: 0, maxWidth: width); + if (newPainter == null) { + if (attached) { + markNeedsSemanticsUpdate(); + } + } else if (oldPainter == null || + newPainter.runtimeType != oldPainter.runtimeType || + newPainter.shouldRebuildSemantics(oldPainter)) { + markNeedsSemanticsUpdate(); + } } - double _getViewHeaderWidth(double width) { - switch (view) { - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - case CalendarView.schedule: - return 0; - case CalendarView.month: - return width / DateTime.daysPerWeek; - case CalendarView.day: - return timeLabelWidth; - case CalendarView.week: - case CalendarView.workWeek: - return width - timeLabelWidth; + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _painter?.addListener(markNeedsPaint); + } + + @override + void detach() { + _painter?.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void performLayout() { + final Size widgetSize = constraints.biggest; + size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, + widgetSize.height.isInfinite ? height : widgetSize.height); + for (dynamic child = firstChild; child != null; child = childAfter(child)) { + child.layout(constraints); } } @override - bool shouldRepaint(_ViewHeaderViewPainter oldDelegate) { - final _ViewHeaderViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates || - oldWidget.viewHeaderStyle != viewHeaderStyle || - oldWidget.viewHeaderHeight != viewHeaderHeight || - oldWidget.todayHighlightColor != todayHighlightColor || - oldWidget.timeSlotViewSettings != timeSlotViewSettings || - oldWidget.monthViewSettings != monthViewSettings || - oldWidget.cellBorderColor != cellBorderColor || - oldWidget.calendarTheme != calendarTheme || - oldWidget.isRTL != isRTL || - oldWidget.locale != locale || - oldWidget.todayTextStyle != todayTextStyle || - oldWidget.textScaleFactor != textScaleFactor || - oldWidget.weekNumberStyle != weekNumberStyle || - oldWidget.showWeekNumber != showWeekNumber; + void paint(PaintingContext context, Offset offset) { + if (_painter != null) { + _painter!.paint(context.canvas, size); + } + + paintStack(context, offset); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.isSemanticBoundary = true; } - //// draw today highlight circle in view header. - void _drawTodayCircle( - Canvas canvas, double x, double y, TextPainter dateTextPainter, - {Color? hoveringColor}) { - _circlePainter.color = (hoveringColor ?? todayHighlightColor)!; - const double circlePadding = 5; - final double painterWidth = dateTextPainter.width / 2; - final double painterHeight = dateTextPainter.height / 2; - final double radius = - painterHeight > painterWidth ? painterHeight : painterWidth; - canvas.drawCircle(Offset(x + painterWidth, y + painterHeight), - radius + circlePadding, _circlePainter); + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + _cacheNodes ??= []; + final List semantics = _semanticsBuilder(size); + final List semanticsNodes = []; + for (int i = 0; i < semantics.length; i++) { + final CustomPainterSemantics currentSemantics = semantics[i]; + final SemanticsNode newChild = _cacheNodes!.isNotEmpty + ? _cacheNodes!.removeAt(0) + : SemanticsNode(key: currentSemantics.key); + + final SemanticsProperties properties = currentSemantics.properties; + final SemanticsConfiguration config = SemanticsConfiguration(); + if (properties.label != null) { + config.label = properties.label!; + } + if (properties.textDirection != null) { + config.textDirection = properties.textDirection; + } + + newChild.updateWith( + config: config, + // As of now CustomPainter does not support multiple tree levels. + childrenInInversePaintOrder: const [], + ); + + newChild + ..rect = currentSemantics.rect + ..transform = currentSemantics.transform + ..tags = currentSemantics.tags; + + semanticsNodes.add(newChild); + } + + final List finalChildren = []; + finalChildren.addAll(semanticsNodes); + finalChildren.addAll(children); + _cacheNodes = semanticsNodes; + super.assembleSemanticsNode(node, config, finalChildren); } - /// overrides this property to build the semantics information which uses to - /// return the required information for accessibility, need to return the list - /// of custom painter semantics which contains the rect area and the semantics - /// properties for accessibility @override - SemanticsBuilderCallback get semanticsBuilder { + void clearSemantics() { + super.clearSemantics(); + _cacheNodes = null; + } + + SemanticsBuilderCallback get _semanticsBuilder { + final List semantics = []; + if (painter != null) { + semantics.addAll(painter!.semanticsBuilder!(size)); + } + // ignore: avoid_as + for (RenderRepaintBoundary? child = firstChild! as RenderRepaintBoundary; + child != null; + // ignore: avoid_as + child = childAfter(child) as RenderRepaintBoundary?) { + if (child.child is! CustomCalendarRenderObject) { + continue; + } + + final CustomCalendarRenderObject appointmentRenderObject = + // ignore: avoid_as + child.child! as CustomCalendarRenderObject; + semantics.addAll(appointmentRenderObject.semanticsBuilder!(size)); + } + return (Size size) { - return _getSemanticsBuilder(size); + return semantics; }; } +} + +class _CustomNeverScrollableScrollPhysics extends NeverScrollableScrollPhysics { + /// Creates scroll physics that does not let the user scroll. + const _CustomNeverScrollableScrollPhysics({ScrollPhysics? parent}) + : super(parent: parent); @override - bool shouldRebuildSemantics(_ViewHeaderViewPainter oldDelegate) { - final _ViewHeaderViewPainter oldWidget = oldDelegate; - return oldWidget.visibleDates != visibleDates; + _CustomNeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) { + /// Set the clamping scroll physics as default parent for never scroll + /// physics, because flutter framework set different parent physics + /// based on platform(iOS, Android, etc.,) + return _CustomNeverScrollableScrollPhysics( + parent: buildParent(const ClampingScrollPhysics( + parent: RangeMaintainingScrollPhysics()))); } +} - String _getAccessibilityText(DateTime date) { - if (!isDateWithInDateRange(minDate, maxDate, date)) { - return DateFormat('EEEEE').format(date).toString() + - DateFormat('dd/MMMM/yyyy').format(date).toString() + - ', Disabled date'; - } +class _CurrentTimeIndicator extends CustomPainter { + _CurrentTimeIndicator( + this.timeIntervalSize, + this.timeRulerSize, + this.timeSlotViewSettings, + this.isTimelineView, + this.visibleDates, + this.todayHighlightColor, + this.isRTL, + ValueNotifier repaintNotifier) + : super(repaint: repaintNotifier); + final double timeIntervalSize; + final TimeSlotViewSettings timeSlotViewSettings; + final bool isTimelineView; + final List visibleDates; + final double timeRulerSize; + final Color? todayHighlightColor; + final bool isRTL; - return DateFormat('EEEEE').format(date).toString() + - DateFormat('dd/MMMM/yyyy').format(date).toString(); - } + @override + void paint(Canvas canvas, Size size) { + final DateTime now = DateTime.now(); + final int hours = now.hour; + final int minutes = now.minute; + final int totalMinutes = (hours * 60) + minutes; + final int viewStartMinutes = (timeSlotViewSettings.startHour * 60).toInt(); + final int viewEndMinutes = (timeSlotViewSettings.endHour * 60).toInt(); + if (totalMinutes < viewStartMinutes || totalMinutes > viewEndMinutes) { + return; + } - List _getSemanticsForMonthViewHeader(Size size) { - final List semanticsBuilder = - []; - final double cellWidth = size.width / DateTime.daysPerWeek; - double left = isRTL ? size.width - cellWidth : 0; - const double top = 0; - for (int i = 0; i < DateTime.daysPerWeek; i++) { - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, size.height), - properties: SemanticsProperties( - label: DateFormat('EEEEE') - .format(visibleDates[i]) - .toString() - .toUpperCase(), - textDirection: TextDirection.ltr, - ), - )); - if (isRTL) { - left -= cellWidth; - } else { - left += cellWidth; + int index = -1; + for (int i = 0; i < visibleDates.length; i++) { + final DateTime date = visibleDates[i]; + if (isSameDate(date, now)) { + index = i; + break; } } - return semanticsBuilder; - } - - List _getSemanticsForDayHeader(Size size) { - final List semanticsBuilder = - []; - const double top = 0; - double left; - final double cellWidth = view == CalendarView.day - ? size.width - : (size.width - timeLabelWidth) / visibleDates.length; - if (isRTL) { - left = view == CalendarView.day - ? size.width - timeLabelWidth - : (size.width - timeLabelWidth) - cellWidth; - } else { - left = view == CalendarView.day ? 0 : timeLabelWidth; + if (index == -1) { + return; } - for (int i = 0; i < visibleDates.length; i++) { - final DateTime visibleDate = visibleDates[i]; - if (showWeekNumber && - ((visibleDate.weekday == DateTime.monday && - view != CalendarView.day) || - (view == CalendarView.workWeek && - timeSlotViewSettings.nonWorkingDays - .contains(DateTime.monday) && - i == visibleDates.length ~/ 2))) { - final int weekNumber = DateTimeHelper.getWeekNumberOfYear(visibleDate); - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(isRTL ? (size.width - timeLabelWidth) : 0, 0, - isRTL ? size.width : timeLabelWidth, viewHeaderHeight), - properties: SemanticsProperties( - label: 'week' + weekNumber.toString(), - textDirection: TextDirection.ltr, - ))); + + final double minuteHeight = timeIntervalSize / + CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + final double currentTimePosition = CalendarViewHelper.getTimeToPosition( + Duration(hours: hours, minutes: minutes), + timeSlotViewSettings, + minuteHeight); + final Paint painter = Paint() + ..color = todayHighlightColor! + ..strokeWidth = 1 + ..isAntiAlias = true + ..style = PaintingStyle.fill; + if (isTimelineView) { + final double viewSize = size.width / visibleDates.length; + double startXPosition = (index * viewSize) + currentTimePosition; + if (isRTL) { + startXPosition = size.width - startXPosition; } - semanticsBuilder.add(CustomPainterSemantics( - rect: Rect.fromLTWH(left, top, cellWidth, size.height), - properties: SemanticsProperties( - label: _getAccessibilityText(visibleDates[i]), - textDirection: TextDirection.ltr, - ), - )); + canvas.drawCircle(Offset(startXPosition, 5), 5, painter); + canvas.drawLine(Offset(startXPosition, 0), + Offset(startXPosition, size.height), painter); + } else { + final double viewSize = + (size.width - timeRulerSize) / visibleDates.length; + final double startYPosition = currentTimePosition; + double viewStartPosition = (index * viewSize) + timeRulerSize; + double viewEndPosition = viewStartPosition + viewSize; + double startXPosition = viewStartPosition < 5 ? 5 : viewStartPosition; if (isRTL) { - left -= cellWidth; - } else { - left += cellWidth; + viewStartPosition = size.width - viewStartPosition; + viewEndPosition = size.width - viewEndPosition; + startXPosition = size.width - startXPosition; } + canvas.drawCircle(Offset(startXPosition, startYPosition), 5, painter); + canvas.drawLine(Offset(viewStartPosition, startYPosition), + Offset(viewEndPosition, startYPosition), painter); } + } - return semanticsBuilder; + @override + bool? hitTest(Offset position) { + return false; } - List _getSemanticsBuilder(Size size) { - switch (view) { - case CalendarView.schedule: - case CalendarView.timelineDay: - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - case CalendarView.timelineMonth: - return []; - case CalendarView.month: - return _getSemanticsForMonthViewHeader(size); - case CalendarView.day: - case CalendarView.week: - case CalendarView.workWeek: - return _getSemanticsForDayHeader(size); + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} + +/// Returns the date time value from the position. +DateTime? _timeFromPosition( + DateTime date, + TimeSlotViewSettings timeSlotViewSettings, + double positionY, + _CalendarViewState? currentState, + double timeIntervalHeight, + bool isTimelineView) { + final double topPosition = + currentState == null ? 0 : currentState._scrollController!.offset; + + final double singleIntervalHeightForAnHour = + ((60 / CalendarViewHelper.getTimeInterval(timeSlotViewSettings)) * + timeIntervalHeight) + .toDouble(); + final double startHour = timeSlotViewSettings.startHour; + final double endHour = timeSlotViewSettings.endHour; + if (isTimelineView) { + if (currentState!._isRTL) { + positionY = (currentState._scrollController!.offset % + _getSingleViewWidthForTimeLineView(currentState)) + + (currentState._scrollController!.position.viewportDimension - + positionY); + } else { + positionY += currentState._scrollController!.offset % + _getSingleViewWidthForTimeLineView(currentState); + } + } else { + positionY += topPosition; + } + if (positionY >= 0) { + double totalHour = positionY / singleIntervalHeightForAnHour; + totalHour += startHour; + int hour = totalHour.toInt(); + final int minute = ((totalHour - hour) * 60).round(); + if (isTimelineView) { + while (hour >= endHour) { + hour = ((hour - endHour) + startHour).toInt(); + } } + return DateTime(date.year, date.month, date.day, hour, minute, 0); } + + return DateTime(date.year, date.month, date.day, 0, 0, 0); } -class _SelectionPainter extends CustomPainter { - _SelectionPainter( - this.calendar, - this.view, - this.visibleDates, - this.selectedDate, - this.selectionDecoration, - this.timeIntervalHeight, - this.calendarTheme, - this.repaintNotifier, +/// Returns the single view width from the time line view for time line +double _getSingleViewWidthForTimeLineView(_CalendarViewState viewState) { + return (viewState._scrollController!.position.maxScrollExtent + + viewState._scrollController!.position.viewportDimension) / + viewState.widget.visibleDates.length; +} + +class _ResizingPaintDetails { + _ResizingPaintDetails( + {this.appointmentView, + required this.position, + this.isAllDayPanel = false, + this.scrollPosition, + this.monthRowCount = 0, + this.monthCellHeight, + this.appointmentColor = Colors.transparent}); + + AppointmentView? appointmentView; + final ValueNotifier position; + bool isAllDayPanel; + double? scrollPosition; + int monthRowCount; + double? monthCellHeight; + Color appointmentColor; +} + +class _ResizingAppointmentPainter extends CustomPainter { + _ResizingAppointmentPainter( + this.resizingDetails, this.isRTL, - this.selectedResourceIndex, - this.resourceItemHeight, - this.showWeekNumber, + this.textScaleFactor, this.isMobilePlatform, - this.getCalendarState) - : super(repaint: repaintNotifier); + this.appointmentTextStyle, + this.allDayHeight, + this.viewHeaderHeight, + this.scrollController, + this.view, + this.mouseCursor, + this.weekNumberPanelWidth, + this.calendarTheme) + : super(repaint: resizingDetails.value.position); + + final ValueNotifier<_ResizingPaintDetails> resizingDetails; - final SfCalendar calendar; - final CalendarView view; - final SfCalendarThemeData calendarTheme; - final List visibleDates; - Decoration? selectionDecoration; - DateTime? selectedDate; - final double timeIntervalHeight; final bool isRTL; - final UpdateCalendarState getCalendarState; - int selectedResourceIndex; - final double? resourceItemHeight; - late BoxPainter _boxPainter; - AppointmentView? appointmentView; - double _cellWidth = 0, _cellHeight = 0, _xPosition = 0, _yPosition = 0; - final ValueNotifier repaintNotifier; - final UpdateCalendarStateDetails _updateCalendarStateDetails = - UpdateCalendarStateDetails(); - final bool showWeekNumber; + final double textScaleFactor; + final bool isMobilePlatform; + final TextStyle appointmentTextStyle; + + final double allDayHeight; + + final double viewHeaderHeight; + + final ScrollController? scrollController; + + final CalendarView view; + + final double weekNumberPanelWidth; + + final SystemMouseCursor mouseCursor; + + final SfCalendarThemeData calendarTheme; + + final Paint _shadowPainter = Paint(); + final TextPainter _textPainter = TextPainter(); + @override void paint(Canvas canvas, Size size) { - selectionDecoration ??= BoxDecoration( - color: Colors.transparent, - border: Border.all(color: calendarTheme.selectionBorderColor!, width: 2), - borderRadius: const BorderRadius.all(Radius.circular(2)), - shape: BoxShape.rectangle, - ); - - getCalendarState(_updateCalendarStateDetails); - selectedDate = _updateCalendarStateDetails.selectedDate; - final bool isMonthView = - view == CalendarView.month || view == CalendarView.timelineMonth; - final int timeInterval = - CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); - if (selectedDate != null && - ((isMonthView && - !isDateWithInDateRange( - calendar.minDate, calendar.maxDate, selectedDate)) || - (!isMonthView && - !CalendarViewHelper.isDateTimeWithInDateTimeRange( - calendar.minDate, - calendar.maxDate, - selectedDate!, - timeInterval)))) { + if (resizingDetails.value.appointmentView == null || + resizingDetails.value.appointmentView!.appointmentRect == null) { return; } - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - final double timeLabelWidth = CalendarViewHelper.getTimeLabelWidth( - calendar.timeSlotViewSettings.timeRulerSize, view); - double width = size.width; - final bool isTimeline = CalendarViewHelper.isTimelineView(view); - if (view != CalendarView.month && !isTimeline) { - width -= timeLabelWidth; - } - final bool isResourceEnabled = isTimeline && - CalendarViewHelper.isResourceEnabled(calendar.dataSource, view); - if ((selectedDate == null && appointmentView == null) || - visibleDates != _updateCalendarStateDetails.currentViewVisibleDates || - (isResourceEnabled && selectedResourceIndex == -1)) { - return; - } + final double scrollOffset = + view == CalendarView.month || resizingDetails.value.isAllDayPanel + ? 0 + : resizingDetails.value.scrollPosition ?? scrollController!.offset; - if (!isTimeline) { - if (view == CalendarView.month) { - _cellWidth = width / DateTime.daysPerWeek; - _cellHeight = - size.height / calendar.monthViewSettings.numberOfWeeksInView; - } else { - _cellWidth = width / visibleDates.length; - _cellHeight = timeIntervalHeight; - } - } else { - _cellWidth = timeIntervalHeight; - _cellHeight = size.height; + final bool isForwardResize = mouseCursor == SystemMouseCursors.resizeDown || + mouseCursor == SystemMouseCursors.resizeRight; + final bool isBackwardResize = mouseCursor == SystemMouseCursors.resizeUp || + mouseCursor == SystemMouseCursors.resizeLeft; - /// The selection view must render on the resource area alone, when the - /// resource enabled. - if (isResourceEnabled && selectedResourceIndex >= 0) { - _cellHeight = resourceItemHeight!; - } - } + const int textStartPadding = 3; + double xPosition = resizingDetails.value.position.value!.dx; + double yPosition = resizingDetails.value.position.value!.dy; - if (appointmentView != null && appointmentView!.appointment != null) { - _drawAppointmentSelection(canvas); - } + _shadowPainter.color = resizingDetails.value.appointmentColor; + final bool isTimelineView = CalendarViewHelper.isTimelineView(view); - switch (view) { - case CalendarView.schedule: - return; - case CalendarView.month: - { - if (selectedDate != null) { - _drawMonthSelection(canvas, size, width); - } - } - break; - case CalendarView.day: - { - if (selectedDate != null) { - _drawDaySelection(canvas, size, width, timeLabelWidth); + final bool isHorizontalResize = resizingDetails.value.isAllDayPanel || + isTimelineView || + view == CalendarView.month; + double left = resizingDetails.value.position.value!.dx, + top = resizingDetails.value.appointmentView!.appointmentRect!.top, + right = resizingDetails.value.appointmentView!.appointmentRect!.right, + bottom = resizingDetails.value.appointmentView!.appointmentRect!.bottom; + + bool canUpdateSubjectPosition = true; + late Rect rect; + if (resizingDetails.value.monthRowCount != 0 && + view == CalendarView.month) { + final int lastRow = resizingDetails.value.monthRowCount; + for (int i = lastRow; i >= 0; i--) { + if (i == 0) { + if (isBackwardResize) { + left = isRTL ? 0 : weekNumberPanelWidth; + right = + resizingDetails.value.appointmentView!.appointmentRect!.right; + if (isRTL) { + top -= resizingDetails.value.monthCellHeight!; + xPosition = right; + yPosition = top; + } else { + top += resizingDetails.value.monthCellHeight!; + } + } else { + left = resizingDetails.value.appointmentView!.appointmentRect!.left; + right = isRTL ? size.width - weekNumberPanelWidth : size.width; + if (isRTL) { + top += resizingDetails.value.monthCellHeight!; + } else { + top -= resizingDetails.value.monthCellHeight!; + } + + if (!isRTL) { + xPosition = left; + yPosition = top; + } } - } - break; - case CalendarView.week: - case CalendarView.workWeek: - { - if (selectedDate != null) { - _drawWeekSelection(canvas, size, timeLabelWidth, width); + } else if (i == lastRow) { + if (isBackwardResize) { + left = resizingDetails.value.position.value!.dx; + right = isRTL ? size.width - weekNumberPanelWidth : size.width; + xPosition = left; + yPosition = resizingDetails.value.position.value!.dy; + } else { + right = resizingDetails.value.position.value!.dx; + left = isRTL ? 0 : weekNumberPanelWidth; + if (!isRTL) { + xPosition = right; + yPosition = top; + } } - } - break; - case CalendarView.timelineDay: - { - if (selectedDate != null) { - _drawTimelineDaySelection(canvas, size, width); + top = resizingDetails.value.position.value!.dy; + } else { + left = isRTL ? 0 : weekNumberPanelWidth; + if (isForwardResize) { + if (isRTL) { + top += resizingDetails.value.monthCellHeight!; + } else { + top -= resizingDetails.value.monthCellHeight!; + } + } else { + if (isRTL) { + top -= resizingDetails.value.monthCellHeight!; + } else { + top += resizingDetails.value.monthCellHeight!; + } } + right = isRTL ? size.width : size.width - weekNumberPanelWidth; } - break; - case CalendarView.timelineWeek: - case CalendarView.timelineWorkWeek: - { - if (selectedDate != null) { - _drawTimelineWeekSelection(canvas, size, width); + + bottom = top + + resizingDetails.value.appointmentView!.appointmentRect!.height; + rect = Rect.fromLTRB(left, top, right, bottom); + canvas.drawRect(rect, _shadowPainter); + paintBorder(canvas, rect, + left: BorderSide( + color: calendarTheme.selectionBorderColor!, width: 2), + right: BorderSide( + color: calendarTheme.selectionBorderColor!, width: 2), + bottom: BorderSide( + color: calendarTheme.selectionBorderColor!, width: 2), + top: BorderSide( + color: calendarTheme.selectionBorderColor!, width: 2)); + } + } else { + if (isForwardResize) { + if (isHorizontalResize) { + if (resizingDetails.value.isAllDayPanel || + view == CalendarView.month) { + left = resizingDetails.value.appointmentView!.appointmentRect!.left; + } else if (isTimelineView) { + left = + resizingDetails.value.appointmentView!.appointmentRect!.left - + scrollOffset; + if (isRTL) { + left = + scrollOffset + scrollController!.position.viewportDimension; + left = left - + ((scrollController!.position.viewportDimension + + scrollController!.position.maxScrollExtent) - + resizingDetails + .value.appointmentView!.appointmentRect!.left); + } } - } - break; - case CalendarView.timelineMonth: - { - if (selectedDate != null) { - _drawTimelineMonthSelection(canvas, size, width); + right = resizingDetails.value.position.value!.dx; + top = resizingDetails.value.position.value!.dy; + bottom = top + + resizingDetails.value.appointmentView!.appointmentRect!.height; + } else { + top = resizingDetails.value.appointmentView!.appointmentRect!.top - + scrollOffset + + allDayHeight + + viewHeaderHeight; + bottom = resizingDetails.value.position.value!.dy; + if (top < viewHeaderHeight + allDayHeight) { + top = viewHeaderHeight + allDayHeight; + canUpdateSubjectPosition = false; } + bottom = bottom > size.height ? size.height : bottom; } - } - } - - @override - bool? hitTest(Offset position) { - return false; - } - - void _drawMonthSelection(Canvas canvas, Size size, double width) { - final int visibleDatesLength = visibleDates.length; - if (!isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDatesLength - 1], selectedDate)) { - return; - } - - final int currentMonth = visibleDates[visibleDatesLength ~/ 2].month; - - /// Check the selected cell date as trailing or leading date when - /// [SfCalendar] month not shown leading and trailing dates. - if (!CalendarViewHelper.isCurrentMonthDate( - calendar.monthViewSettings.numberOfWeeksInView, - calendar.monthViewSettings.showTrailingAndLeadingDates, - currentMonth, - selectedDate!)) { - return; - } - if (CalendarViewHelper.isDateInDateCollection( - calendar.blackoutDates, selectedDate!)) { - return; - } + xPosition = isRTL ? right : left; + } else { + if (isHorizontalResize) { + if (resizingDetails.value.isAllDayPanel || + view == CalendarView.month) { + right = + resizingDetails.value.appointmentView!.appointmentRect!.right; + } else if (isTimelineView) { + right = + resizingDetails.value.appointmentView!.appointmentRect!.right - + scrollOffset; + if (isRTL) { + right = + scrollOffset + scrollController!.position.viewportDimension; + right = right - + ((scrollController!.position.viewportDimension + + scrollController!.position.maxScrollExtent) - + resizingDetails + .value.appointmentView!.appointmentRect!.right); + } + } - for (int i = 0; i < visibleDatesLength; i++) { - if (isSameDate(visibleDates[i], selectedDate)) { - final double weekNumberPanelWidth = - CalendarViewHelper.getWeekNumberPanelWidth( - showWeekNumber, width, isMobilePlatform); - _cellWidth = (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek; - final int columnIndex = (i / DateTime.daysPerWeek).truncate(); - _yPosition = columnIndex * _cellHeight; - final int rowIndex = i % DateTime.daysPerWeek; - if (isRTL) { - _xPosition = (DateTime.daysPerWeek - 1 - rowIndex) * _cellWidth; + left = resizingDetails.value.position.value!.dx; + top = resizingDetails.value.position.value!.dy; + bottom = top + + resizingDetails.value.appointmentView!.appointmentRect!.height; } else { - _xPosition = rowIndex * _cellWidth + weekNumberPanelWidth; + top = resizingDetails.value.position.value!.dy; + bottom = + resizingDetails.value.appointmentView!.appointmentRect!.bottom - + scrollOffset + + allDayHeight + + viewHeaderHeight; + if (top < viewHeaderHeight + allDayHeight) { + top = viewHeaderHeight + allDayHeight; + } + bottom = bottom > size.height ? size.height : bottom; } - _drawSlotSelection(width, size.height, canvas); - break; - } - } - } - void _drawDaySelection( - Canvas canvas, Size size, double width, double timeLabelWidth) { - if (isSameDate(visibleDates[0], selectedDate)) { - if (isRTL) { - _xPosition = 0; - } else { - _xPosition = timeLabelWidth; + xPosition = isRTL ? right : left; + if (!isHorizontalResize) { + if (top < viewHeaderHeight + allDayHeight) { + top = viewHeaderHeight + allDayHeight; + canUpdateSubjectPosition = false; + } + bottom = bottom > size.height ? size.height : bottom; + } } - - selectedDate = _updateSelectedDate(); - - _yPosition = AppointmentHelper.timeToPosition( - calendar, selectedDate!, timeIntervalHeight); - _drawSlotSelection(width + timeLabelWidth, size.height, canvas); + rect = Rect.fromLTRB(left, top, right, bottom); + canvas.drawRect(rect, _shadowPainter); + yPosition = top; } - } - /// Method to update the selected date, when the selected date not fill the - /// exact time slot, and render the mid of time slot, on this scenario we - /// have updated the selected date to update the exact time slot. - /// - /// Eg: If the time interval is 60min, and the selected date is 12.45 PM the - /// selection renders on the center of 12 to 1 PM slot, to avoid this we have - /// modified the selected date to 1 PM so that the selection will render the - /// exact time slot. - DateTime _updateSelectedDate() { - final int timeInterval = - CalendarViewHelper.getTimeInterval(calendar.timeSlotViewSettings); - final int startHour = calendar.timeSlotViewSettings.startHour.toInt(); - final double startMinute = (calendar.timeSlotViewSettings.startHour - - calendar.timeSlotViewSettings.startHour.toInt()) * - 60; - final int selectedMinutes = ((selectedDate!.hour - startHour) * 60) + - (selectedDate!.minute - startMinute.toInt()); - if (selectedMinutes % timeInterval != 0) { - final int diff = selectedMinutes % timeInterval; - if (diff < (timeInterval / 2)) { - return selectedDate!.subtract(Duration(minutes: diff)); - } else { - return selectedDate!.add(Duration(minutes: timeInterval - diff)); - } + if (!canUpdateSubjectPosition) { + return; } - return selectedDate!; + final TextSpan span = TextSpan( + text: resizingDetails.value.appointmentView!.appointment!.subject, + style: appointmentTextStyle, + ); + + final bool isRecurrenceAppointment = + resizingDetails.value.appointmentView!.appointment!.recurrenceRule != + null && + resizingDetails + .value.appointmentView!.appointment!.recurrenceRule!.isNotEmpty; + + _updateTextPainter(span); + + if (view != CalendarView.month) { + _addSubjectTextForTimeslotViews(canvas, textStartPadding, xPosition, + yPosition, isRecurrenceAppointment, rect); + } else { + _addSubjectTextForMonthView( + canvas, + resizingDetails.value.appointmentView!.appointmentRect!, + appointmentTextStyle, + span, + isRecurrenceAppointment, + xPosition, + rect, + yPosition); + } + + paintBorder(canvas, rect, + left: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + right: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + bottom: + BorderSide(color: calendarTheme.selectionBorderColor!, width: 2), + top: BorderSide(color: calendarTheme.selectionBorderColor!, width: 2)); } - void _drawWeekSelection( - Canvas canvas, Size size, double timeLabelWidth, double width) { - final int visibleDatesLength = visibleDates.length; - if (isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDatesLength - 1], selectedDate)) { - for (int i = 0; i < visibleDatesLength; i++) { - if (isSameDate(selectedDate, visibleDates[i])) { - final int rowIndex = i; - if (isRTL) { - _xPosition = _cellWidth * (visibleDatesLength - 1 - rowIndex); - } else { - _xPosition = timeLabelWidth + _cellWidth * rowIndex; - } + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } - selectedDate = _updateSelectedDate(); - _yPosition = AppointmentHelper.timeToPosition( - calendar, selectedDate!, timeIntervalHeight); - _drawSlotSelection(width + timeLabelWidth, size.height, canvas); + void _addSubjectTextForTimeslotViews( + Canvas canvas, + int textStartPadding, + double xPosition, + double yPosition, + bool isRecurrenceAppointment, + Rect rect) { + final double totalHeight = + resizingDetails.value.appointmentView!.appointmentRect!.height - + textStartPadding; + _updatePainterMaxLines(totalHeight); + + double maxTextWidth = + resizingDetails.value.appointmentView!.appointmentRect!.width - + textStartPadding; + maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; + _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + if (isRTL) { + xPosition -= textStartPadding + _textPainter.width; + } + _textPainter.paint( + canvas, + Offset(xPosition + (isRTL ? 0 : textStartPadding), + yPosition + textStartPadding)); + if (isRecurrenceAppointment || + resizingDetails.value.appointmentView!.appointment!.recurrenceId != + null) { + double textSize = appointmentTextStyle.fontSize!; + if (rect.width < textSize || rect.height < textSize) { + textSize = rect.width > rect.height ? rect.height : rect.width; + } + _addRecurrenceIcon( + rect, canvas, textStartPadding, isRecurrenceAppointment, textSize); + } + } + + void _addSubjectTextForMonthView( + Canvas canvas, + RRect appointmentRect, + TextStyle style, + TextSpan span, + bool isRecurrenceAppointment, + double xPosition, + Rect rect, + double yPosition) { + double textSize = -1; + if (textSize == -1) { + //// left and right side padding value 2 subtracted in appointment width + double maxTextWidth = appointmentRect.width - 2; + maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; + for (double j = style.fontSize! - 1; j > 0; j--) { + _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + if (_textPainter.height >= appointmentRect.height) { + style = style.copyWith(fontSize: j); + span = TextSpan( + text: resizingDetails.value.appointmentView!.appointment!.subject, + style: style); + _updateTextPainter(span); + } else { + textSize = j + 1; break; } } + } else { + span = TextSpan( + text: resizingDetails.value.appointmentView!.appointment!.subject, + style: style.copyWith(fontSize: textSize)); + _updateTextPainter(span); + } + final double textWidth = + appointmentRect.width - (isRecurrenceAppointment ? textSize : 1); + _textPainter.layout(minWidth: 0, maxWidth: textWidth > 0 ? textWidth : 0); + if (isRTL) { + xPosition -= (isRTL ? 0 : 2) + _textPainter.width; } - } + yPosition = + yPosition + ((appointmentRect.height - _textPainter.height) / 2); + _textPainter.paint(canvas, Offset(xPosition + (isRTL ? 0 : 2), yPosition)); - /// Returns the yPosition for selection view based on resource associated with - /// the selected cell in timeline views when resource enabled. - double _getTimelineYPosition() { - if (selectedResourceIndex == -1) { - return 0; + if (isRecurrenceAppointment || + resizingDetails.value.appointmentView!.appointment!.recurrenceId != + null) { + _addRecurrenceIcon(rect, canvas, null, isRecurrenceAppointment, textSize); } + } - return selectedResourceIndex * resourceItemHeight!; + void _updateTextPainter(TextSpan span) { + _textPainter.text = span; + _textPainter.maxLines = 1; + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaleFactor = textScaleFactor; } - void _drawTimelineDaySelection(Canvas canvas, Size size, double width) { - if (isSameDate(visibleDates[0], selectedDate)) { - selectedDate = _updateSelectedDate(); - _xPosition = AppointmentHelper.timeToPosition( - calendar, selectedDate!, timeIntervalHeight); - _yPosition = _getTimelineYPosition(); - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight!; - if (isRTL) { - _xPosition = size.width - _xPosition - _cellWidth; - } - _drawSlotSelection(width, height, canvas); + void _addRecurrenceIcon(Rect rect, Canvas canvas, int? textPadding, + bool isRecurrenceAppointment, double textSize) { + const double xPadding = 2; + const double bottomPadding = 2; + + final TextSpan icon = AppointmentHelper.getRecurrenceIcon( + appointmentTextStyle.color!, textSize, isRecurrenceAppointment); + _textPainter.text = icon; + + if (view == CalendarView.month) { + _textPainter.layout( + minWidth: 0, maxWidth: rect.width + 1 > 0 ? rect.width + 1 : 0); + final double yPosition = + rect.top + ((rect.height - _textPainter.height) / 2); + const double rightPadding = 0; + final double recurrenceStartPosition = isRTL + ? rect.left + rightPadding + : rect.right - _textPainter.width - rightPadding; + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTRB(recurrenceStartPosition, yPosition, + recurrenceStartPosition + _textPainter.width, rect.bottom), + resizingDetails.value.appointmentView!.appointmentRect!.tlRadius), + _shadowPainter); + _textPainter.paint(canvas, Offset(recurrenceStartPosition, yPosition)); + } else { + double maxTextWidth = + resizingDetails.value.appointmentView!.appointmentRect!.width - + textPadding! - + 2; + maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; + _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromLTRB( + isRTL + ? rect.left + textSize + xPadding + : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize, + isRTL ? rect.left : rect.right, + rect.bottom), + resizingDetails.value.appointmentView!.appointmentRect!.tlRadius), + _shadowPainter); + _textPainter.paint( + canvas, + Offset( + isRTL ? rect.left + xPadding : rect.right - textSize - xPadding, + rect.bottom - bottomPadding - textSize)); } } - void _drawTimelineMonthSelection(Canvas canvas, Size size, double width) { - if (!isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { + void _updatePainterMaxLines(double height) { + /// [preferredLineHeight] is used to get the line height based on text + /// style and text. floor the calculated value to set the minimum line + /// count to painter max lines property. + final int maxLines = (height / _textPainter.preferredLineHeight).floor(); + if (maxLines <= 0) { return; } - if (CalendarViewHelper.isDateInDateCollection( - calendar.blackoutDates, selectedDate!)) { - return; - } + _textPainter.maxLines = maxLines; + } +} - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate(visibleDates[i], selectedDate)) { - _yPosition = _getTimelineYPosition(); - _xPosition = - isRTL ? size.width - ((i + 1) * _cellWidth) : i * _cellWidth; - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight!; - _drawSlotSelection(width, height, canvas); - break; - } - } +dynamic _getCalendarAppointmentToObject( + CalendarAppointment? calendarAppointment, SfCalendar calendar) { + if (calendarAppointment == null) { + return null; } - void _drawTimelineWeekSelection(Canvas canvas, Size size, double width) { - if (isDateWithInDateRange( - visibleDates[0], visibleDates[visibleDates.length - 1], selectedDate)) { - selectedDate = _updateSelectedDate(); - for (int i = 0; i < visibleDates.length; i++) { - if (isSameDate(selectedDate, visibleDates[i])) { - final double singleViewWidth = width / visibleDates.length; - _xPosition = (i * singleViewWidth) + - AppointmentHelper.timeToPosition( - calendar, selectedDate!, timeIntervalHeight); - if (isRTL) { - _xPosition = size.width - _xPosition - _cellWidth; - } - _yPosition = _getTimelineYPosition(); - final double height = selectedResourceIndex == -1 - ? size.height - : _yPosition + resourceItemHeight!; - _drawSlotSelection(width, height, canvas); - break; - } - } + final Appointment appointment = + calendarAppointment.convertToCalendarAppointment(); + + return calendarAppointment.data is Appointment + ? appointment + : calendar.dataSource!.convertAppointmentToObject( + calendarAppointment.data, appointment) ?? + appointment; +} + +class _DragPaintDetails { + _DragPaintDetails( + {this.appointmentView, required this.position, this.draggingTime}); + + AppointmentView? appointmentView; + final ValueNotifier position; + DateTime? draggingTime; +} + +@immutable +class _DraggingAppointmentWidget extends StatefulWidget { + const _DraggingAppointmentWidget( + this.dragDetails, + this.isRTL, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentTextStyle, + this.dragAndDropSettings, + this.calendarView, + this.allDayPanelHeight, + this.viewHeaderHeight, + this.timeLabelWidth, + this.resourceItemHeight, + this.calendarTheme, + this.calendar, + this.width, + this.height); + + final ValueNotifier<_DragPaintDetails> dragDetails; + + final bool isRTL; + + final double textScaleFactor; + + final bool isMobilePlatform; + + final TextStyle appointmentTextStyle; + + final DragAndDropSettings dragAndDropSettings; + + final CalendarView calendarView; + + final double allDayPanelHeight; + + final double viewHeaderHeight; + + final double timeLabelWidth; + + final double resourceItemHeight; + + final SfCalendarThemeData calendarTheme; + + final SfCalendar calendar; + + final double width; + + final double height; + + @override + _DraggingAppointmentState createState() => _DraggingAppointmentState(); +} + +class _DraggingAppointmentState extends State<_DraggingAppointmentWidget> { + AppointmentView? _draggingAppointmentView; + + @override + void initState() { + _draggingAppointmentView = widget.dragDetails.value.appointmentView; + widget.dragDetails.value.position.addListener(_updateDraggingAppointment); + super.initState(); + } + + @override + void dispose() { + widget.dragDetails.value.position + .removeListener(_updateDraggingAppointment); + super.dispose(); + } + + void _updateDraggingAppointment() { + if (_draggingAppointmentView != widget.dragDetails.value.appointmentView) { + _draggingAppointmentView = widget.dragDetails.value.appointmentView; + setState(() {}); } } - void _drawAppointmentSelection(Canvas canvas) { - Rect rect = appointmentView!.appointmentRect!.outerRect; - rect = Rect.fromLTRB(rect.left, rect.top, rect.right, rect.bottom); - _boxPainter = selectionDecoration! - .createBoxPainter(_updateSelectionDecorationPainter); - _boxPainter.paint(canvas, Offset(rect.left, rect.top), - ImageConfiguration(size: rect.size)); + @override + Widget build(BuildContext context) { + Widget? child; + if (widget.dragDetails.value.appointmentView != null && + widget.calendar.appointmentBuilder != null) { + final DateTime date = DateTime( + _draggingAppointmentView!.appointment!.actualStartTime.year, + _draggingAppointmentView!.appointment!.actualStartTime.month, + _draggingAppointmentView!.appointment!.actualStartTime.day); + + child = widget.calendar.appointmentBuilder!( + context, + CalendarAppointmentDetails( + date, + List.unmodifiable([ + CalendarViewHelper.getAppointmentDetail( + _draggingAppointmentView!.appointment!, + widget.calendar.dataSource) + ]), + Rect.fromLTWH( + widget.dragDetails.value.position.value!.dx, + widget.dragDetails.value.position.value!.dy, + widget.isRTL + ? -_draggingAppointmentView!.appointmentRect!.width + : _draggingAppointmentView!.appointmentRect!.width, + _draggingAppointmentView!.appointmentRect!.height), + isMoreAppointmentRegion: false)); + } + + return _DraggingAppointmentRenderObjectWidget( + widget.dragDetails.value, + widget.isRTL, + widget.textScaleFactor, + widget.isMobilePlatform, + widget.appointmentTextStyle, + widget.dragAndDropSettings, + widget.calendarView, + widget.allDayPanelHeight, + widget.viewHeaderHeight, + widget.timeLabelWidth, + widget.resourceItemHeight, + widget.calendarTheme, + widget.width, + widget.height, + child: child, + ); } +} - /// Used to pass the argument of create box painter and it is called when - /// decoration have asynchronous data like image. - void _updateSelectionDecorationPainter() { - repaintNotifier.value = !repaintNotifier.value; +@immutable +class _DraggingAppointmentRenderObjectWidget + extends SingleChildRenderObjectWidget { + const _DraggingAppointmentRenderObjectWidget( + this.dragDetails, + this.isRTL, + this.textScaleFactor, + this.isMobilePlatform, + this.appointmentTextStyle, + this.dragAndDropSettings, + this.calendarView, + this.allDayPanelHeight, + this.viewHeaderHeight, + this.timeLabelWidth, + this.resourceItemHeight, + this.calendarTheme, + this.width, + this.height, + {Widget? child}) + : super(child: child); + final _DragPaintDetails dragDetails; + + final bool isRTL; + + final double textScaleFactor; + + final bool isMobilePlatform; + + final TextStyle appointmentTextStyle; + + final DragAndDropSettings dragAndDropSettings; + + final CalendarView calendarView; + + final double allDayPanelHeight; + + final double viewHeaderHeight; + + final double timeLabelWidth; + + final double resourceItemHeight; + + final SfCalendarThemeData calendarTheme; + + final double width; + + final double height; + + @override + RenderObject createRenderObject(BuildContext context) { + return _DraggingAppointmentRenderObject( + dragDetails, + isRTL, + textScaleFactor, + isMobilePlatform, + appointmentTextStyle, + dragAndDropSettings, + calendarView, + allDayPanelHeight, + viewHeaderHeight, + timeLabelWidth, + resourceItemHeight, + calendarTheme, + width, + height); } - void _drawSlotSelection(double width, double height, Canvas canvas) { - //// padding used to avoid first, last row and column selection clipping. - const double padding = 0.5; - final Rect rect = Rect.fromLTRB( - _xPosition == 0 ? _xPosition + padding : _xPosition, - _yPosition == 0 ? _yPosition + padding : _yPosition, - _xPosition + _cellWidth == width - ? _xPosition + _cellWidth - padding - : _xPosition + _cellWidth, - _yPosition + _cellHeight == height - ? _yPosition + _cellHeight - padding - : _yPosition + _cellHeight); + @override + void updateRenderObject( + BuildContext context, _DraggingAppointmentRenderObject renderObject) { + renderObject + ..dragDetails = dragDetails + ..isRTL = isRTL + ..textScaleFactor = textScaleFactor + ..isMobilePlatform = isMobilePlatform + ..appointmentTextStyle = appointmentTextStyle + ..dragAndDropSettings = dragAndDropSettings + ..calendarView = calendarView + ..allDayPanelHeight = allDayPanelHeight + ..viewHeaderHeight = viewHeaderHeight + ..timeLabelWidth = timeLabelWidth + ..resourceItemHeight = resourceItemHeight + ..calendarTheme = calendarTheme + ..width = width + ..height = height; + } +} - _boxPainter = selectionDecoration! - .createBoxPainter(_updateSelectionDecorationPainter); - _boxPainter.paint(canvas, Offset(rect.left, rect.top), - ImageConfiguration(size: rect.size, textDirection: TextDirection.ltr)); +class _DraggingAppointmentRenderObject extends RenderBox + with RenderObjectWithChildMixin { + _DraggingAppointmentRenderObject( + this._dragDetails, + this._isRTL, + this._textScaleFactor, + this._isMobilePlatform, + this._appointmentTextStyle, + this._dragAndDropSettings, + this._calendarView, + this._allDayPanelHeight, + this._viewHeaderHeight, + this._timeLabelWidth, + this._resourceItemHeight, + this._calendarTheme, + this._width, + this._height); + + double _width; + + double get width => _width; + + set width(double value) { + if (_width == value) { + return; + } + + _width = value; + if (child != null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } } - @override - bool shouldRepaint(_SelectionPainter oldDelegate) { - final _SelectionPainter oldWidget = oldDelegate; - return oldWidget.selectionDecoration != selectionDecoration || - oldWidget.selectedDate != selectedDate || - oldWidget.view != view || - oldWidget.visibleDates != visibleDates || - oldWidget.selectedResourceIndex != selectedResourceIndex || - oldWidget.isRTL != isRTL; + double _height; + + double get height => _height; + + set height(double value) { + if (_height == value) { + return; + } + + _height = value; + if (child != null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } } -} -class _TimeRulerView extends CustomPainter { - _TimeRulerView( - this.horizontalLinesCount, - this.timeIntervalHeight, - this.timeSlotViewSettings, - this.cellBorderColor, - this.isRTL, - this.locale, - this.calendarTheme, - this.isTimelineView, - this.visibleDates, - this.textScaleFactor); + _DragPaintDetails _dragDetails; - final double horizontalLinesCount; - final double timeIntervalHeight; - final TimeSlotViewSettings timeSlotViewSettings; - final bool isRTL; - final String locale; - final SfCalendarThemeData calendarTheme; - final Color? cellBorderColor; - final bool isTimelineView; - final List visibleDates; - final double textScaleFactor; - final Paint _linePainter = Paint(); - final TextPainter _textPainter = TextPainter(); + _DragPaintDetails get dragDetails => _dragDetails; - @override - void paint(Canvas canvas, Size size) { - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - const double offset = 0.5; - double xPosition, yPosition; - final DateTime date = DateTime.now(); - xPosition = isRTL && isTimelineView ? size.width : 0; - yPosition = timeIntervalHeight; - _linePainter.strokeWidth = offset; - _linePainter.color = cellBorderColor ?? calendarTheme.cellBorderColor; + set dragDetails(_DragPaintDetails value) { + if (_dragDetails == value) { + return; + } - if (!isTimelineView) { - final double lineXPosition = isRTL ? offset : size.width - offset; - // Draw vertical time label line - canvas.drawLine(Offset(lineXPosition, 0), - Offset(lineXPosition, size.height), _linePainter); + _dragDetails.position.removeListener(markNeedsPaint); + _dragDetails = value; + _dragDetails.position.addListener(markNeedsPaint); + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); } + } - _textPainter.textDirection = TextDirection.ltr; - _textPainter.textWidthBasis = TextWidthBasis.longestLine; - _textPainter.textScaleFactor = textScaleFactor; + bool _isRTL; - final TextStyle timeTextStyle = - timeSlotViewSettings.timeTextStyle ?? calendarTheme.timeTextStyle; + bool get isRTL => _isRTL; - final double hour = (timeSlotViewSettings.startHour - - timeSlotViewSettings.startHour.toInt()) * - 60; - if (isTimelineView) { - canvas.drawLine(const Offset(0, 0), Offset(size.width, 0), _linePainter); - final double timelineViewWidth = - timeIntervalHeight * horizontalLinesCount; - for (int i = 0; i < visibleDates.length; i++) { - _drawTimeLabels( - canvas, size, date, hour, xPosition, yPosition, timeTextStyle); - if (isRTL) { - xPosition -= timelineViewWidth; - } else { - xPosition += timelineViewWidth; - } - } + set isRTL(bool value) { + if (_isRTL == value) { + return; + } + + _isRTL = value; + if (child == null) { + markNeedsPaint(); } else { - _drawTimeLabels( - canvas, size, date, hour, xPosition, yPosition, timeTextStyle); + markNeedsLayout(); } } - /// Draws the time labels in the time label view for timeslot views in - /// calendar. - void _drawTimeLabels(Canvas canvas, Size size, DateTime date, double hour, - double xPosition, double yPosition, TextStyle timeTextStyle) { - const int padding = 5; - final int timeInterval = - CalendarViewHelper.getTimeInterval(timeSlotViewSettings); + double _textScaleFactor; - /// For timeline view we will draw 24 lines where as in day, week and work - /// week view we will draw 23 lines excluding the 12 AM, hence to rectify - /// this the i value handled accordingly. - for (int i = isTimelineView ? 0 : 1; - i <= (isTimelineView ? horizontalLinesCount - 1 : horizontalLinesCount); - i++) { - if (isTimelineView) { - canvas.save(); - canvas.clipRect( - Rect.fromLTWH(xPosition, 0, timeIntervalHeight, size.height)); - canvas.restore(); - canvas.drawLine( - Offset(xPosition, 0), Offset(xPosition, size.height), _linePainter); - } + double get textScaleFactor => _textScaleFactor; - final double minute = (i * timeInterval) + hour; - date = DateTime(date.day, date.month, date.year, - timeSlotViewSettings.startHour.toInt(), minute.toInt()); - final String time = DateFormat(timeSlotViewSettings.timeFormat, locale) - .format(date) - .toString(); - final TextSpan span = TextSpan( - text: time, - style: timeTextStyle, - ); + set textScaleFactor(double value) { + if (_textScaleFactor == value) { + return; + } - final double cellWidth = isTimelineView ? timeIntervalHeight : size.width; + _textScaleFactor = value; + markNeedsPaint(); + } - _textPainter.text = span; - _textPainter.layout(minWidth: 0, maxWidth: cellWidth); - if (isTimelineView && _textPainter.height > size.height) { - return; - } + bool _isMobilePlatform; - double startXPosition = (cellWidth - _textPainter.width) / 2; - if (startXPosition < 0) { - startXPosition = 0; - } + bool get isMobilePlatform => _isMobilePlatform; - if (isTimelineView) { - startXPosition = isRTL ? xPosition - _textPainter.width : xPosition; - } + set isMobilePlatform(bool value) { + if (_isMobilePlatform == value) { + return; + } - double startYPosition = yPosition - (_textPainter.height / 2); + _isMobilePlatform = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } - if (isTimelineView) { - startYPosition = (size.height - _textPainter.height) / 2; - startXPosition = - isRTL ? startXPosition - padding : startXPosition + padding; - } + TextStyle _appointmentTextStyle; - _textPainter.paint(canvas, Offset(startXPosition, startYPosition)); + TextStyle get appointmentTextStyle => _appointmentTextStyle; - if (!isTimelineView) { - final Offset start = - Offset(isRTL ? 0 : size.width - (startXPosition / 2), yPosition); - final Offset end = - Offset(isRTL ? startXPosition / 2 : size.width, yPosition); - canvas.drawLine(start, end, _linePainter); - yPosition += timeIntervalHeight; - if (yPosition.round() == size.height.round()) { - break; - } - } else { - if (isRTL) { - xPosition -= timeIntervalHeight; - } else { - xPosition += timeIntervalHeight; - } - } + set appointmentTextStyle(TextStyle value) { + if (_appointmentTextStyle == value) { + return; } - } - @override - bool shouldRepaint(_TimeRulerView oldDelegate) { - final _TimeRulerView oldWidget = oldDelegate; - return oldWidget.timeSlotViewSettings != timeSlotViewSettings || - oldWidget.cellBorderColor != cellBorderColor || - oldWidget.calendarTheme != calendarTheme || - oldWidget.isRTL != isRTL || - oldWidget.locale != locale || - oldWidget.visibleDates != visibleDates || - oldWidget.isTimelineView != isTimelineView || - oldWidget.textScaleFactor != textScaleFactor; + _appointmentTextStyle = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } } -} -class _CalendarMultiChildContainer extends Stack { - _CalendarMultiChildContainer( - {this.painter, - List children = const [], - required this.width, - required this.height}) - : super(children: children); - final CustomPainter? painter; - final double width; - final double height; + DragAndDropSettings _dragAndDropSettings; - @override - RenderStack createRenderObject(BuildContext context) { - final Directionality? widget = - context.dependOnInheritedWidgetOfExactType(); - return _MultiChildContainerRenderObject(width, height, - painter: painter, - direction: widget != null ? widget.textDirection : null); + DragAndDropSettings get dragAndDropSettings => _dragAndDropSettings; + + set dragAndDropSettings(DragAndDropSettings value) { + if (_dragAndDropSettings == value) { + return; + } + + _dragAndDropSettings = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } } - @override - void updateRenderObject(BuildContext context, RenderStack renderObject) { - super.updateRenderObject(context, renderObject); - if (renderObject is _MultiChildContainerRenderObject) { - final Directionality? widget = - context.dependOnInheritedWidgetOfExactType(); - renderObject - ..width = width - ..height = height - ..painter = painter - ..textDirection = widget != null ? widget.textDirection : null; + CalendarView _calendarView; + + CalendarView get calendarView => _calendarView; + + set calendarView(CalendarView value) { + if (_calendarView == value) { + return; + } + + _calendarView = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); } } -} -class _MultiChildContainerRenderObject extends RenderStack { - _MultiChildContainerRenderObject(this._width, this._height, - {CustomPainter? painter, TextDirection? direction}) - : _painter = painter, - super(textDirection: direction); + double _allDayPanelHeight; - CustomPainter? get painter => _painter; - CustomPainter? _painter; + double get allDayPanelHeight => _allDayPanelHeight; - set painter(CustomPainter? value) { - if (_painter == value) { + set allDayPanelHeight(double value) { + if (_allDayPanelHeight == value) { return; } - final CustomPainter? oldPainter = _painter; - _painter = value; - _updatePainter(_painter, oldPainter); - if (attached) { - oldPainter?.removeListener(markNeedsPaint); - _painter?.addListener(markNeedsPaint); + _allDayPanelHeight = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + + double _viewHeaderHeight; + + double get viewHeaderHeight => _viewHeaderHeight; + + set viewHeaderHeight(double value) { + if (_viewHeaderHeight == value) { + return; + } + + _viewHeaderHeight = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); } } - double get width => _width; + double _timeLabelWidth; - set width(double value) { - if (_width == value) { + double get timeLabelWidth => _timeLabelWidth; + + set timeLabelWidth(double value) { + if (_timeLabelWidth == value) { return; } - _width = value; - markNeedsLayout(); + _timeLabelWidth = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } } - double _width; - double _height; + double _resourceItemHeight; - double get height => _height; + double get resourceItemHeight => _resourceItemHeight; - set height(double value) { - if (_height == value) { + set resourceItemHeight(double value) { + if (_resourceItemHeight == value) { return; } - _height = value; - markNeedsLayout(); + _resourceItemHeight = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } } - /// Caches [SemanticsNode]s created during [assembleSemanticsNode] so they - /// can be re-used when [assembleSemanticsNode] is called again. This ensures - /// stable ids for the [SemanticsNode]s of children across - /// [assembleSemanticsNode] invocations. - /// Ref: assembleSemanticsNode method in RenderParagraph class - /// (https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/rendering/paragraph.dart) - List? _cacheNodes; + SfCalendarThemeData _calendarTheme; - void _updatePainter(CustomPainter? newPainter, CustomPainter? oldPainter) { - if (newPainter == null) { - markNeedsPaint(); - } else if (oldPainter == null || - newPainter.runtimeType != oldPainter.runtimeType || - newPainter.shouldRepaint(oldPainter)) { - markNeedsPaint(); + SfCalendarThemeData get calendarTheme => _calendarTheme; + + set calendarTheme(SfCalendarThemeData value) { + if (_calendarTheme == value) { + return; } - if (newPainter == null) { - if (attached) { - markNeedsSemanticsUpdate(); - } - } else if (oldPainter == null || - newPainter.runtimeType != oldPainter.runtimeType || - newPainter.shouldRebuildSemantics(oldPainter)) { - markNeedsSemanticsUpdate(); + _calendarTheme = value; + if (child == null) { + markNeedsPaint(); + } else { + markNeedsLayout(); } } @override void attach(PipelineOwner owner) { super.attach(owner); - _painter?.addListener(markNeedsPaint); + _dragDetails.position.addListener(markNeedsPaint); } @override void detach() { - _painter?.removeListener(markNeedsPaint); + _dragDetails.position.removeListener(markNeedsPaint); super.detach(); } + final Paint _shadowPainter = Paint(); + + final TextPainter _textPainter = TextPainter(); + @override void performLayout() { final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - for (dynamic child = firstChild; child != null; child = childAfter(child)) { - child.layout(constraints); - } - } - - @override - void paint(PaintingContext context, Offset offset) { - if (_painter != null) { - _painter!.paint(context.canvas, size); - } - - paintStack(context, offset); - } - @override - void describeSemanticsConfiguration(SemanticsConfiguration config) { - super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = true; + child?.layout(constraints.copyWith( + minWidth: dragDetails.appointmentView!.appointmentRect!.width, + minHeight: dragDetails.appointmentView!.appointmentRect!.height, + maxWidth: dragDetails.appointmentView!.appointmentRect!.width, + maxHeight: dragDetails.appointmentView!.appointmentRect!.height)); } @override - void assembleSemanticsNode( - SemanticsNode node, - SemanticsConfiguration config, - Iterable children, - ) { - _cacheNodes ??= []; - final List semantics = _semanticsBuilder(size); - final List semanticsNodes = []; - for (int i = 0; i < semantics.length; i++) { - final CustomPainterSemantics currentSemantics = semantics[i]; - final SemanticsNode newChild = _cacheNodes!.isNotEmpty - ? _cacheNodes!.removeAt(0) - : SemanticsNode(key: currentSemantics.key); - - final SemanticsProperties properties = currentSemantics.properties; - final SemanticsConfiguration config = SemanticsConfiguration(); - if (properties.label != null) { - config.label = properties.label!; - } - if (properties.textDirection != null) { - config.textDirection = properties.textDirection; + void paint(PaintingContext context, Offset offset) { + final bool isTimelineView = CalendarViewHelper.isTimelineView(calendarView); + if (child == null) { + _drawDefaultUI(context.canvas, isTimelineView); + } else { + context.paintChild( + child!, + Offset( + isRTL + ? dragDetails.position.value!.dx - + dragDetails.appointmentView!.appointmentRect!.width + : dragDetails.position.value!.dx, + dragDetails.position.value!.dy)); + if (dragAndDropSettings.showTimeIndicator && + dragDetails.draggingTime != null) { + _drawTimeIndicator(context.canvas, isTimelineView, size); } - - newChild.updateWith( - config: config, - // As of now CustomPainter does not support multiple tree levels. - childrenInInversePaintOrder: const [], - ); - - newChild - ..rect = currentSemantics.rect - ..transform = currentSemantics.transform - ..tags = currentSemantics.tags; - - semanticsNodes.add(newChild); } - - final List finalChildren = []; - finalChildren.addAll(semanticsNodes); - finalChildren.addAll(children); - _cacheNodes = semanticsNodes; - super.assembleSemanticsNode(node, config, finalChildren); - } - - @override - void clearSemantics() { - super.clearSemantics(); - _cacheNodes = null; } - SemanticsBuilderCallback get _semanticsBuilder { - final List semantics = []; - if (painter != null) { - semantics.addAll(painter!.semanticsBuilder!(size)); - } - // ignore: avoid_as - for (RenderRepaintBoundary? child = firstChild! as RenderRepaintBoundary; - child != null; - // ignore: avoid_as - child = childAfter(child) as RenderRepaintBoundary?) { - if (child.child is! CustomCalendarRenderObject) { - continue; - } - - final CustomCalendarRenderObject appointmentRenderObject = - // ignore: avoid_as - child.child! as CustomCalendarRenderObject; - semantics.addAll(appointmentRenderObject.semanticsBuilder!(size)); + void _drawDefaultUI(Canvas canvas, bool isTimelineView) { + if (dragDetails.appointmentView == null || + dragDetails.appointmentView!.appointmentRect == null) { + return; } - return (Size size) { - return semantics; - }; - } -} + const int textStartPadding = 3; + double xPosition; + double yPosition; + xPosition = dragDetails.position.value!.dx; + yPosition = dragDetails.position.value!.dy; + _shadowPainter.color = + dragDetails.appointmentView!.appointment!.color.withOpacity(0.5); + + final RRect rect = RRect.fromRectAndRadius( + Rect.fromLTWH( + dragDetails.position.value!.dx, + dragDetails.position.value!.dy, + isRTL + ? -dragDetails.appointmentView!.appointmentRect!.width + : dragDetails.appointmentView!.appointmentRect!.width, + dragDetails.appointmentView!.appointmentRect!.height), + dragDetails.appointmentView!.appointmentRect!.tlRadius); + final Path path = Path(); + path.addRRect(rect); + canvas.drawPath(path, _shadowPainter); + canvas.drawShadow(path, _shadowPainter.color, 0.1, true); + final TextSpan span = TextSpan( + text: dragDetails.appointmentView!.appointment!.subject, + style: appointmentTextStyle, + ); -class _CustomNeverScrollableScrollPhysics extends NeverScrollableScrollPhysics { - /// Creates scroll physics that does not let the user scroll. - const _CustomNeverScrollableScrollPhysics({ScrollPhysics? parent}) - : super(parent: parent); + _textPainter.text = span; + _textPainter.maxLines = 1; + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaleFactor = textScaleFactor; + double maxTextWidth = + dragDetails.appointmentView!.appointmentRect!.width - textStartPadding; + maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; + _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); - @override - _CustomNeverScrollableScrollPhysics applyTo(ScrollPhysics? ancestor) { - /// Set the clamping scroll physics as default parent for never scroll - /// physics, because flutter framework set different parent physics - /// based on platform(iOS, Android, etc.,) - return _CustomNeverScrollableScrollPhysics( - parent: buildParent(const ClampingScrollPhysics( - parent: RangeMaintainingScrollPhysics()))); - } -} + if (isRTL) { + xPosition -= textStartPadding + _textPainter.width; + } -class _CurrentTimeIndicator extends CustomPainter { - _CurrentTimeIndicator( - this.timeIntervalSize, - this.timeRulerSize, - this.timeSlotViewSettings, - this.isTimelineView, - this.visibleDates, - this.todayHighlightColor, - this.isRTL, - ValueNotifier repaintNotifier) - : super(repaint: repaintNotifier); - final double timeIntervalSize; - final TimeSlotViewSettings timeSlotViewSettings; - final bool isTimelineView; - final List visibleDates; - final double timeRulerSize; - final Color? todayHighlightColor; - final bool isRTL; + final double totalHeight = + dragDetails.appointmentView!.appointmentRect!.height - textStartPadding; + _updatePainterMaxLines(totalHeight); - @override - void paint(Canvas canvas, Size size) { - final DateTime now = DateTime.now(); - final int hours = now.hour; - final int minutes = now.minute; - final int totalMinutes = (hours * 60) + minutes; - final int viewStartMinutes = (timeSlotViewSettings.startHour * 60).toInt(); - final int viewEndMinutes = (timeSlotViewSettings.endHour * 60).toInt(); - if (totalMinutes < viewStartMinutes || totalMinutes > viewEndMinutes) { - return; - } + maxTextWidth = + dragDetails.appointmentView!.appointmentRect!.width - textStartPadding; + maxTextWidth = maxTextWidth > 0 ? maxTextWidth : 0; + _textPainter.layout(minWidth: 0, maxWidth: maxTextWidth); - int index = -1; - for (int i = 0; i < visibleDates.length; i++) { - final DateTime date = visibleDates[i]; - if (isSameDate(date, now)) { - index = i; - break; - } + _textPainter.paint( + canvas, + isTimelineView + ? Offset(xPosition + (isRTL ? 0 : textStartPadding), + yPosition + textStartPadding) + : Offset(xPosition + (isRTL ? 0 : textStartPadding), + yPosition + textStartPadding)); + if (dragAndDropSettings.showTimeIndicator && + dragDetails.draggingTime != null) { + _drawTimeIndicator(canvas, isTimelineView, size); } + } - if (index == -1) { + void _drawTimeIndicator(Canvas canvas, bool isTimelineView, Size size) { + if (calendarView == CalendarView.month || + calendarView == CalendarView.timelineMonth) { return; } - final double minuteHeight = timeIntervalSize / - CalendarViewHelper.getTimeInterval(timeSlotViewSettings); - final double currentTimePosition = CalendarViewHelper.getTimeToPosition( - Duration(hours: hours, minutes: minutes), - timeSlotViewSettings, - minuteHeight); - final Paint painter = Paint() - ..color = todayHighlightColor! - ..strokeWidth = 1 - ..isAntiAlias = true - ..style = PaintingStyle.fill; + final TextSpan span = TextSpan( + text: DateFormat(dragAndDropSettings.indicatorTimeFormat) + .format(dragDetails.draggingTime!) + .toString(), + style: dragAndDropSettings.timeIndicatorStyle ?? + calendarTheme.timeIndicatorTextStyle, + ); + _textPainter.text = span; + _textPainter.maxLines = 1; + _textPainter.textDirection = TextDirection.ltr; + _textPainter.textAlign = isRTL ? TextAlign.right : TextAlign.left; + _textPainter.textWidthBasis = TextWidthBasis.longestLine; + _textPainter.textScaleFactor = textScaleFactor; + final double timeLabelSize = timeLabelWidth; + _textPainter.layout(minWidth: 0, maxWidth: timeLabelSize); + double xPosition; + double yPosition; if (isTimelineView) { - final double viewSize = size.width / visibleDates.length; - double startXPosition = (index * viewSize) + currentTimePosition; + yPosition = viewHeaderHeight + (timeLabelSize - _textPainter.height); + xPosition = dragDetails.position.value!.dx; if (isRTL) { - startXPosition = size.width - startXPosition; + xPosition -= _textPainter.width; } - canvas.drawCircle(Offset(startXPosition, 5), 5, painter); - canvas.drawLine(Offset(startXPosition, 0), - Offset(startXPosition, size.height), painter); } else { - final double viewSize = - (size.width - timeRulerSize) / visibleDates.length; - final double startYPosition = currentTimePosition; - double viewStartPosition = (index * viewSize) + timeRulerSize; - double viewEndPosition = viewStartPosition + viewSize; - double startXPosition = viewStartPosition < 5 ? 5 : viewStartPosition; + yPosition = dragDetails.position.value!.dy; + xPosition = (timeLabelSize - _textPainter.width) / 2; if (isRTL) { - viewStartPosition = size.width - viewStartPosition; - viewEndPosition = size.width - viewEndPosition; - startXPosition = size.width - startXPosition; + xPosition = (size.width - timeLabelSize) + xPosition; } - canvas.drawCircle(Offset(startXPosition, startYPosition), 5, painter); - canvas.drawLine(Offset(viewStartPosition, startYPosition), - Offset(viewEndPosition, startYPosition), painter); } + _textPainter.paint(canvas, Offset(xPosition, yPosition)); } - @override - bool? hitTest(Offset position) { - return false; - } + void _updatePainterMaxLines(double height) { + /// [preferredLineHeight] is used to get the line height based on text + /// style and text. floor the calculated value to set the minimum line + /// count to painter max lines property. + final int maxLines = (height / _textPainter.preferredLineHeight).floor(); + if (maxLines <= 0) { + return; + } - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; + _textPainter.maxLines = maxLines; } } - -/// Returns the single view width from the time line view for time line -double _getSingleViewWidthForTimeLineView(_CalendarViewState viewState) { - return (viewState._scrollController!.position.maxScrollExtent + - viewState._scrollController!.position.viewportDimension) / - viewState.widget.visibleDates.length; -} 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 f3ce39dda..4f83da538 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 @@ -767,17 +767,18 @@ class _TimeSlotRenderObject extends CustomCalendarRenderObject { } void _addMouseHoveringForTimeSlot(Canvas canvas, Size size) { - const double padding = 0.5; + const double strokeWidth = 2; + const double padding = strokeWidth / 2; double left = (calendarCellNotifier.value!.dx ~/ _cellWidth) * _cellWidth; double top = (calendarCellNotifier.value!.dy ~/ timeIntervalHeight) * timeIntervalHeight; _linePainter.style = PaintingStyle.stroke; - _linePainter.strokeWidth = 2; + _linePainter.strokeWidth = strokeWidth; _linePainter.color = calendarTheme.selectionBorderColor!.withOpacity(0.4); left += isRTL ? 0 : timeLabelWidth; - top = top == 0 ? padding : top; double height = timeIntervalHeight; - if (top == padding) { + if (top == 0) { + top = padding; height -= padding; } 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 8d68dc6dd..9cdfbd02a 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 @@ -176,8 +176,8 @@ class _MonthViewWidgetState extends State { if (widget.calendar.dataSource != null && !AppointmentHelper.isCalendarAppointment( widget.calendar.dataSource!)) { - monthCellAppointment = - CalendarViewHelper.getCustomAppointments(appointments); + monthCellAppointment = CalendarViewHelper.getCustomAppointments( + appointments, widget.calendar.dataSource); } final Widget child = widget.builder!( @@ -705,7 +705,8 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { final Size widgetSize = constraints.biggest; size = Size(widgetSize.width.isInfinite ? width : widgetSize.width, widgetSize.height.isInfinite ? height : widgetSize.height); - final double cellWidth = size.width / DateTime.daysPerWeek; + final double cellWidth = + (size.width - weekNumberPanelWidth) / DateTime.daysPerWeek; final double cellHeight = size.height / rowCount; for (dynamic child = firstChild; child != null; child = childAfter(child)) { child.layout(constraints.copyWith( @@ -788,8 +789,8 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { currentVisibleDate.weekday == DateTime.monday && (showTrailingAndLeadingDates || (!showTrailingAndLeadingDates && - (i >= (DateTime.daysPerWeek * 2)) && - (i <= visibleDatesCount - (DateTime.daysPerWeek * 2))))) { + (i > (DateTime.daysPerWeek * 2)) && + (i < visibleDatesCount - (DateTime.daysPerWeek * 2))))) { _drawWeekNumber( context.canvas, size, currentVisibleDate, cellHeight, yPosition); } @@ -1073,8 +1074,8 @@ class _MonthViewRenderObject extends CustomCalendarRenderObject { currentVisibleDate.weekday == DateTime.monday && (showTrailingAndLeadingDates || (!showTrailingAndLeadingDates && - (i >= (DateTime.daysPerWeek * 2)) && - (i <= visibleDatesCount - (DateTime.daysPerWeek * 2))))) { + (i > (DateTime.daysPerWeek * 2)) && + (i < visibleDatesCount - (DateTime.daysPerWeek * 2))))) { _drawWeekNumber( canvas, size, currentVisibleDate, cellHeight, yPosition); } diff --git a/packages/syncfusion_flutter_charts/CHANGELOG.md b/packages/syncfusion_flutter_charts/CHANGELOG.md index 610771062..d6f332ced 100644 --- a/packages/syncfusion_flutter_charts/CHANGELOG.md +++ b/packages/syncfusion_flutter_charts/CHANGELOG.md @@ -1,5 +1,44 @@ ## Unreleased +**Features** +* Implemented the error bar series type with all its functionalities to indicate errors or uncertain values in the data. +* Provided support to place the pie and doughnut chart data labels smartly without intersecting one another. +* Provided support to retrieve the internally calculated slope and intercept values of a trendline for later use in the application. +* Provided support to fill the Cartesian chart types data points using the shader. +* Now, the trackball, crosshair, and tooltip states are maintained when the device orientation changes. +* Now the annotations can also be placed on the chart based on the percentage value. +* Provided support to get data point details by passing the logical pixel value as input to the circular, pyramid and funnel charts. +* Provided delay support for animating the series, trendline and indicators after the specified time. + +**Breaking changes** +* [`onTrendlineRender`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/SfCartesianChart/onTrendlineRender.html) callback has been deprecated; instead, use the `onRenderDetailsUpdate` callback in the [`Trendline`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/Trendline-class.html) class to get the trendline details. +* [`enableSmartLabels`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/CircularSeries/enableSmartLabels.html) callback has been deprecated; instead, use `LabelIntersectAction.shift` callback in `DataLabelSettings` class for [`pie`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/PieSeries-class.html) and [`doughnut`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/DoughnutSeries-class.html) series to position the data labels smartly when they intersect. +* [`ChartTextStyle`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/ChartTextStyle-class.html) class has been removed; instead, use the [`TextStyle`](https://api.flutter.dev/flutter/painting/TextStyle-class.html) class. +* In mobile devices, when the height is greater than the width, and the [`legend position`](https://pub.dev/documentation/syncfusion_flutter_charts/latest/charts/LegendPosition-class.html) is `auto`, then the legend gets positioned at the bottom. Hereafter, the legend will get positioned at the top. + +## [19.2.59] - 08/31/2021 +**Bugs** +* With the auto-scrolling feature, a single point will now render at the proper position in the line series. + +## [19.2.57] - 08/24/2021 +**Bugs** +* Multiple scatter series of image type will render properly on invoking the setstate. + +## [19.2.49] - 07/27/2021 +**Bugs** +* Trendline will not throw an exception for the single point. +* Trackball template will not throw any exception. + +## [19.2.46] - 07/06/2021 +**Bugs** +* The `visibleMaximum` will be applied properly after the legend toggle and panning. Also, the exception will not be thrown on panning. + +## [19.2.44+1] - 06/30/2021 +**Bugs** +* Now, the performance of the scatter series with image has been improved. + +## [19.2.44] - 06/30/2021 + **Bugs** * The axis interval, zoom factor and zoom position will be maintained properly when enabled auto-scrolling. * Now, no exception will be thrown while adding multiple indicators and enabling the legend. diff --git a/packages/syncfusion_flutter_charts/README.md b/packages/syncfusion_flutter_charts/README.md index c997ac6cb..2d71eae4b 100644 --- a/packages/syncfusion_flutter_charts/README.md +++ b/packages/syncfusion_flutter_charts/README.md @@ -41,7 +41,7 @@ This [syncfusion_flutter_charts](https://pub.dev/packages/syncfusion_flutter_cha ## Chart features * **Chart types** - Provides functionality for rendering 30+ chart types, namely [line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/line-chart), [spline](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/spline-chart), [column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/column-chart), [bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/bar-chart), [area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/area-chart), [bubble](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/bubble-chart), [box and whisker](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/box-and-whisker-chart), [scatter](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/scatter-chart), [step line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/step-line-chart), [fast line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/line-chart), [range column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/range-column-chart), [range area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/range-area-chart), [candle](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/candle-chart), [hilo](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/hilo-chart), [ohlc](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/ohlc-chart), [histogram](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/histogram-chart), [step area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/step-area-chart), [spline area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/spline-area-chart), [spline range area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/spline-range-area-chart), [stacked area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-area-chart), [stacked bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-bar-chart), [stacked column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-column-chart), [stacked line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-line-chart), [100% stacked area](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-area-100-chart), [100% stacked bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-bar-100-chart), [100% stacked column](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-column-100-chart), [100% stacked line](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/stacked-line-100-chart), [waterfall](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/waterfall-chart), [pie](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/pie-chart), [doughnut](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/doughnut-chart), [radial bar](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/radial-bar-chart), [pyramid](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/pyramid-chart), [funnel](https://www.syncfusion.com/flutter-widgets/flutter-charts/chart-types/funnel-chart). Each chart type is easily configured and customized with built-in features for creating stunning visual effects. -![flutter_chart_types](https://cdn.syncfusion.com/content/images/FTControl/Charts/chart_types.png) +![flutter_chart_types](https://cdn.syncfusion.com/content/images/FTControl/Charts/chart_types_updated.png) * **Axis types** - Plot various types of data in a graph with the help of numeric, category, date-time, date-time category and log axis types. The built-in axis features allow to customize an axis elements further to make the axis more readable. ![flutter_chart_axis_types](https://cdn.syncfusion.com/content/images/FTControl/chart-axis-types.png) @@ -323,4 +323,4 @@ Widget build(BuildContext context) { 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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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 +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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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_charts/lib/charts.dart b/packages/syncfusion_flutter_charts/lib/charts.dart index 5cadf71f4..398715cc9 100644 --- a/packages/syncfusion_flutter_charts/lib/charts.dart +++ b/packages/syncfusion_flutter_charts/lib/charts.dart @@ -13,202 +13,211 @@ library charts; -import 'dart:async'; -import 'dart:math' as math_lib; -import 'dart:math' as math; -import 'dart:math'; -import 'dart:typed_data'; -import 'dart:ui'; -import 'dart:ui' as dart_ui; -import 'package:flutter/material.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/foundation.dart'; -//ignore: implementation_imports -import 'package:flutter/src/foundation/platform.dart'; -import 'package:intl/intl.dart' show DateFormat; -import 'package:intl/intl.dart' show NumberFormat; -import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/tooltip_internal.dart'; -import './src/common/handcursor/mobile.dart' - if (dart.library.html) './src/common/handcursor/web.dart'; - -export 'package:syncfusion_flutter_core/core.dart' - show DataMarkerType, TooltipAlignment; +// export annotation +export './src/chart/annotation/annotation_settings.dart'; + +//export axis +export './src/chart/axis/axis.dart' + hide VisibleRange, AxisHelper, ChartAxisRendererDetails; +export './src/chart/axis/category_axis.dart' hide CategoryAxisDetails; +export './src/chart/axis/datetime_axis.dart' hide DateTimeAxisDetails; +export './src/chart/axis/datetime_category_axis.dart' + hide DateTimeCategoryAxisDetails; +export './src/chart/axis/logarithmic_axis.dart' hide LogarithmicAxisDetails; +export './src/chart/axis/numeric_axis.dart' hide NumericAxisDetails; +export './src/chart/axis/plotband.dart' hide getPlotBandPainter; + +//export chart +export './src/chart/base/chart_base.dart' hide ContainerArea; + +//export chart behavior +export './src/chart/chart_behavior/chart_behavior.dart'; +export './src/chart/chart_behavior/selection_behavior.dart'; +export './src/chart/chart_behavior/zoom_behavior.dart'; + +//export chart segment +export './src/chart/chart_segment/area_segment.dart'; +export './src/chart/chart_segment/bar_segment.dart'; +export './src/chart/chart_segment/box_and_whisker_segment.dart'; +export './src/chart/chart_segment/bubble_segment.dart'; +export './src/chart/chart_segment/candle_segment.dart'; +export './src/chart/chart_segment/chart_segment.dart' hide SegmentHelper; +export './src/chart/chart_segment/column_segment.dart'; +export './src/chart/chart_segment/error_bar_segment.dart'; +export './src/chart/chart_segment/fastline_segment.dart'; +export './src/chart/chart_segment/hilo_segment.dart'; +export './src/chart/chart_segment/hiloopenclose_segment.dart'; +export './src/chart/chart_segment/histogram_segment.dart'; +export './src/chart/chart_segment/line_segment.dart'; +export './src/chart/chart_segment/range_area_segment.dart'; +export './src/chart/chart_segment/range_column_segment.dart'; +export './src/chart/chart_segment/scatter_segment.dart'; +export './src/chart/chart_segment/spline_area_segment.dart'; +export './src/chart/chart_segment/spline_range_area_segment.dart'; +export './src/chart/chart_segment/spline_segment.dart'; +export './src/chart/chart_segment/stacked_area_segment.dart'; +export './src/chart/chart_segment/stacked_bar_segment.dart'; +export './src/chart/chart_segment/stacked_column_segment.dart'; +export './src/chart/chart_segment/stacked_line_segment.dart'; +export './src/chart/chart_segment/stackedarea100_segment.dart'; +export './src/chart/chart_segment/stackedbar100_segment.dart'; +export './src/chart/chart_segment/stackedcolumn100_segment.dart'; +export './src/chart/chart_segment/stackedline100_segment.dart'; +export './src/chart/chart_segment/step_area_segment.dart'; +export './src/chart/chart_segment/stepline_segment.dart'; +export './src/chart/chart_segment/waterfall_segment.dart'; + +//export chart series + +export './src/chart/chart_series/area_series.dart'; +export './src/chart/chart_series/bar_series.dart'; +export './src/chart/chart_series/box_and_whisker_series.dart' + hide BoxPlotQuartileValues; +export './src/chart/chart_series/bubble_series.dart'; +export './src/chart/chart_series/candle_series.dart'; +export './src/chart/chart_series/column_series.dart'; +export './src/chart/chart_series/error_bar_series.dart' + hide ChartErrorValues, ErrorBarMean; +export './src/chart/chart_series/fastline_series.dart'; +export './src/chart/chart_series/hilo_series.dart'; +export './src/chart/chart_series/hiloopenclose_series.dart'; +export './src/chart/chart_series/histogram_series.dart' hide HistogramValues; +export './src/chart/chart_series/line_series.dart'; +export './src/chart/chart_series/range_area_series.dart'; +export './src/chart/chart_series/range_column_series.dart'; +export './src/chart/chart_series/scatter_series.dart'; +export './src/chart/chart_series/series.dart' hide SeriesHelper; +export './src/chart/chart_series/spline_area_series.dart'; +export './src/chart/chart_series/spline_range_area_series.dart'; +export './src/chart/chart_series/spline_series.dart'; +export './src/chart/chart_series/stacked_area_series.dart'; +export './src/chart/chart_series/stacked_bar_series.dart'; +export './src/chart/chart_series/stacked_column_series.dart'; +export './src/chart/chart_series/stacked_line_series.dart'; +export './src/chart/chart_series/stackedarea100_series.dart'; +export './src/chart/chart_series/stackedbar100_series.dart'; +export './src/chart/chart_series/stackedcolumn100_series.dart'; +export './src/chart/chart_series/stackedline100_series.dart'; +export './src/chart/chart_series/step_area_series.dart'; +export './src/chart/chart_series/stepline_series.dart'; +export './src/chart/chart_series/waterfall_series.dart'; +export './src/chart/chart_series/xy_data_series.dart' + hide ChartLocation, CartesianPointHelper; + +//export common +export './src/chart/common/data_label.dart' hide DataLabelSettingsRenderer; +export './src/chart/common/interactive_tooltip.dart' hide ChartPointInfo; +export './src/chart/common/marker.dart' hide MarkerSettingsRenderer; +export './src/chart/common/trackball_marker_settings.dart'; + +// export chart series renderer +export './src/chart/series_painter/area_painter.dart' hide AreaChartPainter; +export './src/chart/series_painter/bar_painter.dart' hide BarChartPainter; +export './src/chart/series_painter/box_and_whisker_painter.dart' + hide BoxAndWhiskerPainter; +export './src/chart/series_painter/bubble_painter.dart' hide BubbleChartPainter; +export './src/chart/series_painter/candle_painter.dart' hide CandlePainter; +export './src/chart/series_painter/column_painter.dart' hide ColumnChartPainter; +export './src/chart/series_painter/error_bar_painter.dart' + hide ErrorBarChartPainter; +export './src/chart/series_painter/fastline_painter.dart' + hide FastLineChartPainter; +export './src/chart/series_painter/hilo_painter.dart' hide HiloPainter; +export './src/chart/series_painter/hiloopenclose_painter.dart' + hide HiloOpenClosePainter; +export './src/chart/series_painter/histogram_painter.dart' + hide HistogramChartPainter; +export './src/chart/series_painter/line_painter.dart' hide LineChartPainter; +export './src/chart/series_painter/range_area_painter.dart' + hide RangeAreaChartPainter; +export './src/chart/series_painter/range_column_painter.dart' + hide RangeColumnChartPainter; +export './src/chart/series_painter/scatter_painter.dart' + hide ScatterChartPainter; +export './src/chart/series_painter/spline_area_painter.dart' + hide SplineAreaChartPainter; +export './src/chart/series_painter/spline_painter.dart' hide SplineChartPainter; +export './src/chart/series_painter/spline_range_area_painter.dart' + hide SplineRangeAreaChartPainter; +export './src/chart/series_painter/stacked_area_painter.dart' + hide + StackedAreaChartPainter, + StackedArea100ChartPainter, + stackedAreaPainter; +export './src/chart/series_painter/stacked_bar_painter.dart' + hide StackedBarChartPainter, StackedBar100ChartPainter; +export './src/chart/series_painter/stacked_column_painter.dart' + hide StackedColummnChartPainter, StackedColumn100ChartPainter; +export './src/chart/series_painter/stacked_line_painter.dart' + hide StackedLineChartPainter, StackedLine100ChartPainter; +export './src/chart/series_painter/step_area_painter.dart' + hide StepAreaChartPainter; +export './src/chart/series_painter/stepline_painter.dart' + hide StepLineChartPainter; +export './src/chart/series_painter/waterfall_painter.dart' + hide WaterfallChartPainter; + +// export technical indicators +export './src/chart/technical_indicators/accumulation_distribution_indicator.dart'; +export './src/chart/technical_indicators/atr_indicator.dart'; +export './src/chart/technical_indicators/bollinger_bands_indicator.dart'; +export './src/chart/technical_indicators/ema_indicator.dart'; +export './src/chart/technical_indicators/macd_indicator.dart'; +export './src/chart/technical_indicators/momentum_indicator.dart'; +export './src/chart/technical_indicators/rsi_indicator.dart'; +export './src/chart/technical_indicators/sma_indicator.dart'; +export './src/chart/technical_indicators/stochastic_indicator.dart'; +export './src/chart/technical_indicators/technical_indicator.dart' + hide TechnicalIndicatorsRenderer; +export './src/chart/technical_indicators/tma_indicator.dart'; + +// export trendlines +export './src/chart/trendlines/trendlines.dart' hide TrendlineRenderer; + +//export user interaction +export './src/chart/user_interaction/crosshair.dart' + hide CrosshairHelper, CrosshairRenderingDetails; +export './src/chart/user_interaction/trackball.dart' + hide TrackballRenderingDetails, TrackballHelper; +export './src/chart/user_interaction/trackball_marker_setting_renderer.dart' + hide TrackballMarkerSettingsRenderer; +export './src/chart/user_interaction/zooming_panning.dart' + show ZoomPanBehavior, ZoomPanBehaviorRenderer; + +//export utils +export './src/chart/utils/enum.dart'; // export circular library -part './src/circular_chart/base/circular_base.dart'; -part './src/circular_chart/base/series_base.dart'; -part './src/circular_chart/renderer/circular_series.dart'; -part './src/circular_chart/renderer/common.dart'; -part './src/circular_chart/renderer/data_label_renderer.dart'; -part './src/circular_chart/renderer/doughnut_series.dart'; -part './src/circular_chart/renderer/pie_series.dart'; -part './src/circular_chart/renderer/radial_bar_series.dart'; -part './src/circular_chart/utils/enum.dart'; -part './src/circular_chart/utils/helper.dart'; +export './src/circular_chart/base/circular_base.dart'; +export './src/circular_chart/renderer/chart_point.dart' + hide PointHelper, getCircularPoint; +export './src/circular_chart/renderer/circular_chart_annotation.dart'; +export './src/circular_chart/renderer/circular_series.dart' + hide getVisiblePointIndex; +export './src/circular_chart/renderer/circular_series_controller.dart'; +export './src/circular_chart/renderer/doughnut_series.dart'; +export './src/circular_chart/renderer/pie_series.dart'; +export './src/circular_chart/renderer/radial_bar_series.dart'; +export './src/circular_chart/renderer/renderer_base.dart'; +export './src/circular_chart/utils/enum.dart'; -// export pyramid library -part './src/pyramid_chart/base/pyramid_base.dart'; -part './src/pyramid_chart/base/series_base.dart'; -part './src/pyramid_chart/renderer/pyramid_series.dart'; -part './src/pyramid_chart/utils/common.dart'; -part './src/pyramid_chart/utils/helper.dart'; -part './src/pyramid_chart/renderer/data_label_renderer.dart'; +//export common +export './src/common/common.dart' + hide ChartContainer, MeasureWidgetContext, LegendRenderer; +export './src/common/event_args.dart' hide ErrorBarValues; +export './src/common/series/chart_series.dart'; +export './src/common/user_interaction/selection_behavior.dart' + hide SelectionDetails, SelectionHelper; +export './src/common/user_interaction/tooltip.dart' hide TooltipHelper; +export './src/common/utils/enum.dart'; +export './src/common/utils/typedef.dart'; // export funnel library -part './src/funnel_chart/base/funnel_base.dart'; -part './src/funnel_chart/base/series_base.dart'; -part './src/funnel_chart/renderer/funnel_series.dart'; -part './src/funnel_chart/renderer/data_label_renderer.dart'; - -// export chart library -part './src/chart/annotation/annotation_settings.dart'; -part './src/chart/axis/axis.dart'; -part './src/chart/axis/axis_panel.dart'; -part './src/chart/axis/axis_renderer.dart'; -part './src/chart/axis/category_axis.dart'; -part './src/chart/axis/datetime_category_axis.dart'; -part './src/chart/axis/datetime_axis.dart'; -part './src/chart/axis/logarithmic_axis.dart'; -part './src/chart/axis/numeric_axis.dart'; -part './src/chart/axis/plotband.dart'; -part './src/chart/base/chart_base.dart'; -part './src/chart/base/series_base.dart'; -part './src/chart/chart_behavior/chart_behavior.dart'; -part './src/chart/chart_behavior/selection_behavior.dart'; -part './src/chart/chart_behavior/zoom_behavior.dart'; -part './src/chart/chart_segment/area_segment.dart'; -part './src/chart/chart_segment/stacked_area_segment.dart'; -part './src/chart/chart_segment/stackedarea100_segment.dart'; -part './src/chart/chart_segment/bar_segment.dart'; -part './src/chart/chart_segment/bubble_segment.dart'; -part './src/chart/chart_segment/chart_segment.dart'; -part './src/chart/chart_segment/column_segment.dart'; -part './src/chart/chart_segment/box_and_whisker_segment.dart'; -part './src/chart/chart_segment/fastline_segment.dart'; -part './src/chart/chart_series/financial_series_base.dart'; -part './src/chart/chart_segment/hilo_segment.dart'; -part './src/chart/chart_segment/hiloopenclose_segment.dart'; -part './src/chart/chart_segment/candle_segment.dart'; -part './src/chart/chart_segment/line_segment.dart'; -part './src/chart/chart_segment/histogram_segment.dart'; -part './src/chart/chart_segment/stacked_line_segment.dart'; -part './src/chart/chart_segment/range_area_segment.dart'; -part './src/chart/chart_segment/stackedline100_segment.dart'; -part './src/chart/chart_segment/range_column_segment.dart'; -part './src/chart/chart_segment/scatter_segment.dart'; -part './src/chart/chart_segment/spline_segment.dart'; -part './src/chart/chart_segment/spline_area_segment.dart'; -part './src/chart/chart_segment/spline_range_area_segment.dart'; -part './src/chart/chart_series/stacked_series_base.dart'; -part './src/chart/chart_segment/stacked_bar_segment.dart'; -part './src/chart/chart_segment/stackedbar100_segment.dart'; -part './src/chart/chart_segment/stacked_column_segment.dart'; -part './src/chart/chart_segment/stackedcolumn100_segment.dart'; -part './src/chart/chart_segment/step_area_segment.dart'; -part './src/chart/chart_segment/stepline_segment.dart'; -part './src/chart/chart_segment/waterfall_segment.dart'; -part './src/chart/chart_series/area_series.dart'; -part './src/chart/chart_series/stacked_area_series.dart'; -part './src/chart/chart_series/stackedarea100_series.dart'; -part './src/chart/chart_series/bar_series.dart'; -part './src/chart/chart_series/bubble_series.dart'; -part './src/chart/chart_series/column_series.dart'; -part './src/chart/chart_series/box_and_whisker_series.dart'; -part './src/chart/chart_series/fastline_series.dart'; -part './src/chart/chart_series/hilo_series.dart'; -part './src/chart/chart_series/hiloopenclose_series.dart'; -part './src/chart/chart_series/candle_series.dart'; -part './src/chart/chart_series/line_series.dart'; -part './src/chart/chart_series/histogram_series.dart'; -part './src/chart/chart_series/stacked_line_series.dart'; -part './src/chart/chart_series/range_area_series.dart'; -part './src/chart/chart_series/stackedline100_series.dart'; -part './src/chart/chart_series/range_column_series.dart'; -part './src/chart/chart_series/scatter_series.dart'; -part './src/chart/chart_series/stacked_bar_series.dart'; -part './src/chart/chart_series/stackedbar100_series.dart'; -part './src/chart/chart_series/stacked_column_series.dart'; -part './src/chart/chart_series/stackedcolumn100_series.dart'; -part './src/chart/chart_series/series.dart'; -part './src/chart/chart_series/spline_series.dart'; -part './src/chart/chart_series/spline_area_series.dart'; -part './src/chart/chart_series/spline_range_area_series.dart'; -part './src/chart/chart_series/step_area_series.dart'; -part './src/chart/chart_series/stepline_series.dart'; -part './src/chart/chart_series/waterfall_series.dart'; -part './src/chart/chart_series/xy_data_series.dart'; -part './src/chart/common/common.dart'; -part './src/chart/common/data_label.dart'; -part './src/chart/common/data_label_renderer.dart'; -part './src/chart/common/marker.dart'; -part './src/chart/common/renderer.dart'; -part './src/chart/series_painter/area_painter.dart'; -part './src/chart/series_painter/stacked_area_painter.dart'; -part './src/chart/series_painter/stackedarea100_painter.dart'; -part './src/chart/series_painter/bar_painter.dart'; -part './src/chart/series_painter/box_and_whisker_painter.dart'; -part './src/chart/series_painter/bubble_painter.dart'; -part './src/chart/series_painter/column_painter.dart'; -part './src/chart/series_painter/fastline_painter.dart'; -part './src/chart/series_painter/hilo_painter.dart'; -part './src/chart/series_painter/hiloopenclose_painter.dart'; -part './src/chart/series_painter/candle_painter.dart'; -part './src/chart/series_painter/line_painter.dart'; -part './src/chart/series_painter/histogram_painter.dart'; -part './src/chart/series_painter/stacked_line_painter.dart'; -part './src/chart/series_painter/range_area_painter.dart'; -part './src/chart/series_painter/stackedline100_painter.dart'; -part './src/chart/series_painter/range_column_painter.dart'; -part './src/chart/series_painter/scatter_painter.dart'; -part './src/chart/series_painter/spline_painter.dart'; -part './src/chart/series_painter/spline_area_painter.dart'; -part './src/chart/series_painter/spline_range_area_painter.dart'; -part './src/chart/series_painter/stacked_bar_painter.dart'; -part './src/chart/series_painter/stackedbar100_painter.dart'; -part './src/chart/series_painter/stacked_column_painter.dart'; -part './src/chart/series_painter/stackedcolumn100_painter.dart'; -part './src/chart/series_painter/step_area_painter.dart'; -part './src/chart/series_painter/stepline_painter.dart'; -part './src/chart/series_painter/waterfall_painter.dart'; -part './src/chart/technical_indicators/accumulation_distribution_indicator.dart'; -part './src/chart/technical_indicators/atr_indicator.dart'; -part './src/chart/technical_indicators/bollinger_bands_indicator.dart'; -part './src/chart/technical_indicators/ema_indicator.dart'; -part './src/chart/technical_indicators/tma_indicator.dart'; -part './src/chart/technical_indicators/rsi_indicator.dart'; -part './src/chart/technical_indicators/sma_indicator.dart'; -part './src/chart/technical_indicators/macd_indicator.dart'; -part './src/chart/technical_indicators/momentum_indicator.dart'; -part './src/chart/technical_indicators/stochastic_indicator.dart'; -part './src/chart/technical_indicators/technical_indicator.dart'; -part './src/chart/trendlines/trendlines.dart'; -part './src/chart/trendlines/trendlines_painter.dart'; -part './src/chart/user_interaction/crosshair.dart'; -part './src/chart/user_interaction/crosshair_painter.dart'; -part './src/chart/user_interaction/selection_renderer.dart'; -part './src/chart/user_interaction/trackball.dart'; -part './src/chart/user_interaction/trackball_painter.dart'; -part './src/chart/user_interaction/trackball_template.dart'; -part './src/chart/user_interaction/zooming_painter.dart'; -part './src/chart/user_interaction/zooming_panning.dart'; -part './src/chart/utils/enum.dart'; -part './src/chart/utils/helper.dart'; - -// export common library -part './src/common/common.dart'; -part './src/common/event_args.dart'; -part './src/common/rendering_details.dart'; -part './src/common/legend/legend.dart'; -part './src/common/legend/renderer.dart'; -part './src/common/series/chart_series.dart'; -part './src/common/template/rendering.dart'; -part './src/common/user_interaction/selection_behavior.dart'; -part './src/common/user_interaction/tooltip.dart'; -part './src/common/utils/enum.dart'; -part './src/common/utils/helper.dart'; -part './src/common/utils/typedef.dart'; +export './src/funnel_chart/base/funnel_base.dart'; +export './src/funnel_chart/renderer/funnel_series.dart'; +// export pyramid library +export './src/pyramid_chart/base/pyramid_base.dart'; +export './src/pyramid_chart/renderer/pyramid_series.dart'; +export './src/pyramid_chart/renderer/series_controller.dart'; +export './src/pyramid_chart/utils/common.dart'; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart b/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart index 2c51306e3..0adb108d6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/annotation/annotation_settings.dart @@ -1,4 +1,8 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../common/utils/enum.dart'; +import '../utils/enum.dart'; /// This class has the properties of cartesian chart annotation. /// @@ -96,7 +100,9 @@ class CartesianChartAnnotation { ///``` final AnnotationRegion region; - ///Specifies the x-values as pixel or point values based on the coordinateUnit. + ///Specifies the x-values as pixel, point or percentage values based on the coordinateUnit. + /// + /// Percentage value refers to the overall width of the chart. i.e. 100% is equal to the width of the chart. /// ///Defaults to `null` /// @@ -119,7 +125,9 @@ class CartesianChartAnnotation { ///``` final dynamic x; - ///Specifies the y-values as pixel or point values based on the coordinateUnit. + ///Specifies the y-values as pixel , point or percentage values based on the coordinateUnit. + /// + /// Percentage value refers to the overall height of the chart. i.e. 100% is equal to the height of the chart. /// ///Defaults to `null` /// @@ -140,7 +148,7 @@ class CartesianChartAnnotation { /// )); ///} ///``` - final num y; + final dynamic y; ///Specifies the x-axis name to the annotation that should be bound. /// diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart index 1a20c11af..ffae664fe 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis.dart @@ -1,4 +1,29 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../common/event_args.dart'; +import '../../common/rendering_details.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../axis/axis_renderer.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 '../axis/plotband.dart'; +import '../base/chart_base.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/interactive_tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; /// This class holds the properties of ChartAxis. /// @@ -63,7 +88,7 @@ abstract class ChartAxis { labelIntersectAction ?? AxisLabelIntersectAction.hide, minorTicksPerInterval = minorTicksPerInterval ?? 0, maximumLabels = maximumLabels ?? 3, - labelStyle = _getTextStyle( + labelStyle = getTextStyle( textStyle: labelStyle, fontSize: 12.0, fontStyle: FontStyle.normal, @@ -589,17 +614,81 @@ abstract class ChartAxis { ///``` final List plotBands; - /// Controller used to set the maximum and minimum values for the chart.By providing the range controller, the maximum and - ///The minimum range of charts can be customized + /// The `rangeController` property is used to set the maximum and minimum values for the chart in the viewport. + /// In the minimum and maximum properties of the axis, you can specify the minimum and maximum values with respect to the entire data source. + /// In the visibleMinimum and visibleMaximum properties, you can specify the values to be viewed in the viewed port i.e. range controller's start and end values respectively. + /// + /// Here you need to specify the `minimum`, `maximum`, `visibleMinimum`, and `visibleMaximum` properties to the axis and the axis values will be visible with respect to + /// visibleMinimum and visibleMaximum properties. /// ///```dart ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// rangeController: controller, - /// ), - /// )); + /// RangeController rangeController = RangeController( + /// start: DateTime(2020, 2, 1), + /// end: DateTime(2020, 2, 30), + /// ); + /// SfCartesianChart sliderChart = SfCartesianChart( + /// margin: const EdgeInsets.all(0), + /// primaryXAxis: + /// DateTimeAxis(isVisible: false), + /// primaryYAxis: NumericAxis(isVisible: false), + /// plotAreaBorderWidth: 0, + /// series: >[ + /// SplineAreaSeries( + /// // Add required properties. + /// ) + /// ], + /// ); + /// return Scaffold( + /// body: Column( + /// children: [ + /// Expanded( + /// child: SfCartesianChart( + /// primaryXAxis: DateTimeAxis( + /// maximum: DateTime(2020, 1, 1), + /// minimum: DateTime(2020, 3, 30), + /// // set maximum value from the range controller + /// visibleMaximum: rangeController.end, + /// // set minimum value from the range controller + /// visibleMinimum: rangeController.start, + /// rangeController: rangeController), + /// primaryYAxis: NumericAxis(), + /// series: >[ + /// SplineSeries( + /// dataSource: splineSeriesData, + /// xValueMapper: (ChartSampleData sales, _) => + /// sales.x as DateTime, + /// yValueMapper: (ChartSampleData sales, _) => sales.y, + /// // Add required properties. + /// ) + /// ], + /// ), + /// ), + /// Expanded( + /// child: SfRangeSelectorTheme( + /// data: SfRangeSelectorThemeData(), + /// child: SfRangeSelector( + /// min: min, + /// max: max, + /// controller: rangeController, + /// showTicks: true, + /// showLabels: true, + /// dragMode: SliderDragMode.both, + /// onChanged: (SfRangeValues value) { + /// // set the start value to rangeController from this callback + /// rangeController.start = value.start; + /// // set the end value to rangeController from this callback + /// rangeController.end = value.end; + /// setState(() {}); + /// }, + /// child: Container( + /// child: sliderChart, + /// ), + /// ), + /// )), + /// ], + /// ), + /// ); ///} ///``` final RangeController? rangeController; @@ -804,7 +893,6 @@ class AxisLabel { ///Stores the location of an label. Rect? _labelRegion; - //ignore: prefer_final_fields bool _needRender = true; } @@ -1176,7 +1264,7 @@ class AxisTitle { /// Creating an argument constructor of AxisTitle class. AxisTitle( {this.text, TextStyle? textStyle, this.alignment = ChartAlignment.center}) - : textStyle = _getTextStyle( + : textStyle = getTextStyle( textStyle: textStyle, fontFamily: 'Segoe UI', fontSize: 15.0, @@ -1357,8 +1445,9 @@ class AxisLine { } ///calculate visible range based on min, max values -class _VisibleRange { - _VisibleRange(dynamic min, dynamic max) { +class VisibleRange { + /// Creates an instance for visible range + VisibleRange(dynamic min, dynamic max) { if ((min < max) == true) { minimum = min; maximum = max; @@ -1382,81 +1471,11 @@ class _VisibleRange { } /// Creates an axis renderer for chart axis. -abstract class ChartAxisRenderer with _CustomizeAxisElements { +abstract class ChartAxisRenderer with CustomizeAxisElements { /// Creating an argument constructor of ChartAxisRenderer class. - ChartAxisRenderer(this._axis) { - _visibleLabels = []; - _maximumLabelSize = const Size(0, 0); - _seriesRenderers = []; - _name = _axis.name; - _labelRotation = _axis.labelRotation; - _zoomFactor = _axis.zoomFactor; - _zoomPosition = _axis.zoomPosition; - } - //ignore: prefer_final_fields - late ChartAxis _axis; - ChartAxis? _oldAxis; - late SfCartesianChartState _chartState; - _RenderingDetails get _renderingDetails => _chartState._renderingDetails; - late SfCartesianChart _chart; - //ignore: prefer_final_fields - bool _isStack100 = false; - dynamic _rangeMinimum, _rangeMaximum; - - late List _visibleLabels; - - /// Holds the size of larger label. - Size _maximumLabelSize = const Size(0, 0); - - /// Specifies axis orientations such as vertical, horizontal. - AxisOrientation? _orientation; - - /// Specifies the visible range based on min, max values. - _VisibleRange? _visibleRange; - - /// Specifies the actual range based on min, max values. - _VisibleRange? _actualRange; - - /// Holds the chart series - late List _seriesRenderers; - - // ignore: prefer_final_fields - Rect _bounds = const Rect.fromLTWH(0, 0, 0, 0); - - bool? _isInsideTickPosition; - - late double _totalSize; - - ///Internal variable - double? _previousZoomFactor, _previousZoomPosition; - - ///Internal variables - String? _name; - - late int _labelRotation; - - late double _zoomFactor, _zoomPosition; - - ///Checking the axis label collision - bool _isCollide = false; - - num? _min, _max, _lowMin, _lowMax, _highMin, _highMax; - - late Size _axisSize; - - late ChartAxisRenderer _crossAxisRenderer; - - num? _crossValue; - - _VisibleRange? _crossRange; + ChartAxisRenderer(); - num? _labelOffset; - - Offset? _xAxisStart, _xAxisEnd; - - num? _visibleMinimum, _visibleMaximum; - - int? _scrollingDelta; + late ChartAxisRendererDetails _axisRendererDetails; @override Color? getAxisLineColor(ChartAxis axis) => axis.axisLine.color; @@ -1509,68 +1528,352 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { @override int getAxisLabelAngle( ChartAxisRenderer axisRenderer, String text, int labelIndex) => - (axisRenderer._axis.labelIntersectAction == - AxisLabelIntersectAction.rotate45 && - axisRenderer._isCollide) - ? -45 - : (axisRenderer._axis.labelIntersectAction == - AxisLabelIntersectAction.rotate90 && - axisRenderer._isCollide) - ? -90 - : axisRenderer._labelRotation; + axisRenderer._axisRendererDetails + .getAxisLabelAngle(axisRenderer, text, labelIndex); /// To draw the horizontal axis line @override void drawHorizontalAxesLine( Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final ChartAxis axis = axisRenderer._axis; - final Rect rect = Rect.fromLTWH( - axisRenderer._bounds.left - axis.plotOffset, - axisRenderer._bounds.top, - axisRenderer._bounds.width + 2 * axis.plotOffset, - axisRenderer._bounds.height); - - final _CustomPaintStyle paintStyle = _CustomPaintStyle( + axisRenderer._axisRendererDetails + .drawHorizontalAxesLine(canvas, axisRenderer, chart); + } + + /// To draw the vertical axis line + @override + void drawVerticalAxesLine( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + axisRenderer._axisRendererDetails + .drawVerticalAxesLine(canvas, axisRenderer, chart); + } + + /// To draw the horizontal axes tick lines + @override + void drawHorizontalAxesTickLines( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + axisRenderer._axisRendererDetails.drawHorizontalAxesTickLines( + canvas, + axisRenderer, + chart, + renderType, + animationFactor, + oldAxisRenderer, + needAnimate); + } + + /// To draw the major grid lines of horizontal axes + @override + void drawHorizontalAxesMajorGridLines( + Canvas canvas, + Offset point, + ChartAxisRenderer axisRenderer, + MajorGridLines grids, + int index, + SfCartesianChart chart) { + axisRenderer._axisRendererDetails.drawHorizontalAxesMajorGridLines( + canvas, point, axisRenderer, grids, index, chart); + } + + /// To draw the minor grid lines of horizontal axes + @override + void drawHorizontalAxesMinorLines( + Canvas canvas, + ChartAxisRenderer axisRenderer, + double tempInterval, + Rect rect, + num nextValue, + int index, + SfCartesianChart chart, + [String? renderType]) { + axisRenderer._axisRendererDetails.drawHorizontalAxesMinorLines(canvas, + axisRenderer, tempInterval, rect, nextValue, index, chart, renderType); + } + + /// To draw tick lines of vertical axes + @override + void drawVerticalAxesTickLines( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + axisRenderer._axisRendererDetails._drawVerticalAxesTickLines( + canvas, + axisRenderer, + chart, + renderType, + animationFactor, + oldAxisRenderer, + needAnimate); + } + + /// To draw the major grid lines of vertical axes + @override + void drawVerticalAxesMajorGridLines( + Canvas canvas, + Offset point, + ChartAxisRenderer axisRenderer, + MajorGridLines grids, + int index, + SfCartesianChart chart) { + axisRenderer._axisRendererDetails._drawVerticalAxesMajorGridLines( + canvas, point, axisRenderer, grids, index, chart); + } + + /// To draw the minor grid lines of vertical axes + @override + void drawVerticalAxesMinorTickLines( + Canvas canvas, + ChartAxisRenderer axisRenderer, + num tempInterval, + Rect rect, + int index, + SfCartesianChart chart, + [String? renderType]) { + axisRenderer._axisRendererDetails._drawVerticalAxesMinorTickLines( + canvas, axisRenderer, tempInterval, rect, index, chart, renderType); + } + + /// To draw the axis labels of horizontal axes + @override + void drawHorizontalAxesLabels( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + axisRenderer._axisRendererDetails._drawHorizontalAxesLabels( + canvas, + axisRenderer, + chart, + renderType, + animationFactor, + oldAxisRenderer, + needAnimate); + } + + /// To draw the axis labels of vertical axes + @override + void drawVerticalAxesLabels( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + axisRenderer._axisRendererDetails._drawVerticalAxesLabels( + canvas, + axisRenderer, + chart, + renderType, + animationFactor, + oldAxisRenderer, + needAnimate); + } + + /// To draw the axis title of horizontal axes + @override + void drawHorizontalAxesTitle( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + axisRenderer._axisRendererDetails + ._drawHorizontalAxesTitle(canvas, axisRenderer, chart); + } + + /// To draw the axis title of vertical axes + @override + void drawVerticalAxesTitle( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + axisRenderer._axisRendererDetails + ._drawVerticalAxesTitle(canvas, axisRenderer, chart); + } + + /// returns the calculated interval for axis + num? calculateInterval(VisibleRange range, Size availableSize); + + /// to apply the range padding for the axis + void applyRangePadding(VisibleRange range, num interval); + + /// calculates the visible range of the axis + void calculateVisibleRange(Size availableSize); + + /// this method generates the visible labels for the specific axis + void generateVisibleLabels(); + + /// To calculate the range points + void calculateRange(ChartAxisRenderer _axisRenderer) { + _axisRenderer._axisRendererDetails._calculateRange(_axisRenderer); + } + + /// To dispose the objects. + void dispose() { + _axisRendererDetails.dispose(); + } +} + +/// Represents the class that holds the chart axis rendering details +class ChartAxisRendererDetails { + /// Creates an instance of chart axis renderer details + ChartAxisRendererDetails(this.axis, this.stateProperties, this.axisRenderer) { + visibleLabels = []; + maximumLabelSize = const Size(0, 0); + seriesRenderers = []; + name = axis.name; + labelRotation = axis.labelRotation; + zoomFactor = axis.zoomFactor; + zoomPosition = axis.zoomPosition; + } + + /// Represents the chart axis renderer + late ChartAxisRenderer axisRenderer; + + /// Represnts the chart axis value + late ChartAxis axis; + + /// Specifies the old axis value + ChartAxis? oldAxis; + + /// Specifies the value of state properties + final CartesianStateProperties stateProperties; + + /// Specifies the rendering details value + RenderingDetails get renderingDetails => stateProperties.renderingDetails; + + /// Specifies the cartesian chart + late SfCartesianChart chart; + + /// Specifies whether the series is stack100 + bool isStack100 = false; + + /// Specifies the value of range minimum and range maximum + dynamic rangeMinimum, rangeMaximum; + + /// Specifies the value of visible labels + late List visibleLabels; + + /// Holds the size of larger label. + Size maximumLabelSize = const Size(0, 0); + + /// Specifies axis orientations such as vertical, horizontal. + AxisOrientation? orientation; + + /// Specifies the visible range based on min, max values. + VisibleRange? visibleRange; + + /// Specifies the actual range based on min, max values. + VisibleRange? actualRange; + + /// Holds the chart series + late List seriesRenderers; + + /// Specifies the value of bounds + // ignore: prefer_final_fields + Rect bounds = const Rect.fromLTWH(0, 0, 0, 0); + + /// Specifies whether the ticks are placed inside + bool? isInsideTickPosition; + + /// Specifies the total size value + late double totalSize; + + /// Specifies the p + double? previousZoomFactor, previousZoomPosition; + + ///Internal variables + String? name; + + /// Holds the value of label rotation + late int labelRotation; + + /// Holds the value of zoom factor and zoom position + late double zoomFactor, zoomPosition; + + ///Checking the axis label collision + bool isCollide = false; + + /// Holds the value of minimum, maximum, lowest and highest minimum and maximum value + num? min, max, lowMin, lowMax, highMin, highMax; + + /// Holds the value of axis size + late Size axisSize; + + /// Holds the value of cross axis renderer + late ChartAxisRenderer crossAxisRenderer; + + /// Specifies the cross value + num? crossValue; + + /// Specifies the cross range + VisibleRange? crossRange; + + /// Holds teh label offset value + num? labelOffset; + + /// Specifies the x-axis start and x-axis end + Offset? xAxisStart, xAxisEnd; + + /// Specifies the value of visible minimum and visible maximum + num? visibleMinimum, visibleMaximum; + + /// Specifies the scrolling delta value + int? scrollingDelta; + + /// Returns the axis label angle + int getAxisLabelAngle( + ChartAxisRenderer axisRenderer, String text, int labelIndex) { + final int labelAngle = + (axis.labelIntersectAction == AxisLabelIntersectAction.rotate45 && + isCollide) + ? -45 + : (axis.labelIntersectAction == AxisLabelIntersectAction.rotate90 && + isCollide) + ? -90 + : labelRotation; + + return labelAngle; + } + + /// Method to draw the horizontal axes line + void drawHorizontalAxesLine( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + final Rect rect = Rect.fromLTWH(bounds.left - axis.plotOffset, bounds.top, + bounds.width + 2 * axis.plotOffset, bounds.height); + + final CustomPaintStyle paintStyle = CustomPaintStyle( axisRenderer.getAxisLineWidth(axis), axisRenderer.getAxisLineColor(axis) ?? - _renderingDetails.chartTheme.axisLineColor, + renderingDetails.chartTheme.axisLineColor, PaintingStyle.stroke); - _drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), + drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), Offset(rect.left + rect.width, rect.top), axis.axisLine.dashArray); - _xAxisStart = Offset(rect.left, rect.top); - _xAxisEnd = Offset(rect.left + rect.width, rect.top); + xAxisStart = Offset(rect.left, rect.top); + xAxisEnd = Offset(rect.left + rect.width, rect.top); } - /// To draw the vertical axis line - @override - void drawVerticalAxesLine(Canvas canvas, ChartAxisRenderer axisRenderer, - SfCartesianChart chartState) { - final ChartAxis axis = axisRenderer._axis; - final Rect rect = Rect.fromLTWH( - axisRenderer._bounds.left, - axisRenderer._bounds.top - axis.plotOffset, - axisRenderer._bounds.width, - axisRenderer._bounds.height + 2 * axis.plotOffset); - final _CustomPaintStyle paintStyle = _CustomPaintStyle( + /// Method to draw the vertical axes line + void drawVerticalAxesLine( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + final Rect rect = Rect.fromLTWH(bounds.left, bounds.top - axis.plotOffset, + bounds.width, bounds.height + 2 * axis.plotOffset); + final CustomPaintStyle paintStyle = CustomPaintStyle( axisRenderer.getAxisLineWidth(axis), axisRenderer.getAxisLineColor(axis) ?? - _renderingDetails.chartTheme.axisLineColor, + renderingDetails.chartTheme.axisLineColor, PaintingStyle.stroke); - _drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), + drawDashedPath(canvas, paintStyle, Offset(rect.left, rect.top), Offset(rect.left, rect.top + rect.height), axis.axisLine.dashArray); } - /// To draw the horizontal axes tick lines - @override + /// Method to draw the horizontal axes tick lines void drawHorizontalAxesTickLines( Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, [String? renderType, double? animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate]) { - final Rect axisBounds = axisRenderer._bounds; - final dynamic axis = axisRenderer._axis; - final List visibleLabels = axisRenderer._visibleLabels; + final dynamic chartAxis = axis; late double tempInterval, pointX, pointY; int length = visibleLabels.length; if (length > 0) { @@ -1578,23 +1881,21 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { const num padding = 1; final bool isBetweenTicks = (axis is CategoryAxis || axis is DateTimeCategoryAxis) && - axis.labelPlacement == LabelPlacement.betweenTicks; + chartAxis.labelPlacement == LabelPlacement.betweenTicks; final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0; length += isBetweenTicks ? 1 : 0; for (int i = 0; i < length; i++) { tempInterval = (isBetweenTicks ? i < length - 1 ? visibleLabels[i].value - tickBetweenLabel - : (visibleLabels[i - 1].value + - axisRenderer._visibleRange!.interval) - + : (visibleLabels[i - 1].value + visibleRange!.interval) - tickBetweenLabel : visibleLabels[i].value) .toDouble(); - pointX = ((_valueToCoefficient(tempInterval, axisRenderer) * - axisBounds.width) + - axisBounds.left) + pointX = ((valueToCoefficient(tempInterval, this) * bounds.width) + + bounds.left) .roundToDouble(); - pointY = axisBounds.top - padding + axis.axisLine.width / 2; + pointY = bounds.top - padding + axis.axisLine.width / 2; if (needAnimate!) { final double? oldLocation = @@ -1603,16 +1904,16 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ? (oldLocation - (oldLocation - pointX) * animationFactor!) : pointX; } - if (axisBounds.left.roundToDouble() <= pointX && - axisBounds.right.roundToDouble() >= pointX) { + if (bounds.left.roundToDouble() <= pointX && + bounds.right.roundToDouble() >= pointX) { if ((axis.majorGridLines.width > 0) == true && renderType == 'outside' && ((axis.plotOffset > 0) == true || (i != 0 && (isBetweenTicks ? i != length - 1 : i != length)) || - (axisBounds.left <= pointX && - axisBounds.right >= pointX && - !_chartState._requireInvertedAxis))) { + (bounds.left <= pointX && + bounds.right >= pointX && + !stateProperties.requireInvertedAxis))) { axisRenderer.drawHorizontalAxesMajorGridLines( canvas, Offset(pointX, pointY), @@ -1624,39 +1925,38 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { if ((axis.minorGridLines.width > 0) == true || (axis.minorTickLines.width > 0) == true) { num? nextValue = isBetweenTicks - ? (tempInterval + axisRenderer._visibleRange!.interval) + ? (tempInterval + visibleRange!.interval) : i == length - 1 - ? axisRenderer._visibleRange!.maximum + ? visibleRange!.maximum : visibleLabels[i + 1].value; if (nextValue != null) { - nextValue = ((_valueToCoefficient(nextValue, axisRenderer) * - axisBounds.width) + - axisBounds.left) - .roundToDouble(); + nextValue = + ((valueToCoefficient(nextValue, this) * bounds.width) + + bounds.left) + .roundToDouble(); axisRenderer.drawHorizontalAxesMinorLines(canvas, axisRenderer, - pointX, axisBounds, nextValue, i, chart, renderType); + pointX, bounds, nextValue, i, chart, renderType); } } } if (axis.majorTickLines.width > 0 == true && - (axisBounds.left <= pointX && - axisBounds.right.roundToDouble() >= pointX) && + (bounds.left <= pointX && bounds.right.roundToDouble() >= pointX) && renderType == axis.tickPosition.toString().split('.')[1]) { - _drawDashedPath( + drawDashedPath( canvas, - _CustomPaintStyle( + CustomPaintStyle( axisRenderer.getAxisMajorTickWidth(axis, i), axisRenderer.getAxisMajorTickColor(axis, i) ?? - _renderingDetails.chartTheme.majorTickLineColor, + renderingDetails.chartTheme.majorTickLineColor, PaintingStyle.stroke), Offset(pointX, pointY), Offset( pointX, axis.opposedPosition == false - ? (axisRenderer._isInsideTickPosition! + ? (isInsideTickPosition! ? pointY - ticks.size : pointY + ticks.size) - : (axisRenderer._isInsideTickPosition! + : (isInsideTickPosition! ? pointY + ticks.size : pointY - ticks.size))); } @@ -1664,8 +1964,47 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } } - /// To draw the major grid lines of horizontal axes - @override + /// To change chart based on range controller + void updateRangeControllerValues(ChartAxisRendererDetails _axisRenderer) { + stateProperties.zoomProgress = false; + stateProperties.isRedrawByZoomPan = false; + if (_axisRenderer is DateTimeAxisRenderer || + _axisRenderer is DateTimeCategoryAxisRenderer) { + _axisRenderer.rangeMinimum = + axis.rangeController!.start.millisecondsSinceEpoch; + _axisRenderer.rangeMaximum = + axis.rangeController!.end.millisecondsSinceEpoch; + } else { + _axisRenderer.rangeMinimum = axis.rangeController!.start; + _axisRenderer.rangeMaximum = axis.rangeController!.end; + } + } + + /// Auto scrolling feature + void updateAutoScrollingDelta( + int scrollingDelta, ChartAxisRenderer axisRenderer) { + this.scrollingDelta = scrollingDelta; + switch (axis.autoScrollingMode) { + case AutoScrollingMode.start: + final VisibleRange autoScrollRange = VisibleRange( + visibleRange!.minimum, visibleRange!.minimum + scrollingDelta); + autoScrollRange.delta = + autoScrollRange.maximum - autoScrollRange.minimum; + zoomFactor = autoScrollRange.delta / actualRange!.delta; + zoomPosition = 0; + break; + case AutoScrollingMode.end: + final VisibleRange autoScrollRange = VisibleRange( + visibleRange!.maximum - scrollingDelta, visibleRange!.maximum); + autoScrollRange.delta = + autoScrollRange.maximum - autoScrollRange.minimum; + zoomFactor = autoScrollRange.delta / actualRange!.delta; + zoomPosition = 1 - zoomFactor; + break; + } + } + + /// Method to drwa the horizontal axes major grid lines void drawHorizontalAxesMajorGridLines( Canvas canvas, Offset point, @@ -1673,24 +2012,23 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { MajorGridLines grids, int index, SfCartesianChart chart) { - final _CustomPaintStyle paintStyle = _CustomPaintStyle( - axisRenderer.getAxisMajorGridWidth(axisRenderer._axis, index), - axisRenderer.getAxisMajorGridColor(axisRenderer._axis, index) ?? - _renderingDetails.chartTheme.majorGridLineColor, + final CustomPaintStyle paintStyle = CustomPaintStyle( + axisRenderer.getAxisMajorGridWidth(axis, index), + axisRenderer.getAxisMajorGridColor(axis, index) ?? + renderingDetails.chartTheme.majorGridLineColor, PaintingStyle.stroke); - _drawDashedPath( + drawDashedPath( canvas, paintStyle, - Offset(point.dx, _chartState._chartAxis._axisClipRect.top), + Offset(point.dx, stateProperties.chartAxis.axisClipRect.top), Offset( point.dx, - _chartState._chartAxis._axisClipRect.top + - _chartState._chartAxis._axisClipRect.height), + stateProperties.chartAxis.axisClipRect.top + + stateProperties.chartAxis.axisClipRect.height), grids.dashArray); } - /// To draw the minor grid lines of horizontal axes - @override + /// Method to draw the horizontal axes minor lines void drawHorizontalAxesMinorLines( Canvas canvas, ChartAxisRenderer axisRenderer, @@ -1701,7 +2039,6 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { SfCartesianChart chart, [String? renderType]) { double position = tempInterval; - final ChartAxis axis = axisRenderer._axis; final MinorTickLines ticks = axis.minorTickLines; final num interval = (tempInterval - nextValue).abs() / (axis.minorTicksPerInterval + 1); @@ -1711,44 +2048,41 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { final double pointY = rect.top; if (axis.minorGridLines.width > 0 && renderType == 'outside' && - (axisRenderer._bounds.left <= position && - axisRenderer._bounds.right >= position)) { - _drawDashedPath( + (bounds.left <= position && bounds.right >= position)) { + drawDashedPath( canvas, - _CustomPaintStyle( - axisRenderer.getAxisMinorGridWidth( - axisRenderer._axis, index, i), - axisRenderer.getAxisMinorGridColor( - axisRenderer._axis, index, i) ?? - _renderingDetails.chartTheme.minorGridLineColor, + CustomPaintStyle( + axisRenderer.getAxisMinorGridWidth(axis, index, i), + axisRenderer.getAxisMinorGridColor(axis, index, i) ?? + renderingDetails.chartTheme.minorGridLineColor, PaintingStyle.stroke), - Offset(position, _chartState._chartAxis._axisClipRect.top), + Offset(position, stateProperties.chartAxis.axisClipRect.top), Offset( position, - _chartState._chartAxis._axisClipRect.top + - _chartState._chartAxis._axisClipRect.height), + stateProperties.chartAxis.axisClipRect.top + + stateProperties.chartAxis.axisClipRect.height), axis.minorGridLines.dashArray); } if (axis.minorTickLines.width > 0 && - axisRenderer._bounds.left <= position && - axisRenderer._bounds.right >= position && + bounds.left <= position && + bounds.right >= position && renderType == axis.tickPosition.toString().split('.')[1]) { - _drawDashedPath( + drawDashedPath( canvas, - _CustomPaintStyle( + CustomPaintStyle( axisRenderer.getAxisMinorTickWidth(axis, index, i), axisRenderer.getAxisMinorTickColor(axis, index, i) ?? - _renderingDetails.chartTheme.minorTickLineColor, + renderingDetails.chartTheme.minorTickLineColor, PaintingStyle.stroke), Offset(position, pointY), Offset( position, !axis.opposedPosition - ? (axisRenderer._isInsideTickPosition! + ? (isInsideTickPosition! ? pointY - ticks.size : pointY + ticks.size) - : (axisRenderer._isInsideTickPosition! + : (isInsideTickPosition! ? pointY + ticks.size : pointY - ticks.size)), axis.minorGridLines.dashArray); @@ -1756,422 +2090,202 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } } - /// To draw tick lines of vertical axes - @override - void drawVerticalAxesTickLines( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - final dynamic axis = axisRenderer._axis; - final Rect axisBounds = axisRenderer._bounds; - final List visibleLabels = axisRenderer._visibleLabels; - double tempInterval, pointX, pointY; - int length = visibleLabels.length; - const num padding = 1; - final bool isBetweenTicks = - (axis is CategoryAxis || axis is DateTimeCategoryAxis) && - axis.labelPlacement == LabelPlacement.betweenTicks; - final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0; - length += isBetweenTicks ? 1 : 0; - for (int i = 0; i < length; i++) { - tempInterval = (isBetweenTicks - ? i < length - 1 - ? visibleLabels[i].value - tickBetweenLabel - : (visibleLabels[i - 1].value + - axisRenderer._visibleRange!.interval) - - tickBetweenLabel - : visibleLabels[i].value) - .toDouble(); - pointY = (_valueToCoefficient(tempInterval, axisRenderer) * - axisBounds.height) + - axisBounds.top; - pointY = (axisBounds.top + axisBounds.height) - - (pointY - axisBounds.top).abs(); - pointX = axisBounds.left + padding - axis.axisLine.width / 2; - - if (needAnimate!) { - final double? oldLocation = - _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval); - pointY = oldLocation != null - ? (oldLocation - (oldLocation - pointY) * animationFactor!) - : pointY; - } - if (pointY >= axisBounds.top && pointY <= axisBounds.bottom) { - if (axis.majorGridLines.width > 0 == true && - renderType == 'outside' && - (axis.plotOffset > 0 == true || - ((i == 0 || i == length - 1) && - chart.plotAreaBorderWidth == 0) || - (((i == 0 && axis.opposedPosition == false) || - (i == length - 1 && axis.opposedPosition == true)) && - axis.axisLine.width == 0) || - (axisBounds.top < pointY - axis.majorGridLines.width && - axisBounds.bottom > pointY + axis.majorGridLines.width))) { - axisRenderer.drawVerticalAxesMajorGridLines( - canvas, - Offset(pointX, pointY), - axisRenderer, - axis.majorGridLines, - i, - chart); - } - if (axis.minorGridLines.width > 0 == true || - axis.minorTickLines.width > 0 == true) { - axisRenderer.drawVerticalAxesMinorTickLines(canvas, axisRenderer, - tempInterval, axisBounds, i, chart, renderType!); - } - if (axis.majorTickLines.width > 0 == true && - renderType == axis.tickPosition.toString().split('.')[1]) { - _drawDashedPath( - canvas, - _CustomPaintStyle( - axisRenderer.getAxisMajorTickWidth(axis, i), - axisRenderer.getAxisMajorTickColor(axis, i) ?? - _renderingDetails.chartTheme.majorTickLineColor, - PaintingStyle.stroke), - Offset(pointX, pointY), - Offset( - axis.opposedPosition == false - ? (axisRenderer._isInsideTickPosition! - ? pointX + axis.majorTickLines.size - : pointX - axis.majorTickLines.size) - : (axisRenderer._isInsideTickPosition! - ? pointX - axis.majorTickLines.size - : pointX + axis.majorTickLines.size), - pointY)); - } + /// To find the height of the current label + double _findMultiRows(int length, num currentX, AxisLabel currentLabel, + ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + AxisLabel label; + num pointX; + final List labelIndex = []; + bool isMultiRows; + for (int i = length - 1; i >= 0; i--) { + label = visibleLabels[i]; + pointX = (valueToCoefficient(label.value, this) * + stateProperties.chartAxis.axisClipRect.width) + + stateProperties.chartAxis.axisClipRect.left; + isMultiRows = !axis.isInversed + ? currentX < (pointX + label.labelSize.width / 2) + : currentX + currentLabel.labelSize.width > + (pointX - label.labelSize.width / 2); + if (isMultiRows) { + labelIndex.add(label._index); + currentLabel._index = (currentLabel._index > label._index) + ? currentLabel._index + : label._index + 1; + } else { + currentLabel._index = labelIndex.contains(label._index) + ? currentLabel._index + : label._index; } } + return currentLabel.labelSize.height * currentLabel._index; } - /// To draw the major grid lines of vertical axes - @override - void drawVerticalAxesMajorGridLines( - Canvas canvas, - Offset point, - ChartAxisRenderer axisRenderer, - MajorGridLines grids, - int index, - SfCartesianChart chart) { - final _CustomPaintStyle paintStyle = _CustomPaintStyle( - axisRenderer.getAxisMajorGridWidth(axisRenderer._axis, index), - axisRenderer.getAxisMajorGridColor(axisRenderer._axis, index) ?? - _renderingDetails.chartTheme.majorGridLineColor, - PaintingStyle.stroke); - if (_chartState._chartAxis._primaryXAxisRenderer._xAxisStart != - Offset(_chartState._chartAxis._axisClipRect.left, point.dy) && - _chartState._chartAxis._primaryXAxisRenderer._xAxisEnd != - Offset( - _chartState._chartAxis._axisClipRect.left + - _chartState._chartAxis._axisClipRect.width, - point.dy)) { - _drawDashedPath( - canvas, - paintStyle, - Offset(_chartState._chartAxis._axisClipRect.left, point.dy), - Offset( - _chartState._chartAxis._axisClipRect.left + - _chartState._chartAxis._axisClipRect.width, - point.dy), - grids.dashArray); - } - } - - /// To draw the minor grid lines of vertical axes - @override - void drawVerticalAxesMinorTickLines( - Canvas canvas, - ChartAxisRenderer axisRenderer, - num tempInterval, - Rect rect, - int index, - SfCartesianChart chart, - [String? renderType]) { - final ChartAxis axis = axisRenderer._axis; - num value = tempInterval; - double position = 0; - final _VisibleRange range = axisRenderer._visibleRange!; - final bool rendering = axis.minorTicksPerInterval > 0 && - (axis.minorGridLines.width > 0 || axis.minorTickLines.width > 0); - if (rendering) { - for (int i = 0; i < axis.minorTicksPerInterval; i++) { - value += range.interval / (axis.minorTicksPerInterval + 1); - if ((value < range.maximum) && (value > range.minimum)) { - position = _valueToCoefficient(value, axisRenderer) * rect.height; - position = (position + rect.top).floor().toDouble(); - if (axis.minorGridLines.width > 0 && - renderType == 'outside' && - rect.top <= position && - rect.bottom >= position) { - _drawDashedPath( - canvas, - _CustomPaintStyle( - axisRenderer.getAxisMinorGridWidth(axis, index, i), - axisRenderer.getAxisMinorGridColor(axis, index, i) ?? - _renderingDetails.chartTheme.minorGridLineColor, - PaintingStyle.stroke), - Offset(_chartState._chartAxis._axisClipRect.left, position), - Offset( - _chartState._chartAxis._axisClipRect.left + - _chartState._chartAxis._axisClipRect.width, - position), - axis.minorGridLines.dashArray); - } - if (axis.minorTickLines.width > 0 && - renderType == axis.tickPosition.toString().split('.')[1]) { - _drawDashedPath( - canvas, - _CustomPaintStyle( - axisRenderer.getAxisMinorTickWidth(axis, index, i), - axisRenderer.getAxisMinorTickColor(axis, index, i) ?? - _renderingDetails.chartTheme.minorTickLineColor, - PaintingStyle.stroke), - Offset(rect.left, position), - Offset( - !axis.opposedPosition - ? (axisRenderer._isInsideTickPosition! - ? rect.left + axis.minorTickLines.size - : rect.left - axis.minorTickLines.size) - : (axisRenderer._isInsideTickPosition! - ? rect.left - axis.minorTickLines.size - : rect.left + axis.minorTickLines.size), - position)); - } - } - } + /// To get the label collection + List _gettingLabelCollection( + String currentLabel, num labelsExtent, ChartAxisRenderer axisRenderer) { + final List textCollection = currentLabel.split(RegExp(' ')); + final List labelCollection = []; + String text; + for (int i = 0; i < textCollection.length; i++) { + text = textCollection[i]; + (measureText(text, axis.labelStyle, labelRotation).width < labelsExtent) + ? labelCollection.add(text) + : labelCollection.add(getTrimmedText( + text, labelsExtent, axis.labelStyle, axisRenderer)); } + return labelCollection; } - /// To draw the axis labels of horizontal axes - @override - void drawHorizontalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - final ChartAxis axis = axisRenderer._axis; - if (renderType == axis.labelPosition.toString().split('.')[1]) { - final Rect axisBounds = axisRenderer._bounds; - int angle; - TextStyle textStyle; - final List visibleLabels = axisRenderer._visibleLabels; - late double tempInterval, pointX, pointY, previousLabelEnd; - for (int i = 0; i < visibleLabels.length; i++) { - final AxisLabel label = visibleLabels[i]; - final String labelText = - axisRenderer.getAxisLabel(axis, label.renderText!, i); - - textStyle = label.labelStyle; - textStyle = _getTextStyle( - textStyle: textStyle, - fontColor: - textStyle.color ?? _renderingDetails.chartTheme.axisLabelColor); - tempInterval = label.value.toDouble(); - angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); - - /// For negative angle calculations - if (angle.isNegative) { - angle = angle + 360; - } - axisRenderer._labelRotation = angle; - final Size textSize = measureText(labelText, textStyle); - final Size rotatedTextSize = measureText(labelText, textStyle, angle); - pointX = ((_valueToCoefficient(tempInterval, axisRenderer) * - axisBounds.width) + - axisBounds.left) - .roundToDouble(); - pointY = _getPointY(axisRenderer, label, axisBounds); - pointY -= angle == 0 ? textSize.height / 2 : 0; - pointY += rotatedTextSize.height / 2; - pointX -= angle == 0 ? textSize.width / 2 : 0; - - /// Edge label placement - shift for x-Axis - pointX = _getShiftedPosition( - axisRenderer, axisBounds, pointX, pointY, textSize, i); - if (axis.labelAlignment == LabelAlignment.end) { - pointX = pointX + textSize.height / 2; - } else if (axis.labelAlignment == LabelAlignment.start) { - pointX = pointX - textSize.height / 2; - } - if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) { - if (axis.labelAlignment == LabelAlignment.center) { - if (i == 0 || (i == axisRenderer._visibleLabels.length - 1)) { - axisRenderer._visibleLabels[i]._needRender = false; - continue; - } - } else if ((axis.labelAlignment == LabelAlignment.end) && - (i == axisRenderer._visibleLabels.length - 1 || - (i == 0 && axis.isInversed))) { - axisRenderer._visibleLabels[i]._needRender = false; - continue; - } else if ((axis.labelAlignment == LabelAlignment.start) && - (i == 0 || - (i == axisRenderer._visibleLabels.length - 1 && - axis.isInversed))) { - axisRenderer._visibleLabels[i]._needRender = false; - continue; - } - } - - if (axis.labelIntersectAction == AxisLabelIntersectAction.hide && - axis.labelRotation % 180 == 0 && - i != 0 && - axisRenderer._visibleLabels[i - 1]._needRender && - (!axis.isInversed - ? pointX <= previousLabelEnd - : (pointX + textSize.width) >= previousLabelEnd)) { - continue; - } - - previousLabelEnd = axis.isInversed ? pointX : pointX + textSize.width; + ///Below method is for changing range while zooming + void calculateZoomRange(ChartAxisRenderer axisRenderer, Size axisSize) { + ChartAxisRenderer? oldAxisRenderer; + assert(axis.zoomFactor >= 0 && axis.zoomFactor <= 1, + 'The zoom factor of the axis should be between 0 and 1.'); + assert(axis.zoomPosition >= 0 && axis.zoomPosition <= 1, + 'The zoom position of the axis should be between 0 and 1.'); - if (needAnimate!) { - final double? oldLocation = _getPrevLocation( - axisRenderer, oldAxisRenderer!, tempInterval, textSize, angle); - pointX = oldLocation != null - ? (oldLocation - (oldLocation - pointX) * animationFactor!) - : pointX; - } - final Offset point = Offset(pointX, pointY); - if (axisBounds.left - textSize.width <= pointX && - axisBounds.right + textSize.width >= pointX) { - _drawText(canvas, labelText, point, textStyle, angle); - } - if (label._labelCollection != null && - label._labelCollection!.isNotEmpty && - axis.labelIntersectAction == AxisLabelIntersectAction.wrap) { - for (int j = 1; j < label._labelCollection!.length; j++) { - final String wrapTxt = label._labelCollection![j]; - _drawText( - canvas, - wrapTxt, - Offset( - pointX, - pointY + - (j * - measureText(wrapTxt, axis.labelStyle, angle) - .height)), - textStyle, - angle); - } - } + /// Restrict zoom factor and zoom position values between 0 to 1 + zoomFactor = zoomFactor > 1 + ? 1 + : zoomFactor < 0 + ? 0 + : zoomFactor; + zoomPosition = zoomPosition > 1 + ? 1 + : zoomPosition < 0 + ? 0 + : zoomPosition; + if (stateProperties.oldAxisRenderers.isNotEmpty) { + oldAxisRenderer = + getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); + } + if (oldAxisRenderer != null) { + zoomFactor = oldAxisRenderer._axisRendererDetails.axis.zoomFactor != + axis.zoomFactor + ? axis.zoomFactor + : zoomFactor; + zoomPosition = oldAxisRenderer._axisRendererDetails.axis.zoomPosition != + axis.zoomPosition + ? axis.zoomPosition + : zoomPosition; + if (axis.autoScrollingDelta == + oldAxisRenderer._axisRendererDetails.axis.autoScrollingDelta) { + scrollingDelta = oldAxisRenderer._axisRendererDetails.scrollingDelta; } } - } - /// To draw the axis labels of vertical axes - @override - void drawVerticalAxesLabels( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, - [String? renderType, - double? animationFactor, - ChartAxisRenderer? oldAxisRenderer, - bool? needAnimate]) { - final ChartAxis axis = axisRenderer._axis; - if (axis.labelPosition.toString().split('.')[1] == renderType) { - final Rect axisBounds = axisRenderer._bounds; - final List visibleLabels = axisRenderer._visibleLabels; - TextStyle textStyle; - late double tempInterval, pointX, pointY, previousEnd; - for (int i = 0; i < visibleLabels.length; i++) { - final String labelText = - axisRenderer.getAxisLabel(axis, visibleLabels[i].renderText!, i); - final int angle = - axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); - assert(angle - angle.floor() == 0, - 'The angle value of the vertical axis must be the whole number.'); - textStyle = visibleLabels[i].labelStyle; - textStyle = _getTextStyle( - textStyle: textStyle, - fontColor: - textStyle.color ?? _renderingDetails.chartTheme.axisLabelColor); - tempInterval = visibleLabels[i].value.toDouble(); - final Size textSize = measureText(labelText, textStyle, 0); - pointY = (_valueToCoefficient(tempInterval, axisRenderer) * - axisBounds.height) + - axisBounds.top; - pointY = ((axisBounds.top + axisBounds.height) - - ((axisBounds.top - pointY).abs())) - - textSize.height / 2; - pointX = _getPointX(axisRenderer, textSize, axisBounds); - final _ChartLocation location = _getRotatedTextLocation( - pointX, pointY, labelText, textStyle, angle, axis); - if (axis.labelAlignment == LabelAlignment.center) { - pointX = location.x; - pointY = location.y; - } else if (axis.labelAlignment == LabelAlignment.end) { - pointX = location.x; - pointY = location.y - textSize.height / 2; - } else if (axis.labelAlignment == LabelAlignment.start) { - pointX = location.x; - pointY = location.y + textSize.height / 2; - } - if (axis.labelIntersectAction == AxisLabelIntersectAction.hide && - i != 0 && - (!axis.isInversed - ? pointY + (textSize.height / 2) > previousEnd - : pointY - (textSize.height / 2) < previousEnd)) { - continue; - } - previousEnd = !axis.isInversed - ? pointY - textSize.height / 2 - : pointY + textSize.height / 2; + final VisibleRange baseRange = visibleRange!; + num start, end; + start = visibleRange!.minimum + zoomPosition * visibleRange!.delta; + end = start + zoomFactor * visibleRange!.delta; + + if (start < baseRange.minimum) { + end = end + (baseRange.minimum - start); + start = baseRange.minimum; + } + if (end > baseRange.maximum) { + start = start - (end - baseRange.maximum); + end = baseRange.maximum; + } + visibleRange!.minimum = start; + visibleRange!.maximum = end; + visibleRange!.delta = end - start; + } - /// Edge label placement for y-Axis - if (axis.edgeLabelPlacement == EdgeLabelPlacement.shift) { - if (axis.labelAlignment == LabelAlignment.center) { - if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) { - pointY = axisBounds.top + axisBounds.height - textSize.height; - } else if (i == axisRenderer._visibleLabels.length - 1 && - axisBounds.top >= pointY + textSize.height / 2) { - pointY = axisBounds.top; - } - } else if (axis.labelAlignment == LabelAlignment.start) { - if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) { - pointY = axisBounds.top + axisBounds.height - textSize.height; - } - } else if (axis.labelAlignment == LabelAlignment.end) { - if (i == axisRenderer._visibleLabels.length - 1 && - axisBounds.top >= pointY + textSize.height / 2) { - pointY = axisBounds.top + textSize.height / 2; - } - } - } else if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) { - if (axis.labelAlignment == LabelAlignment.center) { - if (i == 0 || i == axisRenderer._visibleLabels.length - 1) { - continue; - } - } else if ((axis.labelAlignment == LabelAlignment.end) && - (i == axisRenderer._visibleLabels.length - 1 || - (i == 0 && axis.isInversed))) { - continue; - } else if ((axis.labelAlignment == LabelAlignment.start) && - (i == 0 || - (i == axisRenderer._visibleLabels.length - 1 && - axis.isInversed))) { - continue; + /// To set the zoom factor and position of axis through dynamic update or from + void setZoomFactorAndPosition(ChartAxisRenderer axisRenderer, + List axisRendererStates) { + bool didUpdateAxis; + if (oldAxis != null && + (oldAxis!.zoomPosition != axis.zoomPosition || + oldAxis!.zoomFactor != axis.zoomFactor)) { + zoomFactor = axis.zoomFactor; + zoomPosition = axis.zoomPosition; + didUpdateAxis = true; + } else { + didUpdateAxis = false; + } + for (final ChartAxisRenderer zoomedAxisRenderer + in stateProperties.zoomedAxisRendererStates) { + if (zoomedAxisRenderer._axisRendererDetails.name == name) { + if (didUpdateAxis) { + zoomedAxisRenderer._axisRendererDetails.zoomFactor = zoomFactor; + zoomedAxisRenderer._axisRendererDetails.zoomPosition = zoomPosition; + } else { + if (axis.autoScrollingDelta == null || + scrollingDelta != + zoomedAxisRenderer._axisRendererDetails.visibleRange!.delta) { + zoomFactor = zoomedAxisRenderer._axisRendererDetails.zoomFactor; + zoomPosition = zoomedAxisRenderer._axisRendererDetails.zoomPosition; } } - axisRenderer._visibleLabels[i]._labelRegion = - Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height); + break; + } + } + } - if (needAnimate!) { - final double? oldLocation = _getPrevLocation( - axisRenderer, oldAxisRenderer!, tempInterval, textSize); - pointY = oldLocation != null - ? (oldLocation - (oldLocation - pointY) * animationFactor!) - : pointY; - } + /// To provide chart changes to range controller + void setRangeControllerValues(ChartAxisRenderer _axisRenderer) { + if (_axisRenderer is DateTimeAxisRenderer || + _axisRenderer is DateTimeCategoryAxisRenderer) { + axis.rangeController!.start = DateTime.fromMillisecondsSinceEpoch( + _axisRenderer._axisRendererDetails.visibleRange!.minimum.toInt()); + axis.rangeController!.end = DateTime.fromMillisecondsSinceEpoch( + _axisRenderer._axisRendererDetails.visibleRange!.maximum.toInt()); + } else { + axis.rangeController!.start = + _axisRenderer._axisRendererDetails.visibleRange!.minimum; + axis.rangeController!.end = + _axisRenderer._axisRendererDetails.visibleRange!.maximum; + } + } - final Offset point = Offset(pointX, pointY); - if (axisBounds.top - textSize.height <= pointY && - axisBounds.bottom + textSize.height >= pointY) { - _drawText( - canvas, labelText, point, textStyle, axisRenderer._labelRotation); + /// Method to set zoom values from the range controller + void setZoomValuesFromRangeController() { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + if (!(stateProperties.isRedrawByZoomPan || + stateProperties.canSetRangeController)) { + if (stateProperties.rangeChangeBySlider && + !stateProperties.canSetRangeController && + rangeMinimum != null && + rangeMaximum != null) { + visibleRange!.delta = visibleRange!.maximum - visibleRange!.minimum; + if (this is! DateTimeCategoryAxisRenderer) { + visibleRange!.interval = axisRenderer is LogarithmicAxisRenderer + ? (axisDetails.axisRenderer as LogarithmicAxisRenderer) + .calculateLogNiceInterval(visibleRange!.delta) + : axisRenderer.calculateInterval(visibleRange!, axisSize); } + visibleRange!.interval = + actualRange!.interval != null && actualRange!.interval % 1 != 0 + ? actualRange!.interval + : visibleRange!.interval; + zoomFactor = visibleRange!.delta / (actualRange!.delta); + zoomPosition = + (visibleRange!.minimum - actualRange!.minimum) / actualRange!.delta; + } + } + } + + /// Method to set the old range from range controller + void setOldRangeFromRangeController() { + if (!stateProperties.renderingDetails.initialRender! && + axis.rangeController != null && + !stateProperties.canSetRangeController) { + final ChartAxisRenderer? oldrenderer = + getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); + if (oldrenderer != null) { + final ChartAxisRendererDetails axisRendererDetails = + oldrenderer._axisRendererDetails; + visibleMinimum = rangeMinimum = + axisRendererDetails.rangeMinimum is DateTime + ? axisRendererDetails.rangeMinimum.millisecondsSinceEpoch + : axisRendererDetails.rangeMinimum; + visibleMaximum = rangeMaximum = + axisRendererDetails.rangeMaximum is DateTime + ? axisRendererDetails.rangeMaximum.millisecondsSinceEpoch + : axisRendererDetails.rangeMaximum; } } } @@ -2181,65 +2295,80 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ChartAxisRenderer oldAxisRenderer, num value, [Size? textSize, num? angle]) { double? location; - final Rect bounds = axisRenderer._bounds; - final ChartAxis axis = axisRenderer._axis; + final Rect bounds = axisRenderer._axisRendererDetails.bounds; + final ChartAxis axis = axisRenderer._axisRendererDetails.axis; textSize ??= const Size(0, 0); - if (oldAxisRenderer._visibleRange!.minimum > value == false) { - location = axisRenderer._orientation == AxisOrientation.vertical + if (oldAxisRenderer._axisRendererDetails.visibleRange!.minimum > value == + false) { + location = axisRenderer._axisRendererDetails.orientation == + AxisOrientation.vertical ? (axis.isInversed ? ((bounds.top + bounds.height) - ((bounds.top - (bounds.top - - (_valueToCoefficient(value, oldAxisRenderer) * + (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.height)) .roundToDouble()) .abs())) : (bounds.bottom - - (_valueToCoefficient(value, oldAxisRenderer) * + (valueToCoefficient( + value, oldAxisRenderer._axisRendererDetails) * bounds.height)) .roundToDouble()) : (axis.isInversed - ? ((_valueToCoefficient(value, axisRenderer) * bounds.width) + + ? ((valueToCoefficient(value, axisRenderer._axisRendererDetails) * + bounds.width) + bounds.right) .roundToDouble() - : ((_valueToCoefficient(value, oldAxisRenderer) * bounds.width) - + : ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * + bounds.width) - bounds.left) .roundToDouble()); - } else if (oldAxisRenderer._visibleRange!.maximum < value == false) { - location = axisRenderer._orientation == AxisOrientation.vertical + } else if (oldAxisRenderer._axisRendererDetails.visibleRange!.maximum < + value == + false) { + location = axisRenderer._axisRendererDetails.orientation == + AxisOrientation.vertical ? (axis.isInversed ? (bounds.bottom - - (_valueToCoefficient(value, oldAxisRenderer) * + (valueToCoefficient( + value, oldAxisRenderer._axisRendererDetails) * bounds.height)) .roundToDouble() : ((bounds.top + bounds.height) - ((bounds.top - (bounds.top - - (_valueToCoefficient(value, oldAxisRenderer) * + (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * bounds.height)) .roundToDouble()) .abs()))) : (axis.isInversed - ? ((_valueToCoefficient(value, oldAxisRenderer) * bounds.width) - + ? ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * + bounds.width) - bounds.left) .roundToDouble() - : ((_valueToCoefficient(value, axisRenderer) * bounds.width) + + : ((valueToCoefficient(value, axisRenderer._axisRendererDetails) * + bounds.width) + bounds.right) .roundToDouble()); } else { - if (axisRenderer._orientation == AxisOrientation.vertical) { - location = (_valueToCoefficient(value, oldAxisRenderer) * - oldAxisRenderer._bounds.height) + - oldAxisRenderer._bounds.top; + if (axisRenderer._axisRendererDetails.orientation == + AxisOrientation.vertical) { location = - ((oldAxisRenderer._bounds.top + oldAxisRenderer._bounds.height) - - ((oldAxisRenderer._bounds.top - location).abs())) - - textSize.height / 2; + (valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * + oldAxisRenderer._axisRendererDetails.bounds.height) + + oldAxisRenderer._axisRendererDetails.bounds.top; + location = ((oldAxisRenderer._axisRendererDetails.bounds.top + + oldAxisRenderer._axisRendererDetails.bounds.height) - + ((oldAxisRenderer._axisRendererDetails.bounds.top - location) + .abs())) - + textSize.height / 2; } else { - location = ((_valueToCoefficient(value, oldAxisRenderer) * - oldAxisRenderer._bounds.width) + - oldAxisRenderer._bounds.left) - .roundToDouble(); + location = + ((valueToCoefficient(value, oldAxisRenderer._axisRendererDetails) * + oldAxisRenderer._axisRendererDetails.bounds.width) + + oldAxisRenderer._axisRendererDetails.bounds.left) + .roundToDouble(); if (angle != null) { location -= angle == 0 ? textSize.width / 2 : 0; } @@ -2253,33 +2382,34 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ChartAxisRenderer axisRenderer, Size textSize, Rect axisBounds) { late double pointX; const num innerPadding = 5; - final ChartAxis axis = axisRenderer._axis; + final ChartAxis axis = axisRenderer._axisRendererDetails.axis; if (axis.labelPosition == ChartDataLabelPosition.inside) { pointX = (!axis.opposedPosition) ? (axisBounds.left + innerPadding + - (axisRenderer._isInsideTickPosition! + (axisRenderer._axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0)) : (axisBounds.left - - axisRenderer._maximumLabelSize.width - + axisRenderer._axisRendererDetails.maximumLabelSize.width - innerPadding - - (axisRenderer._isInsideTickPosition! + (axisRenderer._axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0)); } else { pointX = ((!axis.opposedPosition) - ? axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! - textSize.width + ? axisRenderer._axisRendererDetails.labelOffset != null + ? axisRenderer._axisRendererDetails.labelOffset! - + textSize.width : (axisBounds.left - - (axisRenderer._isInsideTickPosition! + (axisRenderer._axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) - textSize.width - innerPadding) - : (axisRenderer._labelOffset ?? + : (axisRenderer._axisRendererDetails.labelOffset ?? (axisBounds.left + - (axisRenderer._isInsideTickPosition! + (axisRenderer._axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding))) @@ -2291,7 +2421,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// Return the y point double _getPointY( ChartAxisRenderer axisRenderer, AxisLabel label, Rect axisBounds) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxis axis = axisRenderer._axisRendererDetails.axis; double pointY; const num innerPadding = 3; if (axis.labelPosition == ChartDataLabelPosition.inside) { @@ -2299,41 +2429,48 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ? axisBounds.top - innerPadding - (label._index > 1 - ? axisRenderer._maximumLabelSize.height / 2 - : axisRenderer._maximumLabelSize.height) - - (axisRenderer._isInsideTickPosition! + ? axisRenderer._axisRendererDetails.maximumLabelSize.height / + 2 + : axisRenderer._axisRendererDetails.maximumLabelSize.height) - + (axisRenderer._axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0) : axisBounds.top + - (axisRenderer._isInsideTickPosition! + (axisRenderer._axisRendererDetails.isInsideTickPosition! ? axis.majorTickLines.size : 0) + (label._index > 1 - ? axisRenderer._maximumLabelSize.height / 2 + ? axisRenderer._axisRendererDetails.maximumLabelSize.height / + 2 : 0); } else { pointY = (!axis.opposedPosition - ? axisRenderer._labelOffset ?? + ? axisRenderer._axisRendererDetails.labelOffset ?? (axisBounds.top + - ((axisRenderer._isInsideTickPosition! + ((axisRenderer._axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding) + (label._index > 1 - ? axisRenderer._maximumLabelSize.height / 2 + ? axisRenderer._axisRendererDetails.maximumLabelSize + .height / + 2 : 0)) - : axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! - - axisRenderer._maximumLabelSize.height + : axisRenderer._axisRendererDetails.labelOffset != null + ? axisRenderer._axisRendererDetails.labelOffset! - + axisRenderer._axisRendererDetails.maximumLabelSize.height : (axisBounds.top - - (((axisRenderer._isInsideTickPosition! + (((axisRenderer._axisRendererDetails.isInsideTickPosition! ? 0 : axis.majorTickLines.size) + innerPadding) - (label._index > 1 - ? axisRenderer._maximumLabelSize.height / 2 + ? axisRenderer._axisRendererDetails + .maximumLabelSize.height / + 2 : 0)) - - axisRenderer._maximumLabelSize.height)) + axisRenderer + ._axisRendererDetails.maximumLabelSize.height)) .toDouble(); } return pointY; @@ -2342,7 +2479,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// To get shifted position for both axes double _getShiftedPosition(ChartAxisRenderer axisRenderer, Rect axisBounds, double pointX, double pointY, Size textSize, int i) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxis axis = axisRenderer._axisRendererDetails.axis; if (axis.edgeLabelPlacement == EdgeLabelPlacement.shift) { if (axis.labelAlignment == LabelAlignment.center) { if (i == 0 && @@ -2354,7 +2491,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { : axisBounds.left; } - if (i == axisRenderer._visibleLabels.length - 1 && + if (i == axisRenderer._axisRendererDetails.visibleLabels.length - 1 && ((((pointX + textSize.width) > axisBounds.right) && !axis.isInversed) || (pointX < axisBounds.left && axis.isInversed))) { @@ -2363,7 +2500,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { : axisBounds.left + axisBounds.width - textSize.width; } } else if ((axis.labelAlignment == LabelAlignment.end) && - (i == axisRenderer._visibleLabels.length - 1 && + (i == axisRenderer._axisRendererDetails.visibleLabels.length - 1 && ((((pointX + textSize.width) > axisBounds.right) && !axis.isInversed) || (pointX < axisBounds.left && axis.isInversed)))) { @@ -2386,319 +2523,533 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { final Rect currentRegion = Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height); final bool isIntersect = i > 0 && - i < axisRenderer._visibleLabels.length - 1 && + i < axisRenderer._axisRendererDetails.visibleLabels.length - 1 && axis.labelIntersectAction == AxisLabelIntersectAction.hide && axis.labelRotation % 180 == 0 && - axisRenderer._visibleLabels[i - 1]._labelRegion != null && - (axisRenderer._axis.isInversed == false + axisRenderer._axisRendererDetails.visibleLabels[i - 1]._labelRegion != + null && + (axisRenderer._axisRendererDetails.axis.isInversed == false ? currentRegion.left < - axisRenderer._visibleLabels[i - 1]._labelRegion!.right + axisRenderer._axisRendererDetails.visibleLabels[i - 1] + ._labelRegion!.right : currentRegion.right > - axisRenderer._visibleLabels[i - 1]._labelRegion!.left); - axisRenderer._visibleLabels[i]._labelRegion = + axisRenderer._axisRendererDetails.visibleLabels[i - 1] + ._labelRegion!.left); + axisRenderer._axisRendererDetails.visibleLabels[i]._labelRegion = !isIntersect ? currentRegion : null; return pointX; } - /// To draw the axis title of horizontal axes - @override - void drawHorizontalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final ChartAxis axis = axisRenderer._axis; - final Rect axisBounds = axisRenderer._bounds; - Offset point; - final String title = axis.title.text ?? ''; - const int labelRotation = 0, innerPadding = 8; - TextStyle style = axis.title.textStyle; - style = _getTextStyle( - textStyle: style, - fontColor: style.color ?? _renderingDetails.chartTheme.axisTitleColor); - final Size textSize = measureText(title, style); - double top; - if (axis.labelPosition == ChartDataLabelPosition.inside) { - top = !axis.opposedPosition - ? axisBounds.top + - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - (!kIsWeb ? innerPadding : innerPadding + textSize.height / 2) - : axisBounds.top - - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) - - innerPadding - - textSize.height; - } else { - top = !axis.opposedPosition - ? axisBounds.top + - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - innerPadding + - (!kIsWeb - ? axisRenderer._maximumLabelSize.height - : axisRenderer._maximumLabelSize.height + textSize.height / 2) - : axisBounds.top - - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) - - innerPadding - - axisRenderer._maximumLabelSize.height - - textSize.height; - } - axis.title.alignment == ChartAlignment.near - ? point = Offset(_chartState._chartAxis._axisClipRect.left, top) - : axis.title.alignment == ChartAlignment.far - ? point = Offset( - _chartState._chartAxis._axisClipRect.right - textSize.width, - top) - : point = Offset( - axisBounds.left + - ((axisBounds.width / 2) - (textSize.width / 2)), - top); - if (axisRenderer._seriesRenderers.isNotEmpty || - axisRenderer._name == 'primaryXAxis') { - _drawText(canvas, title, point, style, labelRotation); + void _drawVerticalAxesTickLines( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + final dynamic axis = axisRenderer._axisRendererDetails.axis; + final Rect axisBounds = axisRenderer._axisRendererDetails.bounds; + final List visibleLabels = + axisRenderer._axisRendererDetails.visibleLabels; + double tempInterval, pointX, pointY; + int length = visibleLabels.length; + const num padding = 1; + final bool isBetweenTicks = + (axis is CategoryAxis || axis is DateTimeCategoryAxis) && + axis.labelPlacement == LabelPlacement.betweenTicks; + final num tickBetweenLabel = isBetweenTicks ? 0.5 : 0; + length += isBetweenTicks ? 1 : 0; + for (int i = 0; i < length; i++) { + tempInterval = (isBetweenTicks + ? i < length - 1 + ? visibleLabels[i].value - tickBetweenLabel + : (visibleLabels[i - 1].value + + axisRenderer + ._axisRendererDetails.visibleRange!.interval) - + tickBetweenLabel + : visibleLabels[i].value) + .toDouble(); + pointY = + (valueToCoefficient(tempInterval, axisRenderer._axisRendererDetails) * + axisBounds.height) + + axisBounds.top; + pointY = (axisBounds.top + axisBounds.height) - + (pointY - axisBounds.top).abs(); + pointX = axisBounds.left + padding - axis.axisLine.width / 2; + + if (needAnimate!) { + final double? oldLocation = + _getPrevLocation(axisRenderer, oldAxisRenderer!, tempInterval); + pointY = oldLocation != null + ? (oldLocation - (oldLocation - pointY) * animationFactor!) + : pointY; + } + if (pointY >= axisBounds.top && pointY <= axisBounds.bottom) { + if (axis.majorGridLines.width > 0 == true && + renderType == 'outside' && + (axis.plotOffset > 0 == true || + ((i == 0 || i == length - 1) && + chart.plotAreaBorderWidth == 0) || + (((i == 0 && axis.opposedPosition == false) || + (i == length - 1 && axis.opposedPosition == true)) && + axis.axisLine.width == 0) || + (axisBounds.top < pointY - axis.majorGridLines.width && + axisBounds.bottom > pointY + axis.majorGridLines.width))) { + axisRenderer.drawVerticalAxesMajorGridLines( + canvas, + Offset(pointX, pointY), + axisRenderer, + axis.majorGridLines, + i, + chart); + } + if (axis.minorGridLines.width > 0 == true || + axis.minorTickLines.width > 0 == true) { + axisRenderer.drawVerticalAxesMinorTickLines(canvas, axisRenderer, + tempInterval, axisBounds, i, chart, renderType!); + } + if (axis.majorTickLines.width > 0 == true && + renderType == axis.tickPosition.toString().split('.')[1]) { + drawDashedPath( + canvas, + CustomPaintStyle( + axisRenderer.getAxisMajorTickWidth(axis, i), + axisRenderer.getAxisMajorTickColor(axis, i) ?? + renderingDetails.chartTheme.majorTickLineColor, + PaintingStyle.stroke), + Offset(pointX, pointY), + Offset( + axis.opposedPosition == false + ? (axisRenderer._axisRendererDetails.isInsideTickPosition! + ? pointX + axis.majorTickLines.size + : pointX - axis.majorTickLines.size) + : (axisRenderer._axisRendererDetails.isInsideTickPosition! + ? pointX - axis.majorTickLines.size + : pointX + axis.majorTickLines.size), + pointY)); + } + } } } - /// To draw the axis title of vertical axes - @override - void drawVerticalAxesTitle( - Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final ChartAxis axis = axisRenderer._axis; - final Rect axisBounds = axisRenderer._bounds; - Offset point; - final String title = axis.title.text ?? ''; - final int labelRotation = axis.opposedPosition ? 90 : 270; - const int innerPadding = 10; - TextStyle style = axis.title.textStyle; - style = _getTextStyle( - textStyle: style, - fontColor: style.color ?? _renderingDetails.chartTheme.axisTitleColor); - final Size textSize = measureText(title, style); - double left; - if (axis.labelPosition == ChartDataLabelPosition.inside) { - left = (!axis.opposedPosition) - ? axisBounds.left - - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) - - innerPadding - - textSize.height - : axisBounds.left + - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - innerPadding * 2; - } else { - left = (!axis.opposedPosition) - ? (axisBounds.left - - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) - - innerPadding - - axisRenderer._maximumLabelSize.width - - textSize.height / 2) - : axisBounds.left + - (axisRenderer._isInsideTickPosition! - ? 0 - : axis.majorTickLines.size) + - innerPadding + - axisRenderer._maximumLabelSize.width + - textSize.height / 2; + /// Method to draw the vertical axes major grid line + void _drawVerticalAxesMajorGridLines( + Canvas canvas, + Offset point, + ChartAxisRenderer axisRenderer, + MajorGridLines grids, + int index, + SfCartesianChart chart) { + final CustomPaintStyle paintStyle = CustomPaintStyle( + axisRenderer.getAxisMajorGridWidth( + axisRenderer._axisRendererDetails.axis, index), + axisRenderer.getAxisMajorGridColor( + axisRenderer._axisRendererDetails.axis, index) ?? + renderingDetails.chartTheme.majorGridLineColor, + PaintingStyle.stroke); + if (stateProperties.chartAxis.primaryXAxisRenderer!._axisRendererDetails + .xAxisStart != + Offset(stateProperties.chartAxis.axisClipRect.left, point.dy) && + stateProperties.chartAxis.primaryXAxisRenderer!._axisRendererDetails + .xAxisEnd != + Offset( + stateProperties.chartAxis.axisClipRect.left + + stateProperties.chartAxis.axisClipRect.width, + point.dy)) { + drawDashedPath( + canvas, + paintStyle, + Offset(stateProperties.chartAxis.axisClipRect.left, point.dy), + Offset( + stateProperties.chartAxis.axisClipRect.left + + stateProperties.chartAxis.axisClipRect.width, + point.dy), + grids.dashArray); } - axis.title.alignment == ChartAlignment.near - ? point = Offset(left, - _chartState._chartAxis._axisClipRect.bottom - textSize.width / 2) - : axis.title.alignment == ChartAlignment.far - ? point = Offset(left, - _chartState._chartAxis._axisClipRect.top + textSize.width / 2) - : point = Offset(left, axisBounds.top + (axisBounds.height / 2)); - if (axisRenderer._seriesRenderers.isNotEmpty || - axisRenderer._name == 'primaryYAxis') { - _drawText(canvas, title, point, style, labelRotation); + } + + void _drawVerticalAxesMinorTickLines( + Canvas canvas, + ChartAxisRenderer axisRenderer, + num tempInterval, + Rect rect, + int index, + SfCartesianChart chart, + [String? renderType]) { + num value = tempInterval; + double position = 0; + final VisibleRange range = visibleRange!; + final bool rendering = axis.minorTicksPerInterval > 0 && + (axis.minorGridLines.width > 0 || axis.minorTickLines.width > 0); + if (rendering) { + for (int i = 0; i < axis.minorTicksPerInterval; i++) { + value += range.interval / (axis.minorTicksPerInterval + 1); + if ((value < range.maximum) && (value > range.minimum)) { + position = + valueToCoefficient(value, axisRenderer._axisRendererDetails) * + rect.height; + position = (position + rect.top).floor().toDouble(); + if (axis.minorGridLines.width > 0 && + renderType == 'outside' && + rect.top <= position && + rect.bottom >= position) { + drawDashedPath( + canvas, + CustomPaintStyle( + axisRenderer.getAxisMinorGridWidth(axis, index, i), + axisRenderer.getAxisMinorGridColor(axis, index, i) ?? + renderingDetails.chartTheme.minorGridLineColor, + PaintingStyle.stroke), + Offset(stateProperties.chartAxis.axisClipRect.left, position), + Offset( + stateProperties.chartAxis.axisClipRect.left + + stateProperties.chartAxis.axisClipRect.width, + position), + axis.minorGridLines.dashArray); + } + if (axis.minorTickLines.width > 0 && + renderType == axis.tickPosition.toString().split('.')[1]) { + drawDashedPath( + canvas, + CustomPaintStyle( + axisRenderer.getAxisMinorTickWidth(axis, index, i), + axisRenderer.getAxisMinorTickColor(axis, index, i) ?? + renderingDetails.chartTheme.minorTickLineColor, + PaintingStyle.stroke), + Offset(rect.left, position), + Offset( + !axis.opposedPosition + ? (isInsideTickPosition! + ? rect.left + axis.minorTickLines.size + : rect.left - axis.minorTickLines.size) + : (isInsideTickPosition! + ? rect.left - axis.minorTickLines.size + : rect.left + axis.minorTickLines.size), + position)); + } + } + } } } - /// returns the calculated interval for axis - num? calculateInterval(_VisibleRange range, Size availableSize); + /// To draw the axis labels of horizontal axes + void _drawHorizontalAxesLabels( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + if (renderType == axis.labelPosition.toString().split('.')[1]) { + final Rect axisBounds = bounds; + int angle; + TextStyle textStyle; - /// to apply the range padding for the axis - void applyRangePadding(_VisibleRange range, num interval); + late double tempInterval, pointX, pointY, previousLabelEnd; + for (int i = 0; i < visibleLabels.length; i++) { + final AxisLabel label = visibleLabels[i]; + final String labelText = + axisRenderer.getAxisLabel(axis, label.renderText!, i); - /// calculates the visible range of the axis - void calculateVisibleRange(Size availableSize); + textStyle = label.labelStyle; + textStyle = getTextStyle( + textStyle: textStyle, + fontColor: + textStyle.color ?? renderingDetails.chartTheme.axisLabelColor); + tempInterval = label.value.toDouble(); + angle = axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); - /// this method generates the visible labels for the specific axis - void generateVisibleLabels(); + /// For negative angle calculations + if (angle.isNegative) { + angle = angle + 360; + } + labelRotation = angle; + final Size textSize = measureText(labelText, textStyle); + final Size rotatedTextSize = measureText(labelText, textStyle, angle); + pointX = ((valueToCoefficient( + tempInterval, axisRenderer._axisRendererDetails) * + axisBounds.width) + + axisBounds.left) + .roundToDouble(); + pointY = _getPointY(axisRenderer, label, axisBounds); + pointY -= angle == 0 ? textSize.height / 2 : 0; + pointY += rotatedTextSize.height / 2; + pointX -= angle == 0 ? textSize.width / 2 : 0; - /// To calculate the range points - void calculateRange(ChartAxisRenderer _axisRenderer) { - _min = null; - _max = null; - List seriesRenderers; - CartesianSeriesRenderer seriesRenderer; - double paddingInterval = 0; - ChartAxisRenderer _xAxisRenderer, _yAxisRenderer; - num? minimumX, maximumX, minimumY, maximumY; - String seriesType; - seriesRenderers = _seriesRenderers; - for (int i = 0; i < seriesRenderers.length; i++) { - seriesRenderer = seriesRenderers[i]; - minimumX = seriesRenderer._minimumX; - maximumX = seriesRenderer._maximumX; - minimumY = seriesRenderer._minimumY; - maximumY = seriesRenderer._maximumY; - seriesType = seriesRenderer._seriesType; - if (seriesRenderer._visible! && - minimumX != null && - maximumX != null && - minimumY != null && - maximumY != null) { - paddingInterval = 0; - _xAxisRenderer = seriesRenderer._xAxisRenderer!; - _yAxisRenderer = seriesRenderer._yAxisRenderer!; - if (((_xAxisRenderer is DateTimeAxisRenderer || - _xAxisRenderer is NumericAxisRenderer) && - _xAxisRenderer._axis.rangePadding == ChartRangePadding.auto) && - (seriesType.contains('column') || - seriesType.contains('bar') || - seriesType == 'histogram')) { - seriesRenderer._minDelta = seriesRenderer._minDelta ?? - _calculateMinPointsDelta( - _xAxisRenderer, seriesRenderers, _chartState); - paddingInterval = seriesRenderer._minDelta! / 2; + /// Edge label placement - shift for x-Axis + pointX = _getShiftedPosition( + axisRenderer, axisBounds, pointX, pointY, textSize, i); + if (axis.labelAlignment == LabelAlignment.end) { + pointX = pointX + textSize.height / 2; + } else if (axis.labelAlignment == LabelAlignment.start) { + pointX = pointX - textSize.height / 2; } - if (((_chartState._requireInvertedAxis - ? _yAxisRenderer - : _xAxisRenderer) == - _axisRenderer) && - _orientation == AxisOrientation.horizontal) { - _chartState._requireInvertedAxis - ? _findMinMax(minimumY, maximumY) - : _findMinMax( - minimumX - paddingInterval, maximumX + paddingInterval); + if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) { + if (axis.labelAlignment == LabelAlignment.center) { + if (i == 0 || (i == visibleLabels.length - 1)) { + visibleLabels[i]._needRender = false; + continue; + } + } else if ((axis.labelAlignment == LabelAlignment.end) && + (i == visibleLabels.length - 1 || (i == 0 && axis.isInversed))) { + visibleLabels[i]._needRender = false; + continue; + } else if ((axis.labelAlignment == LabelAlignment.start) && + (i == 0 || (i == visibleLabels.length - 1 && axis.isInversed))) { + visibleLabels[i]._needRender = false; + continue; + } } - if (((_chartState._requireInvertedAxis - ? _xAxisRenderer - : _yAxisRenderer) == - _axisRenderer) && - _orientation == AxisOrientation.vertical) { - _chartState._requireInvertedAxis - ? _findMinMax( - minimumX - paddingInterval, maximumX + paddingInterval) - : _findMinMax(minimumY, maximumY); + if (axis.labelIntersectAction == AxisLabelIntersectAction.hide && + axis.labelRotation % 180 == 0 && + i != 0 && + visibleLabels[i - 1]._needRender && + (!axis.isInversed + ? pointX <= previousLabelEnd + : (pointX + textSize.width) >= previousLabelEnd)) { + continue; + } + + previousLabelEnd = axis.isInversed ? pointX : pointX + textSize.width; + + if (needAnimate!) { + final double? oldLocation = _getPrevLocation( + axisRenderer, oldAxisRenderer!, tempInterval, textSize, angle); + pointX = oldLocation != null + ? (oldLocation - (oldLocation - pointX) * animationFactor!) + : pointX; + } + final Offset point = Offset(pointX, pointY); + if (axisBounds.left - textSize.width <= pointX && + axisBounds.right + textSize.width >= pointX) { + drawText(canvas, labelText, point, textStyle, angle); + } + if (label._labelCollection != null && + label._labelCollection!.isNotEmpty && + axis.labelIntersectAction == AxisLabelIntersectAction.wrap) { + for (int j = 1; j < label._labelCollection!.length; j++) { + final String wrapTxt = label._labelCollection![j]; + drawText( + canvas, + wrapTxt, + Offset( + pointX, + pointY + + (j * + measureText(wrapTxt, axis.labelStyle, angle) + .height)), + textStyle, + angle); + } } } } } - /// Find min and max values - void _findMinMax(num minVal, num maxVal) { - if (_min == null || _min! > minVal) { - _min = minVal; - } - if (_max == null || _max! < maxVal) { - _max = maxVal; - } - } + /// To draw the axis labels of vertical axes + void _drawVerticalAxesLabels( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart, + [String? renderType, + double? animationFactor, + ChartAxisRenderer? oldAxisRenderer, + bool? needAnimate]) { + if (axis.labelPosition.toString().split('.')[1] == renderType) { + final Rect axisBounds = bounds; + TextStyle textStyle; + late double tempInterval, pointX, pointY, previousEnd; + for (int i = 0; i < visibleLabels.length; i++) { + final String labelText = + axisRenderer.getAxisLabel(axis, visibleLabels[i].renderText!, i); + final int angle = + axisRenderer.getAxisLabelAngle(axisRenderer, labelText, i); + assert(angle - angle.floor() == 0, + 'The angle value of the vertical axis must be the whole number.'); + textStyle = visibleLabels[i].labelStyle; + textStyle = getTextStyle( + textStyle: textStyle, + fontColor: + textStyle.color ?? renderingDetails.chartTheme.axisLabelColor); + tempInterval = visibleLabels[i].value.toDouble(); + final Size textSize = measureText(labelText, textStyle, 0); + pointY = (valueToCoefficient( + tempInterval, axisRenderer._axisRendererDetails) * + axisBounds.height) + + axisBounds.top; + pointY = ((axisBounds.top + axisBounds.height) - + ((axisBounds.top - pointY).abs())) - + textSize.height / 2; + pointX = _getPointX(axisRenderer, textSize, axisBounds); + final ChartLocation location = getRotatedTextLocation( + pointX, pointY, labelText, textStyle, angle, axis); + if (axis.labelAlignment == LabelAlignment.center) { + pointX = location.x; + pointY = location.y; + } else if (axis.labelAlignment == LabelAlignment.end) { + pointX = location.x; + pointY = location.y - textSize.height / 2; + } else if (axis.labelAlignment == LabelAlignment.start) { + pointX = location.x; + pointY = location.y + textSize.height / 2; + } + if (axis.labelIntersectAction == AxisLabelIntersectAction.hide && + i != 0 && + (!axis.isInversed + ? pointY + (textSize.height / 2) > previousEnd + : pointY - (textSize.height / 2) < previousEnd)) { + continue; + } + previousEnd = !axis.isInversed + ? pointY - textSize.height / 2 + : pointY + textSize.height / 2; - /// Calculate the interval based min and max values in axis - num _calculateNumericNiceInterval( - ChartAxisRenderer axisRenderer, num delta, Size size) { - final List intervalDivisions = [10, 5, 2, 1]; - num niceInterval; + /// Edge label placement for y-Axis + if (axis.edgeLabelPlacement == EdgeLabelPlacement.shift) { + if (axis.labelAlignment == LabelAlignment.center) { + if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) { + pointY = axisBounds.top + axisBounds.height - textSize.height; + } else if (i == visibleLabels.length - 1 && + axisBounds.top >= pointY + textSize.height / 2) { + pointY = axisBounds.top; + } + } else if (axis.labelAlignment == LabelAlignment.start) { + if (i == 0 && axisBounds.bottom <= pointY + textSize.height / 2) { + pointY = axisBounds.top + axisBounds.height - textSize.height; + } + } else if (axis.labelAlignment == LabelAlignment.end) { + if (i == visibleLabels.length - 1 && + axisBounds.top >= pointY + textSize.height / 2) { + pointY = axisBounds.top + textSize.height / 2; + } + } + } else if (axis.edgeLabelPlacement == EdgeLabelPlacement.hide) { + if (axis.labelAlignment == LabelAlignment.center) { + if (i == 0 || i == visibleLabels.length - 1) { + continue; + } + } else if ((axis.labelAlignment == LabelAlignment.end) && + (i == visibleLabels.length - 1 || (i == 0 && axis.isInversed))) { + continue; + } else if ((axis.labelAlignment == LabelAlignment.start) && + (i == 0 || (i == visibleLabels.length - 1 && axis.isInversed))) { + continue; + } + } + visibleLabels[i]._labelRegion = + Rect.fromLTWH(pointX, pointY, textSize.width, textSize.height); - /// Get the desired interval if desired interval not specified - final num actualDesiredIntervalCount = - _calculateDesiredIntervalCount(size, axisRenderer); - niceInterval = delta / actualDesiredIntervalCount; - if (axisRenderer._axis.desiredIntervals != null) { - return niceInterval; - } + if (needAnimate!) { + final double? oldLocation = _getPrevLocation( + axisRenderer, oldAxisRenderer!, tempInterval, textSize); + pointY = oldLocation != null + ? (oldLocation - (oldLocation - pointY) * animationFactor!) + : pointY; + } - /// Get the minimum interval - final num minimumInterval = niceInterval == 0 - ? 0 - : math.pow(10, _calculateLogBaseValue(niceInterval, 10).floor()); - for (int i = 0; i < intervalDivisions.length; i++) { - final num interval = intervalDivisions[i]; - final num currentInterval = minimumInterval * interval; - if (actualDesiredIntervalCount < (delta / currentInterval)) { - break; + final Offset point = Offset(pointX, pointY); + if (axisBounds.top - textSize.height <= pointY && + axisBounds.bottom + textSize.height >= pointY) { + drawText(canvas, labelText, point, textStyle, labelRotation); + } } - niceInterval = currentInterval; } - return niceInterval; } - /// Calculate the axis interval for numeric axis - num _calculateDesiredIntervalCount( - Size availableSize, ChartAxisRenderer axisRenderer) { - final num size = axisRenderer._orientation == AxisOrientation.horizontal - ? availableSize.width - : availableSize.height; - if (axisRenderer._axis.desiredIntervals == null) { - num desiredIntervalCount = - (axisRenderer._orientation == AxisOrientation.horizontal - ? 0.533 - : 1) * - axisRenderer._axis.maximumLabels; - desiredIntervalCount = math.max(size * (desiredIntervalCount / 100), 1); - return desiredIntervalCount; + /// To draw the axis title of horizontal axes + void _drawHorizontalAxesTitle( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + final Rect axisBounds = bounds; + Offset point; + final String title = axis.title.text ?? ''; + const int labelRotation = 0, innerPadding = 8; + TextStyle style = axis.title.textStyle; + style = getTextStyle( + textStyle: style, + fontColor: style.color ?? renderingDetails.chartTheme.axisTitleColor); + final Size textSize = measureText(title, style); + double top; + if (axis.labelPosition == ChartDataLabelPosition.inside) { + top = !axis.opposedPosition + ? axisBounds.top + + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + + (!kIsWeb ? innerPadding : innerPadding + textSize.height / 2) + : axisBounds.top - + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - + innerPadding - + textSize.height; } else { - return axisRenderer._axis.desiredIntervals!; + top = !axis.opposedPosition + ? axisBounds.top + + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + + innerPadding + + (!kIsWeb + ? maximumLabelSize.height + : maximumLabelSize.height + textSize.height / 2) + : axisBounds.top - + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - + innerPadding - + maximumLabelSize.height - + textSize.height; } - } - - /// Get the range padding of an axis - ChartRangePadding _calculateRangePadding( - ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - final ChartAxis axis = axisRenderer._axis; - ChartRangePadding padding = ChartRangePadding.auto; - if (axis.rangePadding != ChartRangePadding.auto) { - padding = axis.rangePadding; - } else if (axis.rangePadding == ChartRangePadding.auto && - axisRenderer._orientation != null) { - switch (axisRenderer._orientation!) { - case AxisOrientation.horizontal: - padding = _chartState._requireInvertedAxis - ? (_isStack100 - ? ChartRangePadding.round - : ChartRangePadding.normal) - : ChartRangePadding.none; - break; - case AxisOrientation.vertical: - padding = !_chartState._requireInvertedAxis - ? (_isStack100 - ? ChartRangePadding.round - : ChartRangePadding.normal) - : ChartRangePadding.none; - break; - } + axis.title.alignment == ChartAlignment.near + ? point = Offset(stateProperties.chartAxis.axisClipRect.left, top) + : axis.title.alignment == ChartAlignment.far + ? point = Offset( + stateProperties.chartAxis.axisClipRect.right - textSize.width, + top) + : point = Offset( + axisBounds.left + + ((axisBounds.width / 2) - (textSize.width / 2)), + top); + if (seriesRenderers.isNotEmpty || name == 'primaryXAxis') { + drawText(canvas, title, point, style, labelRotation); } - return padding; } - ///Applying range padding - void _applyRangePadding(ChartAxisRenderer axisRenderer, - SfCartesianChartState chartState, _VisibleRange range, num interval) { - final num start = range.minimum; - final num end = range.maximum; - final ChartRangePadding padding = _calculateRangePadding(this, _chart); - if (padding == ChartRangePadding.additional || - padding == ChartRangePadding.round) { - /// Get the additional range - _findAdditionalRange(this, start, end, interval); - } else if (padding == ChartRangePadding.normal) { - /// Get the normal range - _findNormalRange(this, start, end, interval); + /// To draw the axis title of vertical axes + void _drawVerticalAxesTitle( + Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + final Rect axisBounds = bounds; + Offset point; + final String title = axis.title.text ?? ''; + final int labelRotation = axis.opposedPosition ? 90 : 270; + const int innerPadding = 10; + TextStyle style = axis.title.textStyle; + style = getTextStyle( + textStyle: style, + fontColor: style.color ?? renderingDetails.chartTheme.axisTitleColor); + final Size textSize = measureText(title, style); + double left; + if (axis.labelPosition == ChartDataLabelPosition.inside) { + left = (!axis.opposedPosition) + ? axisBounds.left - + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - + innerPadding - + textSize.height + : axisBounds.left + + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + + innerPadding * 2; } else { - _updateActualRange(this, start, end, interval); + left = (!axis.opposedPosition) + ? (axisBounds.left - + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) - + innerPadding - + maximumLabelSize.width - + textSize.height / 2) + : axisBounds.left + + (isInsideTickPosition! ? 0 : axis.majorTickLines.size) + + innerPadding + + maximumLabelSize.width + + textSize.height / 2; + } + axis.title.alignment == ChartAlignment.near + ? point = Offset(left, + stateProperties.chartAxis.axisClipRect.bottom - textSize.width / 2) + : axis.title.alignment == ChartAlignment.far + ? point = Offset(left, + stateProperties.chartAxis.axisClipRect.top + textSize.width / 2) + : point = Offset(left, axisBounds.top + (axisBounds.height / 2)); + if (seriesRenderers.isNotEmpty || name == 'primaryYAxis') { + drawText(canvas, title, point, style, labelRotation); } - range.delta = range.maximum - range.minimum; } /// Find the additional range @@ -2708,7 +3059,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { num maximum; minimum = ((start / interval).floor()) * interval; maximum = ((end / interval).ceil()) * interval; - if (axisRenderer._axis.rangePadding == ChartRangePadding.additional) { + if (axis.rangePadding == ChartRangePadding.additional) { minimum -= interval; maximum += interval; } @@ -2720,45 +3071,46 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { /// Update visible range void _updateActualRange( ChartAxisRenderer axisRenderer, num minimum, num maximum, num interval) { - final dynamic axis = axisRenderer._axis; - axisRenderer._actualRange!.minimum = axis.minimum == null + final dynamic chartAxis = axis; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + actualRange!.minimum = chartAxis.minimum == null ? minimum - : (axisRenderer is DateTimeCategoryAxisRenderer - ? (axisRenderer._getEffectiveRange(axis.minimum, true)! - - (axis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) - : axis.minimum); - axisRenderer._actualRange!.maximum = axis.maximum == null + : (axisDetails is DateTimeCategoryAxisDetails + ? (axisDetails.getEffectiveRange(chartAxis.minimum, true)! - + (chartAxis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) + : chartAxis.minimum); + actualRange!.maximum = chartAxis.maximum == null ? maximum - : (axisRenderer is DateTimeCategoryAxisRenderer - ? (axisRenderer._getEffectiveRange(axis.maximum, true)! + - (axis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) - : axis.maximum); - axisRenderer._actualRange!.delta = - axisRenderer._actualRange!.maximum - axisRenderer._actualRange!.minimum; - axisRenderer._actualRange!.interval = axis.interval ?? interval; + : (axisDetails is DateTimeCategoryAxisDetails + ? (axisDetails.getEffectiveRange(chartAxis.maximum, true)! + + (chartAxis.labelPlacement != LabelPlacement.onTicks ? 0 : 0.5)) + : chartAxis.maximum); + actualRange!.delta = actualRange!.maximum - actualRange!.minimum; + actualRange!.interval = axis.interval ?? interval; } /// Find the normal range void _findNormalRange( ChartAxisRenderer axisRenderer, num start, num end, num interval) { - final dynamic axis = axisRenderer._axis; + final dynamic chartAxis = axis; num remaining, minimum, maximum; num startValue = start; if ((axis is CategoryAxis || axis is DateTimeCategoryAxis) && - axis.labelPlacement == LabelPlacement.onTicks) { + chartAxis.labelPlacement == LabelPlacement.onTicks) { minimum = start - 0.5; maximum = end + 0.5; } else { if (start < 0) { startValue = 0; minimum = start + (start / 20); - remaining = interval + _getValueByPercentage(minimum, interval); + remaining = interval + getValueByPercentage(minimum, interval); if ((0.365 * interval) >= remaining) { minimum -= interval; } - if (_getValueByPercentage(minimum, interval) < 0) { + if (getValueByPercentage(minimum, interval) < 0) { minimum = - (minimum - interval) - _getValueByPercentage(minimum, interval); + (minimum - interval) - getValueByPercentage(minimum, interval); } } else { minimum = start < ((5.0 / 6.0) * end) ? 0 : (start - (end - start) / 2); @@ -2779,9 +3131,10 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } if (minimum == 0) { interval = (axisRenderer is NumericAxisRenderer) - ? _calculateNumericNiceInterval( - axisRenderer, maximum - minimum, _axisSize) - : calculateInterval(_VisibleRange(minimum, maximum), _axisSize)!; + ? calculateNumericNiceInterval( + axisRenderer, maximum - minimum, axisSize) + : axisRenderer.calculateInterval( + VisibleRange(minimum, maximum), axisSize)!; maximum = (maximum / interval).ceil() * interval; } @@ -2790,31 +3143,30 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { } /// To trigger the render label event - void _triggerLabelRenderEvent(String labelText, num labelValue) { + void triggerLabelRenderEvent(String labelText, num labelValue) { AxisLabelRenderArgs axisLabelArgs; - TextStyle fontStyle = _axis.labelStyle; + TextStyle fontStyle = axis.labelStyle; final String actualText = labelText; - Size textSize = measureText(labelText, _axis.labelStyle, 0); - if (_axis.maximumLabelWidth != null || _axis.labelsExtent != null) { - if (_axis.maximumLabelWidth != null) { - assert(_axis.maximumLabelWidth! >= 0, + Size textSize = measureText(labelText, axis.labelStyle, 0); + if (axis.maximumLabelWidth != null || axis.labelsExtent != null) { + if (axis.maximumLabelWidth != null) { + assert(axis.maximumLabelWidth! >= 0, 'maximumLabelWidth must not be negative'); } - if (_axis.labelsExtent != null) { - assert(_axis.labelsExtent! >= 0, 'labelsExtent must not be negative'); + if (axis.labelsExtent != null) { + assert(axis.labelsExtent! >= 0, 'labelsExtent must not be negative'); } - if ((_axis.maximumLabelWidth != null && - textSize.width > _axis.maximumLabelWidth!) || - (_axis.labelsExtent != null && - textSize.width > _axis.labelsExtent!)) { - labelText = _trimAxisLabelsText( + if ((axis.maximumLabelWidth != null && + textSize.width > axis.maximumLabelWidth!) || + (axis.labelsExtent != null && textSize.width > axis.labelsExtent!)) { + labelText = getTrimmedText( labelText, - (_axis.labelsExtent ?? _axis.maximumLabelWidth)!, - _axis.labelStyle, - this); + (axis.labelsExtent ?? axis.maximumLabelWidth)!, + axis.labelStyle, + axisRenderer); } - textSize = measureText(labelText, _axis.labelStyle, 0); + textSize = measureText(labelText, axis.labelStyle, 0); } final String? trimmedText = labelText.contains('...') || labelText.isEmpty ? labelText : null; @@ -2824,51 +3176,50 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { trimmedText ?? actualText, actualText, fontStyle, - _axis, - _name, - _orientation!); - if (_chart.axisLabelFormatter != null) { + axis, + name, + orientation!); + if (chart.axisLabelFormatter != null) { final ChartAxisLabel axisLabel = - _chart.axisLabelFormatter!(axisLabelDetails); + chart.axisLabelFormatter!(axisLabelDetails); fontStyle = axisLabel.textStyle; renderText = axisLabel.text; - } else if (_chart.onAxisLabelRender != null) { - axisLabelArgs = - AxisLabelRenderArgs(labelValue, _name, _orientation, _axis); + } else if (chart.onAxisLabelRender != null) { + axisLabelArgs = AxisLabelRenderArgs(labelValue, name, orientation, axis); axisLabelArgs.text = actualText; axisLabelArgs.textStyle = fontStyle; - _chart.onAxisLabelRender!(axisLabelArgs); + chart.onAxisLabelRender!(axisLabelArgs); fontStyle = axisLabelArgs.textStyle; renderText = axisLabelArgs.text; } final Size labelSize = - measureText(renderText, fontStyle, _axis.labelRotation); - _visibleLabels.add(AxisLabel( + measureText(renderText, fontStyle, axis.labelRotation); + visibleLabels.add(AxisLabel( fontStyle, labelSize, actualText, labelValue, trimmedText, renderText)); } /// Calculate the maximum lable's size - void _calculateMaximumLabelSize( - ChartAxisRenderer axisRenderer, SfCartesianChartState chartState) { + void calculateMaximumLabelSize(ChartAxisRenderer axisRenderer, + CartesianStateProperties stateProperties) { AxisLabelIntersectAction action; AxisLabel label; - final ChartAxis axis = axisRenderer._axis; double maximumLabelHeight = 0.0, maximumLabelWidth = 0.0, labelMaximumWidth, pointX; action = axis.labelIntersectAction; - labelMaximumWidth = chartState._chartAxis._axisClipRect.width / - axisRenderer._visibleLabels.length; - if (axisRenderer._orientation == AxisOrientation.horizontal && + labelMaximumWidth = + stateProperties.chartAxis.axisClipRect.width / visibleLabels.length; + if (orientation == AxisOrientation.horizontal && axis.labelIntersectAction != AxisLabelIntersectAction.none && - axisRenderer._visibleLabels.length > 1) { - final Rect axisBounds = chartState._chartAxis._axisClipRect; + visibleLabels.length > 1) { + final Rect axisBounds = stateProperties.chartAxis.axisClipRect; AxisLabel label1; num pointX1; - for (int i = 0; i < axisRenderer._visibleLabels.length - 1; i++) { - label = axisRenderer._visibleLabels[i]; - pointX = (_valueToCoefficient(label.value, axisRenderer) * + for (int i = 0; i < visibleLabels.length - 1; i++) { + label = visibleLabels[i]; + pointX = (valueToCoefficient( + label.value, axisRenderer._axisRendererDetails) * axisBounds.width) + axisBounds.left; pointX -= label.labelSize.width / 2; @@ -2880,7 +3231,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ? (axis.isInversed ? axisBounds.left + axisBounds.width - label.labelSize.width : axisBounds.left) - : ((i == axisRenderer._visibleLabels.length - 1 && + : ((i == visibleLabels.length - 1 && axis.edgeLabelPlacement == EdgeLabelPlacement.shift && ((((pointX + label.labelSize.width) > axisBounds.right) && !axis.isInversed) || @@ -2892,23 +3243,24 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { label.labelSize.width) : pointX); - label1 = axisRenderer._visibleLabels[i + 1]; - pointX1 = (_valueToCoefficient(label1.value, axisRenderer) * - chartState._chartAxis._axisClipRect.width) + - chartState._chartAxis._axisClipRect.left; + label1 = visibleLabels[i + 1]; + pointX1 = (valueToCoefficient( + label1.value, axisRenderer._axisRendererDetails) * + stateProperties.chartAxis.axisClipRect.width) + + stateProperties.chartAxis.axisClipRect.left; pointX1 -= label1.labelSize.width / 2; if ((((pointX + label.labelSize.width) > pointX1) && !axis.isInversed) || (((pointX - label.labelSize.width) < pointX1) && axis.isInversed)) { - _isCollide = true; + isCollide = true; break; } } } - for (int i = 0; i < axisRenderer._visibleLabels.length; i++) { - label = axisRenderer._visibleLabels[i]; + for (int i = 0; i < visibleLabels.length; i++) { + label = visibleLabels[i]; if (label.labelSize.width > maximumLabelWidth) { maximumLabelWidth = label.labelSize.width; } @@ -2916,14 +3268,15 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { maximumLabelHeight = label.labelSize.height; } - if (axisRenderer._orientation == AxisOrientation.horizontal) { - pointX = (_valueToCoefficient(label.value, axisRenderer) * - chartState._chartAxis._axisClipRect.width) + - chartState._chartAxis._axisClipRect.left; + if (orientation == AxisOrientation.horizontal) { + pointX = (valueToCoefficient( + label.value, axisRenderer._axisRendererDetails) * + stateProperties.chartAxis.axisClipRect.width) + + stateProperties.chartAxis.axisClipRect.left; pointX -= label.labelSize.width / 2; /// Based on below options, perform label intersection - if (_isCollide) { + if (isCollide) { final List _list = _performLabelIntersectAction( label, action, @@ -2933,14 +3286,13 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { pointX, i, axisRenderer, - _chart); + chart); maximumLabelWidth = _list[0]; maximumLabelHeight = _list[1]; } } } - axisRenderer._maximumLabelSize = - Size(maximumLabelWidth, maximumLabelHeight); + maximumLabelSize = Size(maximumLabelWidth, maximumLabelHeight); } /// Return the height and width values of labelIntersectAction @@ -2955,9 +3307,8 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { ChartAxisRenderer axisRenderer, SfCartesianChart chart) { double height; - int angle = axisRenderer._labelRotation; + int angle = labelRotation; Size currentLabelSize; - final ChartAxis axis = axisRenderer._axis; switch (action) { case AxisLabelIntersectAction.multipleRows: if (i > 0) { @@ -2970,7 +3321,7 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { case AxisLabelIntersectAction.rotate45: case AxisLabelIntersectAction.rotate90: angle = action == AxisLabelIntersectAction.rotate45 ? -45 : -90; - axisRenderer._labelRotation = angle; + labelRotation = angle; currentLabelSize = measureText(label.text, axis.labelStyle, angle); if (currentLabelSize.height > maximumLabelHeight) { maximumLabelHeight = currentLabelSize.height; @@ -2996,235 +3347,206 @@ abstract class ChartAxisRenderer with _CustomizeAxisElements { return [maximumLabelWidth, maximumLabelHeight]; } - /// To find the height of the current label - double _findMultiRows(int length, num currentX, AxisLabel currentLabel, - ChartAxisRenderer axisRenderer, SfCartesianChart chart) { - AxisLabel label; - num pointX; - final ChartAxis axis = axisRenderer._axis; - final List labelIndex = []; - bool isMultiRows; - for (int i = length - 1; i >= 0; i--) { - label = axisRenderer._visibleLabels[i]; - pointX = (_valueToCoefficient(label.value, axisRenderer) * - _chartState._chartAxis._axisClipRect.width) + - _chartState._chartAxis._axisClipRect.left; - isMultiRows = !axis.isInversed - ? currentX < (pointX + label.labelSize.width / 2) - : currentX + currentLabel.labelSize.width > - (pointX - label.labelSize.width / 2); - if (isMultiRows) { - labelIndex.add(label._index); - currentLabel._index = (currentLabel._index > label._index) - ? currentLabel._index - : label._index + 1; - } else { - currentLabel._index = labelIndex.contains(label._index) - ? currentLabel._index - : label._index; + void _calculateRange(ChartAxisRenderer _axisRenderer) { + min = null; + max = null; + CartesianSeriesRenderer seriesRenderer; + double paddingInterval = 0; + ChartAxisRendererDetails _xAxisDetails, _yAxisDetails; + num? minimumX, maximumX, minimumY, maximumY; + String seriesType; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + for (int i = 0; i < seriesRenderers.length; i++) { + seriesRenderer = seriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + minimumX = seriesRendererDetails.minimumX; + maximumX = seriesRendererDetails.maximumX; + minimumY = seriesRendererDetails.minimumY; + maximumY = seriesRendererDetails.maximumY; + seriesType = seriesRendererDetails.seriesType; + if (seriesRendererDetails.visible! == true && + minimumX != null && + maximumX != null && + minimumY != null && + maximumY != null) { + paddingInterval = 0; + _xAxisDetails = seriesRendererDetails.xAxisDetails!; + _yAxisDetails = seriesRendererDetails.yAxisDetails!; + if (((_xAxisDetails is DateTimeAxisDetails || + _xAxisDetails is NumericAxisDetails) && + _xAxisDetails.axis.rangePadding == ChartRangePadding.auto) && + (seriesType.contains('column') || + (seriesType.contains('bar') && + seriesType.contains('errorbar') == false) || + seriesType == 'histogram')) { + seriesRendererDetails.minDelta = seriesRendererDetails.minDelta ?? + calculateMinPointsDelta( + _xAxisDetails.axisRenderer, seriesRenderers, stateProperties); + paddingInterval = seriesRendererDetails.minDelta! / 2; + } + if (((stateProperties.requireInvertedAxis + ? _yAxisDetails + : _xAxisDetails) == + axisDetails) && + orientation == AxisOrientation.horizontal) { + stateProperties.requireInvertedAxis + ? findMinMax(minimumY, maximumY) + : findMinMax( + minimumX - paddingInterval, maximumX + paddingInterval); + } + + if (((stateProperties.requireInvertedAxis + ? _xAxisDetails + : _yAxisDetails) == + axisDetails) && + orientation == AxisOrientation.vertical) { + stateProperties.requireInvertedAxis + ? findMinMax( + minimumX - paddingInterval, maximumX + paddingInterval) + : findMinMax(minimumY, maximumY); + } } } - return currentLabel.labelSize.height * currentLabel._index; } - /// To get the label collection - List _gettingLabelCollection( - String currentLabel, num labelsExtent, ChartAxisRenderer axisRenderer) { - final ChartAxis axis = axisRenderer._axis; - final List textCollection = currentLabel.split(RegExp(' ')); - final List labelCollection = []; - String text; - for (int i = 0; i < textCollection.length; i++) { - text = textCollection[i]; - (measureText(text, axis.labelStyle, axisRenderer._labelRotation).width < - labelsExtent) - ? labelCollection.add(text) - : labelCollection.add(_trimAxisLabelsText( - text, labelsExtent, axis.labelStyle, axisRenderer)); + /// Find min and max values + void findMinMax(num minVal, num maxVal) { + if (min == null || min! > minVal) { + min = minVal; + } + if (max == null || max! < maxVal) { + max = maxVal; } - return labelCollection; } - ///Below method is for changing range while zooming - void _calculateZoomRange(ChartAxisRenderer axisRenderer, Size axisSize) { - ChartAxisRenderer? oldAxisRenderer; - final ChartAxis axis = axisRenderer._axis; - assert(axis.zoomFactor >= 0 && axis.zoomFactor <= 1, - 'The zoom factor of the axis should be between 0 and 1.'); - assert(axis.zoomPosition >= 0 && axis.zoomPosition <= 1, - 'The zoom position of the axis should be between 0 and 1.'); + /// Calculate the interval based min and max values in axis + num calculateNumericNiceInterval( + ChartAxisRenderer axisRenderer, num delta, Size size) { + final List intervalDivisions = [10, 5, 2, 1]; + num niceInterval; - /// Restrict zoom factor and zoom position values between 0 to 1 - axisRenderer._zoomFactor = axisRenderer._zoomFactor > 1 - ? 1 - : axisRenderer._zoomFactor < 0 - ? 0 - : axisRenderer._zoomFactor; - axisRenderer._zoomPosition = axisRenderer._zoomPosition > 1 - ? 1 - : axisRenderer._zoomPosition < 0 - ? 0 - : axisRenderer._zoomPosition; - if (_chartState._oldAxisRenderers.isNotEmpty) { - oldAxisRenderer = - _getOldAxisRenderer(axisRenderer, _chartState._oldAxisRenderers); - } - if (oldAxisRenderer != null) { - axisRenderer._zoomFactor = - oldAxisRenderer._axis.zoomFactor != axis.zoomFactor - ? axis.zoomFactor - : axisRenderer._zoomFactor; - axisRenderer._zoomPosition = - oldAxisRenderer._axis.zoomPosition != axis.zoomPosition - ? axis.zoomPosition - : axisRenderer._zoomPosition; - if (axisRenderer._axis.autoScrollingDelta == - oldAxisRenderer._axis.autoScrollingDelta) { - axisRenderer._scrollingDelta = oldAxisRenderer._scrollingDelta; - } + /// Get the desired interval if desired interval not specified + final num actualDesiredIntervalCount = + calculateDesiredIntervalCount(size, axisRenderer); + niceInterval = delta / actualDesiredIntervalCount; + if (axisRenderer._axisRendererDetails.axis.desiredIntervals != null) { + return niceInterval; } - final _VisibleRange baseRange = axisRenderer._visibleRange!; - num start, end; - start = axisRenderer._visibleRange!.minimum + - axisRenderer._zoomPosition * axisRenderer._visibleRange!.delta; - end = start + axisRenderer._zoomFactor * axisRenderer._visibleRange!.delta; - - if (start < baseRange.minimum) { - end = end + (baseRange.minimum - start); - start = baseRange.minimum; - } - if (end > baseRange.maximum) { - start = start - (end - baseRange.maximum); - end = baseRange.maximum; + /// Get the minimum interval + final num minimumInterval = niceInterval == 0 + ? 0 + : math.pow(10, calculateLogBaseValue(niceInterval, 10).floor()); + for (int i = 0; i < intervalDivisions.length; i++) { + final num interval = intervalDivisions[i]; + final num currentInterval = minimumInterval * interval; + if (actualDesiredIntervalCount < (delta / currentInterval)) { + break; + } + niceInterval = currentInterval; } - axisRenderer._visibleRange!.minimum = start; - axisRenderer._visibleRange!.maximum = end; - axisRenderer._visibleRange!.delta = end - start; + return niceInterval; } - /// To set the zoom factor and position of axis through dynamic update or from - void _setZoomFactorAndPosition(ChartAxisRenderer axisRenderer, - List axisRendererStates) { - bool didUpdateAxis; - if (_oldAxis != null && - (_oldAxis!.zoomPosition != _axis.zoomPosition || - _oldAxis!.zoomFactor != _axis.zoomFactor)) { - _zoomFactor = _axis.zoomFactor; - _zoomPosition = _axis.zoomPosition; - didUpdateAxis = true; + /// Calculate the axis interval for numeric axis + num calculateDesiredIntervalCount( + Size availableSize, ChartAxisRenderer axisRenderer) { + final num size = axisRenderer._axisRendererDetails.orientation == + AxisOrientation.horizontal + ? availableSize.width + : availableSize.height; + if (axisRenderer._axisRendererDetails.axis.desiredIntervals == null) { + num desiredIntervalCount = + (axisRenderer._axisRendererDetails.orientation == + AxisOrientation.horizontal + ? 0.533 + : 1) * + axisRenderer._axisRendererDetails.axis.maximumLabels; + desiredIntervalCount = math.max(size * (desiredIntervalCount / 100), 1); + return desiredIntervalCount; } else { - didUpdateAxis = false; + return axisRenderer._axisRendererDetails.axis.desiredIntervals!; } - for (final ChartAxisRenderer zoomedAxisRenderer - in _chartState._zoomedAxisRendererStates) { - if (zoomedAxisRenderer._name == _name) { - if (didUpdateAxis) { - zoomedAxisRenderer._zoomFactor = _zoomFactor; - zoomedAxisRenderer._zoomPosition = _zoomPosition; - } else { - if (_axis.autoScrollingDelta == null || - _scrollingDelta != zoomedAxisRenderer._visibleRange!.delta) { - axisRenderer._zoomFactor = zoomedAxisRenderer._zoomFactor; - axisRenderer._zoomPosition = zoomedAxisRenderer._zoomPosition; - } - } - break; + } + + /// Get the range padding of an axis + ChartRangePadding calculateRangePadding( + ChartAxisRenderer axisRenderer, SfCartesianChart chart) { + final ChartAxis axis = axisRenderer._axisRendererDetails.axis; + ChartRangePadding padding = ChartRangePadding.auto; + if (axis.rangePadding != ChartRangePadding.auto) { + padding = axis.rangePadding; + } else if (axis.rangePadding == ChartRangePadding.auto && + axisRenderer._axisRendererDetails.orientation != null) { + switch (axisRenderer._axisRendererDetails.orientation!) { + case AxisOrientation.horizontal: + padding = stateProperties.requireInvertedAxis + ? (isStack100 + ? ChartRangePadding.round + : ChartRangePadding.normal) + : ChartRangePadding.none; + break; + case AxisOrientation.vertical: + padding = !stateProperties.requireInvertedAxis + ? (isStack100 + ? ChartRangePadding.round + : ChartRangePadding.normal) + : ChartRangePadding.none; + break; } } + return padding; } - /// To provide chart changes to range controller - void _setRangeControllerValues(ChartAxisRenderer _axisRenderer) { - if (_axisRenderer is DateTimeAxisRenderer || - _axisRenderer is DateTimeCategoryAxisRenderer) { - _axis.rangeController!.start = DateTime.fromMillisecondsSinceEpoch( - _axisRenderer._visibleRange!.minimum.toInt()); - _axis.rangeController!.end = DateTime.fromMillisecondsSinceEpoch( - _axisRenderer._visibleRange!.maximum.toInt()); + ///Applying range padding + void applyRangePaddings( + ChartAxisRenderer axisRenderer, + CartesianStateProperties stateProperties, + VisibleRange range, + num interval) { + final num start = range.minimum; + final num end = range.maximum; + final ChartRangePadding padding = + calculateRangePadding(axisRenderer, chart); + if (padding == ChartRangePadding.additional || + padding == ChartRangePadding.round) { + /// Get the additional range + _findAdditionalRange(axisRenderer, start, end, interval); + } else if (padding == ChartRangePadding.normal) { + /// Get the normal range + _findNormalRange(axisRenderer, start, end, interval); } else { - _axis.rangeController!.start = _axisRenderer._visibleRange!.minimum; - _axis.rangeController!.end = _axisRenderer._visibleRange!.maximum; + _updateActualRange(axisRenderer, start, end, interval); } + range.delta = range.maximum - range.minimum; } - /// To change chart based on range controller - void _updateRangeControllerValues(ChartAxisRenderer _axisRenderer) { - _chartState._zoomProgress = false; - _chartState._isRedrawByZoomPan = false; - if (_axisRenderer is DateTimeAxisRenderer || - _axisRenderer is DateTimeCategoryAxisRenderer) { - _axisRenderer._rangeMinimum = - _axis.rangeController!.start.millisecondsSinceEpoch; - _axisRenderer._rangeMaximum = - _axis.rangeController!.end.millisecondsSinceEpoch; - } else { - _axisRenderer._rangeMinimum = _axis.rangeController!.start; - _axisRenderer._rangeMaximum = _axis.rangeController!.end; - } + /// Dispose the objects. + void dispose() { + visibleLabels.clear(); + seriesRenderers.clear(); } +} - void _setOldRangeFromRangeController() { - if (!_chartState._renderingDetails.initialRender! && - _axis.rangeController != null && - !_chartState._canSetRangeController) { - final ChartAxisRenderer? oldrenderer = - _getOldAxisRenderer(this, _chartState._oldAxisRenderers); - if (oldrenderer != null) { - _visibleMinimum = _rangeMinimum = oldrenderer._rangeMinimum; - _visibleMaximum = _rangeMaximum = oldrenderer._rangeMaximum; - } - } +// ignore: avoid_classes_with_only_static_members +/// Represents the helper class used to get the private fields from the chart axis renderer +class AxisHelper { + /// Methods to get the axis renderer details of axis renderer + static ChartAxisRendererDetails getAxisRendererDetails( + ChartAxisRenderer axisRenderer) { + return axisRenderer._axisRendererDetails; } - void _setZoomValuesFromRangeController() { - if (!(_chartState._isRedrawByZoomPan || - _chartState._canSetRangeController)) { - if (_chartState._rangeChangeBySlider && - !_chartState._canSetRangeController && - _rangeMinimum != null && - _rangeMaximum != null) { - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - if (this is! DateTimeCategoryAxisRenderer) { - _visibleRange!.interval = this is LogarithmicAxisRenderer - ? (this as LogarithmicAxisRenderer) - .calculateLogNiceInterval(_visibleRange!.delta) - : calculateInterval(_visibleRange!, _axisSize); - } - _visibleRange!.interval = - _actualRange!.interval != null && _actualRange!.interval % 1 != 0 - ? _actualRange!.interval - : _visibleRange!.interval; - _zoomFactor = _visibleRange!.delta / (_actualRange!.delta); - _zoomPosition = (_visibleRange!.minimum - _actualRange!.minimum) / - _actualRange!.delta; - } - } + /// Method to get the axis label region value + static Rect? getLabelRegion(AxisLabel label) { + return label._labelRegion; } - /// Auto scrolling feature - void _updateAutoScrollingDelta( - int scrollingDelta, ChartAxisRenderer _axisRenderer) { - _axisRenderer._scrollingDelta = scrollingDelta; - switch (_axis.autoScrollingMode) { - case AutoScrollingMode.start: - final _VisibleRange autoScrollRange = _VisibleRange( - _axisRenderer._visibleRange!.minimum, - _axisRenderer._visibleRange!.minimum + scrollingDelta); - autoScrollRange.delta = - autoScrollRange.maximum - autoScrollRange.minimum; - _zoomFactor = autoScrollRange.delta / _actualRange!.delta; - _zoomPosition = 0; - break; - case AutoScrollingMode.end: - final _VisibleRange autoScrollRange = _VisibleRange( - _axisRenderer._visibleRange!.maximum - scrollingDelta, - _axisRenderer._visibleRange!.maximum); - autoScrollRange.delta = - autoScrollRange.maximum - autoScrollRange.minimum; - _zoomFactor = autoScrollRange.delta / _actualRange!.delta; - _zoomPosition = 1 - _zoomFactor; - break; - } + /// Methods to set the axis renderer details of axis renderer + static void setAxisRendererDetails( + ChartAxisRenderer renderer, ChartAxisRendererDetails rendererDetails) { + renderer._axisRendererDetails = rendererDetails; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart index 0b7671ea5..78911b015 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_panel.dart @@ -1,69 +1,136 @@ -part of charts; - -class _ChartAxis { - _ChartAxis(this._chartState) { - _innerPadding = 5; - _axisPadding = 10; - _axisClipRect = const Rect.fromLTRB(0, 0, 0, 0); - _verticalAxisRenderers = []; - _horizontalAxisRenderers = []; - _needsRepaint = true; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.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/chart_base.dart'; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the chart axis panel +class ChartAxisPanel { + /// Creates an instance of chart axis panel + ChartAxisPanel(this.stateProperties) { + innerPadding = 5; + axisPadding = 10; + axisClipRect = const Rect.fromLTRB(0, 0, 0, 0); + verticalAxisRenderers = []; + horizontalAxisRenderers = []; + needsRepaint = true; } - final SfCartesianChartState _chartState; - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfCartesianChart get _chartWidget => _chartState._chart; - late ChartAxisRenderer _primaryXAxisRenderer, _primaryYAxisRenderer; - List _leftAxisRenderers = []; - List _rightAxisRenderers = []; - List _topAxisRenderers = []; - List _bottomAxisRenderers = []; - late List<_AxisSize> _leftAxesCount; - late List<_AxisSize> _bottomAxesCount; - late List<_AxisSize> _topAxesCount; - late List<_AxisSize> _rightAxesCount; - double _bottomSize = 0; - double _topSize = 0; - double _leftSize = 0; - double _rightSize = 0; - double _innerPadding = 0; - double _axisPadding = 0; - late Rect _axisClipRect; - List _verticalAxisRenderers = []; - List _horizontalAxisRenderers = []; - //ignore: prefer_final_fields - List _axisRenderersCollection = []; + + /// Specifies the cartesian state properties + final CartesianStateProperties stateProperties; + + /// Here, we are using get keyword inorder to get the proper & updated instance of chart widget + //When we initialize chart widget as a property to other classes like ChartSeries, the chart widget is not updated properly and by using get we can rectify this. + SfCartesianChart get chartWidget => stateProperties.chart; + + /// Specifies the value of primary XAxis renderer and primary YAxis renderer + ChartAxisRenderer? primaryXAxisRenderer, primaryYAxisRenderer; + + /// Specifies the value of primary XAxis details and primary YAxis details + late ChartAxisRendererDetails primaryXAxisDetails, primaryYAxisDetails; + + /// Specifies the value of left axis renderer + List leftAxisRenderers = []; + + /// Specifies the value of right axis renderer + List rightAxisRenderers = []; + + /// Specifies the value of top axis renderer + List topAxisRenderers = []; + + /// Specifies the value of bottom axis renderer + List bottomAxisRenderers = []; + + /// Specifies the left axes count value + late List leftAxesCount; + + /// Specifies the bottom axes count value + late List bottomAxesCount; + + /// Specifies the top axes count value + late List topAxesCount; + + /// Specifies the right axes count value + late List rightAxesCount; + + /// Specifies the bottom size value + double bottomSize = 0; + + /// Specifies the top size value + double topSize = 0; + + /// Specifies left size value + double leftSize = 0; + + /// Specifies the right size value + double rightSize = 0; + + /// Specifies the value of inner padding + double innerPadding = 0; + + /// Specifies the axis padding value + double axisPadding = 0; + + /// Specifies the value of axis clip rect + late Rect axisClipRect; + + /// Specifies the list of vertical axis renderers + List verticalAxisRenderers = []; + + /// Specifies the list of horizontal axis renderers + List horizontalAxisRenderers = []; + + /// Specifies the list of axes renderers + List axisRenderersCollection = []; /// Whether to repaint axis or not - late bool _needsRepaint; + late bool needsRepaint; /// To get the crossAt values of a specific axis void _getAxisCrossingValue(ChartAxisRenderer axisRenderer) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisDetails.axis; if (axis.crossesAt != null) { if (axis.associatedAxisName != null) { for (int i = 0; - i < _chartState._chartAxis._axisRenderersCollection.length; + i < stateProperties.chartAxis.axisRenderersCollection.length; i++) { if (axis.associatedAxisName == - _chartState._chartAxis._axisRenderersCollection[i]._name) { - axisRenderer._crossAxisRenderer = - _chartState._chartAxis._axisRenderersCollection[i]; + AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[i]) + .name) { + axisDetails.crossAxisRenderer = + stateProperties.chartAxis.axisRenderersCollection[i]; _calculateCrossingValues( - axisRenderer, axisRenderer._crossAxisRenderer); + axisRenderer, axisDetails.crossAxisRenderer); } } } else { - axisRenderer._crossAxisRenderer = _chartState._requireInvertedAxis - ? (axisRenderer._crossAxisRenderer = - axisRenderer._orientation == AxisOrientation.horizontal - ? _chartState._chartAxis._primaryXAxisRenderer - : _chartState._chartAxis._primaryYAxisRenderer) - : (axisRenderer._orientation == AxisOrientation.horizontal - ? _chartState._chartAxis._primaryYAxisRenderer - : _chartState._chartAxis._primaryXAxisRenderer); - - _calculateCrossingValues(axisRenderer, axisRenderer._crossAxisRenderer); + axisDetails.crossAxisRenderer = stateProperties.requireInvertedAxis + ? (axisDetails.crossAxisRenderer = + axisDetails.orientation == AxisOrientation.horizontal + ? stateProperties.chartAxis.primaryXAxisRenderer! + : stateProperties.chartAxis.primaryYAxisRenderer!) + : (axisDetails.orientation == AxisOrientation.horizontal + ? stateProperties.chartAxis.primaryYAxisRenderer! + : stateProperties.chartAxis.primaryXAxisRenderer!); + + _calculateCrossingValues(axisRenderer, axisDetails.crossAxisRenderer); } } } @@ -71,81 +138,89 @@ class _ChartAxis { ///To get the axis crossing value void _calculateCrossingValues(ChartAxisRenderer currentAxisRenderer, ChartAxisRenderer targetAxisRenderer) { - dynamic value = currentAxisRenderer._axis.crossesAt; + final ChartAxisRendererDetails currentAxisDetails = + AxisHelper.getAxisRendererDetails(currentAxisRenderer); + final ChartAxisRendererDetails targetAxisDetails = + AxisHelper.getAxisRendererDetails(targetAxisRenderer); + dynamic value = currentAxisDetails.axis.crossesAt; value = value is String && num.tryParse(value) != null ? num.tryParse(value) : value; - if (targetAxisRenderer is DateTimeAxisRenderer) { + if (targetAxisDetails is DateTimeAxisDetails) { value = value is DateTime ? value.millisecondsSinceEpoch : value; - targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); - } else if (targetAxisRenderer is CategoryAxisRenderer) { + targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); + } else if (targetAxisDetails is CategoryAxisDetails) { value = value is num ? value.floor() - : targetAxisRenderer._labels.indexOf(value); - targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); - } else if (targetAxisRenderer is DateTimeCategoryAxisRenderer) { + : targetAxisDetails.labels.indexOf(value); + targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); + } else if (targetAxisDetails is DateTimeCategoryAxisDetails) { value = value is num ? value.floor() : (value is DateTime - ? targetAxisRenderer._labels + ? targetAxisDetails.labels .indexOf('${value.microsecondsSinceEpoch}') : null); - targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); - } else if (targetAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis _axis = targetAxisRenderer._axis as LogarithmicAxis; - value = _calculateLogBaseValue(value, _axis.logBase); - targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); - } else if (targetAxisRenderer is NumericAxisRenderer) { - targetAxisRenderer._calculateRangeAndInterval(_chartState, 'AxisCross'); + targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); + } else if (targetAxisDetails is LogarithmicAxisDetails) { + final LogarithmicAxis _axis = targetAxisDetails.axis as LogarithmicAxis; + value = calculateLogBaseValue(value, _axis.logBase); + targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); + } else if (targetAxisDetails is NumericAxisDetails) { + targetAxisDetails.calculateRangeAndInterval(stateProperties, 'AxisCross'); } if (value.isNaN == false) { - currentAxisRenderer._crossValue = - _updateCrossValue(value, targetAxisRenderer._visibleRange!); - currentAxisRenderer._crossRange = targetAxisRenderer._visibleRange; + currentAxisDetails.crossValue = + _updateCrossValue(value, targetAxisDetails.visibleRange!); + currentAxisDetails.crossRange = targetAxisDetails.visibleRange; } } ///To measure the bounds of each axis - void _measureAxesBounds() { - _bottomSize = 0; - _topSize = 0; - _leftSize = 0; - _rightSize = 0; - _leftAxesCount = <_AxisSize>[]; - _bottomAxesCount = <_AxisSize>[]; - _topAxesCount = <_AxisSize>[]; - _rightAxesCount = <_AxisSize>[]; - _bottomAxisRenderers = []; - _rightAxisRenderers = []; - _topAxisRenderers = []; - _leftAxisRenderers = []; - - if (_verticalAxisRenderers.isNotEmpty) { + void measureAxesBounds() { + bottomSize = 0; + topSize = 0; + leftSize = 0; + rightSize = 0; + leftAxesCount = []; + bottomAxesCount = []; + topAxesCount = []; + rightAxesCount = []; + bottomAxisRenderers = []; + rightAxisRenderers = []; + topAxisRenderers = []; + leftAxisRenderers = []; + + if (verticalAxisRenderers.isNotEmpty) { for (int axisIndex = 0; - axisIndex < _verticalAxisRenderers.length; + axisIndex < verticalAxisRenderers.length; axisIndex++) { - final dynamic axisRenderer = _verticalAxisRenderers[axisIndex]; + final dynamic axisRenderer = verticalAxisRenderers[axisIndex]; + final dynamic axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); assert( - !(axisRenderer._axis.interval != null) || - (axisRenderer._axis.interval > 0) == true, + !(axisDetails.axis.interval != null) || + (axisDetails.axis.interval! > 0) == true, 'The vertical axis interval value must be greater than 0.'); - axisRenderer._calculateRangeAndInterval(_chartState); + axisDetails.calculateRangeAndInterval(stateProperties); _getAxisCrossingValue(axisRenderer); _measureAxesSize(axisRenderer); } _calculateSeriesClipRect(); } - if (_horizontalAxisRenderers.isNotEmpty) { + if (horizontalAxisRenderers.isNotEmpty) { for (int axisIndex = 0; - axisIndex < _horizontalAxisRenderers.length; + axisIndex < horizontalAxisRenderers.length; axisIndex++) { - final dynamic axisRenderer = _horizontalAxisRenderers[axisIndex]; - _calculateLabelRotationAngle(axisRenderer); + final dynamic axisRenderer = horizontalAxisRenderers[axisIndex]; + final dynamic axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + _calculateLabelRotationAngle(axisDetails); assert( - !(axisRenderer._axis.interval != null) || - (axisRenderer._axis.interval > 0) == true, + !(axisDetails.axis.interval != null) || + (axisDetails.axis.interval > 0) == true, 'The horizontal axis interval value must be greater than 0.'); - axisRenderer._calculateRangeAndInterval(_chartState); + axisDetails.calculateRangeAndInterval(stateProperties); _getAxisCrossingValue(axisRenderer); _measureAxesSize(axisRenderer); } @@ -155,15 +230,18 @@ class _ChartAxis { ///Calculate the axes total size void _measureAxesSize(ChartAxisRenderer axisRenderer) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + ChartAxisRendererDetails crossAxisRendererDetails; + final ChartAxis axis = axisDetails.axis; num titleSize = 0; - axisRenderer._totalSize = 0; + axisDetails.totalSize = 0; if (axis.isVisible) { if (axis.title.text != null && axis.title.text!.isNotEmpty) { titleSize = measureText(axis.title.text!, axis.title.textStyle).height + - _axisPadding; + axisPadding; } - final Rect rect = _chartState._renderingDetails.chartContainerRect; + final Rect rect = stateProperties.renderingDetails.chartContainerRect; final int axisIndex = _getAxisIndex(axisRenderer); final double tickSize = (axisIndex == 0 && axis.tickPosition == TickPosition.inside) @@ -173,101 +251,104 @@ class _ChartAxis { axis.minorTicksPerInterval > 0 ? axis.minorTickLines.size : 0) + - _innerPadding; + innerPadding; final double labelSize = (axisIndex == 0 && axis.labelPosition == ChartDataLabelPosition.inside) ? 0 : (axis.labelStyle.fontSize == 0 ? 0 - : (axisRenderer._orientation == AxisOrientation.horizontal) - ? axisRenderer._maximumLabelSize.height + : (axisDetails.orientation == AxisOrientation.horizontal) + ? axisDetails.maximumLabelSize.height : (axis.labelsExtent != null && axis.labelsExtent! > 0) ? axis.labelsExtent - : axisRenderer._maximumLabelSize.width)! + - _innerPadding; - axisRenderer._totalSize = titleSize + tickSize + labelSize; - if (axisRenderer._orientation == AxisOrientation.horizontal) { + : axisDetails.maximumLabelSize.width)! + + innerPadding; + axisDetails.totalSize = titleSize + tickSize + labelSize; + if (axisDetails.orientation == AxisOrientation.horizontal) { if (!axis.opposedPosition) { - axisRenderer._totalSize += - _bottomAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 - ? _axisPadding.toDouble() + axisDetails.totalSize += + bottomAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 + ? axisPadding.toDouble() : 0; - if (axisRenderer._crossValue != null && - axisRenderer._crossRange != null) { - final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + if (axisDetails.crossValue != null && + axisDetails.crossRange != null) { + crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( + axisDetails.crossAxisRenderer); + final num crosPosition = valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.height; - axisRenderer._totalSize = crosPosition - axisRenderer._totalSize < 0 - ? (crosPosition - axisRenderer._totalSize).abs() + axisDetails.totalSize = crosPosition - axisDetails.totalSize < 0 + ? (crosPosition - axisDetails.totalSize).abs() : !axis.placeLabelsNearAxisLine ? labelSize : 0; } - _bottomSize += axisRenderer._totalSize; - _bottomAxesCount - .add(_AxisSize(axisRenderer, axisRenderer._totalSize)); + bottomSize += axisDetails.totalSize; + bottomAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); } else { - axisRenderer._totalSize += - _topAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 - ? _axisPadding.toDouble() + axisDetails.totalSize += + topAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 + ? axisPadding.toDouble() : 0; - if (axisRenderer._crossValue != null && - axisRenderer._crossRange != null) { - final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + if (axisDetails.crossValue != null && + axisDetails.crossRange != null) { + crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( + axisDetails.crossAxisRenderer); + final num crosPosition = valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.height; - axisRenderer._totalSize = crosPosition + axisRenderer._totalSize > + axisDetails.totalSize = crosPosition + axisDetails.totalSize > rect.height - ? ((crosPosition + axisRenderer._totalSize) - rect.height).abs() + ? ((crosPosition + axisDetails.totalSize) - rect.height).abs() : !axis.placeLabelsNearAxisLine ? labelSize : 0; } - _topSize += axisRenderer._totalSize; - _topAxesCount.add(_AxisSize(axisRenderer, axisRenderer._totalSize)); + topSize += axisDetails.totalSize; + topAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); } - } else if (axisRenderer._orientation == AxisOrientation.vertical) { + } else if (axisDetails.orientation == AxisOrientation.vertical) { if (!axis.opposedPosition) { - axisRenderer._totalSize += - _leftAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 - ? _axisPadding.toDouble() + axisDetails.totalSize += + leftAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 + ? axisPadding.toDouble() : 0; - if (axisRenderer._crossValue != null && - axisRenderer._crossRange != null) { - final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + if (axisDetails.crossValue != null && + axisDetails.crossRange != null) { + crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( + axisDetails.crossAxisRenderer); + final num crosPosition = valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.width; - axisRenderer._totalSize = crosPosition - axisRenderer._totalSize < 0 - ? (crosPosition - axisRenderer._totalSize).abs() + axisDetails.totalSize = crosPosition - axisDetails.totalSize < 0 + ? (crosPosition - axisDetails.totalSize).abs() : !axis.placeLabelsNearAxisLine ? labelSize : 0; } - _leftSize += axisRenderer._totalSize; - _leftAxesCount.add(_AxisSize(axisRenderer, axisRenderer._totalSize)); + leftSize += axisDetails.totalSize; + leftAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); } else { - axisRenderer._totalSize += - _rightAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 - ? _axisPadding.toDouble() + axisDetails.totalSize += + rightAxisRenderers.isNotEmpty && axis.labelStyle.fontSize! > 0 + ? axisPadding.toDouble() : 0; - if (axisRenderer._crossValue != null && - axisRenderer._crossRange != null) { - final num crosPosition = _valueToCoefficient( - axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + if (axisDetails.crossValue != null && + axisDetails.crossRange != null) { + crossAxisRendererDetails = AxisHelper.getAxisRendererDetails( + axisDetails.crossAxisRenderer); + final num crosPosition = valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.width; - axisRenderer._totalSize = crosPosition + axisRenderer._totalSize > + axisDetails.totalSize = crosPosition + axisDetails.totalSize > rect.width - ? ((crosPosition + axisRenderer._totalSize) - rect.width).abs() + ? ((crosPosition + axisDetails.totalSize) - rect.width).abs() : !axis.placeLabelsNearAxisLine ? labelSize : 0; } - _rightSize += axisRenderer._totalSize; - _rightAxesCount.add(_AxisSize(axisRenderer, axisRenderer._totalSize)); + rightSize += axisDetails.totalSize; + rightAxesCount.add(AxisSize(axisRenderer, axisDetails.totalSize)); } } } @@ -276,22 +357,24 @@ class _ChartAxis { /// To get the axis index int _getAxisIndex(ChartAxisRenderer axisRenderer) { int index; - final ChartAxis axis = axisRenderer._axis; - if (axisRenderer._orientation == AxisOrientation.horizontal) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisDetails.axis; + if (axisDetails.orientation == AxisOrientation.horizontal) { if (!axis.opposedPosition) { - _bottomAxisRenderers.add(axisRenderer); - index = _bottomAxisRenderers.length; + bottomAxisRenderers.add(axisRenderer); + index = bottomAxisRenderers.length; } else { - _topAxisRenderers.add(axisRenderer); - index = _topAxisRenderers.length; + topAxisRenderers.add(axisRenderer); + index = topAxisRenderers.length; } - } else if (axisRenderer._orientation == AxisOrientation.vertical) { + } else if (axisDetails.orientation == AxisOrientation.vertical) { if (!axis.opposedPosition) { - _leftAxisRenderers.add(axisRenderer); - index = _leftAxisRenderers.length; + leftAxisRenderers.add(axisRenderer); + index = leftAxisRenderers.length; } else { - _rightAxisRenderers.add(axisRenderer); - index = _rightAxisRenderers.length; + rightAxisRenderers.add(axisRenderer); + index = rightAxisRenderers.length; } } else { index = 0; @@ -300,30 +383,31 @@ class _ChartAxis { } ///To find the axis label rotation angle - void _calculateLabelRotationAngle(ChartAxisRenderer axisRenderer) { - int angle = axisRenderer._labelRotation; + void _calculateLabelRotationAngle(ChartAxisRendererDetails axisDetails) { + int angle = axisDetails.labelRotation; if (angle < -360 || angle > 360) { angle %= 360; } if (angle.isNegative) { angle = angle + 360; } - axisRenderer._labelRotation = angle; + axisDetails.labelRotation = angle; } /// Calculate series clip rect size void _calculateSeriesClipRect() { - final Rect containerRect = _chartState._renderingDetails.chartContainerRect; - final num padding = _chartWidget.title.text.isNotEmpty ? 10 : 0; - _chartState._chartAxis._axisClipRect = Rect.fromLTWH( - containerRect.left + _leftSize, - containerRect.top + _topSize + padding, - containerRect.width - _leftSize - _rightSize, - containerRect.height - _topSize - _bottomSize - padding); + final Rect containerRect = + stateProperties.renderingDetails.chartContainerRect; + final num padding = chartWidget.title.text.isNotEmpty ? 10 : 0; + stateProperties.chartAxis.axisClipRect = Rect.fromLTWH( + containerRect.left + leftSize, + containerRect.top + topSize + padding, + containerRect.width - leftSize - rightSize, + containerRect.height - topSize - bottomSize - padding); } /// To return the crossAt value - num _updateCrossValue(num value, _VisibleRange range) { + num _updateCrossValue(num value, VisibleRange range) { if (value < range.minimum) { value = range.minimum; } @@ -335,54 +419,52 @@ class _ChartAxis { /// Return the axis offset value for x and y axis num? _getPrevAxisOffset( - List<_AxisSize> axesSize, Rect rect, int currentAxisIndex, String type) { + List axesSize, Rect rect, int currentAxisIndex, String type) { num? prevAxisOffsetValue; if (currentAxisIndex > 0) { for (int i = currentAxisIndex - 1; i >= 0; i--) { final ChartAxisRenderer axisRenderer = axesSize[i].axisRenderer; - final Rect bounds = axisRenderer._bounds; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final Rect bounds = axisDetails.bounds; if (type == 'Left' && - ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! - - axisRenderer._maximumLabelSize.width + ((axisDetails.labelOffset != null + ? axisDetails.labelOffset! - + axisDetails.maximumLabelSize.width : bounds.left - bounds.width) < rect.left)) { - prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! - - axisRenderer._maximumLabelSize.width + prevAxisOffsetValue = axisDetails.labelOffset != null + ? axisDetails.labelOffset! - axisDetails.maximumLabelSize.width : bounds.left - bounds.width; break; } else if (type == 'Bottom' && - ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! + - axisRenderer._maximumLabelSize.height + ((axisDetails.labelOffset != null + ? axisDetails.labelOffset! + + axisDetails.maximumLabelSize.height : bounds.top + bounds.height) > rect.top + rect.height)) { - prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! + - axisRenderer._maximumLabelSize.height + prevAxisOffsetValue = axisDetails.labelOffset != null + ? axisDetails.labelOffset! + axisDetails.maximumLabelSize.height : bounds.top + bounds.height; break; } else if (type == 'Right' && - ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! + - axisRenderer._maximumLabelSize.width + ((axisDetails.labelOffset != null + ? axisDetails.labelOffset! + + axisDetails.maximumLabelSize.width : bounds.left + bounds.width) > rect.left + rect.width)) { - prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! + - axisRenderer._maximumLabelSize.width + prevAxisOffsetValue = axisDetails.labelOffset != null + ? axisDetails.labelOffset! + axisDetails.maximumLabelSize.width : bounds.left + bounds.width; break; } else if (type == 'Top' && - ((axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! - - axisRenderer._maximumLabelSize.height + ((axisDetails.labelOffset != null + ? axisDetails.labelOffset! - + axisDetails.maximumLabelSize.height : bounds.top - bounds.height) < rect.top)) { - prevAxisOffsetValue = axisRenderer._labelOffset != null - ? axisRenderer._labelOffset! - - axisRenderer._maximumLabelSize.height + prevAxisOffsetValue = axisDetails.labelOffset != null + ? axisDetails.labelOffset! - axisDetails.maximumLabelSize.height : bounds.top - bounds.height; break; } @@ -396,22 +478,22 @@ class _ChartAxis { _calculateSeriesClipRect(); /// Calculate the left axes rect - if (_leftAxesCount.isNotEmpty) { + if (leftAxesCount.isNotEmpty) { _calculateLeftAxesBounds(); } /// Calculate the bottom axes rect - if (_bottomAxesCount.isNotEmpty) { + if (bottomAxesCount.isNotEmpty) { _calculateBottomAxesBounds(); } /// Calculate the right axes rect - if (_rightAxesCount.isNotEmpty) { + if (rightAxesCount.isNotEmpty) { _calculateRightAxesBounds(); } /// Calculate the top axes rect - if (_topAxesCount.isNotEmpty) { + if (topAxesCount.isNotEmpty) { _calculateTopAxesBounds(); } } @@ -419,31 +501,36 @@ class _ChartAxis { /// Calculate the left axes bounds void _calculateLeftAxesBounds() { double axisSize, width; - final int axesLength = _leftAxesCount.length; - final Rect rect = _chartState._chartAxis._axisClipRect; + final int axesLength = leftAxesCount.length; + final Rect rect = stateProperties.chartAxis.axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { - width = _leftAxesCount[axisIndex].size; + width = leftAxesCount[axisIndex].size; final ChartAxisRenderer axisRenderer = - _leftAxesCount[axisIndex].axisRenderer; - final ChartAxis axis = axisRenderer._axis; + leftAxesCount[axisIndex].axisRenderer; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + ChartAxisRendererDetails crossAxisRendererDetails; + final ChartAxis axis = axisDetails.axis; assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); - if (axisRenderer._crossValue != null) { - axisSize = (_valueToCoefficient(axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + if (axisDetails.crossValue != null) { + crossAxisRendererDetails = + AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); + axisSize = (valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.width) + rect.left; if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisRenderer._labelOffset = rect.left - 5; + axisDetails.labelOffset = rect.left - 5; } } else { final num? prevAxisOffsetValue = - _getPrevAxisOffset(_leftAxesCount, rect, axisIndex, 'Left'); + _getPrevAxisOffset(leftAxesCount, rect, axisIndex, 'Left'); axisSize = prevAxisOffsetValue == null ? rect.left : (prevAxisOffsetValue - (axis.labelPosition == ChartDataLabelPosition.inside - ? (_innerPadding + axisRenderer._maximumLabelSize.width) + ? (innerPadding + axisDetails.maximumLabelSize.width) : 0)) - (axis.tickPosition == TickPosition.inside ? math.max( @@ -452,9 +539,9 @@ class _ChartAxis { ? axis.minorTickLines.size : 0) : 0) - - _axisPadding; + axisPadding; } - axisRenderer._bounds = Rect.fromLTWH(axisSize, rect.top + axis.plotOffset, + axisDetails.bounds = Rect.fromLTWH(axisSize, rect.top + axis.plotOffset, width, rect.height - 2 * axis.plotOffset); } } @@ -462,33 +549,38 @@ class _ChartAxis { /// Calculate the bottom axes bounds void _calculateBottomAxesBounds() { double axisSize, height; - final int axesLength = _bottomAxesCount.length; - final Rect rect = _chartState._chartAxis._axisClipRect; + final int axesLength = bottomAxesCount.length; + final Rect rect = stateProperties.chartAxis.axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { - height = _bottomAxesCount[axisIndex].size; + height = bottomAxesCount[axisIndex].size; final ChartAxisRenderer axisRenderer = - _bottomAxesCount[axisIndex].axisRenderer; - final ChartAxis axis = axisRenderer._axis; + bottomAxesCount[axisIndex].axisRenderer; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + ChartAxisRendererDetails crossAxisRendererDetails; + final ChartAxis axis = axisDetails.axis; assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); - if (axisRenderer._crossValue != null) { + if (axisDetails.crossValue != null) { + crossAxisRendererDetails = + AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); axisSize = rect.top + rect.height - - (_valueToCoefficient(axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + (valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.height); if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisRenderer._labelOffset = rect.top + rect.height + 5; + axisDetails.labelOffset = rect.top + rect.height + 5; } } else { final num? prevAxisOffsetValue = - _getPrevAxisOffset(_bottomAxesCount, rect, axisIndex, 'Bottom'); + _getPrevAxisOffset(bottomAxesCount, rect, axisIndex, 'Bottom'); axisSize = (prevAxisOffsetValue == null) ? rect.top + rect.height - : _axisPadding + + : axisPadding + prevAxisOffsetValue + (axis.labelPosition == ChartDataLabelPosition.inside - ? (_innerPadding + axisRenderer._maximumLabelSize.height) + ? (innerPadding + axisDetails.maximumLabelSize.height) : 0) + (axis.tickPosition == TickPosition.inside ? math.max( @@ -498,39 +590,44 @@ class _ChartAxis { : 0) : 0); } - axisRenderer._bounds = Rect.fromLTWH(rect.left + axis.plotOffset, - axisSize, rect.width - 2 * axis.plotOffset, height); + axisDetails.bounds = Rect.fromLTWH(rect.left + axis.plotOffset, axisSize, + rect.width - 2 * axis.plotOffset, height); } } /// Calculate the right axes bounds void _calculateRightAxesBounds() { double axisSize, width; - final int axesLength = _rightAxesCount.length; - final Rect rect = _chartState._chartAxis._axisClipRect; + final int axesLength = rightAxesCount.length; + final Rect rect = stateProperties.chartAxis.axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { final ChartAxisRenderer axisRenderer = - _rightAxesCount[axisIndex].axisRenderer; - final ChartAxis axis = axisRenderer._axis; + rightAxesCount[axisIndex].axisRenderer; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + ChartAxisRendererDetails crossAxisRendererDetails; + final ChartAxis axis = axisDetails.axis; assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); - width = _rightAxesCount[axisIndex].size; - if (axisRenderer._crossValue != null) { + width = rightAxesCount[axisIndex].size; + if (axisDetails.crossValue != null) { + crossAxisRendererDetails = + AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); axisSize = rect.left + - (_valueToCoefficient(axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + (valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.width); if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisRenderer._labelOffset = rect.left + rect.width + 5; + axisDetails.labelOffset = rect.left + rect.width + 5; } } else { final num? prevAxisOffsetValue = - _getPrevAxisOffset(_rightAxesCount, rect, axisIndex, 'Right'); + _getPrevAxisOffset(rightAxesCount, rect, axisIndex, 'Right'); axisSize = (prevAxisOffsetValue == null) ? rect.left + rect.width : (prevAxisOffsetValue + (axis.labelPosition == ChartDataLabelPosition.inside - ? axisRenderer._maximumLabelSize.width + _innerPadding + ? axisDetails.maximumLabelSize.width + innerPadding : 0)) + (axis.tickPosition == TickPosition.inside ? math.max( @@ -539,9 +636,9 @@ class _ChartAxis { ? axis.minorTickLines.size : 0) : 0) + - _axisPadding; + axisPadding; } - axisRenderer._bounds = Rect.fromLTWH(axisSize, rect.top + axis.plotOffset, + axisDetails.bounds = Rect.fromLTWH(axisSize, rect.top + axis.plotOffset, width, rect.height - 2 * axis.plotOffset); } } @@ -549,32 +646,37 @@ class _ChartAxis { /// Calculate the top axes bounds void _calculateTopAxesBounds() { double axisSize, height; - final int axesLength = _topAxesCount.length; - final Rect rect = _chartState._chartAxis._axisClipRect; + final int axesLength = topAxesCount.length; + final Rect rect = stateProperties.chartAxis.axisClipRect; for (int axisIndex = 0; axisIndex < axesLength; axisIndex++) { final ChartAxisRenderer axisRenderer = - _topAxesCount[axisIndex].axisRenderer; - final ChartAxis axis = axisRenderer._axis; + topAxesCount[axisIndex].axisRenderer; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + ChartAxisRendererDetails crossAxisRendererDetails; + final ChartAxis axis = axisDetails.axis; assert(axis.plotOffset >= 0, 'The plot offset value of the axis must be greater than or equal to 0.'); - height = _topAxesCount[axisIndex].size; - if (axisRenderer._crossValue != null) { + height = topAxesCount[axisIndex].size; + if (axisDetails.crossValue != null) { + crossAxisRendererDetails = + AxisHelper.getAxisRendererDetails(axisDetails.crossAxisRenderer); axisSize = rect.top + rect.height - - (_valueToCoefficient(axisRenderer._crossValue!, - axisRenderer._crossAxisRenderer) * + (valueToCoefficient( + axisDetails.crossValue!, crossAxisRendererDetails) * rect.height); if (axisIndex == 0 && !axis.placeLabelsNearAxisLine) { - axisRenderer._labelOffset = rect.top - 5; + axisDetails.labelOffset = rect.top - 5; } } else { final num? prevAxisOffsetValue = - _getPrevAxisOffset(_topAxesCount, rect, axisIndex, 'Top'); + _getPrevAxisOffset(topAxesCount, rect, axisIndex, 'Top'); axisSize = (prevAxisOffsetValue == null) ? rect.top : prevAxisOffsetValue - (axis.labelPosition == ChartDataLabelPosition.inside - ? (_axisPadding + axisRenderer._maximumLabelSize.height) + ? (axisPadding + axisDetails.maximumLabelSize.height) : 0) - (axis.tickPosition == TickPosition.inside ? math.max( @@ -583,146 +685,165 @@ class _ChartAxis { ? axis.minorTickLines.size : 0) : 0) - - _axisPadding; + axisPadding; } - axisRenderer._bounds = Rect.fromLTWH(rect.left + axis.plotOffset, - axisSize, rect.width - 2 * axis.plotOffset, height); + axisDetails.bounds = Rect.fromLTWH(rect.left + axis.plotOffset, axisSize, + rect.width - 2 * axis.plotOffset, height); } } /// Calculate the visible axes - void _calculateVisibleAxes() { - _innerPadding = _chartWidget.borderWidth; - _axisPadding = 5; - _axisClipRect = const Rect.fromLTRB(0, 0, 0, 0); - _verticalAxisRenderers = []; - _horizontalAxisRenderers = []; - _axisRenderersCollection = []; - _primaryXAxisRenderer = _getAxisRenderer(_chartWidget.primaryXAxis); - _primaryYAxisRenderer = _getAxisRenderer(_chartWidget.primaryYAxis); - _primaryXAxisRenderer._name = - (_primaryXAxisRenderer._name) ?? 'primaryXAxis'; - _primaryYAxisRenderer._name = _primaryYAxisRenderer._name ?? 'primaryYAxis'; + void calculateVisibleAxes() { + if (primaryXAxisRenderer != null) { + primaryXAxisRenderer!.dispose(); + } + + if (primaryYAxisRenderer != null) { + primaryYAxisRenderer!.dispose(); + } + + innerPadding = chartWidget.borderWidth; + axisPadding = 5; + axisClipRect = const Rect.fromLTRB(0, 0, 0, 0); + verticalAxisRenderers = []; + horizontalAxisRenderers = []; + axisRenderersCollection = []; + primaryXAxisRenderer = _getAxisRenderer(chartWidget.primaryXAxis); + primaryYAxisRenderer = _getAxisRenderer(chartWidget.primaryYAxis); + primaryXAxisDetails = + AxisHelper.getAxisRendererDetails(primaryXAxisRenderer!); + primaryYAxisDetails = + AxisHelper.getAxisRendererDetails(primaryYAxisRenderer!); + primaryXAxisDetails.name = (primaryXAxisDetails.name) ?? 'primaryXAxis'; + primaryYAxisDetails.name = primaryYAxisDetails.name ?? 'primaryYAxis'; final List _axesCollection = [ - _chartWidget.primaryXAxis, - _chartWidget.primaryYAxis + chartWidget.primaryXAxis, + chartWidget.primaryYAxis ]; final List visibleSeriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers; + stateProperties.chartSeries.visibleSeriesRenderers; if (visibleSeriesRenderer.isNotEmpty) { - if (_chartWidget.axes.isNotEmpty) { - _axesCollection.addAll(_chartWidget.axes); + if (chartWidget.axes.isNotEmpty) { + _axesCollection.addAll(chartWidget.axes); } for (int axisIndex = 0; axisIndex < _axesCollection.length; axisIndex++) { final ChartAxisRenderer axisRenderer = axisIndex == 0 - ? _primaryXAxisRenderer + ? primaryXAxisRenderer! : (axisIndex == 1 - ? _primaryYAxisRenderer + ? primaryYAxisRenderer! : _getAxisRenderer(_axesCollection[axisIndex])); - if (axisRenderer is CategoryAxisRenderer) { - axisRenderer._labels = []; - } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - axisRenderer._labels = []; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + if (axisDetails is CategoryAxisDetails) { + axisDetails.labels = []; + } else if (axisDetails is DateTimeCategoryAxisDetails) { + axisDetails.labels = []; } - axisRenderer._seriesRenderers = []; - axisRenderer._chartState = _chartState; + axisDetails.seriesRenderers = []; for (int seriesIndex = 0; seriesIndex < visibleSeriesRenderer.length; seriesIndex++) { final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[seriesIndex]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final XyDataSeries series = - seriesRenderer._series as XyDataSeries; - if ((axisRenderer._name != null && - axisRenderer._name == series.xAxisName) || + seriesRendererDetails.series as XyDataSeries; + if ((axisDetails.name != null && + axisDetails.name == series.xAxisName) || (series.xAxisName == null && - axisRenderer._name == _primaryXAxisRenderer._name) || + axisDetails.name == primaryXAxisDetails.name) || (series.xAxisName != null && - axisRenderer._name != series.xAxisName && - axisRenderer._name == - _chartState._chartAxis._primaryXAxisRenderer._name)) { - axisRenderer._orientation = _chartState._requireInvertedAxis + axisDetails.name != series.xAxisName && + axisDetails.name == + stateProperties.chartAxis.primaryXAxisDetails.name)) { + axisDetails.orientation = stateProperties.requireInvertedAxis ? AxisOrientation.vertical : AxisOrientation.horizontal; - seriesRenderer._xAxisRenderer = axisRenderer; - axisRenderer._seriesRenderers.add(seriesRenderer); - } else if ((axisRenderer._name != null && - axisRenderer._name == series.yAxisName) || + seriesRendererDetails.xAxisDetails = axisDetails; + axisDetails.seriesRenderers.add(seriesRenderer); + } else if ((axisDetails.name != null && + axisDetails.name == series.yAxisName) || (series.yAxisName == null && - axisRenderer._name == _primaryYAxisRenderer._name) || + axisDetails.name == primaryYAxisDetails.name) || (series.yAxisName != null && - axisRenderer._name != series.yAxisName && - axisRenderer._name == - _chartState._chartAxis._primaryYAxisRenderer._name)) { - axisRenderer._orientation = _chartState._requireInvertedAxis + axisDetails.name != series.yAxisName && + axisDetails.name == + stateProperties.chartAxis.primaryYAxisDetails.name)) { + axisDetails.orientation = stateProperties.requireInvertedAxis ? AxisOrientation.horizontal : AxisOrientation.vertical; - seriesRenderer._yAxisRenderer = axisRenderer; - axisRenderer._seriesRenderers.add(seriesRenderer); + seriesRendererDetails.yAxisDetails = axisDetails; + axisDetails.seriesRenderers.add(seriesRenderer); } } ///Adding unmapped axes which were mapped with the indicators - if (axisRenderer._orientation == null && - _chartWidget.indicators.isNotEmpty) { - for (int i = 0; i < _chartWidget.indicators.length; i++) { - if (_chartWidget.indicators[i].isVisible) { - if (_chartWidget.indicators[i].xAxisName == axisRenderer._name) { - axisRenderer._orientation = _chartState._requireInvertedAxis + if (axisDetails.orientation == null && + chartWidget.indicators.isNotEmpty) { + for (int i = 0; i < chartWidget.indicators.length; i++) { + if (chartWidget.indicators[i].isVisible) { + if (chartWidget.indicators[i].xAxisName == axisDetails.name) { + axisDetails.orientation = stateProperties.requireInvertedAxis ? AxisOrientation.vertical : AxisOrientation.horizontal; - } else if (_chartWidget.indicators[i].yAxisName == - axisRenderer._name) { - axisRenderer._orientation = _chartState._requireInvertedAxis + } else if (chartWidget.indicators[i].yAxisName == + axisDetails.name) { + axisDetails.orientation = stateProperties.requireInvertedAxis ? AxisOrientation.horizontal : AxisOrientation.vertical; } } } } - if (axisRenderer._orientation != null) { - axisRenderer._orientation == AxisOrientation.vertical - ? _verticalAxisRenderers.add(axisRenderer) - : _horizontalAxisRenderers.add(axisRenderer); + if (axisDetails.orientation != null) { + axisDetails.orientation == AxisOrientation.vertical + ? verticalAxisRenderers.add(axisRenderer) + : horizontalAxisRenderers.add(axisRenderer); } - axisRenderer._oldAxis = _chartState._renderingDetails.widgetNeedUpdate - ? _getOldAxisRenderer(axisRenderer, _chartState._oldAxisRenderers) - ?._axis - : null; - _axisRenderersCollection.add(axisRenderer); + final ChartAxisRenderer? oldAxisRenderer = + getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); + axisDetails.oldAxis = + stateProperties.renderingDetails.widgetNeedUpdate && + oldAxisRenderer != null + ? AxisHelper.getAxisRendererDetails(oldAxisRenderer).axis + : null; + axisRenderersCollection.add(axisRenderer); } } else { - _chartState._chartAxis._primaryXAxisRenderer._orientation = - _chartState._requireInvertedAxis + stateProperties.chartAxis.primaryXAxisDetails.orientation = + stateProperties.requireInvertedAxis ? AxisOrientation.vertical : AxisOrientation.horizontal; - _chartState._chartAxis._primaryYAxisRenderer._orientation = - _chartState._requireInvertedAxis + stateProperties.chartAxis.primaryYAxisDetails.orientation = + stateProperties.requireInvertedAxis ? AxisOrientation.horizontal : AxisOrientation.vertical; - _horizontalAxisRenderers.add(_primaryXAxisRenderer); - _verticalAxisRenderers.add(_primaryYAxisRenderer); - _axisRenderersCollection.add(_primaryXAxisRenderer); - _axisRenderersCollection.add(_primaryYAxisRenderer); + horizontalAxisRenderers.add(primaryXAxisRenderer!); + verticalAxisRenderers.add(primaryYAxisRenderer!); + axisRenderersCollection.add(primaryXAxisRenderer!); + axisRenderersCollection.add(primaryYAxisRenderer!); } } ChartAxisRenderer _getAxisRenderer(ChartAxis axis) { switch (axis.runtimeType) { case NumericAxis: - return NumericAxisRenderer(axis as NumericAxis); + return NumericAxisRenderer(axis as NumericAxis, stateProperties); case LogarithmicAxis: - return LogarithmicAxisRenderer(axis as LogarithmicAxis); + return LogarithmicAxisRenderer( + axis as LogarithmicAxis, stateProperties); case CategoryAxis: - return CategoryAxisRenderer(axis as CategoryAxis); + return CategoryAxisRenderer(axis as CategoryAxis, stateProperties); case DateTimeAxis: - return DateTimeAxisRenderer(axis as DateTimeAxis); + return DateTimeAxisRenderer(axis as DateTimeAxis, stateProperties); case DateTimeCategoryAxis: - return DateTimeCategoryAxisRenderer(axis as DateTimeCategoryAxis); + return DateTimeCategoryAxisRenderer( + axis as DateTimeCategoryAxis, stateProperties); default: - return NumericAxisRenderer(axis as NumericAxis); + return NumericAxisRenderer(axis as NumericAxis, stateProperties); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart index 1d16b48b1..95e1b0fe1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/axis_renderer.dart @@ -1,6 +1,20 @@ -part of charts; - -abstract class _CustomizeAxisElements { +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../common/rendering_details.dart'; +import '../../common/utils/helper.dart'; +import '../axis/axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../base/chart_base.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../common/cartesian_state_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the class for customize axis elements +abstract class CustomizeAxisElements { /// To get axis line color Color? getAxisLineColor(ChartAxis axis); @@ -18,6 +32,7 @@ abstract class _CustomizeAxisElements { Color? getAxisMinorGridColor( ChartAxis axis, int majorGridIndex, int minorGridIndex); + /// To get the axis line width double getAxisLineWidth(ChartAxis axis); /// To get major tick width @@ -114,22 +129,28 @@ abstract class _CustomizeAxisElements { Canvas canvas, ChartAxisRenderer axisRenderer, SfCartesianChart chart); } +/// Represents the cartesian axis widgets // ignore: must_be_immutable -class _CartesianAxisRenderer extends StatefulWidget { +class CartesianAxisWidget extends StatefulWidget { + /// Creates an instance for cartesian axis widget // ignore: prefer_const_constructors_in_immutables - _CartesianAxisRenderer({required this.chartState, required this.renderType}); + CartesianAxisWidget( + {required this.stateProperties, required this.renderType}); - final SfCartesianChartState chartState; + /// Specifies the cartesian state properties + final CartesianStateProperties stateProperties; + /// Specifies the render types String renderType; - late _CartesianAxisRendererState state; + /// Specifies the cartesian axis widget state + late _CartesianAxisWidgetState state; @override - State createState() => _CartesianAxisRendererState(); + State createState() => _CartesianAxisWidgetState(); } -class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> +class _CartesianAxisWidgetState extends State with SingleTickerProviderStateMixin { late List animationControllersList; @@ -161,10 +182,10 @@ class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> child: RepaintBoundary( child: CustomPaint( painter: _CartesianAxesPainter( - chartState: widget.chartState, + stateProperties: widget.stateProperties, axisAnimation: axisAnimation, renderType: widget.renderType, - isRepaint: widget.chartState._chartAxis._needsRepaint, + isRepaint: widget.stateProperties.chartAxis.needsRepaint, notifier: axisRepaintNotifier)))); } @@ -172,87 +193,91 @@ class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> final double animationFactor = animationController.value; for (int axisIndex = 0; axisIndex < - widget.chartState._chartAxis._axisRenderersCollection.length; + widget.stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { ///visibleMinimum and visibleMaximum not defined in the chart axis class, /// so dynamic datatype used here - final dynamic axisRenderer = - widget.chartState._chartAxis._axisRenderersCollection[axisIndex]; + final ChartAxisRenderer axisRenderer = + widget.stateProperties.chartAxis.axisRenderersCollection[axisIndex]; + final dynamic axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); ///visibleMinimum and visibleMaximum not defined in the chart axis class, /// so dynamic datatype used here dynamic oldAxisRenderer; bool needAnimate = false; - if ((widget.chartState._requireInvertedAxis - ? axisRenderer._orientation == AxisOrientation.vertical - : axisRenderer._orientation == AxisOrientation.horizontal) && + if ((widget.stateProperties.requireInvertedAxis + ? axisDetails.orientation == AxisOrientation.vertical + : axisDetails.orientation == AxisOrientation.horizontal) && // ignore: unnecessary_null_comparison - widget.chartState._oldAxisRenderers != null && - widget.chartState._oldAxisRenderers.isNotEmpty && - (axisRenderer._axis.visibleMinimum != null || - axisRenderer._axis.visibleMaximum != null)) { - oldAxisRenderer = _getOldAxisRenderer( - axisRenderer, widget.chartState._oldAxisRenderers); + widget.stateProperties.oldAxisRenderers != null && + widget.stateProperties.oldAxisRenderers.isNotEmpty && + (axisDetails.axis.visibleMinimum != null || + axisDetails.axis.visibleMaximum != null)) { + oldAxisRenderer = getOldAxisRenderer( + axisRenderer, widget.stateProperties.oldAxisRenderers); + final dynamic oldAxisDetails = + AxisHelper.getAxisRendererDetails(oldAxisRenderer); if (oldAxisRenderer != null && - (oldAxisRenderer._axis.visibleMinimum != null && - oldAxisRenderer._axis.visibleMaximum != null)) { - needAnimate = - axisRenderer.runtimeType == oldAxisRenderer.runtimeType && - ((oldAxisRenderer._axis.visibleMinimum != null && - oldAxisRenderer._axis.visibleMinimum != - axisRenderer._axis.visibleMinimum) || - (oldAxisRenderer._axis.visibleMaximum != null && - oldAxisRenderer._axis.visibleMaximum != - axisRenderer._axis.visibleMaximum)) && - _checkSeriesAnimation(axisRenderer._seriesRenderers); + (oldAxisDetails.axis.visibleMinimum != null && + oldAxisDetails.axis.visibleMaximum != null)) { + needAnimate = axisDetails.runtimeType == oldAxisDetails.runtimeType && + ((oldAxisDetails.axis.visibleMinimum != null && + oldAxisDetails.axis.visibleMinimum != + axisDetails.axis.visibleMinimum) || + (oldAxisDetails.axis.visibleMaximum != null && + oldAxisDetails.axis.visibleMaximum != + axisDetails.axis.visibleMaximum)) && + _checkSeriesAnimation(axisDetails.seriesRenderers); if (needAnimate) { if (axisRenderer is DateTimeAxisRenderer || axisRenderer is DateTimeCategoryAxisRenderer) { - axisRenderer._visibleMinimum = - (oldAxisRenderer._axis.visibleMinimum.millisecondsSinceEpoch - - (oldAxisRenderer._axis.visibleMinimum + axisDetails.visibleMinimum = + (oldAxisDetails.axis.visibleMinimum.millisecondsSinceEpoch - + (oldAxisDetails.axis.visibleMinimum .millisecondsSinceEpoch - - axisRenderer._axis.visibleMinimum + axisDetails.axis.visibleMinimum .millisecondsSinceEpoch) * animationFactor) .toInt(); - axisRenderer._visibleMaximum = - (oldAxisRenderer._axis.visibleMaximum.millisecondsSinceEpoch - - (oldAxisRenderer._axis.visibleMaximum + axisDetails.visibleMaximum = + (oldAxisDetails.axis.visibleMaximum.millisecondsSinceEpoch - + (oldAxisDetails.axis.visibleMaximum .millisecondsSinceEpoch - - axisRenderer._axis.visibleMaximum + axisDetails.axis.visibleMaximum .millisecondsSinceEpoch) * animationFactor) .toInt(); } else { - axisRenderer._visibleMinimum = - oldAxisRenderer._axis.visibleMinimum - - (oldAxisRenderer._axis.visibleMinimum - - axisRenderer._axis.visibleMinimum) * - animationFactor; - axisRenderer._visibleMaximum = - oldAxisRenderer._axis.visibleMaximum - - (oldAxisRenderer._axis.visibleMaximum - - axisRenderer._axis.visibleMaximum) * - animationFactor; + axisDetails.visibleMinimum = oldAxisDetails.axis.visibleMinimum - + (oldAxisDetails.axis.visibleMinimum - + axisDetails.axis.visibleMinimum) * + animationFactor; + axisDetails.visibleMaximum = oldAxisDetails.axis.visibleMaximum - + (oldAxisDetails.axis.visibleMaximum - + axisDetails.axis.visibleMaximum) * + animationFactor; } - if (axisRenderer is DateTimeCategoryAxisRenderer) { - axisRenderer._labels.clear(); + if (axisDetails is DateTimeCategoryAxisDetails) { + axisDetails.labels.clear(); //ignore: prefer_foreach for (final CartesianSeriesRenderer seriesRenderer - in axisRenderer._seriesRenderers) { - widget.chartState._chartSeries - ._findSeriesMinMax(seriesRenderer); + in axisDetails.seriesRenderers) { + widget.stateProperties.chartSeries.findSeriesMinMax( + SeriesHelper.getSeriesRendererDetails(seriesRenderer)); } } - axisRenderer._calculateRangeAndInterval(widget.chartState); + axisDetails.calculateRangeAndInterval(widget.stateProperties); for (final CartesianSeriesRenderer seriesRenderer - in axisRenderer._seriesRenderers) { - seriesRenderer._calculateRegion = true; - seriesRenderer._repaintNotifier.value++; - if (seriesRenderer._series.dataLabelSettings.isVisible && - widget.chartState._renderDataLabel?.state != null) { - widget.chartState._renderDataLabel?.state! + in axisDetails.seriesRenderers) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + seriesRendererDetails.calculateRegion = true; + seriesRendererDetails.repaintNotifier.value++; + if (seriesRendererDetails.series.dataLabelSettings.isVisible == + true && + widget.stateProperties.renderDataLabel?.state != null) { + widget.stateProperties.renderDataLabel?.state! .dataLabelRepaintNotifier.value++; } } @@ -264,7 +289,9 @@ class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> bool _checkSeriesAnimation(List seriesRenderers) { for (int i = 0; i < seriesRenderers.length; i++) { - if (seriesRenderers[i]._series.animationDuration <= 0) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderers[i]); + if (seriesRendererDetails.series.animationDuration <= 0) { return false; } } @@ -273,7 +300,7 @@ class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> @override void dispose() { - _disposeAnimationController(animationController, _repaintAxisElements); + disposeAnimationController(animationController, _repaintAxisElements); super.dispose(); } @@ -285,14 +312,14 @@ class _CartesianAxisRendererState extends State<_CartesianAxisRenderer> class _CartesianAxesPainter extends CustomPainter { _CartesianAxesPainter( - {required this.chartState, + {required this.stateProperties, required this.isRepaint, required ValueNotifier notifier, required this.renderType, required this.axisAnimation}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + final CartesianStateProperties stateProperties; final SfCartesianChart chart; final bool isRepaint; final String renderType; @@ -308,11 +335,11 @@ class _CartesianAxesPainter extends CustomPainter { if (renderType == 'outside') { _drawPlotAreaBorder(canvas); if (chart.plotAreaBackgroundImage != null && - chartState._backgroundImage != null) { + stateProperties.backgroundImage != null) { paintImage( canvas: canvas, - rect: chartState._chartAxis._axisClipRect, - image: chartState._backgroundImage!, + rect: stateProperties.chartAxis.axisClipRect, + image: stateProperties.backgroundImage!, fit: BoxFit.fill); } } @@ -321,8 +348,8 @@ class _CartesianAxesPainter extends CustomPainter { /// To draw a plot area border of a container void _drawPlotAreaBorder(Canvas canvas) { - final Rect axisClipRect = chartState._chartAxis._axisClipRect; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final Rect axisClipRect = stateProperties.chartAxis.axisClipRect; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final Paint paint = Paint(); paint.color = chart.plotAreaBorderColor ?? renderingDetails.chartTheme.plotAreaBorderColor; @@ -348,7 +375,9 @@ class _CartesianAxesPainter extends CustomPainter { double animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisDetails.axis; if (axis.isVisible) { if (axis.axisLine.width > 0 && renderType == 'outside') { axisRenderer.drawHorizontalAxesLine(canvas, axisRenderer, chart); @@ -372,12 +401,14 @@ class _CartesianAxesPainter extends CustomPainter { double animationFactor, ChartAxisRenderer? oldAxisRenderer, bool? needAnimate) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisDetails.axis; if (axis.isVisible) { if (axis.axisLine.width > 0 && renderType == 'outside') { axisRenderer.drawVerticalAxesLine(canvas, axisRenderer, chart); } - if (axisRenderer._visibleLabels.isNotEmpty && + if (axisDetails.visibleLabels.isNotEmpty && (axis.majorTickLines.width > 0 || axis.majorGridLines.width > 0)) { axisRenderer.drawVerticalAxesTickLines(canvas, axisRenderer, chart, renderType, animationFactor, oldAxisRenderer, needAnimate); @@ -396,30 +427,36 @@ class _CartesianAxesPainter extends CustomPainter { // ignore: unnecessary_null_comparison axisAnimation != null ? axisAnimation.value : 1; for (int axisIndex = 0; - axisIndex < chartState._chartAxis._axisRenderersCollection.length; + axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { final ChartAxisRenderer axisRenderer = - chartState._chartAxis._axisRenderersCollection[axisIndex]; - final ChartAxis axis = axisRenderer._axis; - axisRenderer._isInsideTickPosition = + stateProperties.chartAxis.axisRenderersCollection[axisIndex]; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisDetails.axis; + axisDetails.isInsideTickPosition = axis.tickPosition == TickPosition.inside; ChartAxisRenderer? oldAxisRenderer; bool needAnimate = false; // ignore: unnecessary_null_comparison - if (chartState._oldAxisRenderers != null && - chartState._oldAxisRenderers.isNotEmpty && - axisRenderer._visibleRange != null) { + if (stateProperties.oldAxisRenderers != null && + stateProperties.oldAxisRenderers.isNotEmpty && + axisDetails.visibleRange != null) { oldAxisRenderer = - _getOldAxisRenderer(axisRenderer, chartState._oldAxisRenderers); - if (oldAxisRenderer != null && oldAxisRenderer._visibleRange != null) { - needAnimate = chart.enableAxisAnimation && - (oldAxisRenderer._visibleRange!.minimum != - axisRenderer._visibleRange!.minimum || - oldAxisRenderer._visibleRange!.maximum != - axisRenderer._visibleRange!.maximum); + getOldAxisRenderer(axisRenderer, stateProperties.oldAxisRenderers); + if (oldAxisRenderer != null) { + final ChartAxisRendererDetails oldAxisDetails = + AxisHelper.getAxisRendererDetails(oldAxisRenderer); + if (oldAxisDetails.visibleRange != null) { + needAnimate = chart.enableAxisAnimation && + (oldAxisDetails.visibleRange!.minimum != + axisDetails.visibleRange!.minimum || + oldAxisDetails.visibleRange!.maximum != + axisDetails.visibleRange!.maximum); + } } } - axisRenderer._orientation == AxisOrientation.horizontal + axisDetails.orientation == AxisOrientation.horizontal ? _drawHorizontalAxes(canvas, axisRenderer, animationFactor, oldAxisRenderer, needAnimate) : _drawVerticalAxes(canvas, axisRenderer, animationFactor, diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart index 2017cbe91..639761096 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/category_axis.dart @@ -1,4 +1,18 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../common/event_args.dart'; +import '../axis/axis.dart'; +import '../axis/plotband.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/interactive_tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; /// This class has the properties of the category axis. /// @@ -291,234 +305,287 @@ class CategoryAxis extends ChartAxis { /// Creates an axis renderer for Category axis class CategoryAxisRenderer extends ChartAxisRenderer { /// Creating an argument constructor of CategoryAxisRenderer class. - CategoryAxisRenderer(this._categoryAxis) : super(_categoryAxis) { - _labels = []; + CategoryAxisRenderer( + CategoryAxis _categoryAxis, CartesianStateProperties _stateProperties) { + _axisDetails = CategoryAxisDetails(_categoryAxis, this, _stateProperties); + AxisHelper.setAxisRendererDetails(this, _axisDetails); } - late List _labels; - late Rect _rect; - final CategoryAxis _categoryAxis; - void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint point, int pointIndex, int dataLength, - [bool? isXVisibleRange, bool? isYVisibleRange]) { - if (_categoryAxis.arrangeByIndex) { - // ignore: unnecessary_null_comparison - pointIndex < _labels.length && _labels[pointIndex] != null - ? _labels[pointIndex] += ', ' + point.x - : _labels.add(point.x.toString()); - point.xValue = pointIndex; - } else { - if (!_labels.contains(point.x.toString())) { - _labels.add(point.x.toString()); - } - point.xValue = _labels.indexOf(point.x.toString()); - } - point.yValue = point.y; - _setCategoryMinMaxValues(this, isXVisibleRange!, isYVisibleRange!, point, - pointIndex, dataLength, seriesRenderer); - } - - /// Listener for range controller - void _controlListener() { - _chartState._canSetRangeController = false; - if (_axis.rangeController != null && !_chartState._rangeChangedByChart) { - _updateRangeControllerValues(this); - _chartState._rangeChangeBySlider = true; - _chartState._redrawByRangeChange(); - } - } - - /// Calculate the range and interval - void _calculateRangeAndInterval(SfCartesianChartState chartState, - [String? type]) { - _chartState = chartState; - _chart = chartState._chart; - if (_axis.rangeController != null) { - _chartState._rangeChangeBySlider = true; - _axis.rangeController!.addListener(_controlListener); - } - final Rect containerRect = _chartState._renderingDetails.chartContainerRect; - _rect = Rect.fromLTWH(containerRect.left, containerRect.top, - containerRect.width, containerRect.height); - _axisSize = Size(_rect.width, _rect.height); - calculateRange(this); - _calculateActualRange(); - if (_actualRange != null) { - applyRangePadding(_actualRange!, _actualRange!.interval); - if (type == null && type != 'AxisCross' && _categoryAxis.isVisible) { - generateVisibleLabels(); - } - } - } - - /// Calculate the required values of the actual range for the category axis - void _calculateActualRange() { - if (_min != null && _max != null) { - _actualRange = _VisibleRange( - _categoryAxis.minimum ?? _min, _categoryAxis.maximum ?? _max); - final List seriesRenderers = _seriesRenderers; - CartesianSeriesRenderer seriesRenderer; - for (int i = 0; i < seriesRenderers.length; i++) { - seriesRenderer = seriesRenderers[i]; - if ((_actualRange!.maximum > seriesRenderer._dataPoints.length - 1) == - true) { - for (int i = _labels.length; i < _actualRange!.maximum + 1; i++) { - _labels.add(i.toString()); - } - } - } - _actualRange = _VisibleRange( - _categoryAxis.minimum ?? _min, _categoryAxis.maximum ?? _max); - - ///Below condition is for checking the min, max value is equal - if ((_actualRange!.minimum == _actualRange!.maximum) && - (_categoryAxis.labelPlacement == LabelPlacement.onTicks)) { - _actualRange!.maximum += 1; - } - _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; - _actualRange!.interval = _categoryAxis.interval ?? - calculateInterval(_actualRange!, Size(_rect.width, _rect.height)); - _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; - } - } + late CategoryAxisDetails _axisDetails; /// Calculates the visible range for an axis in chart. @override void calculateVisibleRange(Size availableSize) { - _setOldRangeFromRangeController(); - _visibleRange = _chartState._rangeChangeBySlider && - _rangeMinimum != null && - _rangeMaximum != null - ? _VisibleRange(_rangeMinimum, _rangeMaximum) - : _VisibleRange(_actualRange!.minimum, _actualRange!.maximum); - _visibleRange!.delta = _actualRange!.delta; - _visibleRange!.interval = _actualRange!.interval; + _axisDetails.setOldRangeFromRangeController(); + _axisDetails.visibleRange = + _axisDetails.stateProperties.rangeChangeBySlider && + _axisDetails.rangeMinimum != null && + _axisDetails.rangeMaximum != null + ? VisibleRange(_axisDetails.rangeMinimum, _axisDetails.rangeMaximum) + : VisibleRange(_axisDetails.actualRange!.minimum, + _axisDetails.actualRange!.maximum); + _axisDetails.visibleRange!.delta = _axisDetails.actualRange!.delta; + _axisDetails.visibleRange!.interval = _axisDetails.actualRange!.interval; bool canAutoScroll = false; - if (_categoryAxis.autoScrollingDelta != null && - _categoryAxis.autoScrollingDelta! > 0 && - !_chartState._isRedrawByZoomPan) { + if (_axisDetails._categoryAxis.autoScrollingDelta != null && + _axisDetails._categoryAxis.autoScrollingDelta! > 0 && + !_axisDetails.stateProperties.isRedrawByZoomPan) { canAutoScroll = true; - super._updateAutoScrollingDelta(_categoryAxis.autoScrollingDelta!, this); + _axisDetails.updateAutoScrollingDelta( + _axisDetails._categoryAxis.autoScrollingDelta!, this); } - if ((!canAutoScroll || _chartState._zoomedState == true) && - !(_chartState._rangeChangeBySlider && - !_chartState._canSetRangeController)) { - _setZoomFactorAndPosition(this, _chartState._zoomedAxisRendererStates); + if ((!canAutoScroll || _axisDetails.stateProperties.zoomedState == true) && + !(_axisDetails.stateProperties.rangeChangeBySlider && + !_axisDetails.stateProperties.canSetRangeController)) { + _axisDetails.setZoomFactorAndPosition( + this, _axisDetails.stateProperties.zoomedAxisRendererStates); } - if (_zoomFactor < 1 || - _zoomPosition > 0 || - (_axis.rangeController != null && - !_chartState._renderingDetails.initialRender!)) { - _chartState._zoomProgress = true; - _calculateZoomRange(this, availableSize); - if (_axis.rangeController != null && - _chartState._isRedrawByZoomPan && - _chartState._canSetRangeController && - _chartState._zoomProgress) { - _chartState._rangeChangedByChart = true; - _setRangeControllerValues(this); + if (_axisDetails.zoomFactor < 1 || + _axisDetails.zoomPosition > 0 || + (_axisDetails.axis.rangeController != null && + !_axisDetails.stateProperties.renderingDetails.initialRender!)) { + _axisDetails.stateProperties.zoomProgress = true; + _axisDetails.calculateZoomRange(this, availableSize); + if (_axisDetails.axis.rangeController != null && + _axisDetails.stateProperties.isRedrawByZoomPan && + _axisDetails.stateProperties.canSetRangeController && + _axisDetails.stateProperties.zoomProgress) { + _axisDetails.stateProperties.rangeChangedByChart = true; + _axisDetails.setRangeControllerValues(this); } } - _setZoomValuesFromRangeController(); + _axisDetails.setZoomValuesFromRangeController(); } /// Applies range padding @override - void applyRangePadding(_VisibleRange range, num? interval) { + void applyRangePadding(VisibleRange range, num? interval) { ActualRangeChangedArgs rangeChangedArgs; - if (_categoryAxis.labelPlacement == LabelPlacement.betweenTicks) { + if (_axisDetails._categoryAxis.labelPlacement == + LabelPlacement.betweenTicks) { range.minimum -= 0.5; range.maximum += 0.5; range.delta = range.maximum - range.minimum; } - if (!(_categoryAxis.minimum != null && _categoryAxis.maximum != null)) { + if (!(_axisDetails._categoryAxis.minimum != null && + _axisDetails._categoryAxis.maximum != null)) { ///Calculating range padding - _applyRangePadding(this, _chartState, range, interval!); + _axisDetails.applyRangePaddings( + this, _axisDetails.stateProperties, range, interval!); } - calculateVisibleRange(Size(_rect.width, _rect.height)); + calculateVisibleRange( + Size(_axisDetails.rect.width, _axisDetails.rect.height)); /// Setting range as visible zoomRange - if ((_categoryAxis.visibleMinimum != null || - _categoryAxis.visibleMaximum != null) && - (_categoryAxis.visibleMinimum != _categoryAxis.visibleMaximum) && - (!_chartState._isRedrawByZoomPan)) { - _chartState._isRedrawByZoomPan = false; - _visibleRange!.minimum = _visibleMinimum ?? - _categoryAxis.visibleMinimum ?? - _visibleRange!.minimum; - _visibleRange!.maximum = _visibleMaximum ?? - _categoryAxis.visibleMaximum ?? - _visibleRange!.maximum; - if (_categoryAxis.labelPlacement == LabelPlacement.betweenTicks) { - _visibleRange!.minimum = _categoryAxis.visibleMinimum != null - ? (_visibleMinimum ?? _categoryAxis.visibleMinimum!) - 0.5 - : _visibleRange!.minimum; - _visibleRange!.maximum = _categoryAxis.visibleMaximum != null - ? (_visibleMaximum ?? _categoryAxis.visibleMaximum!) + 0.5 - : _visibleRange!.maximum; + if ((_axisDetails._categoryAxis.visibleMinimum != null || + _axisDetails._categoryAxis.visibleMaximum != null) && + (_axisDetails._categoryAxis.visibleMinimum != + _axisDetails._categoryAxis.visibleMaximum) && + (!_axisDetails.stateProperties.isRedrawByZoomPan)) { + _axisDetails.stateProperties.isRedrawByZoomPan = false; + _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum ?? + _axisDetails._categoryAxis.visibleMinimum ?? + _axisDetails.actualRange!.minimum; + _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum ?? + _axisDetails._categoryAxis.visibleMaximum ?? + _axisDetails.actualRange!.maximum; + if (_axisDetails._categoryAxis.labelPlacement == + LabelPlacement.betweenTicks) { + _axisDetails.visibleRange!.minimum = + _axisDetails._categoryAxis.visibleMinimum != null + ? (_axisDetails.visibleMinimum ?? + _axisDetails._categoryAxis.visibleMinimum!) - + 0.5 + : _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.maximum = + _axisDetails._categoryAxis.visibleMaximum != null + ? (_axisDetails.visibleMaximum ?? + _axisDetails._categoryAxis.visibleMaximum!) + + 0.5 + : _axisDetails.visibleRange!.maximum; } - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = interval == null - ? calculateInterval(_visibleRange!, _axisSize) - : _visibleRange!.interval; - _zoomFactor = _visibleRange!.delta / (range.delta); - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; + _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - + _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.interval = interval == null + ? calculateInterval(_axisDetails.visibleRange!, _axisDetails.axisSize) + : _axisDetails.visibleRange!.interval; + _axisDetails.zoomFactor = + _axisDetails.visibleRange!.delta / (range.delta); + _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - + _axisDetails.actualRange!.minimum) / + range.delta; } - if (_chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs(_name!, _categoryAxis, - range.minimum, range.maximum, range.interval, _orientation!); - rangeChangedArgs.visibleMin = _visibleRange!.minimum; - rangeChangedArgs.visibleMax = _visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _visibleRange!.interval; - _chart.onActualRangeChanged!(rangeChangedArgs); - _visibleRange!.minimum = rangeChangedArgs.visibleMin; - _visibleRange!.maximum = rangeChangedArgs.visibleMax; - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange!.delta / (range.delta); - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; + if (_axisDetails.chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs( + _axisDetails.name!, + _axisDetails._categoryAxis, + range.minimum, + range.maximum, + range.interval, + _axisDetails.orientation!); + rangeChangedArgs.visibleMin = _axisDetails.visibleRange!.minimum; + rangeChangedArgs.visibleMax = _axisDetails.visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _axisDetails.visibleRange!.interval; + _axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); + _axisDetails.visibleRange!.minimum = rangeChangedArgs.visibleMin; + _axisDetails.visibleRange!.maximum = rangeChangedArgs.visibleMax; + _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - + _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; + _axisDetails.zoomFactor = + _axisDetails.visibleRange!.delta / (range.delta); + _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - + _axisDetails.actualRange!.minimum) / + range.delta; } } /// Generates the visible axis labels. @override void generateVisibleLabels() { - num tempInterval = _visibleRange!.minimum.ceil(); + num tempInterval = _axisDetails.visibleRange!.minimum.ceil(); int position; String labelText; - _visibleLabels = []; + _axisDetails.visibleLabels = []; for (; - tempInterval <= _visibleRange!.maximum; - tempInterval += _visibleRange!.interval) { - if (_withInRange(tempInterval, _visibleRange!)) { + tempInterval <= _axisDetails.visibleRange!.maximum; + tempInterval += _axisDetails.visibleRange!.interval) { + if (withInRange(tempInterval, _axisDetails.visibleRange!)) { position = tempInterval.round(); if (position <= -1 || - (_labels.isNotEmpty && position >= _labels.length)) { + (_axisDetails.labels.isNotEmpty && + position >= _axisDetails.labels.length)) { continue; // ignore: unnecessary_null_comparison - } else if (_labels.isNotEmpty && _labels[position] != null) { - labelText = _labels[position]; + } else if (_axisDetails.labels.isNotEmpty && + _axisDetails.labels[position] != null) { + labelText = _axisDetails.labels[position]; } else { continue; } - _triggerLabelRenderEvent(labelText, tempInterval); + _axisDetails.triggerLabelRenderEvent(labelText, tempInterval); } } - _calculateMaximumLabelSize(this, _chartState); + _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); } /// Finds the interval of an axis. @override - num calculateInterval(_VisibleRange range, Size availableSize) => math + num calculateInterval(VisibleRange range, Size availableSize) => math .max( 1, - (_actualRange!.delta / - _calculateDesiredIntervalCount( - Size(_rect.width, _rect.height), this)) + (_axisDetails.actualRange!.delta / + _axisDetails.calculateDesiredIntervalCount( + Size(_axisDetails.rect.width, _axisDetails.rect.height), + this)) .floor()) .toInt(); } + +/// Represents the cartegory axis details class +class CategoryAxisDetails extends ChartAxisRendererDetails { + /// Creates an instance an in + CategoryAxisDetails(this._categoryAxis, ChartAxisRenderer axisRenderer, + CartesianStateProperties _stateProperties) + : super(_categoryAxis, _stateProperties, axisRenderer) { + labels = []; + } + + /// Specifies the list of labels + late List labels; + + /// Represents the rect value + late Rect rect; + final CategoryAxis _categoryAxis; + + /// Method to find the axis minimum and maximum value + void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, + CartesianChartPoint point, int pointIndex, int dataLength, + [bool? isXVisibleRange, bool? isYVisibleRange]) { + if (_categoryAxis.arrangeByIndex) { + // ignore: unnecessary_null_comparison + pointIndex < labels.length && labels[pointIndex] != null + ? labels[pointIndex] += ', ' + point.x + : labels.add(point.x.toString()); + point.xValue = pointIndex; + } else { + if (!labels.contains(point.x.toString())) { + labels.add(point.x.toString()); + } + point.xValue = labels.indexOf(point.x.toString()); + } + point.yValue = point.y; + setCategoryMinMaxValues(axisRenderer, isXVisibleRange!, isYVisibleRange!, + point, pointIndex, dataLength, seriesRendererDetails); + } + + /// Listener for range controller + void _controlListener() { + stateProperties.canSetRangeController = false; + if (_categoryAxis.rangeController != null && + !stateProperties.rangeChangedByChart) { + updateRangeControllerValues(this); + stateProperties.rangeChangeBySlider = true; + stateProperties.redrawByRangeChange(); + } + } + + /// Calculate the range and interval + void calculateRangeAndInterval(CartesianStateProperties stateProperties, + [String? type]) { + chart = stateProperties.chart; + if (axis.rangeController != null) { + stateProperties.rangeChangeBySlider = true; + axis.rangeController!.addListener(_controlListener); + } + final Rect containerRect = + stateProperties.renderingDetails.chartContainerRect; + rect = Rect.fromLTWH(containerRect.left, containerRect.top, + containerRect.width, containerRect.height); + axisSize = Size(rect.width, rect.height); + axisRenderer.calculateRange(axisRenderer); + _calculateActualRange(); + if (actualRange != null) { + axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); + if (type == null && type != 'AxisCross' && _categoryAxis.isVisible) { + axisRenderer.generateVisibleLabels(); + } + } + } + + /// Calculate the required values of the actual range for the category axis + void _calculateActualRange() { + if (min != null && max != null) { + actualRange = VisibleRange( + _categoryAxis.minimum ?? min, _categoryAxis.maximum ?? max); + CartesianSeriesRenderer seriesRenderer; + for (int i = 0; i < seriesRenderers.length; i++) { + seriesRenderer = seriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if ((actualRange!.maximum > + seriesRendererDetails.dataPoints.length - 1) == + true) { + for (int i = labels.length; i < actualRange!.maximum + 1; i++) { + labels.add(i.toString()); + } + } + } + actualRange = VisibleRange( + _categoryAxis.minimum ?? min, _categoryAxis.maximum ?? max); + + ///Below condition is for checking the min, max value is equal + if ((actualRange!.minimum == actualRange!.maximum) && + (_categoryAxis.labelPlacement == LabelPlacement.onTicks)) { + actualRange!.maximum += 1; + } + actualRange!.delta = actualRange!.maximum - actualRange!.minimum; + actualRange!.interval = _categoryAxis.interval ?? + axisRenderer.calculateInterval( + actualRange!, Size(rect.width, rect.height)); + actualRange!.delta = actualRange!.maximum - actualRange!.minimum; + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart index 3c90b1bfa..b78fce777 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_axis.dart @@ -1,4 +1,19 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../common/event_args.dart'; +import '../axis/axis.dart'; +import '../axis/plotband.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart' show updateErrorBarAxisRange; +import '../common/interactive_tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; /// This class holds the properties of the DateTime axis. /// @@ -315,31 +330,207 @@ class DateTimeAxis extends ChartAxis { /// Creates an axis renderer for Datetime axis class DateTimeAxisRenderer extends ChartAxisRenderer { /// Creating an argument constructor of DateTimeAxisRenderer class. - DateTimeAxisRenderer(this._dateTimeAxis) : super(_dateTimeAxis); - late DateTimeIntervalType _actualIntervalType; - late int _dateTimeInterval; + DateTimeAxisRenderer( + DateTimeAxis dateTimeAxis, CartesianStateProperties stateProperties) { + _axisDetails = DateTimeAxisDetails(dateTimeAxis, this, stateProperties); + AxisHelper.setAxisRendererDetails(this, _axisDetails); + } + + late DateTimeAxisDetails _axisDetails; + /// Applies range padding to auto, normal, additional, round, and none types. @override - late SfCartesianChart _chart; + void applyRangePadding(VisibleRange range, num? interval) { + _axisDetails.min = range.minimum.toInt(); + _axisDetails.max = range.maximum.toInt(); + ActualRangeChangedArgs rangeChangedArgs; + if (_axisDetails.dateTimeAxis.minimum == null && + _axisDetails.dateTimeAxis.maximum == null) { + final ChartRangePadding rangePadding = + _axisDetails.calculateRangePadding(this, _axisDetails.chart); + final DateTime minimum = + DateTime.fromMillisecondsSinceEpoch(_axisDetails.min!.toInt()); + final DateTime maximum = + DateTime.fromMillisecondsSinceEpoch(_axisDetails.max!.toInt()); + if (rangePadding == ChartRangePadding.none) { + _axisDetails.min = minimum.millisecondsSinceEpoch; + _axisDetails.max = maximum.millisecondsSinceEpoch; + } else if (rangePadding == ChartRangePadding.additional || + rangePadding == ChartRangePadding.round) { + switch (_axisDetails.actualIntervalType) { + case DateTimeIntervalType.years: + _axisDetails._calculateYear( + minimum, maximum, rangePadding, interval!.toInt()); + break; + case DateTimeIntervalType.months: + _axisDetails._calculateMonth( + minimum, maximum, rangePadding, interval!.toInt()); + break; + case DateTimeIntervalType.days: + _axisDetails._calculateDay( + minimum, maximum, rangePadding, interval!.toInt()); + break; + case DateTimeIntervalType.hours: + _axisDetails._calculateHour( + minimum, maximum, rangePadding, interval!.toInt()); + break; + case DateTimeIntervalType.minutes: + _axisDetails._calculateMinute( + minimum, maximum, rangePadding, interval!.toInt()); + break; + case DateTimeIntervalType.seconds: + _axisDetails._calculateSecond( + minimum, maximum, rangePadding, interval!.toInt()); + break; + case DateTimeIntervalType.milliseconds: + _axisDetails._calculateMilliSecond( + minimum, maximum, rangePadding, interval!.toInt()); + break; + case DateTimeIntervalType.auto: + break; + } + } + } + range.minimum = _axisDetails.min; + range.maximum = _axisDetails.max; + range.delta = range.maximum - range.minimum; + + calculateVisibleRange(_axisDetails.axisSize); + + /// Setting range as visible zoomRange + if ((_axisDetails.dateTimeAxis.visibleMinimum != null || + _axisDetails.dateTimeAxis.visibleMaximum != null) && + (_axisDetails.dateTimeAxis.visibleMinimum != + _axisDetails.dateTimeAxis.visibleMaximum) && + (!_axisDetails.stateProperties.isRedrawByZoomPan)) { + _axisDetails.stateProperties.isRedrawByZoomPan = false; + _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum ?? + (_axisDetails.dateTimeAxis.visibleMinimum != null + ? _axisDetails.dateTimeAxis.visibleMinimum!.millisecondsSinceEpoch + : _axisDetails.actualRange!.minimum); + _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum ?? + (_axisDetails.dateTimeAxis.visibleMaximum != null + ? _axisDetails.dateTimeAxis.visibleMaximum!.millisecondsSinceEpoch + : _axisDetails.actualRange!.maximum); + _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - + _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.interval = + calculateInterval(_axisDetails.visibleRange!, _axisDetails.axisSize); + _axisDetails.visibleRange!.interval = + interval != null && interval % 1 != 0 + ? interval + : _axisDetails.visibleRange!.interval; + _axisDetails.zoomFactor = + _axisDetails.visibleRange!.delta / (range.delta); + _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - + _axisDetails.actualRange!.minimum) / + range.delta; + } + if (_axisDetails.chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs( + _axisDetails.name!, + _axisDetails.dateTimeAxis, + range.minimum, + range.maximum, + range.interval, + _axisDetails.orientation!); + rangeChangedArgs.visibleMin = _axisDetails.visibleRange!.minimum; + rangeChangedArgs.visibleMax = _axisDetails.visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _axisDetails.visibleRange!.interval; + _axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); + _axisDetails.visibleRange!.minimum = + rangeChangedArgs.visibleMin is DateTime + ? rangeChangedArgs.visibleMin.millisecondsSinceEpoch + : rangeChangedArgs.visibleMin; + _axisDetails.visibleRange!.maximum = + rangeChangedArgs.visibleMax is DateTime + ? rangeChangedArgs.visibleMax.millisecondsSinceEpoch + : rangeChangedArgs.visibleMax; + _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - + _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; + _axisDetails.zoomFactor = + _axisDetails.visibleRange!.delta / (range.delta); + _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - + _axisDetails.actualRange!.minimum) / + range.delta; + } + } + + /// Calculates the visible range for an axis in chart. @override - late Size _axisSize; + void calculateVisibleRange(Size availableSize) { + calculateDateTimeVisibleRange(availableSize, this); + } + + /// Generates the visible axis labels. + @override + void generateVisibleLabels() { + _axisDetails.visibleLabels = []; + int interval = _axisDetails.visibleRange!.minimum; + interval = _axisDetails._alignRangeStart( + this, interval, _axisDetails.visibleRange!.interval); + while (interval <= _axisDetails.visibleRange!.maximum) { + if (withInRange(interval, _axisDetails.visibleRange!)) { + final DateFormat format = _axisDetails.dateTimeAxis.dateFormat ?? + getDateTimeLabelFormat(this); + String labelText = + format.format(DateTime.fromMillisecondsSinceEpoch(interval)); + if (_axisDetails.dateTimeAxis.labelFormat != null && + _axisDetails.dateTimeAxis.labelFormat != '') { + labelText = _axisDetails.dateTimeAxis.labelFormat! + .replaceAll(RegExp('{value}'), labelText); + } + _axisDetails.triggerLabelRenderEvent(labelText, interval); + } + interval = _axisDetails + ._increaseDateTimeInterval( + this, interval, _axisDetails.visibleRange!.interval) + .millisecondsSinceEpoch; + } + _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); + } + + /// Finds the interval of an axis. + @override + num calculateInterval(VisibleRange range, Size availableSize) => + calculateDateTimeNiceInterval(this, _axisDetails.axisSize, range).floor(); +} - final DateTimeAxis _dateTimeAxis; +/// Represents the date time axis details +class DateTimeAxisDetails extends ChartAxisRendererDetails { + ///Argument constructor for DateTimeAxisDetails class + DateTimeAxisDetails(this.dateTimeAxis, ChartAxisRenderer axisRenderer, + CartesianStateProperties stateProperties) + : super(dateTimeAxis, stateProperties, axisRenderer); + + /// Holds the value of actual interval type + late DateTimeIntervalType actualIntervalType; + + /// Holds the value of date time interval + late int dateTimeInterval; + + /// Specifies the value of date time axis + final DateTimeAxis dateTimeAxis; + + /// To check the series has only one data point + bool isSingleDataPoint = false; /// Find the series min and max values of an series - void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, + void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, int pointIndex, int dataLength, [bool? isXVisibleRange, bool? isYVisibleRange]) { + isSingleDataPoint = seriesRendererDetails.dataPoints.length == 1; if (point.x != null) { point.xValue = (point.x).millisecondsSinceEpoch; } final bool _anchorRangeToVisiblePoints = - seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; - final String seriesType = seriesRenderer._seriesType; + seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; + final String seriesType = seriesRendererDetails.seriesType; point.yValue = point.y; if (isYVisibleRange!) { - seriesRenderer._minimumX ??= point.xValue; - seriesRenderer._maximumX ??= point.xValue; + seriesRendererDetails.minimumX ??= point.xValue; + seriesRendererDetails.maximumX ??= point.xValue; } if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && !seriesType.contains('range') && @@ -347,14 +538,14 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { !seriesType.contains('candle') && seriesType != 'boxandwhisker' && seriesType != 'waterfall') { - seriesRenderer._minimumY ??= point.yValue; - seriesRenderer._maximumY ??= point.yValue; + seriesRendererDetails.minimumY ??= point.yValue; + seriesRendererDetails.maximumY ??= point.yValue; } if (isYVisibleRange && point.xValue != null) { - seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX!, point.xValue); - seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX!, point.xValue); + seriesRendererDetails.minimumX = + math.min(seriesRendererDetails.minimumX!, point.xValue as num); + seriesRendererDetails.maximumX = + math.max(seriesRendererDetails.maximumX!, point.xValue as num); } if (isXVisibleRange || !_anchorRangeToVisiblePoints) { if (point.yValue != null && @@ -363,34 +554,37 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { !seriesType.contains('candle') && seriesType != 'boxandwhisker' && seriesType != 'waterfall')) { - seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY!, point.yValue); - seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY!, point.yValue); + seriesRendererDetails.minimumY = + math.min(seriesRendererDetails.minimumY!, point.yValue as num); + seriesRendererDetails.maximumY = + math.max(seriesRendererDetails.maximumY!, point.yValue as num); } if (point.high != null) { - _highMin = math.min(_highMin ?? point.high, point.high); - _highMax = math.max(_highMax ?? point.high, point.high); + highMin = math.min(highMin ?? point.high, point.high); + highMax = math.max(highMax ?? point.high, point.high); } if (point.low != null) { - _lowMin = math.min(_lowMin ?? point.low, point.low); - _lowMax = math.max(_lowMax ?? point.low, point.low); + lowMin = math.min(lowMin ?? point.low, point.low); + lowMax = math.max(lowMax ?? point.low, point.low); } if (point.maximum != null) { - _highMin = _findMinValue(_highMin ?? point.maximum!, point.maximum!); - _highMax = _findMaxValue(_highMax ?? point.maximum!, point.maximum!); + highMin = findMinValue(highMin ?? point.maximum!, point.maximum!); + highMax = findMaxValue(highMax ?? point.maximum!, point.maximum!); } if (point.minimum != null) { - _lowMin = _findMinValue(_lowMin ?? point.minimum!, point.minimum!); - _lowMax = _findMaxValue(_lowMax ?? point.minimum!, point.minimum!); + lowMin = findMinValue(lowMin ?? point.minimum!, point.minimum!); + lowMax = findMaxValue(lowMax ?? point.minimum!, point.minimum!); } if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. point.yValue ??= 0; - seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY ?? point.yValue, point.yValue); - seriesRenderer._maximumY = math.max( - seriesRenderer._maximumY ?? point.maxYValue, point.maxYValue); + seriesRendererDetails.minimumY = math.min( + (seriesRendererDetails.minimumY ?? point.yValue) as num, + point.yValue as num); + seriesRendererDetails.maximumY = math.max( + seriesRendererDetails.maximumY ?? point.maxYValue, point.maxYValue); + } else if (seriesType == 'errorbar') { + updateErrorBarAxisRange(seriesRendererDetails, point); } } if (pointIndex >= dataLength - 1) { @@ -398,47 +592,47 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { seriesType.contains('hilo') || seriesType.contains('candle') || seriesType == 'boxandwhisker') { - _lowMin ??= 0; - _lowMax ??= 5; - _highMin ??= 0; - _highMax ??= 5; - seriesRenderer._minimumY = math.min(_lowMin!, _highMin!); - seriesRenderer._maximumY = math.max(_lowMax!, _highMax!); + lowMin ??= 0; + lowMax ??= 5; + highMin ??= 0; + highMax ??= 5; + seriesRendererDetails.minimumY = math.min(lowMin!, highMin!); + seriesRendererDetails.maximumY = math.max(lowMax!, highMax!); } - seriesRenderer._minimumX ??= 2717008000; - seriesRenderer._maximumX ??= 13085008000; + seriesRendererDetails.minimumX ??= 2717008000; + seriesRendererDetails.maximumX ??= 13085008000; } } /// Listener for range controller void _controlListener() { - _chartState._canSetRangeController = false; - if (_axis.rangeController != null && !_chartState._rangeChangedByChart) { - _updateRangeControllerValues(this); - _chartState._rangeChangeBySlider = true; - _chartState._redrawByRangeChange(); + stateProperties.canSetRangeController = false; + if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { + updateRangeControllerValues(this); + stateProperties.rangeChangeBySlider = true; + stateProperties.redrawByRangeChange(); } } /// Calculate axis range and interval - void _calculateRangeAndInterval(SfCartesianChartState chartState, + void calculateRangeAndInterval(CartesianStateProperties stateProperties, [String? type]) { - _chartState = chartState; - _chart = chartState._chart; - if (_axis.rangeController != null) { - _chartState._rangeChangeBySlider = true; - _axis.rangeController!.addListener(_controlListener); + chart = stateProperties.chart; + if (axis.rangeController != null) { + stateProperties.rangeChangeBySlider = true; + axis.rangeController!.addListener(_controlListener); } - final Rect containerRect = _chartState._renderingDetails.chartContainerRect; + final Rect containerRect = + stateProperties.renderingDetails.chartContainerRect; final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, containerRect.width, containerRect.height); - _axisSize = Size(rect.width, rect.height); - calculateRange(this); + axisSize = Size(rect.width, rect.height); + axisRenderer.calculateRange(axisRenderer); _calculateActualRange(); - if (_actualRange != null) { - applyRangePadding(_actualRange!, _actualRange!.interval); - if (type == null && type != 'AxisCross' && _dateTimeAxis.isVisible) { - generateVisibleLabels(); + if (actualRange != null) { + axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); + if (type == null && type != 'AxisCross' && dateTimeAxis.isVisible) { + axisRenderer.generateVisibleLabels(); } } } @@ -446,30 +640,40 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { /// Calculate the required values of the actual range for the date-time axis void _calculateActualRange() { ///When chart series is empty, Rendering default chart with below min, max - _min ??= 2717008000; - _max ??= 13085008000; - _actualRange = _VisibleRange( - _dateTimeAxis.minimum != null - ? _dateTimeAxis.minimum!.millisecondsSinceEpoch - : _min, - _dateTimeAxis.maximum != null - ? _dateTimeAxis.maximum!.millisecondsSinceEpoch - : _max); - if (_actualRange!.minimum == _actualRange!.maximum) { - _actualRange!.minimum = _actualRange!.minimum - 2592000000; - _actualRange!.maximum = _actualRange!.maximum + 2592000000; + min ??= 2717008000; + max ??= 13085008000; + //Default date-time value (January 1, 1970) converted into milliseconds + const int defaultTimeStamp = 2592000000; + actualRange = VisibleRange( + dateTimeAxis.minimum != null + ? dateTimeAxis.minimum!.millisecondsSinceEpoch + : min, + dateTimeAxis.maximum != null + ? dateTimeAxis.maximum!.millisecondsSinceEpoch + : max); + if (actualRange!.minimum == actualRange!.maximum) { + if (isSingleDataPoint && + dateTimeAxis.autoScrollingDelta != null && + dateTimeAxis.autoScrollingDelta! > 0) { + final num minMilliSeconds = const Duration(days: 1).inMilliseconds; + actualRange!.minimum = actualRange!.minimum - minMilliSeconds; + } else { + actualRange!.minimum = actualRange!.minimum - defaultTimeStamp; + actualRange!.maximum = actualRange!.maximum + defaultTimeStamp; + } } - _dateTimeInterval = - _calculateDateTimeNiceInterval(this, _axisSize, _actualRange!).floor(); - _actualRange!.interval = _dateTimeAxis.interval ?? _dateTimeInterval; - _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; + dateTimeInterval = + calculateDateTimeNiceInterval(axisRenderer, axisSize, actualRange!) + .floor(); + actualRange!.interval = dateTimeAxis.interval ?? dateTimeInterval; + actualRange!.delta = actualRange!.maximum - actualRange!.minimum; } /// Returns the range start values based on actual interval type int _alignRangeStart( DateTimeAxisRenderer axisRenderer, int startDate, num interval) { DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(startDate); - switch (axisRenderer._actualIntervalType) { + switch (axisRenderer._axisDetails.actualIntervalType) { case DateTimeIntervalType.years: final int year = ((dateTime.year / interval).floor() * interval).floor(); @@ -517,12 +721,12 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { DateTime _increaseDateTimeInterval( DateTimeAxisRenderer axisRenderer, int value, num dateInterval) { DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(value); - axisRenderer._visibleRange!.interval = dateInterval; + axisRenderer._axisDetails.visibleRange!.interval = dateInterval; final bool isIntervalDecimal = dateInterval % 1 == 0; final num interval = dateInterval; if (isIntervalDecimal) { final int interval = dateInterval.floor(); - switch (axisRenderer._actualIntervalType) { + switch (axisRenderer._axisDetails.actualIntervalType) { case DateTimeIntervalType.years: dateTime = DateTime(dateTime.year + interval, dateTime.month, dateTime.day, dateTime.hour, dateTime.minute, dateTime.second, 0); @@ -567,7 +771,7 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { break; } } else { - switch (axisRenderer._actualIntervalType) { + switch (axisRenderer._axisDetails.actualIntervalType) { case DateTimeIntervalType.years: dateTime = DateTime( dateTime.year, @@ -651,12 +855,12 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { final int startYear = minimum.year; final int endYear = maximum.year; if (rangePadding == ChartRangePadding.additional) { - _min = + min = DateTime(startYear - interval, 1, 1, 0, 0, 0).millisecondsSinceEpoch; - _max = DateTime(endYear + interval, 1, 1, 0, 0, 0).millisecondsSinceEpoch; + max = DateTime(endYear + interval, 1, 1, 0, 0, 0).millisecondsSinceEpoch; } else { - _min = DateTime(startYear, 0, 0, 0, 0, 0).millisecondsSinceEpoch; - _max = DateTime(endYear, 11, 30, 23, 59, 59).millisecondsSinceEpoch; + min = DateTime(startYear, 0, 0, 0, 0, 0).millisecondsSinceEpoch; + max = DateTime(endYear, 11, 30, 23, 59, 59).millisecondsSinceEpoch; } } @@ -666,16 +870,16 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { final int startMonth = minimum.month; final int endMonth = maximum.month; if (rangePadding == ChartRangePadding.round) { - _min = + min = DateTime(minimum.year, startMonth, 0, 0, 0, 0).millisecondsSinceEpoch; - _max = DateTime(maximum.year, endMonth, + max = DateTime(maximum.year, endMonth, DateTime(maximum.year, maximum.month, 0).day, 23, 59, 59) .millisecondsSinceEpoch; } else { - _min = DateTime(minimum.year, startMonth + (-interval), 1, 0, 0, 0) + min = DateTime(minimum.year, startMonth + (-interval), 1, 0, 0, 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, endMonth + interval, - endMonth == 2 ? 28 : 30, 0, 0, 0) + max = DateTime(maximum.year, endMonth + interval, endMonth == 2 ? 28 : 30, + 0, 0, 0) .millisecondsSinceEpoch; } } @@ -686,15 +890,15 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { final int startDay = minimum.day; final int endDay = maximum.day; if (rangePadding == ChartRangePadding.round) { - _min = DateTime(minimum.year, minimum.month, startDay, 0, 0, 0) + min = DateTime(minimum.year, minimum.month, startDay, 0, 0, 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, endDay, 23, 59, 59) + max = DateTime(maximum.year, maximum.month, endDay, 23, 59, 59) .millisecondsSinceEpoch; } else { - _min = + min = DateTime(minimum.year, minimum.month, startDay + (-interval), 0, 0, 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, endDay + interval, 0, 0, 0) + max = DateTime(maximum.year, maximum.month, endDay + interval, 0, 0, 0) .millisecondsSinceEpoch; } } @@ -705,16 +909,16 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { final int startHour = ((minimum.hour / interval) * interval).toInt(); final int endHour = maximum.hour + (minimum.hour - startHour).toInt(); if (rangePadding == ChartRangePadding.round) { - _min = DateTime(minimum.year, minimum.month, minimum.day, startHour, 0, 0) + min = DateTime(minimum.year, minimum.month, minimum.day, startHour, 0, 0) .millisecondsSinceEpoch; - _max = + max = DateTime(maximum.year, maximum.month, maximum.day, startHour, 59, 59) .millisecondsSinceEpoch; } else { - _min = DateTime(minimum.year, minimum.month, minimum.day, + min = DateTime(minimum.year, minimum.month, minimum.day, startHour + (-interval), 0, 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, maximum.day, + max = DateTime(maximum.year, maximum.month, maximum.day, endHour + interval, 0, 0) .millisecondsSinceEpoch; } @@ -727,17 +931,17 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { final int endMinute = maximum.minute + (minimum.minute - startMinute).toInt(); if (rangePadding == ChartRangePadding.round) { - _min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, + min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, startMinute, 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, + max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, endMinute, 59) .millisecondsSinceEpoch; } else { - _min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, + min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, startMinute + (-interval), 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, + max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, endMinute + interval, 0) .millisecondsSinceEpoch; } @@ -750,17 +954,17 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { final int endSecond = maximum.second + (minimum.second - startSecond).toInt(); if (rangePadding == ChartRangePadding.round) { - _min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, + min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, minimum.minute, startSecond, 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, + max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, maximum.minute, endSecond, 0) .millisecondsSinceEpoch; } else { - _min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, + min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, minimum.minute, startSecond + (-interval), 0) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, + max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, maximum.minute, endSecond + interval, 0) .millisecondsSinceEpoch; } @@ -774,170 +978,37 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { final int endMilliSecond = maximum.millisecond + (minimum.millisecond - startMilliSecond).toInt(); if (rangePadding == ChartRangePadding.round) { - _min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, + min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, minimum.minute, minimum.second, startMilliSecond) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, + max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, maximum.minute, maximum.second, endMilliSecond) .millisecondsSinceEpoch; } else { - _min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, + min = DateTime(minimum.year, minimum.month, minimum.day, minimum.hour, minimum.minute, minimum.second, startMilliSecond + (-interval)) .millisecondsSinceEpoch; - _max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, + max = DateTime(maximum.year, maximum.month, maximum.day, maximum.hour, maximum.minute, maximum.second, endMilliSecond + interval) .millisecondsSinceEpoch; } } - /// Applies range padding to auto, normal, additional, round, and none types. - @override - void applyRangePadding(_VisibleRange range, num? interval) { - _min = range.minimum.toInt(); - _max = range.maximum.toInt(); - ActualRangeChangedArgs rangeChangedArgs; - if (_dateTimeAxis.minimum == null && _dateTimeAxis.maximum == null) { - final ChartRangePadding rangePadding = - _calculateRangePadding(this, _chart); - final DateTime minimum = - DateTime.fromMillisecondsSinceEpoch(_min!.toInt()); - final DateTime maximum = - DateTime.fromMillisecondsSinceEpoch(_max!.toInt()); - if (rangePadding == ChartRangePadding.none) { - _min = minimum.millisecondsSinceEpoch; - _max = maximum.millisecondsSinceEpoch; - } else if (rangePadding == ChartRangePadding.additional || - rangePadding == ChartRangePadding.round) { - switch (_actualIntervalType) { - case DateTimeIntervalType.years: - _calculateYear(minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.months: - _calculateMonth(minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.days: - _calculateDay(minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.hours: - _calculateHour(minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.minutes: - _calculateMinute(minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.seconds: - _calculateSecond(minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.milliseconds: - _calculateMilliSecond( - minimum, maximum, rangePadding, interval!.toInt()); - break; - case DateTimeIntervalType.auto: - break; - } - } - } - range.minimum = _min; - range.maximum = _max; - range.delta = range.maximum - range.minimum; - - calculateVisibleRange(_axisSize); - - /// Setting range as visible zoomRange - if ((_dateTimeAxis.visibleMinimum != null || - _dateTimeAxis.visibleMaximum != null) && - (_dateTimeAxis.visibleMinimum != _dateTimeAxis.visibleMaximum) && - (!_chartState._isRedrawByZoomPan)) { - _chartState._isRedrawByZoomPan = false; - _visibleRange!.minimum = _visibleMinimum ?? - (_dateTimeAxis.visibleMinimum != null - ? _dateTimeAxis.visibleMinimum!.millisecondsSinceEpoch - : _visibleRange!.minimum); - _visibleRange!.maximum = _visibleMaximum ?? - (_dateTimeAxis.visibleMaximum != null - ? _dateTimeAxis.visibleMaximum!.millisecondsSinceEpoch - : _visibleRange!.maximum); - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = calculateInterval(_visibleRange!, _axisSize); - _visibleRange!.interval = interval != null && interval % 1 != 0 - ? interval - : _visibleRange!.interval; - _zoomFactor = _visibleRange!.delta / (range.delta); - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; - } - if (_chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs(_name!, _dateTimeAxis, - range.minimum, range.maximum, range.interval, _orientation!); - rangeChangedArgs.visibleMin = _visibleRange!.minimum; - rangeChangedArgs.visibleMax = _visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _visibleRange!.interval; - _chart.onActualRangeChanged!(rangeChangedArgs); - _visibleRange!.minimum = rangeChangedArgs.visibleMin is DateTime - ? rangeChangedArgs.visibleMin.millisecondsSinceEpoch - : rangeChangedArgs.visibleMin; - _visibleRange!.maximum = rangeChangedArgs.visibleMax is DateTime - ? rangeChangedArgs.visibleMax.millisecondsSinceEpoch - : rangeChangedArgs.visibleMax; - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange!.delta / (range.delta); - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; - } - } - - /// Calculates the visible range for an axis in chart. - @override - void calculateVisibleRange(Size availableSize) { - _calculateDateTimeVisibleRange(availableSize, this); - } - - /// Generates the visible axis labels. - @override - void generateVisibleLabels() { - _visibleLabels = []; - int interval = _visibleRange!.minimum; - interval = _alignRangeStart(this, interval, _visibleRange!.interval); - while (interval <= _visibleRange!.maximum) { - if (_withInRange(interval, _visibleRange!)) { - final DateFormat format = - _dateTimeAxis.dateFormat ?? _getDateTimeLabelFormat(this); - String labelText = - format.format(DateTime.fromMillisecondsSinceEpoch(interval)); - if (_dateTimeAxis.labelFormat != null && - _dateTimeAxis.labelFormat != '') { - labelText = _dateTimeAxis.labelFormat! - .replaceAll(RegExp('{value}'), labelText); - } - _triggerLabelRenderEvent(labelText, interval); - } - interval = - _increaseDateTimeInterval(this, interval, _visibleRange!.interval) - .millisecondsSinceEpoch; - } - _calculateMaximumLabelSize(this, _chartState); - } - - /// Finds the interval of an axis. - @override - num calculateInterval(_VisibleRange range, Size availableSize) => - _calculateDateTimeNiceInterval(this, _axisSize, range).floor(); - ///Auto Scrolling feature - void _updateScrollingDelta() { - if (_dateTimeAxis.autoScrollingDelta != null && - _dateTimeAxis.autoScrollingDelta! > 0) { + void updateScrollingDelta() { + if (dateTimeAxis.autoScrollingDelta != null && + dateTimeAxis.autoScrollingDelta! > 0) { final DateTimeIntervalType intervalType = - _dateTimeAxis.autoScrollingDeltaType == DateTimeIntervalType.auto - ? _actualIntervalType - : _dateTimeAxis.autoScrollingDeltaType; + dateTimeAxis.autoScrollingDeltaType == DateTimeIntervalType.auto + ? actualIntervalType + : dateTimeAxis.autoScrollingDeltaType; int scrollingDelta; DateTime dateTime = - DateTime.fromMillisecondsSinceEpoch(_visibleRange!.maximum); + DateTime.fromMillisecondsSinceEpoch(visibleRange!.maximum); switch (intervalType) { case DateTimeIntervalType.years: dateTime = DateTime( - dateTime.year - _dateTimeAxis.autoScrollingDelta!, + dateTime.year - dateTimeAxis.autoScrollingDelta!, dateTime.month, dateTime.day, dateTime.hour, @@ -946,12 +1017,12 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { dateTime.millisecond, dateTime.microsecond); scrollingDelta = - _visibleRange!.maximum - dateTime.millisecondsSinceEpoch; + visibleRange!.maximum - dateTime.millisecondsSinceEpoch; break; case DateTimeIntervalType.months: dateTime = DateTime( dateTime.year, - dateTime.month - _dateTimeAxis.autoScrollingDelta!, + dateTime.month - dateTimeAxis.autoScrollingDelta!, dateTime.day, dateTime.hour, dateTime.minute, @@ -959,34 +1030,34 @@ class DateTimeAxisRenderer extends ChartAxisRenderer { dateTime.millisecond, dateTime.microsecond); scrollingDelta = - _visibleRange!.maximum - dateTime.millisecondsSinceEpoch; + visibleRange!.maximum - dateTime.millisecondsSinceEpoch; break; case DateTimeIntervalType.days: scrollingDelta = - Duration(days: _dateTimeAxis.autoScrollingDelta!).inMilliseconds; + Duration(days: dateTimeAxis.autoScrollingDelta!).inMilliseconds; break; case DateTimeIntervalType.hours: scrollingDelta = - Duration(hours: _dateTimeAxis.autoScrollingDelta!).inMilliseconds; + Duration(hours: dateTimeAxis.autoScrollingDelta!).inMilliseconds; break; case DateTimeIntervalType.minutes: - scrollingDelta = Duration(minutes: _dateTimeAxis.autoScrollingDelta!) + scrollingDelta = Duration(minutes: dateTimeAxis.autoScrollingDelta!) .inMilliseconds; break; case DateTimeIntervalType.seconds: - scrollingDelta = Duration(seconds: _dateTimeAxis.autoScrollingDelta!) + scrollingDelta = Duration(seconds: dateTimeAxis.autoScrollingDelta!) .inMilliseconds; break; case DateTimeIntervalType.milliseconds: scrollingDelta = - Duration(milliseconds: _dateTimeAxis.autoScrollingDelta!) + Duration(milliseconds: dateTimeAxis.autoScrollingDelta!) .inMilliseconds; break; case DateTimeIntervalType.auto: - scrollingDelta = _dateTimeAxis.autoScrollingDelta!; + scrollingDelta = dateTimeAxis.autoScrollingDelta!; break; } - super._updateAutoScrollingDelta(scrollingDelta, this); + super.updateAutoScrollingDelta(scrollingDelta, axisRenderer); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart index f2287aca9..e96de097f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/datetime_category_axis.dart @@ -1,4 +1,18 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../common/event_args.dart'; +import '../axis/axis.dart'; +import '../axis/plotband.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/interactive_tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; ///An axis which is used to plot date-time values. It is similar to DateTimeAxis except that it /// excludes missing dates. @@ -334,241 +348,308 @@ class DateTimeCategoryAxis extends ChartAxis { /// Creates an axis renderer for Category axis class DateTimeCategoryAxisRenderer extends ChartAxisRenderer { /// Creating an argument constructor of CategoryAxisRenderer class. - DateTimeCategoryAxisRenderer(this._dateTimeCategoryAxis) - : super(_dateTimeCategoryAxis) { - _labels = []; + DateTimeCategoryAxisRenderer(DateTimeCategoryAxis dateTimeCategoryAxis, + CartesianStateProperties stateProperties) { + _axisDetails = DateTimeCategoryAxisDetails( + dateTimeCategoryAxis, stateProperties, this); + AxisHelper.setAxisRendererDetails(this, _axisDetails); } - late List _labels; - late Rect _rect; - final DateTimeCategoryAxis _dateTimeCategoryAxis; - late DateTimeIntervalType _actualIntervalType; - DateFormat? _dateTimeFormat; - DateFormat get _dateFormat => - _dateTimeFormat ?? _getDateTimeLabelFormat(this); - /// Find the series min and max values of an series - void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint point, int pointIndex, int dataLength, - [bool? isXVisibleRange, bool? isYVisibleRange]) { - final bool _anchorRangeToVisiblePoints = - seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; - final String seriesType = seriesRenderer._seriesType; - - if (!_labels.contains('${point.x.microsecondsSinceEpoch}')) { - _labels.add('${point.x.microsecondsSinceEpoch}'); - } - point.xValue = _labels.indexOf('${point.x.microsecondsSinceEpoch}'); - point.yValue = point.y; - if (isYVisibleRange!) { - seriesRenderer._minimumX ??= point.xValue; - seriesRenderer._maximumX ??= point.xValue; - } - if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && - !seriesType.contains('range') && - !seriesType.contains('hilo') && - !seriesType.contains('candle') && - seriesType != 'boxandwhisker' && - seriesType != 'waterfall') { - seriesRenderer._minimumY ??= point.yValue; - seriesRenderer._maximumY ??= point.yValue; - } - _setCategoryMinMaxValues(this, isXVisibleRange, isYVisibleRange, point, - pointIndex, dataLength, seriesRenderer); - } - - /// Listener for range controller - void _controlListener() { - _chartState._canSetRangeController = false; - if (_axis.rangeController != null && !_chartState._rangeChangedByChart) { - _updateRangeControllerValues(this); - _chartState._rangeChangeBySlider = true; - _chartState._redrawByRangeChange(); - } - } - - /// Calculate axis range and interval - void _calculateRangeAndInterval(SfCartesianChartState chartState, - [String? type]) { - _chartState = chartState; - _chart = chartState._chart; - if (_axis.rangeController != null) { - _chartState._rangeChangeBySlider = true; - _axis.rangeController!.addListener(_controlListener); - } - final Rect containerRect = _chartState._renderingDetails.chartContainerRect; - _rect = Rect.fromLTWH(containerRect.left, containerRect.top, - containerRect.width, containerRect.height); - _axisSize = Size(_rect.width, _rect.height); - calculateRange(this); - _calculateActualRange(); - if (_actualRange != null) { - applyRangePadding(_actualRange!, _actualRange!.interval); - if (type == null && - type != 'AxisCross' && - _dateTimeCategoryAxis.isVisible) { - generateVisibleLabels(); - } - } - } - - /// Calculate the required values of the actual range for the date-time axis - void _calculateActualRange() { - ///When chart series is empty, Rendering default chart with below min, max - _min ??= 0; - _max ??= 5; - _actualRange = _VisibleRange( - _dateTimeCategoryAxis.minimum != null - ? _getEffectiveRange(_dateTimeCategoryAxis.minimum, true) - : _min, - _dateTimeCategoryAxis.maximum != null - ? _getEffectiveRange(_dateTimeCategoryAxis.maximum, false) - : _max); - - ///Below condition is for checking the min, max value is equal - if ((_actualRange!.minimum == _actualRange!.maximum) && - (_dateTimeCategoryAxis.labelPlacement == LabelPlacement.onTicks)) { - _actualRange!.maximum += 1; - } - if (_labels.isNotEmpty) { - final List startAndEnd = _getStartAndEndDate(_labels); - _calculateDateTimeNiceInterval( - this, _axisSize, _actualRange!, startAndEnd[0], startAndEnd[1]) - .floor(); - } else { - _actualIntervalType = DateTimeIntervalType.days; - } - _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; - _actualRange!.interval = _dateTimeCategoryAxis.interval ?? - calculateInterval(_actualRange!, Size(_rect.width, _rect.height)); - } + late DateTimeCategoryAxisDetails _axisDetails; /// Applies range padding to auto, normal, additional, round, and none types. @override - void applyRangePadding(_VisibleRange range, num? interval) { + void applyRangePadding(VisibleRange range, num? interval) { ActualRangeChangedArgs rangeChangedArgs; - if (_dateTimeCategoryAxis.labelPlacement == LabelPlacement.betweenTicks) { + if (_axisDetails.dateTimeCategoryAxis.labelPlacement == + LabelPlacement.betweenTicks) { range.minimum -= 0.5; range.maximum += 0.5; range.delta = range.maximum - range.minimum; } - if (_dateTimeCategoryAxis.isVisible && - !(_dateTimeCategoryAxis.minimum != null && - _dateTimeCategoryAxis.maximum != null)) { + if (_axisDetails.dateTimeCategoryAxis.isVisible && + !(_axisDetails.dateTimeCategoryAxis.minimum != null && + _axisDetails.dateTimeCategoryAxis.maximum != null)) { ///Calculating range padding - _applyRangePadding(this, _chartState, range, interval!); + _axisDetails.applyRangePaddings( + this, _axisDetails.stateProperties, range, interval!); } - calculateVisibleRange(Size(_rect.width, _rect.height)); + calculateVisibleRange( + Size(_axisDetails.rect.width, _axisDetails.rect.height)); /// Setting range as visible zoomRange - if ((_dateTimeCategoryAxis.visibleMinimum != null || - _dateTimeCategoryAxis.visibleMaximum != null) && - (_dateTimeCategoryAxis.visibleMinimum != - _dateTimeCategoryAxis.visibleMaximum) && - _chartState._zoomedAxisRendererStates.isEmpty) { - _visibleRange!.minimum = _visibleMinimum != null - ? _getEffectiveRange( - DateTime.fromMillisecondsSinceEpoch(_visibleMinimum!.toInt()), + if ((_axisDetails.dateTimeCategoryAxis.visibleMinimum != null || + _axisDetails.dateTimeCategoryAxis.visibleMaximum != null) && + (_axisDetails.dateTimeCategoryAxis.visibleMinimum != + _axisDetails.dateTimeCategoryAxis.visibleMaximum) && + _axisDetails.stateProperties.zoomedAxisRendererStates.isEmpty) { + _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum != null + ? _axisDetails.getEffectiveRange( + DateTime.fromMillisecondsSinceEpoch( + _axisDetails.visibleMinimum!.toInt()), true) - : _getEffectiveRange(_dateTimeCategoryAxis.visibleMinimum, true) ?? - _visibleRange!.minimum; - _visibleRange!.maximum = _visibleMaximum != null - ? _getEffectiveRange( - DateTime.fromMillisecondsSinceEpoch(_visibleMaximum!.toInt()), + : _axisDetails.getEffectiveRange( + _axisDetails.dateTimeCategoryAxis.visibleMinimum, true) ?? + _axisDetails.actualRange!.minimum; + _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum != null + ? _axisDetails.getEffectiveRange( + DateTime.fromMillisecondsSinceEpoch( + _axisDetails.visibleMaximum!.toInt()), false) - : _getEffectiveRange(_dateTimeCategoryAxis.visibleMaximum, false) ?? - _visibleRange!.maximum; - if (_dateTimeCategoryAxis.labelPlacement == LabelPlacement.betweenTicks) { - _visibleRange!.minimum = _visibleMinimum != null - ? _getEffectiveRange( + : _axisDetails.getEffectiveRange( + _axisDetails.dateTimeCategoryAxis.visibleMaximum, false) ?? + _axisDetails.actualRange!.maximum; + if (_axisDetails.dateTimeCategoryAxis.labelPlacement == + LabelPlacement.betweenTicks) { + _axisDetails.visibleRange!.minimum = _axisDetails.visibleMinimum != null + ? _axisDetails.getEffectiveRange( DateTime.fromMillisecondsSinceEpoch( - _visibleMinimum!.toInt()), + _axisDetails.visibleMinimum!.toInt()), true)! - 0.5 - : (_dateTimeCategoryAxis.visibleMinimum != null - ? _getEffectiveRange( - _dateTimeCategoryAxis.visibleMinimum, true)! - + : (_axisDetails.dateTimeCategoryAxis.visibleMinimum != null + ? _axisDetails.getEffectiveRange( + _axisDetails.dateTimeCategoryAxis.visibleMinimum, + true)! - 0.5 - : _visibleRange!.minimum); - _visibleRange!.maximum = _visibleMaximum != null - ? _getEffectiveRange( + : _axisDetails.visibleRange!.minimum); + _axisDetails.visibleRange!.maximum = _axisDetails.visibleMaximum != null + ? _axisDetails.getEffectiveRange( DateTime.fromMillisecondsSinceEpoch( - _visibleMaximum!.toInt()), + _axisDetails.visibleMaximum!.toInt()), false)! + 0.5 - : (_dateTimeCategoryAxis.visibleMaximum != null - ? _getEffectiveRange( - _dateTimeCategoryAxis.visibleMaximum, false)! + + : (_axisDetails.dateTimeCategoryAxis.visibleMaximum != null + ? _axisDetails.getEffectiveRange( + _axisDetails.dateTimeCategoryAxis.visibleMaximum, + false)! + 0.5 - : _visibleRange!.maximum); + : _axisDetails.visibleRange!.maximum); } - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = interval == null - ? calculateInterval(_visibleRange!, _axisSize) - : _visibleRange!.interval; - _zoomFactor = _visibleRange!.delta / (range.delta); - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; + _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - + _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.interval = interval == null + ? calculateInterval(_axisDetails.visibleRange!, _axisDetails.axisSize) + : _axisDetails.visibleRange!.interval; + _axisDetails.zoomFactor = + _axisDetails.visibleRange!.delta / (range.delta); + _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - + _axisDetails.actualRange!.minimum) / + range.delta; } - if (_chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs(_name!, _dateTimeCategoryAxis, - range.minimum, range.maximum, range.interval, _orientation!); - rangeChangedArgs.visibleMin = _visibleRange!.minimum; - rangeChangedArgs.visibleMax = _visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _visibleRange!.interval; - _chart.onActualRangeChanged!(rangeChangedArgs); - _visibleRange!.minimum = rangeChangedArgs.visibleMin is DateTime - ? _getEffectiveRange(rangeChangedArgs.visibleMin, true) + if (_axisDetails.chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs( + _axisDetails.name!, + _axisDetails.dateTimeCategoryAxis, + range.minimum, + range.maximum, + range.interval, + _axisDetails.orientation!); + rangeChangedArgs.visibleMin = _axisDetails.visibleRange!.minimum; + rangeChangedArgs.visibleMax = _axisDetails.visibleRange!.maximum; + rangeChangedArgs.visibleInterval = _axisDetails.visibleRange!.interval; + _axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); + _axisDetails.visibleRange!.minimum = rangeChangedArgs.visibleMin + is DateTime + ? _axisDetails.getEffectiveRange(rangeChangedArgs.visibleMin, true) : rangeChangedArgs.visibleMin; - _visibleRange!.maximum = rangeChangedArgs.visibleMax is DateTime - ? _getEffectiveRange(rangeChangedArgs.visibleMax, false) + _axisDetails.visibleRange!.maximum = rangeChangedArgs.visibleMax + is DateTime + ? _axisDetails.getEffectiveRange(rangeChangedArgs.visibleMax, false) : rangeChangedArgs.visibleMax; - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange!.delta / (range.delta); - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; + _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - + _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; + _axisDetails.zoomFactor = + _axisDetails.visibleRange!.delta / (range.delta); + _axisDetails.zoomPosition = (_axisDetails.visibleRange!.minimum - + _axisDetails.actualRange!.minimum) / + range.delta; } } /// Calculates the visible range for an axis in chart. @override void calculateVisibleRange(Size availableSize) { - _calculateDateTimeVisibleRange(availableSize, this); + calculateDateTimeVisibleRange(availableSize, this); } /// Generates the visible axis labels. @override void generateVisibleLabels() { - num tempInterval = _visibleRange!.minimum.ceil(); + num tempInterval = _axisDetails.visibleRange!.minimum.ceil(); int position; String labelText; - _visibleLabels = []; - _dateTimeFormat = - _dateTimeCategoryAxis.dateFormat ?? _getDateTimeLabelFormat(this); + _axisDetails.visibleLabels = []; + _axisDetails.dateTimeFormat = + _axisDetails.dateTimeCategoryAxis.dateFormat ?? + getDateTimeLabelFormat(this); for (; - tempInterval <= _visibleRange!.maximum; - tempInterval += _visibleRange!.interval) { - if (_withInRange(tempInterval, _visibleRange!)) { + tempInterval <= _axisDetails.visibleRange!.maximum; + tempInterval += _axisDetails.visibleRange!.interval) { + if (withInRange(tempInterval, _axisDetails.visibleRange!)) { position = tempInterval.round(); if (position <= -1 || - (_labels.isNotEmpty && position >= _labels.length)) { + (_axisDetails.labels.isNotEmpty && + position >= _axisDetails.labels.length)) { continue; // ignore: unnecessary_null_comparison - } else if (_labels.isNotEmpty && _labels[position] != null) { - labelText = _getFormattedLabel(_labels[position], _dateFormat); - _labels[position] = labelText; + } else if (_axisDetails.labels.isNotEmpty && + _axisDetails.labels[position] != null) { + labelText = _axisDetails.getFormattedLabel( + _axisDetails.labels[position], _axisDetails.dateFormat); + _axisDetails.labels[position] = labelText; } else { continue; } - _triggerLabelRenderEvent(labelText, tempInterval); + _axisDetails.triggerLabelRenderEvent(labelText, tempInterval); + } + } + _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); + } + + /// Finds the interval of an axis. + @override + num calculateInterval(VisibleRange range, Size availableSize) => math + .max( + 1, + (_axisDetails.actualRange!.delta / + _axisDetails.calculateDesiredIntervalCount( + Size(_axisDetails.rect.width, _axisDetails.rect.height), + this)) + .floor()) + .toInt(); +} + +/// This class holds the details of DateTimeCategoryAxis +class DateTimeCategoryAxisDetails extends ChartAxisRendererDetails { + ///Argument constructor for DateTimeCategoryAxisDetails class + DateTimeCategoryAxisDetails(this.dateTimeCategoryAxis, + CartesianStateProperties stateProperties, ChartAxisRenderer axisRenderer) + : super(dateTimeCategoryAxis, stateProperties, axisRenderer) { + labels = []; + } + + /// Holds the list of labels + late List labels; + + /// Holds the value of rect + late Rect rect; + + /// Specifies the date time category axis + final DateTimeCategoryAxis dateTimeCategoryAxis; + + /// Specifies the actual interval type + late DateTimeIntervalType actualIntervalType; + + /// Specifies the date time format + DateFormat? dateTimeFormat; + + /// Gets the value of date time format + DateFormat get dateFormat => + dateTimeFormat ?? getDateTimeLabelFormat(axisRenderer); + + /// Find the series min and max values of an series + void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, + CartesianChartPoint point, int pointIndex, int dataLength, + [bool? isXVisibleRange, bool? isYVisibleRange]) { + final bool _anchorRangeToVisiblePoints = + seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; + final String seriesType = seriesRendererDetails.seriesType; + + if (!labels.contains('${point.x.microsecondsSinceEpoch}')) { + labels.add('${point.x.microsecondsSinceEpoch}'); + } + point.xValue = labels.indexOf('${point.x.microsecondsSinceEpoch}'); + point.yValue = point.y; + if (isYVisibleRange!) { + seriesRendererDetails.minimumX ??= point.xValue; + seriesRendererDetails.maximumX ??= point.xValue; + } + if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && + !seriesType.contains('range') && + !seriesType.contains('hilo') && + !seriesType.contains('candle') && + seriesType != 'boxandwhisker' && + seriesType != 'waterfall') { + seriesRendererDetails.minimumY ??= point.yValue; + seriesRendererDetails.maximumY ??= point.yValue; + } + setCategoryMinMaxValues(axisRenderer, isXVisibleRange, isYVisibleRange, + point, pointIndex, dataLength, seriesRendererDetails); + } + + /// Listener for range controller + void _controlListener() { + stateProperties.canSetRangeController = false; + if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { + updateRangeControllerValues(this); + stateProperties.rangeChangeBySlider = true; + stateProperties.redrawByRangeChange(); + } + } + + /// Calculate axis range and interval + void calculateRangeAndInterval(CartesianStateProperties stateProperties, + [String? type]) { + chart = stateProperties.chart; + if (axis.rangeController != null) { + stateProperties.rangeChangeBySlider = true; + axis.rangeController!.addListener(_controlListener); + } + final Rect containerRect = + stateProperties.renderingDetails.chartContainerRect; + rect = Rect.fromLTWH(containerRect.left, containerRect.top, + containerRect.width, containerRect.height); + axisSize = Size(rect.width, rect.height); + axisRenderer.calculateRange(axisRenderer); + _calculateActualRange(); + if (actualRange != null) { + axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); + if (type == null && + type != 'AxisCross' && + dateTimeCategoryAxis.isVisible) { + axisRenderer.generateVisibleLabels(); } } - _calculateMaximumLabelSize(this, _chartState); } - String _getFormattedLabel(String label, DateFormat dateFormat) { + /// Calculate the required values of the actual range for the date-time axis + void _calculateActualRange() { + ///When chart series is empty, Rendering default chart with below min, max + min ??= 0; + max ??= 5; + actualRange = VisibleRange( + dateTimeCategoryAxis.minimum != null + ? getEffectiveRange(dateTimeCategoryAxis.minimum, true) + : min, + dateTimeCategoryAxis.maximum != null + ? getEffectiveRange(dateTimeCategoryAxis.maximum, false) + : max); + + ///Below condition is for checking the min, max value is equal + if ((actualRange!.minimum == actualRange!.maximum) && + (dateTimeCategoryAxis.labelPlacement == LabelPlacement.onTicks)) { + actualRange!.maximum += 1; + } + if (labels.isNotEmpty) { + final List startAndEnd = _getStartAndEndDate(labels); + calculateDateTimeNiceInterval(axisRenderer, axisSize, actualRange!, + startAndEnd[0], startAndEnd[1]) + .floor(); + } else { + actualIntervalType = DateTimeIntervalType.days; + } + actualRange!.delta = actualRange!.maximum - actualRange!.minimum; + actualRange!.interval = dateTimeCategoryAxis.interval ?? + axisRenderer.calculateInterval( + actualRange!, Size(rect.width, rect.height)); + } + + /// Method to get the formatted labels + String getFormattedLabel(String label, DateFormat dateFormat) { return dateFormat .format(DateTime.fromMicrosecondsSinceEpoch(int.parse(label))); } @@ -584,28 +665,29 @@ class DateTimeCategoryAxisRenderer extends ChartAxisRenderer { ]; } - num? _getEffectiveRange(DateTime? rangeDate, bool needMin) { + /// Method to get the effective range + num? getEffectiveRange(DateTime? rangeDate, bool needMin) { num index = 0; if (rangeDate == null) { return null; } - for (final String label in _labels) { + for (final String label in labels) { final int value = int.parse(label); if (needMin) { if (value > rangeDate.microsecondsSinceEpoch) { - if (!(_labels.first == label)) { + if (!(labels.first == label)) { index++; } break; } else if (value < rangeDate.microsecondsSinceEpoch) { - index = _labels.indexOf(label); + index = labels.indexOf(label); } else { - index = _labels.indexOf(label); + index = labels.indexOf(label); break; } } else { if (value <= rangeDate.microsecondsSinceEpoch) { - index = _labels.indexOf(label); + index = labels.indexOf(label); } if (value >= rangeDate.microsecondsSinceEpoch) { break; @@ -614,15 +696,4 @@ class DateTimeCategoryAxisRenderer extends ChartAxisRenderer { } return index; } - - /// Finds the interval of an axis. - @override - num calculateInterval(_VisibleRange range, Size availableSize) => math - .max( - 1, - (_actualRange!.delta / - _calculateDesiredIntervalCount( - Size(_rect.width, _rect.height), this)) - .floor()) - .toInt(); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart index e2b500743..c0c6b7be9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/logarithmic_axis.dart @@ -1,4 +1,23 @@ -part of charts; +import 'dart:math' as math; +import 'dart:math'; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show NumberFormat; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../common/event_args.dart'; +import '../axis/axis.dart'; +import '../axis/plotband.dart'; +import '../base/chart_base.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart' show updateErrorBarAxisRange; +import '../common/interactive_tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; ///Logarithmic axis uses logarithmic scale and displays numbers as axis labels. /// @@ -307,273 +326,301 @@ class LogarithmicAxis extends ChartAxis { /// Creates an axis renderer for Logarithmic axis class LogarithmicAxisRenderer extends ChartAxisRenderer { /// Creating an argument constructor of LogarithmicAxisRenderer class. - LogarithmicAxisRenderer(this._logarithmicAxis) : super(_logarithmicAxis); + LogarithmicAxisRenderer(LogarithmicAxis logarithmicAxis, + CartesianStateProperties stateProperties) { + _axisDetails = + LogarithmicAxisDetails(logarithmicAxis, stateProperties, this); + AxisHelper.setAxisRendererDetails(this, _axisDetails); + } + + late LogarithmicAxisDetails _axisDetails; + + /// Calculates the visible range for an axis in chart. + @override + void calculateVisibleRange(Size availableSize) { + _axisDetails.setOldRangeFromRangeController(); + _axisDetails.visibleRange = + _axisDetails.stateProperties.rangeChangeBySlider && + _axisDetails.rangeMinimum != null && + _axisDetails.rangeMaximum != null + ? VisibleRange(_axisDetails.rangeMinimum, _axisDetails.rangeMaximum) + : VisibleRange(_axisDetails.actualRange!.minimum, + _axisDetails.actualRange!.maximum); + _axisDetails.visibleRange!.delta = _axisDetails.actualRange!.delta; + _axisDetails.visibleRange!.interval = _axisDetails.actualRange!.interval; + bool canAutoScroll = false; + if (_axisDetails.logarithmicAxis.autoScrollingDelta != null && + _axisDetails.logarithmicAxis.autoScrollingDelta! > 0 && + !_axisDetails.stateProperties.isRedrawByZoomPan) { + canAutoScroll = true; + _axisDetails.updateAutoScrollingDelta( + _axisDetails.logarithmicAxis.autoScrollingDelta!, this); + } + if ((!canAutoScroll || _axisDetails.stateProperties.zoomedState == true) && + !(_axisDetails.stateProperties.rangeChangeBySlider && + !_axisDetails.stateProperties.canSetRangeController)) { + _axisDetails.setZoomFactorAndPosition( + this, _axisDetails.stateProperties.zoomedAxisRendererStates); + } + if (_axisDetails.zoomFactor < 1 || + _axisDetails.zoomPosition > 0 || + (_axisDetails.axis.rangeController != null && + !_axisDetails + .stateProperties.renderingDetails.initialRender!) && + !(_axisDetails.stateProperties.rangeChangeBySlider || + !_axisDetails.stateProperties.canSetRangeController)) { + _axisDetails.stateProperties.zoomProgress = true; + _axisDetails.calculateZoomRange(this, availableSize); + _axisDetails.visibleRange!.delta = _axisDetails.visibleRange!.maximum - + _axisDetails.visibleRange!.minimum; + _axisDetails.visibleRange!.interval = + _axisDetails.axis.enableAutoIntervalOnZooming && + _axisDetails.stateProperties.zoomProgress + ? (_axisDetails.axisRenderer as LogarithmicAxisRenderer) + .calculateLogNiceInterval(_axisDetails.visibleRange!.delta) + : _axisDetails.visibleRange!.interval; + _axisDetails.visibleRange!.interval = + _axisDetails.visibleRange!.interval.floor() == 0 + ? 1 + : _axisDetails.visibleRange!.interval.floor(); + if (_axisDetails.axis.rangeController != null && + _axisDetails.stateProperties.isRedrawByZoomPan && + _axisDetails.stateProperties.canSetRangeController && + _axisDetails.stateProperties.zoomProgress) { + _axisDetails.stateProperties.rangeChangedByChart = true; + _axisDetails.setRangeControllerValues(this); + } + } + _axisDetails.setZoomValuesFromRangeController(); + } - final LogarithmicAxis _logarithmicAxis; + /// Applies range padding to auto, normal, additional, round, and none types. + @override + void applyRangePadding(VisibleRange range, num interval) {} + + /// Generates the visible axis labels. + @override + void generateVisibleLabels() { + num tempInterval = _axisDetails.visibleRange!.minimum; + String labelText; + _axisDetails.visibleLabels = []; + for (; + tempInterval <= _axisDetails.visibleRange!.maximum; + tempInterval += _axisDetails.visibleRange!.interval) { + labelText = pow(_axisDetails.logarithmicAxis.logBase, tempInterval) + .floor() + .toString(); + if (_axisDetails.logarithmicAxis.numberFormat != null) { + labelText = _axisDetails.logarithmicAxis.numberFormat!.format( + pow(_axisDetails.logarithmicAxis.logBase, tempInterval).floor()); + } + if (_axisDetails.logarithmicAxis.labelFormat != null && + _axisDetails.logarithmicAxis.labelFormat != '') { + labelText = _axisDetails.logarithmicAxis.labelFormat! + .replaceAll(RegExp('{value}'), labelText); + } + _axisDetails.triggerLabelRenderEvent(labelText, tempInterval); + } + + /// Get the maximum label of width and height in axis. + _axisDetails.calculateMaximumLabelSize(this, _axisDetails.stateProperties); + } + + /// Finds the interval of an axis. + @override + num? calculateInterval(VisibleRange range, Size availableSize) => null; + + /// To get the axis interval for logarithmic axis + num calculateLogNiceInterval(num delta) { + final List intervalDivisions = [10, 5, 2, 1]; + final num actualDesiredIntervalCount = + _axisDetails.calculateDesiredIntervalCount( + _axisDetails.axisSize, _axisDetails.axisRenderer); + num niceInterval = delta; + final num minInterval = + math.pow(10, calculateLogBaseValue(niceInterval, 10).floor()); + for (int i = 0; i < intervalDivisions.length; i++) { + final num interval = intervalDivisions[i]; + final num currentInterval = minInterval * interval; + if (actualDesiredIntervalCount < (delta / currentInterval)) { + break; + } + niceInterval = currentInterval; + } + return niceInterval; + } +} + +/// This class holds the details of LogarithmicAxis +class LogarithmicAxisDetails extends ChartAxisRendererDetails { + ///Argument constructor for LogarthmicAxisDetails class + LogarithmicAxisDetails(this.logarithmicAxis, + CartesianStateProperties stateProperties, ChartAxisRenderer axisRenderer) + : super(logarithmicAxis, stateProperties, axisRenderer); + + /// Specifies the value of logarithmic axis + final LogarithmicAxis logarithmicAxis; /// Find the series min and max values of an series - void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, + void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, int pointIndex, int dataLength, [bool? isXVisibleRange, bool? isYVisibleRange]) { - final String seriesType = seriesRenderer._seriesType; + final String seriesType = seriesRendererDetails.seriesType; point.xValue = point.x; point.yValue = point.y; - seriesRenderer._minimumX ??= point.xValue; - seriesRenderer._maximumX ??= point.xValue; + seriesRendererDetails.minimumX ??= point.xValue; + seriesRendererDetails.maximumX ??= point.xValue; if (!seriesType.contains('range') && (!seriesType.contains('hilo')) && (!seriesType.contains('candle'))) { - seriesRenderer._minimumY ??= point.yValue; - seriesRenderer._maximumY ??= point.yValue; + seriesRendererDetails.minimumY ??= point.yValue; + seriesRendererDetails.maximumY ??= point.yValue; } - _lowMin ??= point.low; - _lowMax ??= point.low; - _highMin ??= point.high; - _highMax ??= point.high; + lowMin ??= point.low; + lowMax ??= point.low; + highMin ??= point.high; + highMax ??= point.high; if (point.xValue != null) { - seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX!, point.xValue); - seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX!, point.xValue); + seriesRendererDetails.minimumX = + math.min(seriesRendererDetails.minimumX!, point.xValue as num); + seriesRendererDetails.maximumX = + math.max(seriesRendererDetails.maximumX!, point.xValue as num); } if (point.yValue != null && (!seriesType.contains('range') && !seriesType.contains('hilo') && !seriesType.contains('candle'))) { - seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY!, point.yValue); - seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY!, point.yValue); + seriesRendererDetails.minimumY = + math.min(seriesRendererDetails.minimumY!, point.yValue as num); + seriesRendererDetails.maximumY = + math.max(seriesRendererDetails.maximumY!, point.yValue as num); } if (point.high != null) { - _highMin = math.min(_highMin!, point.high); - _highMax = math.max(_highMax!, point.high); + highMin = math.min(highMin!, point.high); + highMax = math.max(highMax!, point.high); } if (point.low != null) { - _lowMin = math.min(_lowMin!, point.low); - _lowMax = math.max(_lowMax!, point.low); + lowMin = math.min(lowMin!, point.low); + lowMax = math.max(lowMax!, point.low); + } + if (seriesType == 'errorbar') { + updateErrorBarAxisRange(seriesRendererDetails, point); } if (pointIndex >= dataLength - 1) { if (seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle')) { - _lowMin ??= 0; - _lowMax ??= 5; - _highMin ??= 0; - _highMax ??= 5; - seriesRenderer._minimumY = - math.min(_lowMin!.toDouble(), _highMin!.toDouble()); - seriesRenderer._maximumY = - math.max(_lowMax!.toDouble(), _highMax!.toDouble()); + lowMin ??= 0; + lowMax ??= 5; + highMin ??= 0; + highMax ??= 5; + seriesRendererDetails.minimumY = + math.min(lowMin!.toDouble(), highMin!.toDouble()); + seriesRendererDetails.maximumY = + math.max(lowMax!.toDouble(), highMax!.toDouble()); } - seriesRenderer._minimumX ??= 0; - seriesRenderer._minimumY ??= 0; - seriesRenderer._maximumX ??= 5; - seriesRenderer._maximumY ??= 5; + seriesRendererDetails.minimumX ??= 0; + seriesRendererDetails.minimumY ??= 0; + seriesRendererDetails.maximumX ??= 5; + seriesRendererDetails.maximumY ??= 5; } } /// Listener for range controller void _controlListener() { - _chartState._canSetRangeController = false; - if (_axis.rangeController != null && !_chartState._rangeChangedByChart) { - _updateRangeControllerValues(this); - _chartState._rangeChangeBySlider = true; - _chartState._redrawByRangeChange(); + stateProperties.canSetRangeController = false; + if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { + updateRangeControllerValues(this); + stateProperties.rangeChangeBySlider = true; + stateProperties.redrawByRangeChange(); } } /// Calculate the range and interval - void _calculateRangeAndInterval(SfCartesianChartState chartState, + void calculateRangeAndInterval(CartesianStateProperties stateProperties, [String? type]) { - _chartState = chartState; - _chart = chartState._chart; - if (_logarithmicAxis.rangeController != null) { - _chartState._rangeChangeBySlider = true; - _logarithmicAxis.rangeController!.addListener(_controlListener); + chart = stateProperties.chart; + if (logarithmicAxis.rangeController != null) { + stateProperties.rangeChangeBySlider = true; + logarithmicAxis.rangeController!.addListener(_controlListener); } - final Rect containerRect = _chartState._renderingDetails.chartContainerRect; + final Rect containerRect = + stateProperties.renderingDetails.chartContainerRect; final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, containerRect.width, containerRect.height); - _axisSize = Size(rect.width, rect.height); - calculateRange(this); + axisSize = Size(rect.width, rect.height); + axisRenderer.calculateRange(axisRenderer); _calculateActualRange(); - calculateVisibleRange(_axisSize); + axisRenderer.calculateVisibleRange(axisSize); /// Setting range as visible zoomRange - if ((_logarithmicAxis.visibleMinimum != null || - _logarithmicAxis.visibleMaximum != null) && - (_logarithmicAxis.visibleMinimum != _logarithmicAxis.visibleMaximum) && - (!_chartState._isRedrawByZoomPan)) { - _chartState._isRedrawByZoomPan = false; - _visibleRange!.minimum = _logarithmicAxis.visibleMinimum != null - ? (math.log(_logarithmicAxis.visibleMinimum!) / (math.log(10))) - .round() - : _visibleRange!.minimum; - _visibleRange!.maximum = _logarithmicAxis.visibleMaximum != null - ? (math.log(_logarithmicAxis.visibleMaximum!) / (math.log(10))) - .round() - : _visibleRange!.maximum; - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _zoomFactor = _visibleRange!.delta / (_actualRange!.delta); - _zoomPosition = (_visibleRange!.minimum - _actualRange!.minimum) / - _actualRange!.delta; + if ((logarithmicAxis.visibleMinimum != null || + logarithmicAxis.visibleMaximum != null) && + (logarithmicAxis.visibleMinimum != logarithmicAxis.visibleMaximum) && + (!stateProperties.isRedrawByZoomPan)) { + stateProperties.isRedrawByZoomPan = false; + visibleRange!.minimum = logarithmicAxis.visibleMinimum != null + ? (math.log(logarithmicAxis.visibleMinimum!) / (math.log(10))).round() + : actualRange!.minimum; + visibleRange!.maximum = logarithmicAxis.visibleMaximum != null + ? (math.log(logarithmicAxis.visibleMaximum!) / (math.log(10))).round() + : actualRange!.maximum; + visibleRange!.delta = visibleRange!.maximum - visibleRange!.minimum; + zoomFactor = visibleRange!.delta / (actualRange!.delta); + zoomPosition = + (visibleRange!.minimum - actualRange!.minimum) / actualRange!.delta; } ActualRangeChangedArgs rangeChangedArgs; - if (_chart.onActualRangeChanged != null) { - final _VisibleRange range = _actualRange!; - rangeChangedArgs = ActualRangeChangedArgs(_name!, _logarithmicAxis, - range.minimum, range.maximum, range.interval, _orientation!); - rangeChangedArgs.visibleMin = _visibleRange!.minimum; - rangeChangedArgs.visibleMax = _visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _visibleRange!.interval; - _chart.onActualRangeChanged!(rangeChangedArgs); - _visibleRange!.minimum = rangeChangedArgs.visibleMin; - _visibleRange!.maximum = rangeChangedArgs.visibleMax; - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange!.delta / range.delta; - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; + if (chart.onActualRangeChanged != null) { + final VisibleRange range = actualRange!; + rangeChangedArgs = ActualRangeChangedArgs(name!, logarithmicAxis, + range.minimum, range.maximum, range.interval, orientation!); + rangeChangedArgs.visibleMin = visibleRange!.minimum; + rangeChangedArgs.visibleMax = visibleRange!.maximum; + rangeChangedArgs.visibleInterval = visibleRange!.interval; + chart.onActualRangeChanged!(rangeChangedArgs); + visibleRange!.minimum = rangeChangedArgs.visibleMin; + visibleRange!.maximum = rangeChangedArgs.visibleMax; + visibleRange!.delta = visibleRange!.maximum - visibleRange!.minimum; + visibleRange!.interval = rangeChangedArgs.visibleInterval; + zoomFactor = visibleRange!.delta / range.delta; + zoomPosition = + (visibleRange!.minimum - actualRange!.minimum) / range.delta; } - if (type == null && type != 'AxisCross' && _logarithmicAxis.isVisible) { - generateVisibleLabels(); + if (type == null && type != 'AxisCross' && logarithmicAxis.isVisible) { + axisRenderer.generateVisibleLabels(); } } /// Calculate the required values of the actual range for logarithmic axis void _calculateActualRange() { num logStart, logEnd; - _min ??= 0; - _max ??= 5; - _min = _logarithmicAxis.minimum ?? _min; - _max = _logarithmicAxis.maximum ?? _max; - if (_axis.anchorRangeToVisiblePoints && - _needCalculateYrange(_logarithmicAxis.minimum, _logarithmicAxis.maximum, - _chartState, _orientation!)) { - final _VisibleRange range = _calculateYRangeOnZoomX(_actualRange!, this); - _min = range.minimum; - _max = range.maximum; - } - _min = _min! < 0 ? 0 : _min; - logStart = _calculateLogBaseValue(_min!, _logarithmicAxis.logBase); - logStart = logStart.isFinite ? logStart : _min!; - logEnd = _calculateLogBaseValue(_max!, _logarithmicAxis.logBase); - logEnd = logEnd.isFinite ? logEnd : _max!; - _min = (logStart / 1).floor(); - _max = (logEnd / 1).ceil(); - if (_min == _max) { - _max = _max! + 1; - } - _actualRange = _VisibleRange(_min, _max); - _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; - _actualRange!.interval = _logarithmicAxis.interval ?? - calculateLogNiceInterval(_actualRange!.delta); - } - - /// To get the axis interval for logarithmic axis - num calculateLogNiceInterval(num delta) { - final List intervalDivisions = [10, 5, 2, 1]; - final num actualDesiredIntervalCount = - _calculateDesiredIntervalCount(_axisSize, this); - num niceInterval = delta; - final num minInterval = - math.pow(10, _calculateLogBaseValue(niceInterval, 10).floor()); - for (int i = 0; i < intervalDivisions.length; i++) { - final num interval = intervalDivisions[i]; - final num currentInterval = minInterval * interval; - if (actualDesiredIntervalCount < (delta / currentInterval)) { - break; - } - niceInterval = currentInterval; - } - return niceInterval; - } - - /// Calculates the visible range for an axis in chart. - @override - void calculateVisibleRange(Size availableSize) { - _setOldRangeFromRangeController(); - _visibleRange = _chartState._rangeChangeBySlider && - _rangeMinimum != null && - _rangeMaximum != null - ? _VisibleRange(_rangeMinimum, _rangeMaximum) - : _VisibleRange(_actualRange!.minimum, _actualRange!.maximum); - _visibleRange!.delta = _actualRange!.delta; - _visibleRange!.interval = _actualRange!.interval; - bool canAutoScroll = false; - if (_logarithmicAxis.autoScrollingDelta != null && - _logarithmicAxis.autoScrollingDelta! > 0 && - !_chartState._isRedrawByZoomPan) { - canAutoScroll = true; - super._updateAutoScrollingDelta( - _logarithmicAxis.autoScrollingDelta!, this); + this.min ??= 0; + this.max ??= 5; + this.min = logarithmicAxis.minimum ?? this.min; + this.max = logarithmicAxis.maximum ?? this.max; + if (axis.anchorRangeToVisiblePoints && + needCalculateYrange(logarithmicAxis.minimum, logarithmicAxis.maximum, + stateProperties, orientation!)) { + final VisibleRange range = calculateYRangeOnZoomX(actualRange!, this); + this.min = range.minimum; + this.max = range.maximum; } - if ((!canAutoScroll || _chartState._zoomedState == true) && - !(_chartState._rangeChangeBySlider && - !_chartState._canSetRangeController)) { - _setZoomFactorAndPosition(this, _chartState._zoomedAxisRendererStates); + this.min = this.min! < 0 ? 0 : this.min; + logStart = calculateLogBaseValue(this.min!, logarithmicAxis.logBase); + logStart = logStart.isFinite ? logStart : this.min!; + logEnd = calculateLogBaseValue(this.max!, logarithmicAxis.logBase); + logEnd = logEnd.isFinite ? logEnd : this.max!; + this.min = (logStart / 1).floor(); + this.max = (logEnd / 1).ceil(); + if (this.min == this.max) { + this.max = this.max! + 1; } - if (_zoomFactor < 1 || - _zoomPosition > 0 || - (_axis.rangeController != null && - !_chartState._renderingDetails.initialRender!) && - !(_chartState._rangeChangeBySlider || - !_chartState._canSetRangeController)) { - _chartState._zoomProgress = true; - _calculateZoomRange(this, availableSize); - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = - _axis.enableAutoIntervalOnZooming && _chartState._zoomProgress - ? calculateLogNiceInterval(_visibleRange!.delta) - : _visibleRange!.interval; - _visibleRange!.interval = _visibleRange!.interval.floor() == 0 - ? 1 - : _visibleRange!.interval.floor(); - if (_axis.rangeController != null && - _chartState._isRedrawByZoomPan && - _chartState._canSetRangeController && - _chartState._zoomProgress) { - _chartState._rangeChangedByChart = true; - _setRangeControllerValues(this); - } - } - _setZoomValuesFromRangeController(); + actualRange = VisibleRange(this.min, this.max); + actualRange!.delta = actualRange!.maximum - actualRange!.minimum; + actualRange!.interval = logarithmicAxis.interval ?? + (axisRenderer as LogarithmicAxisRenderer) + .calculateLogNiceInterval(actualRange!.delta); } - - /// Applies range padding to auto, normal, additional, round, and none types. - @override - void applyRangePadding(_VisibleRange range, num interval) {} - - /// Generates the visible axis labels. - @override - void generateVisibleLabels() { - num tempInterval = _visibleRange!.minimum; - String labelText; - _visibleLabels = []; - for (; - tempInterval <= _visibleRange!.maximum; - tempInterval += _visibleRange!.interval) { - labelText = - pow(_logarithmicAxis.logBase, tempInterval).floor().toString(); - if (_logarithmicAxis.numberFormat != null) { - labelText = _logarithmicAxis.numberFormat! - .format(pow(_logarithmicAxis.logBase, tempInterval).floor()); - } - if (_logarithmicAxis.labelFormat != null && - _logarithmicAxis.labelFormat != '') { - labelText = _logarithmicAxis.labelFormat! - .replaceAll(RegExp('{value}'), labelText); - } - _triggerLabelRenderEvent(labelText, tempInterval); - } - - /// Get the maximum label of width and height in axis. - _calculateMaximumLabelSize(this, _chartState); - } - - /// Finds the interval of an axis. - @override - num? calculateInterval(_VisibleRange range, Size availableSize) => null; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart index dd23988d8..7d7e93e34 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/numeric_axis.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show NumberFormat; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../common/event_args.dart'; +import '../axis/axis.dart'; +import '../axis/plotband.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart' show updateErrorBarAxisRange; +import '../common/interactive_tooltip.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; /// This class has the properties of the numeric axis. /// @@ -309,29 +326,248 @@ class NumericAxis extends ChartAxis { /// Creates an axis renderer for Numeric axis. class NumericAxisRenderer extends ChartAxisRenderer { /// Creating an argument constructor of NumericAxisRenderer class. - NumericAxisRenderer(this._numericAxis) : super(_numericAxis); + NumericAxisRenderer( + NumericAxis numericAxis, CartesianStateProperties stateProperties) { + _axisDetails = NumericAxisDetails(numericAxis, this, stateProperties); + AxisHelper.setAxisRendererDetails(this, _axisDetails); + } + + late NumericAxisDetails _axisDetails; + + /// Applies range padding to auto, normal, additional, round, and none types. + @override + void applyRangePadding(VisibleRange range, num? interval) { + ActualRangeChangedArgs rangeChangedArgs; + final NumericAxisDetails axisDetails = + AxisHelper.getAxisRendererDetails(this) as NumericAxisDetails; + if (!(axisDetails.numericAxis.minimum != null && + axisDetails.numericAxis.maximum != null)) { + ///Calculating range padding + axisDetails.applyRangePaddings( + this, axisDetails.stateProperties, range, interval!); + } + + calculateVisibleRange(axisDetails.axisSize); + + /// Setting range as visible zoomRange + if ((axisDetails.numericAxis.visibleMinimum != null || + axisDetails.numericAxis.visibleMaximum != null) && + (axisDetails.numericAxis.visibleMinimum != + axisDetails.numericAxis.visibleMaximum) && + (!axisDetails.stateProperties.isRedrawByZoomPan)) { + axisDetails.stateProperties.isRedrawByZoomPan = false; + axisDetails.visibleRange!.minimum = axisDetails.visibleMinimum ?? + axisDetails.numericAxis.visibleMinimum ?? + axisDetails.actualRange!.minimum; + axisDetails.visibleRange!.maximum = axisDetails.visibleMaximum ?? + axisDetails.numericAxis.visibleMaximum ?? + axisDetails.actualRange!.maximum; + axisDetails.visibleRange!.delta = + axisDetails.visibleRange!.maximum - axisDetails.visibleRange!.minimum; + axisDetails.visibleRange!.interval = interval == null + ? axisDetails.calculateNumericNiceInterval( + this, + axisDetails.visibleRange!.maximum - + axisDetails.visibleRange!.minimum, + axisDetails.axisSize) + : axisDetails.visibleRange!.interval; + axisDetails.zoomFactor = axisDetails.visibleRange!.delta / range.delta; + axisDetails.zoomPosition = (axisDetails.visibleRange!.minimum - + axisDetails.actualRange!.minimum) / + range.delta; + } + if (axisDetails.chart.onActualRangeChanged != null) { + rangeChangedArgs = ActualRangeChangedArgs( + axisDetails.name!, + axisDetails.numericAxis, + range.minimum, + range.maximum, + range.interval, + axisDetails.orientation!); + rangeChangedArgs.visibleMin = axisDetails.visibleRange!.minimum; + rangeChangedArgs.visibleMax = axisDetails.visibleRange!.maximum; + rangeChangedArgs.visibleInterval = axisDetails.visibleRange!.interval; + axisDetails.chart.onActualRangeChanged!(rangeChangedArgs); + axisDetails.visibleRange!.minimum = rangeChangedArgs.visibleMin; + axisDetails.visibleRange!.maximum = rangeChangedArgs.visibleMax; + axisDetails.visibleRange!.delta = + axisDetails.visibleRange!.maximum - axisDetails.visibleRange!.minimum; + axisDetails.visibleRange!.interval = rangeChangedArgs.visibleInterval; + axisDetails.zoomFactor = axisDetails.visibleRange!.delta / range.delta; + axisDetails.zoomPosition = (axisDetails.visibleRange!.minimum - + axisDetails.actualRange!.minimum) / + range.delta; + } + } + + /// Generates the visible axis labels. + @override + void generateVisibleLabels() { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(this); + final VisibleRange? visibleRange = axisDetails.visibleRange; + final NumericAxis numericAxis = axisDetails.axis as NumericAxis; + num tempInterval = visibleRange!.minimum; + String text; + final num maximumVisibleRange = visibleRange.maximum; + num interval = visibleRange.interval; + interval = interval.toString().split('.').length >= 2 + ? interval.toString().split('.')[1].length == 1 && + interval.toString().split('.')[1] == '0' + ? interval.floor() + : interval + : interval; + axisDetails.visibleLabels = []; + for (; tempInterval <= maximumVisibleRange; tempInterval += interval) { + num minimumVisibleRange = tempInterval; + if (minimumVisibleRange <= maximumVisibleRange && + minimumVisibleRange >= visibleRange.minimum) { + final int fractionDigits = + (minimumVisibleRange.toString().split('.').length >= 2) + ? minimumVisibleRange.toString().split('.')[1].toString().length + : 0; + final int fractionDigitValue = + fractionDigits > 20 ? 20 : fractionDigits; + minimumVisibleRange = minimumVisibleRange.toString().contains('e') + ? minimumVisibleRange + : num.tryParse( + minimumVisibleRange.toStringAsFixed(fractionDigitValue))!; + if (minimumVisibleRange.toString().split('.').length > 1) { + final String str = minimumVisibleRange.toString(); + final List? list = str.split('.'); + minimumVisibleRange = double.parse( + minimumVisibleRange.toStringAsFixed(numericAxis.decimalPlaces)); + if (list != null && + list.length > 1 && + (list[1] == '0' || + list[1] == '00' || + list[1] == '000' || + list[1] == '0000' || + list[1] == '00000' || + minimumVisibleRange % 1 == 0)) { + minimumVisibleRange = minimumVisibleRange.round(); + } + } + text = minimumVisibleRange.toString(); + if (numericAxis.numberFormat != null) { + text = numericAxis.numberFormat!.format(minimumVisibleRange); + } + if (numericAxis.labelFormat != null && numericAxis.labelFormat != '') { + text = numericAxis.labelFormat!.replaceAll(RegExp('{value}'), text); + } + text = axisDetails.stateProperties.chartAxis.primaryYAxisDetails + .isStack100 == + true && + axisDetails.name == + axisDetails + .stateProperties.chartAxis.primaryYAxisDetails.name + ? (text + '%') + : text; + axisDetails.triggerLabelRenderEvent(text, tempInterval); + } + } + + /// Get the maximum label of width and height in axis. + axisDetails.calculateMaximumLabelSize(this, axisDetails.stateProperties); + } + + /// Calculates the visible range for an axis in chart. + @override + void calculateVisibleRange(Size availableSize) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(this); + final CartesianStateProperties stateProperties = + axisDetails.stateProperties; + axisDetails.setOldRangeFromRangeController(); + axisDetails.visibleRange = stateProperties.rangeChangeBySlider && + axisDetails.rangeMinimum != null && + axisDetails.rangeMaximum != null + ? VisibleRange(axisDetails.rangeMinimum, axisDetails.rangeMaximum) + : VisibleRange( + axisDetails.actualRange!.minimum, axisDetails.actualRange!.maximum); + axisDetails.visibleRange!.delta = axisDetails.actualRange!.delta; + axisDetails.visibleRange!.interval = axisDetails.actualRange!.interval; + bool canAutoScroll = false; + if (axisDetails.axis.autoScrollingDelta != null && + axisDetails.axis.autoScrollingDelta! > 0 && + !stateProperties.isRedrawByZoomPan) { + canAutoScroll = true; + axisDetails.updateAutoScrollingDelta( + axisDetails.axis.autoScrollingDelta!, this); + } + if ((!canAutoScroll || stateProperties.zoomedState == true) && + !(stateProperties.rangeChangeBySlider && + !stateProperties.canSetRangeController)) { + axisDetails.setZoomFactorAndPosition( + this, stateProperties.zoomedAxisRendererStates); + } + if (axisDetails.zoomFactor < 1 || + axisDetails.zoomPosition > 0 || + (axisDetails.axis.rangeController != null && + !stateProperties.renderingDetails.initialRender!) && + !(stateProperties.rangeChangeBySlider || + !stateProperties.canSetRangeController)) { + stateProperties.zoomProgress = true; + axisDetails.calculateZoomRange(this, availableSize); + axisDetails.visibleRange!.interval = !canAutoScroll && + axisDetails.axis.enableAutoIntervalOnZooming && + stateProperties.zoomProgress + ? calculateInterval(axisDetails.visibleRange!, axisDetails.axisSize) + : axisDetails.visibleRange!.interval; + if (axisDetails.axis.rangeController != null && + stateProperties.isRedrawByZoomPan && + stateProperties.canSetRangeController && + stateProperties.zoomProgress) { + stateProperties.rangeChangedByChart = true; + axisDetails.setRangeControllerValues(this); + } + } + axisDetails.setZoomValuesFromRangeController(); + } + + /// Finds the interval of an axis. + @override + num calculateInterval(VisibleRange range, Size availableSize) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(this); + return axisDetails.calculateNumericNiceInterval( + this, range.maximum - range.minimum, axisDetails.axisSize); + } +} + +/// This class holds the details of NumericAxis +class NumericAxisDetails extends ChartAxisRendererDetails { + ///Argument constructor for NumericAxisDetails class + NumericAxisDetails(this.numericAxis, ChartAxisRenderer axisRenderer, + CartesianStateProperties stateProperties) + : super(numericAxis, stateProperties, axisRenderer); + + /// Holds the value of axis padding // ignore:unused_field - final int _axisPadding = 5; + final int axisPadding = 5; + + /// Holds the value of inner padding // ignore:unused_field - final int _innerPadding = 5; + final int innerPadding = 5; @override - late Size _axisSize; + late Size axisSize; - final NumericAxis _numericAxis; + /// Represents the value of numeric axis + final NumericAxis numericAxis; /// Find the series min and max values of an series - void _findAxisMinMaxValues(CartesianSeriesRenderer seriesRenderer, + void findAxisMinMaxValues(SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, int pointIndex, int dataLength, [bool? isXVisibleRange, bool? isYVisibleRange]) { final bool _anchorRangeToVisiblePoints = - seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; - final String seriesType = seriesRenderer._seriesType; + seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; + final String seriesType = seriesRendererDetails.seriesType; point.xValue = point.x; point.yValue = point.y; if (isYVisibleRange!) { - seriesRenderer._minimumX ??= point.xValue; - seriesRenderer._maximumX ??= point.xValue; + seriesRendererDetails.minimumX ??= point.xValue; + seriesRendererDetails.maximumX ??= point.xValue; } if ((isXVisibleRange! || !_anchorRangeToVisiblePoints) && !seriesType.contains('range') && @@ -339,15 +575,15 @@ class NumericAxisRenderer extends ChartAxisRenderer { !seriesType.contains('candle') && seriesType != 'boxandwhisker' && seriesType != 'waterfall') { - seriesRenderer._minimumY ??= point.yValue; - seriesRenderer._maximumY ??= point.yValue; + seriesRendererDetails.minimumY ??= point.yValue; + seriesRendererDetails.maximumY ??= point.yValue; } if (isYVisibleRange && point.xValue != null) { - seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX!, point.xValue); - seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX!, point.xValue); + seriesRendererDetails.minimumX = + math.min(seriesRendererDetails.minimumX!, point.xValue as num); + seriesRendererDetails.maximumX = + math.max(seriesRendererDetails.maximumX!, point.xValue as num); } if (isXVisibleRange || !_anchorRangeToVisiblePoints) { if (point.yValue != null && @@ -356,34 +592,37 @@ class NumericAxisRenderer extends ChartAxisRenderer { !seriesType.contains('candle') && seriesType != 'boxandwhisker' && seriesType != 'waterfall')) { - seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY!, point.yValue); - seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY!, point.yValue); + seriesRendererDetails.minimumY = + math.min(seriesRendererDetails.minimumY!, point.yValue as num); + seriesRendererDetails.maximumY = + math.max(seriesRendererDetails.maximumY!, point.yValue as num); } if (point.high != null) { - _highMin = _findMinValue(_highMin ?? point.high, point.high); - _highMax = _findMaxValue(_highMax ?? point.high, point.high); + highMin = findMinValue(highMin ?? point.high, point.high); + highMax = findMaxValue(highMax ?? point.high, point.high); } if (point.low != null) { - _lowMin = _findMinValue(_lowMin ?? point.low, point.low); - _lowMax = _findMaxValue(_lowMax ?? point.low, point.low); + lowMin = findMinValue(lowMin ?? point.low, point.low); + lowMax = findMaxValue(lowMax ?? point.low, point.low); } if (point.maximum != null) { - _highMin = _findMinValue(_highMin ?? point.maximum!, point.maximum!); - _highMax = _findMaxValue(_highMax ?? point.maximum!, point.maximum!); + highMin = findMinValue(highMin ?? point.maximum!, point.maximum!); + highMax = findMaxValue(highMax ?? point.maximum!, point.maximum!); } if (point.minimum != null) { - _lowMin = _findMinValue(_lowMin ?? point.minimum!, point.minimum!); - _lowMax = _findMaxValue(_lowMax ?? point.minimum!, point.minimum!); + lowMin = findMinValue(lowMin ?? point.minimum!, point.minimum!); + lowMax = findMaxValue(lowMax ?? point.minimum!, point.minimum!); } if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. point.yValue ??= 0; - seriesRenderer._minimumY = _findMinValue( - seriesRenderer._minimumY ?? point.yValue, point.yValue); - seriesRenderer._maximumY = _findMaxValue( - seriesRenderer._maximumY ?? point.maxYValue, point.maxYValue); + seriesRendererDetails.minimumY = findMinValue( + seriesRendererDetails.minimumY ?? point.yValue, point.yValue); + seriesRendererDetails.maximumY = findMaxValue( + seriesRendererDetails.maximumY ?? point.maxYValue, point.maxYValue); + } + if (seriesType == 'errorbar') { + updateErrorBarAxisRange(seriesRendererDetails, point); } } if (pointIndex >= dataLength - 1) { @@ -391,258 +630,92 @@ class NumericAxisRenderer extends ChartAxisRenderer { seriesType.contains('hilo') || seriesType.contains('candle') || seriesType == 'boxandwhisker') { - _lowMin ??= 0; - _lowMax ??= 5; - _highMin ??= 0; - _highMax ??= 5; - seriesRenderer._minimumY = math.min(_lowMin!, _highMin!); - seriesRenderer._maximumY = math.max(_lowMax!, _highMax!); + lowMin ??= 0; + lowMax ??= 5; + highMin ??= 0; + highMax ??= 5; + seriesRendererDetails.minimumY = math.min(lowMin!, highMin!); + seriesRendererDetails.maximumY = math.max(lowMax!, highMax!); } - seriesRenderer._minimumX ??= 0; - seriesRenderer._minimumY ??= 0; - seriesRenderer._maximumX ??= 5; - seriesRenderer._maximumY ??= 5; + seriesRendererDetails.minimumX ??= 0; + seriesRendererDetails.minimumY ??= 0; + seriesRendererDetails.maximumX ??= 5; + seriesRendererDetails.maximumY ??= 5; } } /// Listener for range controller void _controlListener() { - _chartState._canSetRangeController = false; - if (_axis.rangeController != null && !_chartState._rangeChangedByChart) { - _updateRangeControllerValues(this); - _chartState._rangeChangeBySlider = true; - _chartState._redrawByRangeChange(); + stateProperties.canSetRangeController = false; + if (axis.rangeController != null && !stateProperties.rangeChangedByChart) { + updateRangeControllerValues(this); + stateProperties.rangeChangeBySlider = true; + stateProperties.redrawByRangeChange(); } } /// Calculate the range and interval - void _calculateRangeAndInterval(SfCartesianChartState chartState, + void calculateRangeAndInterval(CartesianStateProperties stateProperties, [String? type]) { - _chartState = chartState; - _chart = chartState._chart; - if (_axis.rangeController != null) { - _chartState._rangeChangeBySlider = true; - _axis.rangeController!.addListener(_controlListener); + chart = stateProperties.chart; + if (axis.rangeController != null) { + stateProperties.rangeChangeBySlider = true; + axis.rangeController!.addListener(_controlListener); } - final Rect containerRect = _chartState._renderingDetails.chartContainerRect; + final Rect containerRect = + stateProperties.renderingDetails.chartContainerRect; final Rect rect = Rect.fromLTWH(containerRect.left, containerRect.top, containerRect.width, containerRect.height); - _axisSize = Size(rect.width, rect.height); - calculateRange(this); + axisSize = Size(rect.width, rect.height); + axisRenderer.calculateRange(axisRenderer); _calculateActualRange(); - if (_actualRange != null) { - applyRangePadding(_actualRange!, _actualRange!.interval); - if (type == null && type != 'AxisCross' && _numericAxis.isVisible) { - generateVisibleLabels(); + if (actualRange != null) { + axisRenderer.applyRangePadding(actualRange!, actualRange!.interval); + if (type == null && type != 'AxisCross' && numericAxis.isVisible) { + axisRenderer.generateVisibleLabels(); } } } /// Calculate the required values of the actual range for numeric axis void _calculateActualRange() { - _min ??= 0; - _max ??= 5; + min ??= 0; + max ??= 5; /// Below condition is for checking whether the min and max are equal and /// also whether they are positive or negative in order /// to set the min and max as zero accordingly. - if (_min == _max && _min! < 0 && _max! < 0) { - _max = 0; + if (min == max && min! < 0 && max! < 0) { + max = 0; } - if (_min == _max && _min! > 0 && _max! > 0) { - _min = 0; + if (min == max && min! > 0 && max! > 0) { + min = 0; } - _actualRange = _VisibleRange( - _numericAxis.minimum ?? _min, _numericAxis.maximum ?? _max); - if (_axis.anchorRangeToVisiblePoints && - _needCalculateYrange(_numericAxis.minimum, _numericAxis.maximum, - _chartState, _orientation!)) { - _actualRange = _calculateYRangeOnZoomX(_actualRange!, this); + actualRange = + VisibleRange(numericAxis.minimum ?? min, numericAxis.maximum ?? max); + if (axis.anchorRangeToVisiblePoints && + needCalculateYrange(numericAxis.minimum, numericAxis.maximum, + stateProperties, orientation!)) { + actualRange = calculateYRangeOnZoomX(actualRange!, this); } ///Below condition is for checking the min, max value is equal - if (_actualRange!.minimum == _actualRange!.maximum) { - _actualRange!.maximum += 1; + if (actualRange!.minimum == actualRange!.maximum) { + actualRange!.maximum += 1; } ///Below condition is for checking the axis min value is greater than max value, then swapping min max values - else if ((_actualRange!.minimum > _actualRange!.maximum) == true) { - _actualRange!.minimum = _actualRange!.minimum + _actualRange!.maximum; - _actualRange!.maximum = _actualRange!.minimum - _actualRange!.maximum; - _actualRange!.minimum = _actualRange!.minimum - _actualRange!.maximum; - } - _actualRange!.delta = _actualRange!.maximum - _actualRange!.minimum; - - _actualRange!.interval = _numericAxis.interval ?? - _calculateNumericNiceInterval(this, _actualRange!.delta, _axisSize); - } - - /// Applies range padding to auto, normal, additional, round, and none types. - @override - void applyRangePadding(_VisibleRange range, num? interval) { - ActualRangeChangedArgs rangeChangedArgs; - if (!(_numericAxis.minimum != null && _numericAxis.maximum != null)) { - ///Calculating range padding - _applyRangePadding(this, _chartState, range, interval!); - } - - calculateVisibleRange(_axisSize); - - /// Setting range as visible zoomRange - if ((_numericAxis.visibleMinimum != null || - _numericAxis.visibleMaximum != null) && - (_numericAxis.visibleMinimum != _numericAxis.visibleMaximum) && - (!_chartState._isRedrawByZoomPan)) { - _chartState._isRedrawByZoomPan = false; - _visibleRange!.minimum = _visibleMinimum ?? - _numericAxis.visibleMinimum ?? - _visibleRange!.minimum; - _visibleRange!.maximum = _visibleMaximum ?? - _numericAxis.visibleMaximum ?? - _visibleRange!.maximum; - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = interval == null - ? _calculateNumericNiceInterval( - this, _visibleRange!.maximum - _visibleRange!.minimum, _axisSize) - : _visibleRange!.interval; - _zoomFactor = _visibleRange!.delta / range.delta; - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; - } - if (_chart.onActualRangeChanged != null) { - rangeChangedArgs = ActualRangeChangedArgs(_name!, _numericAxis, - range.minimum, range.maximum, range.interval, _orientation!); - rangeChangedArgs.visibleMin = _visibleRange!.minimum; - rangeChangedArgs.visibleMax = _visibleRange!.maximum; - rangeChangedArgs.visibleInterval = _visibleRange!.interval; - _chart.onActualRangeChanged!(rangeChangedArgs); - _visibleRange!.minimum = rangeChangedArgs.visibleMin; - _visibleRange!.maximum = rangeChangedArgs.visibleMax; - _visibleRange!.delta = _visibleRange!.maximum - _visibleRange!.minimum; - _visibleRange!.interval = rangeChangedArgs.visibleInterval; - _zoomFactor = _visibleRange!.delta / range.delta; - _zoomPosition = - (_visibleRange!.minimum - _actualRange!.minimum) / range.delta; - } - } - - /// Generates the visible axis labels. - @override - void generateVisibleLabels() { - num tempInterval = _visibleRange!.minimum; - String text; - final num maximumVisibleRange = _visibleRange!.maximum; - num interval = _visibleRange!.interval; - interval = interval.toString().split('.').length >= 2 - ? interval.toString().split('.')[1].length == 1 && - interval.toString().split('.')[1] == '0' - ? interval.floor() - : interval - : interval; - _visibleLabels = []; - for (; tempInterval <= maximumVisibleRange; tempInterval += interval) { - num minimumVisibleRange = tempInterval; - if (minimumVisibleRange <= maximumVisibleRange && - minimumVisibleRange >= _visibleRange!.minimum) { - final int fractionDigits = - (minimumVisibleRange.toString().split('.').length >= 2) - ? minimumVisibleRange.toString().split('.')[1].toString().length - : 0; - final int fractionDigitValue = - fractionDigits > 20 ? 20 : fractionDigits; - minimumVisibleRange = minimumVisibleRange.toString().contains('e') - ? minimumVisibleRange - : num.tryParse( - minimumVisibleRange.toStringAsFixed(fractionDigitValue))!; - if (minimumVisibleRange.toString().split('.').length > 1) { - final String str = minimumVisibleRange.toString(); - final List? list = str.split('.'); - minimumVisibleRange = double.parse( - minimumVisibleRange.toStringAsFixed(_numericAxis.decimalPlaces)); - if (list != null && - list.length > 1 && - (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - minimumVisibleRange % 1 == 0)) { - minimumVisibleRange = minimumVisibleRange.round(); - } - } - text = minimumVisibleRange.toString(); - if (_numericAxis.numberFormat != null) { - text = _numericAxis.numberFormat!.format(minimumVisibleRange); - } - if (_numericAxis.labelFormat != null && - _numericAxis.labelFormat != '') { - text = _numericAxis.labelFormat!.replaceAll(RegExp('{value}'), text); - } - text = - _chartState._chartAxis._primaryYAxisRenderer._isStack100 == true && - _name == _chartState._chartAxis._primaryYAxisRenderer._name - ? (text + '%') - : text; - _triggerLabelRenderEvent(text, tempInterval); - } + else if ((actualRange!.minimum > actualRange!.maximum) == true) { + actualRange!.minimum = actualRange!.minimum + actualRange!.maximum; + actualRange!.maximum = actualRange!.minimum - actualRange!.maximum; + actualRange!.minimum = actualRange!.minimum - actualRange!.maximum; } + actualRange!.delta = actualRange!.maximum - actualRange!.minimum; - /// Get the maximum label of width and height in axis. - _calculateMaximumLabelSize(this, _chartState); + actualRange!.interval = numericAxis.interval ?? + calculateNumericNiceInterval( + axisRenderer, actualRange!.delta, axisSize); } - - /// Calculates the visible range for an axis in chart. - @override - void calculateVisibleRange(Size availableSize) { - _setOldRangeFromRangeController(); - _visibleRange = _chartState._rangeChangeBySlider && - _rangeMinimum != null && - _rangeMaximum != null - ? _VisibleRange(_rangeMinimum, _rangeMaximum) - : _VisibleRange(_actualRange!.minimum, _actualRange!.maximum); - _visibleRange!.delta = _actualRange!.delta; - _visibleRange!.interval = _actualRange!.interval; - bool canAutoScroll = false; - if (_numericAxis.autoScrollingDelta != null && - _numericAxis.autoScrollingDelta! > 0 && - !_chartState._isRedrawByZoomPan) { - canAutoScroll = true; - super._updateAutoScrollingDelta(_numericAxis.autoScrollingDelta!, this); - } - if ((!canAutoScroll || _chartState._zoomedState == true) && - !(_chartState._rangeChangeBySlider && - !_chartState._canSetRangeController)) { - _setZoomFactorAndPosition(this, _chartState._zoomedAxisRendererStates); - } - if (_zoomFactor < 1 || - _zoomPosition > 0 || - (_axis.rangeController != null && - !_chartState._renderingDetails.initialRender!) && - !(_chartState._rangeChangeBySlider || - !_chartState._canSetRangeController)) { - _chartState._zoomProgress = true; - _calculateZoomRange(this, availableSize); - _visibleRange!.interval = !canAutoScroll && - _axis.enableAutoIntervalOnZooming && - _chartState._zoomProgress - ? calculateInterval(_visibleRange!, _axisSize) - : _visibleRange!.interval; - if (_axis.rangeController != null && - _chartState._isRedrawByZoomPan && - _chartState._canSetRangeController && - _chartState._zoomProgress) { - _chartState._rangeChangedByChart = true; - _setRangeControllerValues(this); - } - } - _setZoomValuesFromRangeController(); - } - - /// Finds the interval of an axis. - @override - num calculateInterval(_VisibleRange range, Size availableSize) => - _calculateNumericNiceInterval( - this, range.maximum - range.minimum, _axisSize); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart b/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart index 81f5cd50b..1dd02bb0d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/axis/plotband.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../common/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/chart_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; /// Render plot band. /// @@ -634,12 +652,21 @@ class PlotBand { } } +/// Method to get the plot band painter +CustomPainter getPlotBandPainter( + {required CartesianStateProperties stateProperties, + required bool shouldRenderAboveSeries}) { + return _PlotBandPainter( + stateProperties: stateProperties, + shouldRenderAboveSeries: shouldRenderAboveSeries); +} + class _PlotBandPainter extends CustomPainter { _PlotBandPainter( - {required this.chartState, required this.shouldRenderAboveSeries}) - : chart = chartState._chart; + {required this.stateProperties, required this.shouldRenderAboveSeries}) + : chart = stateProperties.chart; - final SfCartesianChartState chartState; + final CartesianStateProperties stateProperties; final SfCartesianChart chart; @@ -650,20 +677,22 @@ class _PlotBandPainter extends CustomPainter { void paint(Canvas canvas, Size size) { Rect clipRect; for (int axisIndex = 0; - axisIndex < chartState._chartAxis._axisRenderersCollection.length; + axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { final ChartAxisRenderer axisRenderer = - chartState._chartAxis._axisRenderersCollection[axisIndex]; - final ChartAxis axis = axisRenderer._axis; + stateProperties.chartAxis.axisRenderersCollection[axisIndex]; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisDetails.axis; for (int j = 0; j < axis.plotBands.length; j++) { final PlotBand plotBand = axis.plotBands[j]; if (plotBand.isVisible && shouldRenderAboveSeries != plotBand.shouldRenderAboveSeries) { clipRect = Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left, - chartState._chartAxis._axisClipRect.top, - chartState._chartAxis._axisClipRect.right, - chartState._chartAxis._axisClipRect.bottom); + stateProperties.chartAxis.axisClipRect.left, + stateProperties.chartAxis.axisClipRect.top, + stateProperties.chartAxis.axisClipRect.right, + stateProperties.chartAxis.axisClipRect.bottom); canvas.clipRect(clipRect); _renderPlotBand(canvas, axisRenderer, plotBand); } @@ -672,8 +701,10 @@ class _PlotBandPainter extends CustomPainter { } /// To find the start and end location for plotband - _ChartLocation _getStartAndEndValues(ChartAxisRenderer axisRenderer, + ChartLocation _getStartAndEndValues(ChartAxisRenderer axisRenderer, dynamic start, dynamic end, PlotBand plotBand, bool isNeedRepeat) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); dynamic startValue = start is String && num.tryParse(start) != null ? num.tryParse(start) : start; @@ -691,57 +722,58 @@ class _PlotBandPainter extends CustomPainter { : endValue is DateTime ? endValue.millisecondsSinceEpoch : endValue; - } else if (axisRenderer is CategoryAxisRenderer) { + } else if (axisDetails is CategoryAxisDetails) { startValue = startValue is num ? startValue - : axisRenderer._labels.indexOf(startValue); + : axisDetails.labels.indexOf(startValue); endValue = isNeedRepeat ? plotBand.repeatUntil is num ? plotBand.repeatUntil.floor() - : axisRenderer._labels.indexOf(plotBand.repeatUntil) + : axisDetails.labels.indexOf(plotBand.repeatUntil) : endValue is num ? endValue - : axisRenderer._labels.indexOf(endValue); + : axisDetails.labels.indexOf(endValue); } - if (axisRenderer is DateTimeCategoryAxisRenderer) { + if (axisDetails is DateTimeCategoryAxisDetails) { startValue = startValue is num ? startValue : (startValue is DateTime - ? axisRenderer._labels.indexOf(axisRenderer._axis.isVisible - ? axisRenderer._dateFormat.format(startValue) + ? axisDetails.labels.indexOf(axisDetails.axis.isVisible + ? axisDetails.dateFormat.format(startValue) : startValue.microsecondsSinceEpoch.toString()) - : axisRenderer._labels.indexOf(startValue)); + : axisDetails.labels.indexOf(startValue)); endValue = isNeedRepeat ? plotBand.repeatUntil is num ? plotBand.repeatUntil.floor() - : axisRenderer._labels.indexOf(plotBand.repeatUntil) + : axisDetails.labels.indexOf(plotBand.repeatUntil) : endValue is num ? endValue : endValue is DateTime - ? axisRenderer._labels.indexOf(axisRenderer._axis.isVisible - ? axisRenderer._dateFormat.format(endValue) + ? axisDetails.labels.indexOf(axisDetails.axis.isVisible + ? axisDetails.dateFormat.format(endValue) : endValue.microsecondsSinceEpoch.toString()) - : axisRenderer._labels.indexOf(endValue); + : axisDetails.labels.indexOf(endValue); } else if (axisRenderer is LogarithmicAxisRenderer || axisRenderer is NumericAxisRenderer) { endValue = isNeedRepeat ? plotBand.repeatUntil : endValue; } - return _ChartLocation(startValue.toDouble(), endValue.toDouble()); + return ChartLocation(startValue.toDouble(), endValue.toDouble()); } /// Render a method for plotband void _renderPlotBand( Canvas canvas, ChartAxisRenderer axisRenderer, PlotBand plotBand) { num startValue, endValue; - + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); final bool isNeedRepeat = plotBand.isRepeatable && plotBand.repeatUntil != null && plotBand.repeatEvery != null; - final _ChartLocation startAndEndValues = _getStartAndEndValues( + final ChartLocation startAndEndValues = _getStartAndEndValues( axisRenderer, - plotBand.start ?? axisRenderer._visibleRange!.minimum, - plotBand.end ?? axisRenderer._visibleRange!.maximum, + plotBand.start ?? axisDetails.visibleRange!.minimum, + plotBand.end ?? axisDetails.visibleRange!.maximum, plotBand, isNeedRepeat); startValue = startAndEndValues.x; @@ -769,10 +801,12 @@ class _PlotBandPainter extends CustomPainter { num _getPlotBandValue(ChartAxisRenderer axisRenderer, PlotBand plotBand, num value, num increment) { final int addValue = increment.toInt(); + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); DateTimeIntervalType intervalType; - if (axisRenderer is DateTimeAxisRenderer) { + if (axisDetails is DateTimeAxisDetails) { intervalType = (plotBand.sizeType == DateTimeIntervalType.auto) - ? axisRenderer._actualIntervalType + ? axisDetails.actualIntervalType : plotBand.sizeType; DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt()); switch (intervalType) { @@ -817,90 +851,96 @@ class _PlotBandPainter extends CustomPainter { /// Render plotband element void _renderPlotBandElement(ChartAxisRenderer axisRenderer, num startValue, num endValue, PlotBand plotBand, Canvas canvas) { - _ChartLocation startPoint, endPoint, segmentStartPoint, segmentEndPoint; + ChartLocation startPoint, endPoint, segmentStartPoint, segmentEndPoint; Rect plotBandRect; int textAngle; double? left, top, bottom, right; - final ChartAxis axis = axisRenderer._axis; - final Rect axisRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisDetails.axis; + final Rect axisRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, Offset( - axisRenderer._orientation == AxisOrientation.horizontal + axisDetails.orientation == AxisOrientation.horizontal ? axis.plotOffset : 0, - axisRenderer._orientation == AxisOrientation.vertical + axisDetails.orientation == AxisOrientation.vertical ? axis.plotOffset : 0)); startValue = axis is LogarithmicAxis - ? _calculateLogBaseValue(startValue, axis.logBase) + ? calculateLogBaseValue(startValue, axis.logBase) : startValue; endValue = axis is LogarithmicAxis - ? _calculateLogBaseValue(endValue, axis.logBase) + ? calculateLogBaseValue(endValue, axis.logBase) : endValue; endValue < 0 - ? endValue <= axisRenderer._visibleRange!.minimum - ? endValue = axisRenderer._visibleRange!.minimum + ? endValue <= axisDetails.visibleRange!.minimum + ? endValue = axisDetails.visibleRange!.minimum : endValue = endValue - : endValue >= axisRenderer._visibleRange!.maximum - ? endValue = axisRenderer._visibleRange!.maximum + : endValue >= axisDetails.visibleRange!.maximum + ? endValue = axisDetails.visibleRange!.maximum : endValue = endValue; startValue < 0 - ? startValue <= axisRenderer._visibleRange!.minimum - ? startValue = axisRenderer._visibleRange!.minimum + ? startValue <= axisDetails.visibleRange!.minimum + ? startValue = axisDetails.visibleRange!.minimum : startValue = startValue - : startValue >= axisRenderer._visibleRange!.maximum - ? startValue = axisRenderer._visibleRange!.maximum + : startValue >= axisDetails.visibleRange!.maximum + ? startValue = axisDetails.visibleRange!.maximum : startValue = startValue; - startPoint = _calculatePoint(startValue, startValue, axisRenderer, - axisRenderer, chartState._requireInvertedAxis, null, axisRect); - endPoint = _calculatePoint(endValue, endValue, axisRenderer, axisRenderer, - chartState._requireInvertedAxis, null, axisRect); + startPoint = calculatePoint(startValue, startValue, axisDetails, + axisDetails, stateProperties.requireInvertedAxis, null, axisRect); + endPoint = calculatePoint(endValue, endValue, axisDetails, axisDetails, + stateProperties.requireInvertedAxis, null, axisRect); ChartAxisRenderer? segmentAxisRenderer; if (plotBand.associatedAxisStart != null || plotBand.associatedAxisEnd != null) { if (axis.associatedAxisName == null) { segmentAxisRenderer = - (axisRenderer._orientation == AxisOrientation.horizontal) - ? chartState._chartAxis._primaryYAxisRenderer - : chartState._chartAxis._primaryXAxisRenderer; + (axisDetails.orientation == AxisOrientation.horizontal) + ? stateProperties.chartAxis.primaryYAxisRenderer + : stateProperties.chartAxis.primaryXAxisRenderer; } else { for (int axisIndex = 0; - axisIndex < chartState._chartAxis._axisRenderersCollection.length; + axisIndex < + stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { final ChartAxisRenderer targetAxisRenderer = - chartState._chartAxis._axisRenderersCollection[axisIndex]; - if (axis.associatedAxisName == targetAxisRenderer._name) { + stateProperties.chartAxis.axisRenderersCollection[axisIndex]; + final ChartAxisRendererDetails targetAxisDetails = + AxisHelper.getAxisRendererDetails(targetAxisRenderer); + if (axis.associatedAxisName == targetAxisDetails.name) { segmentAxisRenderer = axisRenderer; } } } - final _ChartLocation startAndEndValues = _getStartAndEndValues( + final ChartLocation startAndEndValues = _getStartAndEndValues( segmentAxisRenderer!, plotBand.associatedAxisStart ?? startValue, plotBand.associatedAxisEnd ?? endValue, plotBand, false); - - if (segmentAxisRenderer._orientation == AxisOrientation.horizontal) { - segmentStartPoint = _calculatePoint( + final ChartAxisRendererDetails segmentAxisDetails = + AxisHelper.getAxisRendererDetails(segmentAxisRenderer); + if (segmentAxisDetails.orientation == AxisOrientation.horizontal) { + segmentStartPoint = calculatePoint( startAndEndValues.x, startValue, - segmentAxisRenderer, - axisRenderer, - chartState._requireInvertedAxis, + segmentAxisDetails, + axisDetails, + stateProperties.requireInvertedAxis, null, axisRect); - segmentEndPoint = _calculatePoint( + segmentEndPoint = calculatePoint( startAndEndValues.y, endValue, - segmentAxisRenderer, - axisRenderer, - chartState._requireInvertedAxis, + segmentAxisDetails, + axisDetails, + stateProperties.requireInvertedAxis, null, axisRect); left = plotBand.associatedAxisStart != null @@ -910,20 +950,20 @@ class _PlotBandPainter extends CustomPainter { ? segmentEndPoint.x : axisRect.right; } else { - segmentStartPoint = _calculatePoint( + segmentStartPoint = calculatePoint( startValue, startAndEndValues.x, - axisRenderer, - segmentAxisRenderer, - chartState._requireInvertedAxis, + axisDetails, + segmentAxisDetails, + stateProperties.requireInvertedAxis, null, axisRect); - segmentEndPoint = _calculatePoint( + segmentEndPoint = calculatePoint( endValue, startAndEndValues.y, - axisRenderer, - segmentAxisRenderer, - chartState._requireInvertedAxis, + axisDetails, + segmentAxisDetails, + stateProperties.requireInvertedAxis, null, axisRect); top = plotBand.associatedAxisStart != null @@ -935,7 +975,7 @@ class _PlotBandPainter extends CustomPainter { } } - if (axisRenderer._orientation == AxisOrientation.horizontal) { + if (axisDetails.orientation == AxisOrientation.horizontal) { textAngle = plotBand.textAngle != null ? plotBand.textAngle!.toInt() : 270; plotBandRect = Rect.fromLTRB(left ?? startPoint.x, top ?? axisRect.top, @@ -971,15 +1011,14 @@ class _PlotBandPainter extends CustomPainter { ..close(); final Paint paint = Paint(); - Path dashPath; + Path? _dashPath; if (needDashLine) { paint.isAntiAlias = false; - dashPath = !kIsWeb - ? _dashPath(path, - dashArray: _CircularIntervalList(dashArray))! + _dashPath = !kIsWeb + ? dashPath(path, dashArray: CircularIntervalList(dashArray)) : path; } else { - dashPath = path; + _dashPath = path; } // ignore: unnecessary_null_comparison if (path != null) { @@ -998,9 +1037,9 @@ class _PlotBandPainter extends CustomPainter { // ignore: unnecessary_null_comparison plotBand.borderColor != null && // ignore: unnecessary_null_comparison - dashPath != null) { + _dashPath != null) { canvas.drawPath( - dashPath, + _dashPath, paint ..color = plotBand.borderColor.withOpacity(plotBand.opacity) ..style = PaintingStyle.stroke @@ -1016,11 +1055,11 @@ class _PlotBandPainter extends CustomPainter { if (plotBand.horizontalTextPadding != null && plotBand.horizontalTextPadding != '') { if (plotBand.horizontalTextPadding!.contains('%')) { - x = _percentageToValue( + x = percentageToValue( plotBand.horizontalTextPadding!, chart.isTransposed - ? chartState._chartAxis._axisClipRect.bottom - : chartState._chartAxis._axisClipRect.right); + ? stateProperties.chartAxis.axisClipRect.bottom + : stateProperties.chartAxis.axisClipRect.right); } else if (plotBand.verticalTextPadding!.contains('px')) { x = double.parse(plotBand.horizontalTextPadding! .substring(0, plotBand.horizontalTextPadding!.length - 2)); @@ -1031,11 +1070,11 @@ class _PlotBandPainter extends CustomPainter { if (plotBand.verticalTextPadding != null && plotBand.verticalTextPadding != '') { if (plotBand.verticalTextPadding!.contains('%')) { - y = _percentageToValue( + y = percentageToValue( plotBand.verticalTextPadding!, chart.isTransposed - ? chartState._chartAxis._axisClipRect.right - : chartState._chartAxis._axisClipRect.bottom); + ? stateProperties.chartAxis.axisClipRect.right + : stateProperties.chartAxis.axisClipRect.bottom); } else if (plotBand.verticalTextPadding!.contains('px')) { y = double.parse(plotBand.verticalTextPadding! .substring(0, plotBand.verticalTextPadding!.length - 2)); @@ -1044,7 +1083,7 @@ class _PlotBandPainter extends CustomPainter { } } - _drawText( + drawText( canvas, plotBand.text!, Offset( @@ -1052,10 +1091,11 @@ class _PlotBandPainter extends CustomPainter { ? (plotBandRect.left + plotBandRect.width / 2 - ((plotBandRect.left == - chartState._chartAxis._axisClipRect.right) + stateProperties.chartAxis.axisClipRect.right) ? 0 : (plotBandRect.right == - chartState._chartAxis._axisClipRect.left) + stateProperties + .chartAxis.axisClipRect.left) ? textSize.width : textSize.width / 2) + x!) + @@ -1067,11 +1107,11 @@ class _PlotBandPainter extends CustomPainter { ? (plotBandRect.top + plotBandRect.height / 2 - ((plotBandRect.bottom == - chartState._chartAxis._axisClipRect.top) + stateProperties.chartAxis.axisClipRect.top) ? textSize.height : (plotBandRect.top == - chartState - ._chartAxis._axisClipRect.bottom) + stateProperties + .chartAxis.axisClipRect.bottom) ? 0 + y! : textSize.height + y!)) + (textAngle != 0 ? textSize.height / 2 : 0) diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart index 2b850194d..d93bb76fa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/base/chart_base.dart @@ -1,8 +1,89 @@ -part of charts; - -///Renders the cartesian type charts. +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/tooltip_internal.dart'; + +import '../../common/common.dart'; +import '../../common/event_args.dart'; +import '../../common/legend/legend.dart'; +import '../../common/legend/renderer.dart'; +import '../../common/rendering_details.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/template/rendering.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../../common/utils/typedef.dart'; +import '../annotation/annotation_settings.dart'; +import '../axis/axis.dart'; +import '../axis/axis_panel.dart'; +import '../axis/axis_renderer.dart'; +import '../axis/numeric_axis.dart'; +import '../axis/plotband.dart'; +import '../base/series_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/column_segment.dart'; +import '../chart_series/error_bar_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/data_label_renderer.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../series_painter/area_painter.dart'; +import '../series_painter/bar_painter.dart'; +import '../series_painter/box_and_whisker_painter.dart'; +import '../series_painter/bubble_painter.dart'; +import '../series_painter/candle_painter.dart'; +import '../series_painter/column_painter.dart'; +import '../series_painter/error_bar_painter.dart'; +import '../series_painter/fastline_painter.dart'; +import '../series_painter/hilo_painter.dart'; +import '../series_painter/hiloopenclose_painter.dart'; +import '../series_painter/histogram_painter.dart'; +import '../series_painter/line_painter.dart'; +import '../series_painter/range_area_painter.dart'; +import '../series_painter/range_column_painter.dart'; +import '../series_painter/scatter_painter.dart'; +import '../series_painter/spline_area_painter.dart'; +import '../series_painter/spline_painter.dart'; +import '../series_painter/spline_range_area_painter.dart'; +import '../series_painter/stacked_area_painter.dart'; +import '../series_painter/stacked_bar_painter.dart'; +import '../series_painter/stacked_column_painter.dart'; +import '../series_painter/stacked_line_painter.dart'; +import '../series_painter/step_area_painter.dart'; +import '../series_painter/stepline_painter.dart'; +import '../series_painter/waterfall_painter.dart'; +import '../technical_indicators/technical_indicator.dart'; +import '../trendlines/trendlines.dart'; +import '../trendlines/trendlines_painter.dart'; +import '../user_interaction/crosshair.dart'; +import '../user_interaction/crosshair_painter.dart'; +import '../user_interaction/selection_renderer.dart'; +import '../user_interaction/trackball.dart'; +import '../user_interaction/trackball_marker_setting_renderer.dart'; +import '../user_interaction/trackball_painter.dart'; +import '../user_interaction/trackball_template.dart'; +import '../user_interaction/zooming_painter.dart'; +import '../user_interaction/zooming_panning.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +///Renders the Cartesian type charts. /// -///Cartesian charts are generally charts with horizontal and vertical axes.[SfCartesianChart] provides options to cusomize +///Cartesian charts are generally charts with horizontal and vertical axes.[SfCartesianChart] provides options to customize /// chart types using the `series` property. /// ///```dart @@ -91,7 +172,6 @@ class SfCartesianChart extends StatefulWidget { @deprecated this.onPointTapped, this.onAxisLabelTapped, this.onDataLabelTapped, - this.onTrendlineRender, this.onLegendTapped, this.onSelectionChanged, this.onChartTouchInteractionUp, @@ -408,21 +488,6 @@ class SfCartesianChart extends StatefulWidget { ///``` final ChartLegendRenderCallback? onLegendItemRender; - /// Occurs when the trendline is rendered. Here, you can get the legend’s text, - /// shape, series index, and point index of circular series. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// onTrendlineRender: (TrendlineRenderArgs args) => trendline(args) - /// )); - ///} - ///void trendline(TrendlineRenderArgs args) { - /// args.seriesIndex = 2; - ///} - ///``` - final ChartTrendlineRenderCallback? onTrendlineRender; - /// Occurs while the trackball position is changed. Here, you can customize the text of /// the trackball. ///```dart @@ -504,7 +569,7 @@ class SfCartesianChart extends StatefulWidget { ///``` final ChartZoomingCallback? onZoomReset; - /// Occurs when Zoooming event is performed. Here, you can get the axis, current zoom factor, + /// Occurs when Zooming event is performed. Here, you can get the axis, current zoom factor, /// current zoom position, previous zoom factor, and previous zoom position. ///```dart ///Widget build(BuildContext context) { @@ -1059,182 +1124,75 @@ class SfCartesianChart extends StatefulWidget { /// class SfCartesianChartState extends State with TickerProviderStateMixin { - /// Specifies the chart rendering details - late _RenderingDetails _renderingDetails; - - /// Holds the animation controller along with their listener for all series and trenddlines - late Map _controllerList; - - late Map> _repaintNotifiers; - - late List _zoomedAxisRendererStates; - - late List _oldAxisRenderers; - late bool _zoomProgress; - late List<_ZoomAxisRange> _zoomAxes; - late List _selectedSegments; - late List _unselectedSegments; - late List _renderDatalabelRegions; - late List _annotationRegions; - bool _legendRefresh = false; - _DataLabelRenderer? _renderDataLabel; - late _CartesianAxisRenderer _renderOutsideAxis; - late _CartesianAxisRenderer _renderInsideAxis; - late List _oldSeriesRenderers; - late List?> _oldSeriesKeys; - late List _segments; - late List _oldSeriesVisible; - bool? _zoomedState; - late List _touchStartPositions; - late List _touchMovePositions; - late bool _enableDoubleTap; - bool _legendToggling = false; - dart_ui.Image? _backgroundImage; - dart_ui.Image? _legendIconImage; - bool _isTrendlineToggled = false; - late List<_PainterKey> _painterKeys; - late bool _triggerLoaded; - //ignore: prefer_final_fields - bool _rangeChangeBySlider = false; - //ignore: prefer_final_fields - bool _rangeChangedByChart = false; - //ignore: prefer_final_fields - bool _isRangeSelectionSlider = false; - bool? _isSeriesLoaded; - late bool _isNeedUpdate; - late List _seriesRenderers; - - /// Holds the information of AxisBase class - late _ChartAxis _chartAxis; - - /// Holds the information of SeriesBase class - late _ChartSeries _chartSeries; - - /// Holds the information of _ContainerArea class - /// ignore: unused_field - late _ContainerArea _containerArea; - - /// Whether to check chart axis is inverted or not - late bool _requireInvertedAxis; - - /// To check if axis trimmed text is tapped - //ignore: prefer_final_fields - bool _requireAxisTooltip = false; - - //ignore: prefer_final_fields - List<_ChartPointInfo> _chartPointInfo = <_ChartPointInfo>[]; - - late ZoomPanBehaviorRenderer _zoomPanBehaviorRenderer; - - late TrackballBehaviorRenderer _trackballBehaviorRenderer; - - late CrosshairBehaviorRenderer _crosshairBehaviorRenderer; - - late List _technicalIndicatorRenderer; - - late TrackballMarkerSettingsRenderer _trackballMarkerSettingsRenderer; - - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfCartesianChart get _chart => widget; - - /// Setting series animation duration factor - final double _seriesDurationFactor = 0.85; - - /// Setting trendline animation duration factor - final double _trendlineDurationFactor = 0.85; - - //holds the count for total no of series that should be animated - late int _totalAnimatingSeries; - - //holds the no of animation completed series - late int _animationCompleteCount; - - SelectionArgs? _selectionArgs; - - bool _isTouchUp = false; + late CartesianStateProperties _stateProperties; - late StateSetter _loadMoreViewStateSetter; - - late ChartSwipeDirection _swipeDirection; - - Offset? _startOffset, _currentPosition; - late bool _isRedrawByZoomPan; - late PointerDeviceKind _pointerDeviceKind; - - ///To check the load more widget is in progress or not - late bool _isLoadMoreIndicator; - - bool _canSetRangeController = false; - - late bool _enableMouseHover; - - // ignore: unused_element - bool get _animationCompleted { - for (final CartesianSeriesRenderer seriesRenderer in _seriesRenderers) { - if (seriesRenderer._animationController.status == - AnimationStatus.forward) { - return false; - } - } - return true; - } - - /// To intialize default values + /// To initialize default values void _initializeDefaultValues() { - _renderingDetails = _RenderingDetails(); - _chartAxis = _ChartAxis(this); - _chartSeries = _ChartSeries(this); - _renderingDetails.chartLegend = _ChartLegend(this); - _containerArea = _ContainerArea(this); - _seriesRenderers = []; - _controllerList = {}; - _repaintNotifiers = >{ + _stateProperties = CartesianStateProperties( + renderingDetails: RenderingDetails(), chartState: this); + _stateProperties.chartAxis = ChartAxisPanel(_stateProperties); + _stateProperties.chartSeries = ChartSeriesPanel(_stateProperties); + _stateProperties.renderingDetails.chartLegend = + ChartLegend(_stateProperties); + _stateProperties.seriesRenderers = []; + _stateProperties.controllerList = {}; + _stateProperties.repaintNotifiers = >{ 'zoom': ValueNotifier(0), 'trendline': ValueNotifier(0), 'trackball': ValueNotifier(0), 'crosshair': ValueNotifier(0), 'indicator': ValueNotifier(0), }; - _renderingDetails.legendWidgetContext = <_MeasureWidgetContext>[]; - _renderingDetails.didSizeChange = false; - _renderingDetails.templates = <_ChartTemplateInfo>[]; - _oldAxisRenderers = []; - _zoomedAxisRendererStates = []; - _zoomAxes = <_ZoomAxisRange>[]; - _renderingDetails.chartContainerRect = const Rect.fromLTRB(0, 0, 0, 0); - _zoomProgress = false; - _renderingDetails.legendToggleStates = <_LegendRenderContext>[]; - _selectedSegments = []; - _unselectedSegments = []; - _renderingDetails.legendToggleTemplateStates = <_MeasureWidgetContext>[]; - _renderDatalabelRegions = []; - _renderingDetails.dataLabelTemplateRegions = []; - _annotationRegions = []; - _renderingDetails.animateCompleted = false; - _renderingDetails.widgetNeedUpdate = false; - _oldSeriesRenderers = []; - _oldSeriesKeys = ?>[]; - _renderingDetails.isLegendToggled = false; - _oldSeriesVisible = []; - _touchStartPositions = []; - _touchMovePositions = []; - _enableDoubleTap = false; - _legendToggling = false; - _painterKeys = <_PainterKey>[]; - _isNeedUpdate = true; - _isRedrawByZoomPan = false; - _isLoadMoreIndicator = false; - _technicalIndicatorRenderer = []; - _zoomPanBehaviorRenderer = ZoomPanBehaviorRenderer(this); - _trackballBehaviorRenderer = TrackballBehaviorRenderer(this); - _crosshairBehaviorRenderer = CrosshairBehaviorRenderer(this); - _renderingDetails.tooltipBehaviorRenderer = TooltipBehaviorRenderer(this); - _renderingDetails.legendRenderer = LegendRenderer(widget.legend); - _trackballMarkerSettingsRenderer = TrackballMarkerSettingsRenderer( - widget.trackballBehavior.markerSettings); + _stateProperties.renderingDetails.legendWidgetContext = + []; + _stateProperties.renderingDetails.didSizeChange = false; + _stateProperties.renderingDetails.templates = []; + _stateProperties.oldAxisRenderers = []; + _stateProperties.zoomedAxisRendererStates = []; + _stateProperties.zoomAxes = []; + _stateProperties.renderingDetails.chartContainerRect = + const Rect.fromLTRB(0, 0, 0, 0); + _stateProperties.zoomProgress = false; + _stateProperties.renderingDetails.legendToggleStates = + []; + _stateProperties.selectedSegments = []; + _stateProperties.unselectedSegments = []; + _stateProperties.renderingDetails.legendToggleTemplateStates = + []; + _stateProperties.renderDatalabelRegions = []; + _stateProperties.renderingDetails.dataLabelTemplateRegions = []; + _stateProperties.annotationRegions = []; + _stateProperties.renderingDetails.animateCompleted = false; + _stateProperties.renderingDetails.widgetNeedUpdate = false; + _stateProperties.oldSeriesRenderers = []; + _stateProperties.oldSeriesKeys = ?>[]; + _stateProperties.renderingDetails.isLegendToggled = false; + _stateProperties.oldSeriesVisible = []; + _stateProperties.touchStartPositions = []; + _stateProperties.touchMovePositions = []; + _stateProperties.enableDoubleTap = false; + _stateProperties.legendToggling = false; + _stateProperties.painterKeys = []; + _stateProperties.isNeedUpdate = true; + _stateProperties.isRedrawByZoomPan = false; + _stateProperties.isLoadMoreIndicator = false; + _stateProperties.technicalIndicatorRenderer = + []; + _stateProperties.zoomPanBehaviorRenderer = + ZoomPanBehaviorRenderer(_stateProperties); + _stateProperties.trackballBehaviorRenderer = + TrackballBehaviorRenderer(_stateProperties); + _stateProperties.crosshairBehaviorRenderer = + CrosshairBehaviorRenderer(_stateProperties); + _stateProperties.renderingDetails.tooltipBehaviorRenderer = + TooltipBehaviorRenderer(_stateProperties); + _stateProperties.renderingDetails.legendRenderer = + LegendRenderer(widget.legend); + _stateProperties.trackballMarkerSettingsRenderer = + TrackballMarkerSettingsRenderer( + widget.trackballBehavior.markerSettings); final TargetPlatform platform = defaultTargetPlatform; - _enableMouseHover = kIsWeb || + _stateProperties.enableMouseHover = kIsWeb || platform == TargetPlatform.windows || platform == TargetPlatform.macOS || platform == TargetPlatform.linux; @@ -1270,7 +1228,7 @@ class SfCartesianChartState extends State @override void didChangeDependencies() { - _renderingDetails.chartTheme = SfChartTheme.of(context); + _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -1289,44 +1247,52 @@ class SfCartesianChartState extends State @override void didUpdateWidget(SfCartesianChart oldWidget) { - _isRedrawByZoomPan = false; - _isLoadMoreIndicator = false; - _zoomProgress = false; + _stateProperties.isRedrawByZoomPan = false; + _stateProperties.isLoadMoreIndicator = false; + _stateProperties.zoomProgress = false; final List oldWidgetSeriesRenderers = //ignore: prefer_spread_collections - []..addAll(_seriesRenderers); + []..addAll(_stateProperties.seriesRenderers); final List oldWidgetOldSeriesRenderers = - //ignore: prefer_spread_collections - []..addAll(_oldSeriesRenderers); + [] + //ignore: prefer_spread_collections + ..addAll(_stateProperties.oldSeriesRenderers); //Update and maintain the series state, when we update the series in the series collection // _createAndUpdateSeriesRenderer( oldWidget, oldWidgetSeriesRenderers, oldWidgetOldSeriesRenderers); - _needsRepaintChart( - this, _chartAxis._axisRenderersCollection, oldWidgetSeriesRenderers); - _renderingDetails.isLegendToggled = false; + needsRepaintChart( + _stateProperties, + _stateProperties.chartAxis.axisRenderersCollection, + oldWidgetSeriesRenderers); + _stateProperties.renderingDetails.isLegendToggled = false; // ignore: unnecessary_null_comparison - if (_renderingDetails.legendWidgetContext != null && - _renderingDetails.legendWidgetContext.isNotEmpty) { - _renderingDetails.legendWidgetContext.clear(); - } - if (_seriesRenderers.isNotEmpty && - _seriesRenderers[0]._selectionBehaviorRenderer?._selectionRenderer != - null) { - _seriesRenderers[0] - ._selectionBehaviorRenderer - ?._selectionRenderer - ?._isInteraction = false; - } - if (_isNeedUpdate) { - _renderingDetails.widgetNeedUpdate = true; - _renderingDetails.isImageDrawn = false; - _oldSeriesRenderers = oldWidgetSeriesRenderers; - _getOldSeriesKeys(_oldSeriesRenderers); - _oldAxisRenderers = [] + if (_stateProperties.renderingDetails.legendWidgetContext != null && + _stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { + _stateProperties.renderingDetails.legendWidgetContext.clear(); + } + final SeriesRendererDetails? seriesRendererDetails = + _stateProperties.seriesRenderers.isNotEmpty + ? SeriesHelper.getSeriesRendererDetails( + _stateProperties.seriesRenderers[0]) + : null; + if (seriesRendererDetails?.selectionBehaviorRenderer != null) { + final SelectionDetails? selectionDetails = + SelectionHelper.getRenderingDetails( + seriesRendererDetails!.selectionBehaviorRenderer!); + if (_stateProperties.seriesRenderers.isNotEmpty && + selectionDetails?.selectionRenderer != null) { + selectionDetails?.selectionRenderer?.isInteraction = false; + } + } + if (_stateProperties.isNeedUpdate) { + _stateProperties.renderingDetails.widgetNeedUpdate = true; + _stateProperties.oldSeriesRenderers = oldWidgetSeriesRenderers; + _getOldSeriesKeys(_stateProperties.oldSeriesRenderers); + _stateProperties.oldAxisRenderers = [] //ignore: prefer_spread_collections - ..addAll(_chartAxis._axisRenderersCollection); + ..addAll(_stateProperties.chartAxis.axisRenderersCollection); } super.didUpdateWidget(oldWidget); } @@ -1344,23 +1310,26 @@ class SfCartesianChartState extends State @override Widget build(BuildContext context) { - _renderingDetails.oldDeviceOrientation = - _renderingDetails.oldDeviceOrientation == null + _stateProperties.renderingDetails.oldDeviceOrientation = + _stateProperties.renderingDetails.oldDeviceOrientation == null ? MediaQuery.of(context).orientation - : _renderingDetails.deviceOrientation; - _renderingDetails.deviceOrientation = MediaQuery.of(context).orientation; - _renderingDetails.initialRender = _renderingDetails.initialRender == null; - _requireInvertedAxis = false; - _triggerLoaded = false; - _isSeriesLoaded = _isSeriesLoaded ?? true; + : _stateProperties.renderingDetails.deviceOrientation; + _stateProperties.renderingDetails.deviceOrientation = + MediaQuery.of(context).orientation; + _stateProperties.renderingDetails.initialRender = + _stateProperties.renderingDetails.initialRender == null; + _stateProperties.requireInvertedAxis = false; + _stateProperties.triggerLoaded = false; + _stateProperties.isSeriesLoaded = _stateProperties.isSeriesLoaded ?? true; _findVisibleSeries(context); - _isSeriesLoaded = false; + _stateProperties.isSeriesLoaded = false; + return RepaintBoundary( - child: _ChartContainer( + child: ChartContainer( child: Container( decoration: BoxDecoration( color: widget.backgroundColor ?? - _renderingDetails.chartTheme.backgroundColor, + _stateProperties.renderingDetails.chartTheme.backgroundColor, border: Border.all(color: widget.borderColor, width: widget.borderWidth)), child: Container( @@ -1376,9 +1345,9 @@ class SfCartesianChartState extends State /// /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this - /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// point. This stage of the life cycle is terminal: there is no way to remount a [State] object that has been disposed. /// - /// Subclasses should override this method to release any resources retained by this object. + /// Sub classes should override this method to release any resources retained by this object. /// /// * In [dispose], unsubscribe from the object. /// @@ -1386,7 +1355,7 @@ class SfCartesianChartState extends State @override void dispose() { - _controllerList.forEach(_disposeAnimationController); + _stateProperties.controllerList.forEach(disposeAnimationController); super.dispose(); } @@ -1459,13 +1428,16 @@ class SfCartesianChartState extends State ///Storing old series key values void _getOldSeriesKeys(List oldSeriesRenderers) { - _oldSeriesKeys = ?>[]; + _stateProperties.oldSeriesKeys = ?>[]; for (int i = 0; i < oldSeriesRenderers.length; i++) { - _oldSeriesKeys.add(oldSeriesRenderers[i]._series.key); + _stateProperties.oldSeriesKeys.add( + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[i]) + .series + .key); } } - // In this method, create and update the series renderer for each series // + /// In this method, create and update the series renderer for each series // void _createAndUpdateSeriesRenderer( [SfCartesianChart? oldWidget, List? oldWidgetSeriesRenderers, @@ -1473,10 +1445,10 @@ class SfCartesianChartState extends State // ignore: unnecessary_null_comparison if (widget.series != null && widget.series.isNotEmpty) { if (oldWidget != null) { - _oldSeriesRenderers = []; - _oldSeriesRenderers.addAll(oldWidgetSeriesRenderers!); + _stateProperties.oldSeriesRenderers = []; + _stateProperties.oldSeriesRenderers.addAll(oldWidgetSeriesRenderers!); } - _seriesRenderers = []; + _stateProperties.seriesRenderers = []; final int seriesLength = widget.series.length; dynamic series; int? index, oldSeriesIndex; @@ -1486,16 +1458,23 @@ class SfCartesianChartState extends State oldSeriesIndex = null; if (oldWidget != null) { if (oldWidgetOldSeriesRenderers!.isNotEmpty) { - // Check the current series is already exist in oldwidget // + // Check the current series is already exist in old widget // index = i < oldWidgetOldSeriesRenderers.length && - _isSameSeries( - oldWidgetOldSeriesRenderers[i]._series, series) + isSameSeries( + SeriesHelper.getSeriesRendererDetails( + oldWidgetOldSeriesRenderers[i]) + .series, + series) ? i : _getExistingSeriesIndex(series, oldWidgetOldSeriesRenderers); } if (oldWidgetSeriesRenderers!.isNotEmpty) { oldSeriesIndex = i < oldWidgetSeriesRenderers.length && - _isSameSeries(oldWidgetSeriesRenderers[i]._series, series) + isSameSeries( + SeriesHelper.getSeriesRendererDetails( + oldWidgetSeriesRenderers[i]) + .series, + series) ? i : _getExistingSeriesIndex(series, oldWidgetSeriesRenderers); } @@ -1510,65 +1489,82 @@ class SfCartesianChartState extends State seriesRenderer = oldWidgetOldSeriesRenderers[index]; } else { seriesRenderer = series.createRenderer(series); - seriesRenderer._repaintNotifier = ValueNotifier(0); + SeriesHelper.setSeriesRendererDetails( + seriesRenderer, SeriesRendererDetails(seriesRenderer)); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + seriesRendererDetails.seriesIndex = i; + seriesRendererDetails.seriesType = getSeriesType(seriesRenderer); + seriesRendererDetails.stateProperties = _stateProperties; + seriesRendererDetails.repaintNotifier = ValueNotifier(0); if (seriesRenderer is XyDataSeriesRenderer) { - seriesRenderer._animationController = + seriesRendererDetails.animationController = AnimationController(vsync: this) - ..addListener(seriesRenderer._repaintSeriesElement); - _controllerList[seriesRenderer._animationController] = - seriesRenderer._repaintSeriesElement; - seriesRenderer._animationController - .addStatusListener(seriesRenderer._animationStatusListener); + ..addListener(seriesRendererDetails.repaintSeriesElement); + _stateProperties + .controllerList[seriesRendererDetails.animationController] = + seriesRendererDetails.repaintSeriesElement; + seriesRendererDetails.animationController.addStatusListener( + seriesRendererDetails.animationStatusListener); } - seriesRenderer._controller ??= + seriesRendererDetails.controller ??= ChartSeriesController(seriesRenderer as XyDataSeriesRenderer); } + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); if (series.onRendererCreated != null) { - series.onRendererCreated(seriesRenderer._controller); + series.onRendererCreated(seriesRendererDetails.controller); } - seriesRenderer._series = series; - seriesRenderer._isSelectionEnable = + seriesRendererDetails.series = series; + seriesRendererDetails.isSelectionEnable = series.selectionBehavior.enable == true; - seriesRenderer._visible = null; - seriesRenderer._chart = widget; - seriesRenderer._hasDataLabelTemplate = false; + seriesRendererDetails.visible = null; + seriesRendererDetails.chart = widget; + seriesRendererDetails.hasDataLabelTemplate = false; if (oldWidgetSeriesRenderers != null && oldSeriesIndex != null && oldWidgetSeriesRenderers.length > oldSeriesIndex) { - seriesRenderer._oldSeries = - oldWidgetSeriesRenderers[oldSeriesIndex]._series; + final SeriesRendererDetails oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + oldWidgetSeriesRenderers[oldSeriesIndex]); + seriesRendererDetails.oldSeries = oldSeriesRendererDetails.series; if (seriesRenderer is FastLineSeriesRenderer && oldWidgetSeriesRenderers[oldSeriesIndex] is FastLineSeriesRenderer) { final FastLineSeriesRenderer fastlineSeriesRenderer = oldWidgetSeriesRenderers[oldSeriesIndex] as FastLineSeriesRenderer; - seriesRenderer._oldDataPoints = >[] + seriesRendererDetails + .oldDataPoints = >[] //ignore: prefer_spread_collections - ..addAll(fastlineSeriesRenderer._overallDataPoints); + ..addAll( + SeriesHelper.getSeriesRendererDetails(fastlineSeriesRenderer) + .overallDataPoints); } else { - seriesRenderer._oldDataPoints = >[] - //ignore: prefer_spread_collections - ..addAll(oldWidgetSeriesRenderers[oldSeriesIndex]._dataPoints); + seriesRendererDetails.oldDataPoints = + >[] + //ignore: prefer_spread_collections + ..addAll(oldSeriesRendererDetails.dataPoints); } - seriesRenderer._oldSelectedIndexes = - oldWidgetSeriesRenderers[oldSeriesIndex]._oldSelectedIndexes; - seriesRenderer._repaintNotifier = - oldWidgetSeriesRenderers[oldSeriesIndex]._repaintNotifier; - seriesRenderer._animationController = - oldWidgetSeriesRenderers[oldSeriesIndex]._animationController; + seriesRendererDetails.oldSelectedIndexes = + oldSeriesRendererDetails.oldSelectedIndexes; + seriesRendererDetails.repaintNotifier = + oldSeriesRendererDetails.repaintNotifier; + seriesRendererDetails.animationController = + oldSeriesRendererDetails.animationController; } else { - seriesRenderer._oldSeries = null; - seriesRenderer._oldDataPoints = >[]; + seriesRendererDetails.oldSeries = null; + seriesRendererDetails.oldDataPoints = + >[]; } - _seriesRenderers.add(seriesRenderer); - _chartSeries.visibleSeriesRenderers.add(seriesRenderer); + _stateProperties.seriesRenderers.add(seriesRenderer); + _stateProperties.chartSeries.visibleSeriesRenderers.add(seriesRenderer); } } else { - _seriesRenderers.clear(); - _chartSeries.visibleSeriesRenderers.clear(); + _stateProperties.seriesRenderers.clear(); + _stateProperties.chartSeries.visibleSeriesRenderers.clear(); } } @@ -1578,8 +1574,9 @@ class SfCartesianChartState extends State if (currentSeries.key != null) { for (int index = 0; index < oldSeriesRenderers.length; index++) { final CartesianSeries series = - oldSeriesRenderers[index]._series; - if (_isSameSeries(series, currentSeries)) { + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[index]) + .series; + if (isSameSeries(series, currentSeries)) { return index; } } @@ -1589,126 +1586,37 @@ class SfCartesianChartState extends State /// Refresh method for axis void _refresh() { - if (_renderingDetails.legendWidgetContext.isNotEmpty) { - for (int i = 0; i < _renderingDetails.legendWidgetContext.length; i++) { - final _MeasureWidgetContext templateContext = - _renderingDetails.legendWidgetContext[i]; + if (_stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { + for (int i = 0; + i < _stateProperties.renderingDetails.legendWidgetContext.length; + i++) { + final MeasureWidgetContext templateContext = + _stateProperties.renderingDetails.legendWidgetContext[i]; final RenderBox renderBox = templateContext.context!.findRenderObject() as RenderBox; templateContext.size = renderBox.size; } - _legendRefresh = true; + _stateProperties.legendRefresh = true; setState(() { /// The chart will be rebuilding again, Once legend template sizes will be calculated. }); } } - /// Redraw method for chart axis - void _redraw() { - _oldAxisRenderers = _chartAxis._axisRenderersCollection; - if (_trackballBehaviorRenderer._trackballPainter?.timer != null) { - _trackballBehaviorRenderer._trackballPainter?.timer!.cancel(); - } - if (_renderingDetails.isLegendToggled) { - _segments = []; - _oldSeriesVisible = - List.filled(_chartSeries.visibleSeriesRenderers.length, null); - for (int i = 0; i < _chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartSeries.visibleSeriesRenderers[i]; - if (seriesRenderer is ColumnSeriesRenderer || - seriesRenderer is BarSeriesRenderer) { - for (int j = 0; j < seriesRenderer._segments.length; j++) { - _segments.add(seriesRenderer._segments[j]); - } - } - } - } - // ignore: unnecessary_null_comparison - if (_zoomedAxisRendererStates != null && - _zoomedAxisRendererStates.isNotEmpty) { - _zoomedState = false; - for (final ChartAxisRenderer axisRenderer in _zoomedAxisRendererStates) { - _zoomedState = axisRenderer._zoomFactor != 1; - if (_zoomedState!) { - break; - } - } - } - - _renderingDetails.widgetNeedUpdate = false; - - if (mounted) { - _isRedrawByZoomPan = true; - setState(() { - /// check the "mounted" property of this object and to ensure the object is still in the tree. - /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. - }); - } - } - - void _redrawByRangeChange() { - _oldAxisRenderers = _chartAxis._axisRenderersCollection; - if (mounted) { - setState(() { - /// check the "mounted" property of this object and to ensure the object is still in the tree. - /// When we do the range change by using the slider or other way, chart will be rebuilding again. - }); - } - } - - void _forwardAnimation(CartesianSeriesRenderer seriesRenderer) { - seriesRenderer._animationController.duration = Duration( - milliseconds: seriesRenderer._series.animationDuration.toInt()); - seriesRenderer._seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: seriesRenderer._animationController, - curve: const Interval(0.1, 0.8, curve: Curves.decelerate), - )); - seriesRenderer._seriesElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: seriesRenderer._animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - seriesRenderer._animationController.forward(from: 0.0); - } - - void _repaintTrendlines() { - _repaintNotifiers['trendline']!.value++; - } - - void _setPainterKey(int index, String name, bool renderComplete) { - int value = 0; - for (int i = 0; i < _painterKeys.length; i++) { - final _PainterKey painterKey = _painterKeys[i]; - if (painterKey.isRenderCompleted) { - value++; - } else if (painterKey.index == index && - painterKey.name == name && - !painterKey.isRenderCompleted) { - painterKey.isRenderCompleted = renderComplete; - value++; - } - if (value >= _painterKeys.length && !_triggerLoaded) { - _triggerLoaded = true; - } - } - } - Widget _renderTitle() { Widget titleWidget; // ignore: unnecessary_null_comparison - if (_chart.title.text != null && _chart.title.text.isNotEmpty) { + if (_stateProperties.chart.title.text != null && + _stateProperties.chart.title.text.isNotEmpty) { final Paint titleBackground = Paint() - ..color = _chart.title.borderColor + ..color = _stateProperties.chart.title.borderColor ..style = PaintingStyle.stroke - ..strokeWidth = _chart.title.borderWidth; - final TextStyle titleStyle = _getTextStyle( - textStyle: _chart.title.textStyle, + ..strokeWidth = _stateProperties.chart.title.borderWidth; + final TextStyle titleStyle = getTextStyle( + textStyle: _stateProperties.chart.title.textStyle, background: titleBackground, - fontColor: _chart.title.textStyle.color ?? - _renderingDetails.chartTheme.titleTextColor); + fontColor: _stateProperties.chart.title.textStyle.color ?? + _stateProperties.renderingDetails.chartTheme.titleTextColor); final TextStyle textStyle = TextStyle( color: titleStyle.color, fontSize: titleStyle.fontSize, @@ -1717,28 +1625,31 @@ class SfCartesianChartState extends State fontWeight: titleStyle.fontWeight); titleWidget = Container( child: Container( - child: Text(_chart.title.text, + child: Text(_stateProperties.chart.title.text, overflow: TextOverflow.clip, textAlign: TextAlign.center, textScaleFactor: 1.2, style: textStyle), decoration: BoxDecoration( - color: _chart.title.backgroundColor ?? - _renderingDetails.chartTheme.titleBackgroundColor, + color: _stateProperties.chart.title.backgroundColor ?? + _stateProperties + .renderingDetails.chartTheme.titleBackgroundColor, border: Border.all( - color: _chart.title.borderWidth == 0 + color: _stateProperties.chart.title.borderWidth == 0 ? Colors.transparent - : _chart - .title.borderColor, // ?? _chartTheme.titleTextColor, - width: _chart.title.borderWidth)), + : _stateProperties.chart.title + .borderColor, // ?? _chartTheme.titleTextColor, + width: _stateProperties.chart.title.borderWidth)), ), - alignment: (_chart.title.alignment == ChartAlignment.near) - ? Alignment.topLeft - : (_chart.title.alignment == ChartAlignment.far) - ? Alignment.topRight - : (_chart.title.alignment == ChartAlignment.center) - ? Alignment.topCenter - : Alignment.topCenter, + alignment: + (_stateProperties.chart.title.alignment == ChartAlignment.near) + ? Alignment.topLeft + : (_stateProperties.chart.title.alignment == ChartAlignment.far) + ? Alignment.topRight + : (_stateProperties.chart.title.alignment == + ChartAlignment.center) + ? Alignment.topCenter + : Alignment.topCenter, ); } else { titleWidget = Container(); @@ -1749,29 +1660,38 @@ class SfCartesianChartState extends State /// To arrange the chart area and legend area based on the legend position Widget _renderChartElements(BuildContext context) { if (widget.plotAreaBackgroundImage != null || widget.legend.image != null) { - _calculateImage(this); + calculateImage(_stateProperties); } - _renderingDetails.deviceOrientation = MediaQuery.of(context).orientation; + _stateProperties.renderingDetails.deviceOrientation = + MediaQuery.of(context).orientation; return Expanded( child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { Widget? element; - _renderingDetails.prevSize = - _renderingDetails.prevSize ?? constraints.biggest; - _renderingDetails.didSizeChange = - _renderingDetails.prevSize != constraints.biggest; - _renderingDetails.prevSize = constraints.biggest; + _stateProperties.renderingDetails.prevSize = + _stateProperties.renderingDetails.prevSize ?? constraints.biggest; + _stateProperties.renderingDetails.didSizeChange = + _stateProperties.renderingDetails.prevSize != constraints.biggest; + _stateProperties.renderingDetails.prevSize = constraints.biggest; + _stateProperties.isTooltipOrientationChanged = false; + final List> _pointCollections = + _getChartPoints(_stateProperties); + SchedulerBinding.instance!.addPostFrameCallback((_) { + _validateStateMaintenance(_stateProperties, _pointCollections); + }); final List legendTemplates = _bindCartesianLegendTemplateWidgets(); if (legendTemplates.isNotEmpty && - _renderingDetails.legendWidgetContext.isEmpty) { + _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); SchedulerBinding.instance?.addPostFrameCallback((_) => _refresh()); } else { _initialize(constraints); - _renderingDetails.chartLegend - ._calculateLegendBounds(_renderingDetails.chartLegend.chartSize); - element = _getElements(this, _ContainerArea(this), constraints); + _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( + _stateProperties.renderingDetails.chartLegend.chartSize); + _stateProperties.containerArea = ContainerArea(_stateProperties); + element = getElements( + _stateProperties, _stateProperties.containerArea, constraints); } return element!; }), @@ -1783,13 +1703,19 @@ class SfCartesianChartState extends State Widget? legendWidget; final List templates = []; if (widget.legend.isVisible! && widget.legend.legendItemBuilder != null) { - for (int i = 0; i < _seriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = _seriesRenderers[i]; - if (seriesRenderer._series.isVisibleInLegend) { + for (int i = 0; i < _stateProperties.seriesRenderers.length; i++) { + final CartesianSeriesRenderer seriesRenderer = + _stateProperties.seriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.series.isVisibleInLegend == true) { legendWidget = widget.legend.legendItemBuilder!( - seriesRenderer._seriesName!, seriesRenderer._series, null, i); - templates.add(_MeasureWidgetSize( - chartState: this, + seriesRendererDetails.seriesName!, + seriesRendererDetails.series, + null, + i); + templates.add(MeasureWidgetSize( + stateProperties: _stateProperties, seriesIndex: i, pointIndex: null, type: 'Legend', @@ -1802,16 +1728,23 @@ class SfCartesianChartState extends State return templates; } - /// To initialise chart legend + /// To initialize chart legend void _initialize(BoxConstraints constraints) { final double width = constraints.maxWidth; final double height = constraints.maxHeight; - _renderingDetails.legendRenderer._legendPosition = + final bool isMobilePlatform = + defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS; + _stateProperties.renderingDetails.legendRenderer.legendPosition = (widget.legend.position == LegendPosition.auto) - ? (height > width ? LegendPosition.bottom : LegendPosition.right) + ? (height > width + ? isMobilePlatform + ? LegendPosition.top + : LegendPosition.bottom + : LegendPosition.right) : widget.legend.position; final LegendPosition position = - _renderingDetails.legendRenderer._legendPosition; + _stateProperties.renderingDetails.legendRenderer.legendPosition; final double widthPadding = position == LegendPosition.left || position == LegendPosition.right ? 5 @@ -1820,167 +1753,293 @@ class SfCartesianChartState extends State position == LegendPosition.top || position == LegendPosition.bottom ? 5 : 0; - _renderingDetails.chartLegend.chartSize = + _stateProperties.renderingDetails.chartLegend.chartSize = Size(width - widthPadding, height - heightPadding); } /// To find the visible series void _findVisibleSeries(BuildContext context) { + final List _seriesRenderers = + _stateProperties.seriesRenderers; bool legendCheck = false; - _chartSeries.visibleSeriesRenderers = []; + _stateProperties.chartSeries.visibleSeriesRenderers = + []; final List visibleSeriesRenderers = - _chartSeries.visibleSeriesRenderers; + _stateProperties.chartSeries.visibleSeriesRenderers; for (int i = 0; i < _seriesRenderers.length; i++) { - _seriesRenderers[i]._seriesName = - _seriesRenderers[i]._series.name ?? 'Series $i'; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(_seriesRenderers[i]); + seriesRendererDetails.seriesName = + seriesRendererDetails.series.name ?? 'Series $i'; final CartesianSeries cartesianSeries = - _seriesRenderers[i]._series; - _seriesRenderers[i]._markerSettingsRenderer = - MarkerSettingsRenderer(_seriesRenderers[i]._series.markerSettings); + seriesRendererDetails.series; + seriesRendererDetails.markerSettingsRenderer = + MarkerSettingsRenderer(seriesRendererDetails.series.markerSettings); Trendline trendline; TrendlineRenderer trendlineRenderer; if (cartesianSeries.trendlines != null) { - _seriesRenderers[i]._trendlineRenderer = []; + seriesRendererDetails.trendlineRenderer = []; final List trendlineTypes = [0, 0, 0, 0, 0, 0]; for (final Trendline trendline in cartesianSeries.trendlines!) { trendlineRenderer = TrendlineRenderer(trendline); - _seriesRenderers[i]._trendlineRenderer.add(trendlineRenderer); - trendlineRenderer._name ??= + seriesRendererDetails.trendlineRenderer.add(trendlineRenderer); + trendlineRenderer.name ??= (trendline.type == TrendlineType.movingAverage ? 'Moving average' : trendline.type.toString().substring(14)) + (' ' + (trendlineTypes[trendline.type.index]++).toString()); } for (final TrendlineRenderer trendlineRenderer - in _seriesRenderers[i]._trendlineRenderer) { - trendline = trendlineRenderer._trendline; - trendlineRenderer._name = trendlineRenderer._name![0].toUpperCase() + - trendlineRenderer._name!.substring(1); + in seriesRendererDetails.trendlineRenderer) { + trendline = trendlineRenderer.trendline; + trendlineRenderer.name = trendlineRenderer.name![0].toUpperCase() + + trendlineRenderer.name!.substring(1); if (trendlineTypes[trendline.type.index] == 1 && - trendlineRenderer._name![trendlineRenderer._name!.length - 1] == + trendlineRenderer.name![trendlineRenderer.name!.length - 1] == '0') { - trendlineRenderer._name = trendlineRenderer._name! - .substring(0, trendlineRenderer._name!.length - 2); + trendlineRenderer.name = trendlineRenderer.name! + .substring(0, trendlineRenderer.name!.length - 2); } } } - if (_renderingDetails.initialRender! || - (_renderingDetails.widgetNeedUpdate && - !_legendToggling && - (_renderingDetails.oldDeviceOrientation == + if (_stateProperties.renderingDetails.initialRender! || + (_stateProperties.renderingDetails.widgetNeedUpdate && + !_stateProperties.legendToggling && + (_stateProperties.renderingDetails.oldDeviceOrientation == MediaQuery.of(context).orientation))) { - if (_seriesRenderers[i]._oldSeries != null && - (_seriesRenderers[i]._oldSeries!.isVisible == - _seriesRenderers[i]._series.isVisible)) { + if (seriesRendererDetails.oldSeries != null && + (seriesRendererDetails.oldSeries!.isVisible == + seriesRendererDetails.series.isVisible)) { legendCheck = true; } else { - if (_renderingDetails.legendToggleStates.isNotEmpty) { - _renderingDetails.legendToggleStates.clear(); + if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { + _stateProperties.renderingDetails.legendToggleStates.clear(); } - _seriesRenderers[i]._visible = _renderingDetails.initialRender! - ? _seriesRenderers[i]._series.isVisible - : _seriesRenderers[i]._visible ?? - _seriesRenderers[i]._series.isVisible; + seriesRendererDetails.visible = + _stateProperties.renderingDetails.initialRender! + ? seriesRendererDetails.series.isVisible + : seriesRendererDetails.visible ?? + seriesRendererDetails.series.isVisible; } } else { legendCheck = true; } + + final SeriesRendererDetails seriesDetails = + SeriesHelper.getSeriesRendererDetails(_seriesRenderers[0]); + final bool isFirstBarSeries = + seriesDetails.series.toString().contains('Bar') && + !seriesDetails.series.toString().contains('ErrorBar'); + final bool isMultipleBarSeries = + seriesRendererDetails.series.toString().contains('Bar') && + !seriesRendererDetails.series.toString().contains('ErrorBar'); if (i == 0 || - (!_seriesRenderers[0]._series.toString().contains('Bar') && - !_seriesRenderers[i]._series.toString().contains('Bar')) || - (_seriesRenderers[0]._series.toString().contains('Bar') && - (_seriesRenderers[i]._series.toString().contains('Bar')))) { + (!isFirstBarSeries && !isMultipleBarSeries) || + (isFirstBarSeries && isMultipleBarSeries)) { visibleSeriesRenderers.add(_seriesRenderers[i]); - if (!_renderingDetails.initialRender! && - _oldSeriesVisible.isNotEmpty && + if (!_stateProperties.renderingDetails.initialRender! && + _stateProperties.oldSeriesVisible.isNotEmpty && i < visibleSeriesRenderers.length) { if (i < visibleSeriesRenderers.length && - i < _oldSeriesVisible.length) { - _oldSeriesVisible[i] = visibleSeriesRenderers[i]._visible; + i < _stateProperties.oldSeriesVisible.length) { + _stateProperties.oldSeriesVisible[i] = + SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderers[i]) + .visible; } } if (legendCheck) { final int index = visibleSeriesRenderers.length - 1; + final SeriesRendererDetails visibleSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + visibleSeriesRenderers[index]); final String? legendItemText = - visibleSeriesRenderers[index]._series.legendItemText; - final String? legendText = _chart.legend.legendItemBuilder != null - ? visibleSeriesRenderers[index]._seriesName - : visibleSeriesRenderers[index]._series.isVisibleInLegend && - _chartSeries._renderingDetails.chartLegend - .legendCollections != - null && - _chartSeries._renderingDetails.chartLegend - .legendCollections!.isNotEmpty - ? _getLegendItemCollection(index)!.text - : null; - - final String? seriesName = visibleSeriesRenderers[index]._series.name; - _chartSeries.visibleSeriesRenderers[visibleSeriesRenderers.length - 1] - ._visible = - _checkWithLegendToggleState( - visibleSeriesRenderers.length - 1, - visibleSeriesRenderers[visibleSeriesRenderers.length - 1] - ._series, - legendText ?? - legendItemText ?? - seriesName ?? - 'Series $index'); + visibleSeriesDetails.series.legendItemText; + final String? legendText = + _stateProperties.chart.legend.legendItemBuilder != null + ? visibleSeriesDetails.seriesName + : visibleSeriesDetails.series.isVisibleInLegend == true && + _stateProperties.renderingDetails.chartLegend + .legendCollections != + null && + _stateProperties.renderingDetails.chartLegend + .legendCollections!.isNotEmpty + ? _getLegendItemCollection(index)!.text + : null; + + final String? seriesName = visibleSeriesDetails.series.name; + final SeriesRendererDetails seriesDetails = + SeriesHelper.getSeriesRendererDetails(_stateProperties.chartSeries + .visibleSeriesRenderers[visibleSeriesRenderers.length - 1]); + seriesDetails.visible = _checkWithLegendToggleState( + visibleSeriesRenderers.length - 1, + SeriesHelper.getSeriesRendererDetails( + visibleSeriesRenderers[visibleSeriesRenderers.length - 1]) + .series, + legendText ?? legendItemText ?? seriesName ?? 'Series $index'); } - final CartesianSeriesRenderer? cSeriesRenderer = _chartSeries + final CartesianSeriesRenderer? cSeriesRenderer = _stateProperties + .chartSeries .visibleSeriesRenderers[visibleSeriesRenderers.length - 1] is CartesianSeriesRenderer - ? _chartSeries + ? _stateProperties.chartSeries .visibleSeriesRenderers[visibleSeriesRenderers.length - 1] : null; - if (cSeriesRenderer?._series != null && - cSeriesRenderer?._series.trendlines != null) { + final SeriesRendererDetails cSeriesDetails = + SeriesHelper.getSeriesRendererDetails(cSeriesRenderer!); + if (cSeriesDetails.series != null && + cSeriesDetails.series.trendlines != null) { Trendline? trendline; TrendlineRenderer trendlineRenderer; - for (int j = 0; - j < cSeriesRenderer!._series.trendlines!.length; - j++) { - trendline = cSeriesRenderer._series.trendlines![j]; - trendlineRenderer = cSeriesRenderer._trendlineRenderer[j]; - trendlineRenderer._visible = _checkWithTrendlineLegendToggleState( + for (int j = 0; j < cSeriesDetails.series.trendlines!.length; j++) { + trendline = cSeriesDetails.series.trendlines![j]; + trendlineRenderer = cSeriesDetails.trendlineRenderer[j]; + trendlineRenderer.visible = _checkWithTrendlineLegendToggleState( visibleSeriesRenderers.length - 1, - cSeriesRenderer._series, + cSeriesDetails.series, j, trendline, - trendlineRenderer._name!) && - cSeriesRenderer._visible!; + trendlineRenderer.name!) && + cSeriesDetails.visible! == true; } - _isTrendlineToggled = false; + _stateProperties.isTrendlineToggled = false; } } legendCheck = false; } - _chartSeries.visibleSeriesRenderers = visibleSeriesRenderers; + _stateProperties.chartSeries.visibleSeriesRenderers = + visibleSeriesRenderers; /// setting indicators visibility - if (_chart.indicators.isNotEmpty) { + if (_stateProperties.chart.indicators.isNotEmpty) { TechnicalIndicatorsRenderer technicalIndicatorRenderer; - _technicalIndicatorRenderer.clear(); - for (int i = 0; i < _chart.indicators.length; i++) { - technicalIndicatorRenderer = - TechnicalIndicatorsRenderer(_chart.indicators[i]); - _technicalIndicatorRenderer.add(technicalIndicatorRenderer); - technicalIndicatorRenderer._visible = _renderingDetails.initialRender! - ? _chart.indicators[i].isVisible - : _checkIndicatorLegendToggleState( - visibleSeriesRenderers.length + i, - technicalIndicatorRenderer._visible ?? - _chart.indicators[i].isVisible); + _stateProperties.technicalIndicatorRenderer.clear(); + for (int i = 0; i < _stateProperties.chart.indicators.length; i++) { + technicalIndicatorRenderer = TechnicalIndicatorsRenderer( + _stateProperties.chart.indicators[i], _stateProperties); + _stateProperties.technicalIndicatorRenderer + .add(technicalIndicatorRenderer); + technicalIndicatorRenderer.visible = + _stateProperties.renderingDetails.initialRender! + ? _stateProperties.chart.indicators[i].isVisible + : _checkIndicatorLegendToggleState( + visibleSeriesRenderers.length + i, + technicalIndicatorRenderer.visible ?? + _stateProperties.chart.indicators[i].isVisible); } } + _stateProperties.seriesRenderers = _seriesRenderers; } - // this method returns the legend render context of a particular series - // since there is no necessity that the series index will match with the legend index - // especially when the previous series is made invisible in legend - _LegendRenderContext? _getLegendItemCollection(int index) { - for (final _LegendRenderContext legendContext - in _renderingDetails.chartLegend.legendCollections!) { + /// This will return crosshair and tooltip chart points + List> _getChartPoints( + CartesianStateProperties _stateProperties) { + CartesianChartPoint crosshairChartPoint = + CartesianChartPoint(null, null), + tooltipChartPoint = CartesianChartPoint(null, null); + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + final CrosshairBehaviorRenderer crosshairBehaviorRenderer = + _stateProperties.crosshairBehaviorRenderer; + final CrosshairRenderingDetails crosshairRenderingDetails = + CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (crosshairRenderingDetails.position != null && + _stateProperties.chart.crosshairBehavior.enable) { + crosshairChartPoint = calculatePixelToPoint( + crosshairRenderingDetails.position!, + _stateProperties.seriesRenderers.first); + } + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable && + !_stateProperties.isTooltipHidden && + !_stateProperties.requireAxisTooltip) { + if ((tooltipRenderingDetails.currentSeriesDetails.seriesType + .contains('bar') == + true && + tooltipRenderingDetails + .currentSeriesDetails.chart.isTransposed == + false) || + tooltipRenderingDetails.currentSeriesDetails.chart.isTransposed == + true) { + tooltipRenderingDetails + .currentSeriesDetails.stateProperties.requireInvertedAxis = true; + } + if (tooltipRenderingDetails.currentSeriesDetails.visible == true) { + tooltipChartPoint = calculatePixelToPoint( + Offset(tooltipRenderingDetails.showLocation!.dx, + tooltipRenderingDetails.showLocation!.dy), + tooltipRenderingDetails.currentSeriesDetails); + } + } + } + return >[ + crosshairChartPoint, + tooltipChartPoint + ]; + } + + /// Here for orientation change/browser resize, the logic in this method will get executed + void _validateStateMaintenance(CartesianStateProperties _stateProperties, + List _pointCollections) { + final TrackballBehaviorRenderer trackballBehaviorRenderer = + _stateProperties.trackballBehaviorRenderer; + final TrackballRenderingDetails trackballRenderingDetails = + TrackballHelper.getRenderingDetails(trackballBehaviorRenderer); + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + final CrosshairBehaviorRenderer crosshairBehaviorRenderer = + _stateProperties.crosshairBehaviorRenderer; + final CrosshairRenderingDetails crosshairRenderingDetails = + CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); + late Offset _crosshairOffset; + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (trackballRenderingDetails.chartPointInfo.isNotEmpty && + _stateProperties.chart.trackballBehavior.enable) { + _stateProperties.isTrackballOrientationChanged = true; + trackballRenderingDetails.internalShowByIndex( + trackballRenderingDetails.chartPointInfo[0].dataPointIndex!); + } + if (crosshairRenderingDetails.position != null && + _stateProperties.chart.crosshairBehavior.enable) { + _stateProperties.isCrosshairOrientationChanged = true; + _crosshairOffset = calculatePointToPixel( + _pointCollections[0], _stateProperties.seriesRenderers.first); + crosshairRenderingDetails.internalShow( + _crosshairOffset.dx, _crosshairOffset.dy, 'pixel'); + } + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable && + !_stateProperties.isTooltipHidden && + _pointCollections[1].x != null) { + _stateProperties.isTooltipOrientationChanged = true; + final Offset _tooltipPosition = calculatePointToPixel( + _pointCollections[1], tooltipRenderingDetails.currentSeriesDetails); + if (_stateProperties.chart.tooltipBehavior.builder != null) { + tooltipRenderingDetails.showTemplateTooltip( + Offset(_tooltipPosition.dx, _tooltipPosition.dy)); + } else { + tooltipRenderingDetails.internalShowByPixel( + _tooltipPosition.dx, _tooltipPosition.dy); + } + } + } + } + + /// This method returns the legend render context of a particular series + /// since there is no necessity that the series index will match with the legend index + /// especially when the previous series is made invisible in legend + LegendRenderContext? _getLegendItemCollection(int index) { + for (final LegendRenderContext legendContext + in _stateProperties.renderingDetails.chartLegend.legendCollections!) { if (legendContext.seriesIndex == index) { return legendContext; } @@ -1992,11 +2051,11 @@ class SfCartesianChartState extends State bool _checkIndicatorLegendToggleState(int seriesIndex, bool seriesVisible) { bool? seriesRender; if (widget.legend.legendItemBuilder != null) { - final List<_MeasureWidgetContext> legendToggles = - _renderingDetails.legendToggleTemplateStates; + final List legendToggles = + _stateProperties.renderingDetails.legendToggleTemplateStates; if (legendToggles.isNotEmpty) { for (int j = 0; j < legendToggles.length; j++) { - final _MeasureWidgetContext item = legendToggles[j]; + final MeasureWidgetContext item = legendToggles[j]; if (seriesIndex == item.seriesIndex) { seriesRender = false; break; @@ -2004,10 +2063,12 @@ class SfCartesianChartState extends State } } } else { - if (_renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; j < _renderingDetails.legendToggleStates.length; j++) { - final _LegendRenderContext legendRenderContext = - _renderingDetails.legendToggleStates[j]; + if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { + for (int j = 0; + j < _stateProperties.renderingDetails.legendToggleStates.length; + j++) { + final LegendRenderContext legendRenderContext = + _stateProperties.renderingDetails.legendToggleStates[j]; if (seriesIndex == legendRenderContext.seriesIndex) { seriesRender = false; break; @@ -2026,14 +2087,16 @@ class SfCartesianChartState extends State Trendline trendline, String text) { bool? seriesRender; - if (_renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; j < _renderingDetails.legendToggleStates.length; j++) { - final _LegendRenderContext legendRenderContext = - _renderingDetails.legendToggleStates[j]; + if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { + for (int j = 0; + j < _stateProperties.renderingDetails.legendToggleStates.length; + j++) { + final LegendRenderContext legendRenderContext = + _stateProperties.renderingDetails.legendToggleStates[j]; if ((legendRenderContext.text == text && legendRenderContext.seriesIndex == seriesIndex && legendRenderContext.trendlineIndex == trendlineIndex) || - _isTrendlineToggled) { + _stateProperties.isTrendlineToggled) { seriesRender = false; break; } @@ -2042,16 +2105,16 @@ class SfCartesianChartState extends State return seriesRender ?? true; } - /// To toggle series visiblity based on legend toggle states + /// To toggle series visibility based on legend toggle states bool _checkWithLegendToggleState( int seriesIndex, ChartSeries series, String text) { bool? seriesRender; - if (_chart.legend.legendItemBuilder != null) { - final List<_MeasureWidgetContext> legendToggles = - _renderingDetails.legendToggleTemplateStates; + if (_stateProperties.chart.legend.legendItemBuilder != null) { + final List legendToggles = + _stateProperties.renderingDetails.legendToggleTemplateStates; if (legendToggles.isNotEmpty) { for (int j = 0; j < legendToggles.length; j++) { - final _MeasureWidgetContext item = legendToggles[j]; + final MeasureWidgetContext item = legendToggles[j]; if (seriesIndex == item.seriesIndex) { seriesRender = false; break; @@ -2059,16 +2122,18 @@ class SfCartesianChartState extends State } } } else { - if (_renderingDetails.legendToggleStates.isNotEmpty) { - for (int j = 0; j < _renderingDetails.legendToggleStates.length; j++) { - final _LegendRenderContext legendRenderContext = - _renderingDetails.legendToggleStates[j]; + if (_stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { + for (int j = 0; + j < _stateProperties.renderingDetails.legendToggleStates.length; + j++) { + final LegendRenderContext legendRenderContext = + _stateProperties.renderingDetails.legendToggleStates[j]; if (seriesIndex == legendRenderContext.seriesIndex && legendRenderContext.text == text) { if (series is CartesianSeries) { final CartesianSeries cSeries = series; if (cSeries.trendlines != null) { - _isTrendlineToggled = true; + _stateProperties.isTrendlineToggled = true; } } seriesRender = false; @@ -2081,15 +2146,19 @@ class SfCartesianChartState extends State } } +/// Represents the container area // ignore: must_be_immutable -class _ContainerArea extends StatelessWidget { +class ContainerArea extends StatelessWidget { + /// Creates an instance for container area // ignore: prefer_const_constructors_in_immutables - _ContainerArea(this._chartState); - final SfCartesianChartState _chartState; - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfCartesianChart get chart => _chartState._chart; - _RenderingDetails get _renderingDetails => _chartState._renderingDetails; + ContainerArea(this._stateProperties); + final CartesianStateProperties _stateProperties; + + /// Gets the chart from state properties + SfCartesianChart get chart => _stateProperties.chart; + RenderingDetails get _renderingDetails => _stateProperties.renderingDetails; + + /// Specifies the render box late RenderBox renderBox; Offset? _touchPosition; Offset? _tapDownDetails; @@ -2097,26 +2166,51 @@ class _ContainerArea extends StatelessWidget { late CartesianSeries _series; late XyDataSeriesRenderer _seriesRenderer; Offset? _zoomStartPosition; - bool get _enableMouseHover => _chartState._enableMouseHover; + bool get _enableMouseHover => _stateProperties.enableMouseHover; + + /// Get trackball rendering Details + TrackballRenderingDetails get trackballRenderingDetails => + TrackballHelper.getRenderingDetails( + _stateProperties.trackballBehaviorRenderer); + @override Widget build(BuildContext context) { - final bool isUserInteractionEnabled = - chart.zoomPanBehavior.enableDoubleTapZooming || - chart.zoomPanBehavior.enableMouseWheelZooming || - _chartState._zoomPanBehaviorRenderer._canPerformSelection || - chart.zoomPanBehavior.enablePanning || - chart.zoomPanBehavior.enablePinching || - (chart.trackballBehavior.enable && - chart.trackballBehavior.activationMode == - ActivationMode.singleTap) || - (chart.crosshairBehavior.enable && - chart.crosshairBehavior.activationMode == - ActivationMode.singleTap) || - _chartState._trackballBehaviorRenderer._isLongPressActivated || - _chartState._crosshairBehaviorRenderer._isLongPressActivated || - chart.onChartTouchInteractionMove != null || - chart.loadMoreIndicatorBuilder != null || - chart.onPlotAreaSwipe != null; + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + + //this boolean prohibits both x and y scrolls for the parent widget + final bool isXYPanMode = (chart.crosshairBehavior.enable && + chart.crosshairBehavior.activationMode == + ActivationMode.singleTap) || + ((chart.zoomPanBehavior.enablePinching || + chart.zoomPanBehavior.enablePanning) && + chart.zoomPanBehavior.zoomMode == ZoomMode.xy); + + final bool requireInvertedAxis = _stateProperties.seriesRenderers.isNotEmpty + ? (chart.isTransposed ^ + getSeriesType(_stateProperties.seriesRenderers[0]) + .toLowerCase() + .contains('bar')) + : chart.isTransposed; + + //this boolean prohibits x scrolls for the parent widget + final bool isXPan = (chart.trackballBehavior.enable && + !requireInvertedAxis && + chart.trackballBehavior.activationMode == + ActivationMode.singleTap) || + chart.onPlotAreaSwipe != null || + (chart.zoomPanBehavior.enablePanning && + chart.zoomPanBehavior.zoomMode == ZoomMode.x); + + //this boolean prohibits y scrolls for the parent widget + final bool isYPan = (chart.trackballBehavior.enable && + requireInvertedAxis && + chart.trackballBehavior.activationMode == + ActivationMode.singleTap) || + (chart.zoomPanBehavior.enablePanning && + chart.zoomPanBehavior.zoomMode == ZoomMode.y); + return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Container( @@ -2131,7 +2225,7 @@ class _ContainerArea extends StatelessWidget { onExit: (PointerEvent event) => _performMouseExit(event), child: Listener( onPointerDown: (PointerDownEvent event) { - _chartState._pointerDeviceKind = event.kind; + _stateProperties.pointerDeviceKind = event.kind; _performPointerDown(event); ChartTouchInteractionArgs touchArgs; if (chart.onChartTouchInteractionDown != null) { @@ -2152,9 +2246,9 @@ class _ContainerArea extends StatelessWidget { } }, onPointerUp: (PointerUpEvent event) { - _chartState._isTouchUp = true; + _stateProperties.isTouchUp = true; _performPointerUp(event); - _chartState._isTouchUp = false; + _stateProperties.isTouchUp = false; ChartTouchInteractionArgs touchArgs; if (chart.onChartTouchInteractionUp != null) { touchArgs = ChartTouchInteractionArgs(); @@ -2175,28 +2269,35 @@ class _ContainerArea extends StatelessWidget { _touchPosition = position; }, onTap: () { - final Offset position = _touchPosition!; - if (_chartState._chartSeries.visibleSeriesRenderers != + if (_stateProperties + .chartSeries.visibleSeriesRenderers != null && - _chartState._chartSeries.visibleSeriesRenderers + _stateProperties.chartSeries.visibleSeriesRenderers .isNotEmpty && + _touchPosition != null && chart.selectionGesture == ActivationMode.singleTap && - _chartState._zoomPanBehaviorRenderer._isPinching != - true) { + zoomingBehaviorDetails.isPinching != true) { + final Offset position = _touchPosition!; final CartesianSeriesRenderer - selectionSeriesRenderer = _findSeries(position)!; + selectionseriesRenderer = _findSeries(position)!; + final SeriesRendererDetails selectionSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + selectionseriesRenderer); final SelectionBehaviorRenderer? selectionBehaviorRenderer = - selectionSeriesRenderer - ._selectionBehaviorRenderer; - if (selectionSeriesRenderer._isSelectionEnable && - selectionBehaviorRenderer?._selectionRenderer != - null && - !selectionSeriesRenderer._isOuterRegion) { - selectionBehaviorRenderer?._selectionRenderer - ?.seriesRenderer = selectionSeriesRenderer; - selectionBehaviorRenderer?.onTouchDown( + selectionSeriesDetails.selectionBehaviorRenderer; + final SelectionDetails? selectionDetails = + SelectionHelper.getRenderingDetails( + selectionBehaviorRenderer!); + if (selectionSeriesDetails.isSelectionEnable == + true && + selectionDetails?.selectionRenderer != null && + selectionSeriesDetails.isOuterRegion == false) { + selectionDetails + ?.selectionRenderer?.seriesRendererDetails = + selectionSeriesDetails; + selectionBehaviorRenderer.onTouchDown( position.dx, position.dy); } } @@ -2206,20 +2307,27 @@ class _ContainerArea extends StatelessWidget { renderBox.globalToLocal(details.globalPosition); final List visibleSeriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers; + _stateProperties.chartSeries.visibleSeriesRenderers; if (chart.onPointTapped != null) { - _calculatePointSeriesIndex( - chart, _chartState, position); + calculatePointSeriesIndex( + chart, _stateProperties, position); } - if (_findSeries(position)!._series.onPointTap != null) { - _calculatePointSeriesIndex(chart, _chartState, + final CartesianSeriesRenderer? cartesianSeriesRenderer = + _findSeries(position); + if (cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails( + cartesianSeriesRenderer) + .series + .onPointTap != + null) { + calculatePointSeriesIndex(chart, _stateProperties, position, null, ActivationMode.singleTap); } if (chart.onAxisLabelTapped != null) { _triggerAxisLabelEvent(position); } if (chart.onDataLabelTapped != null) { - _triggerDataLabelEvent( + triggerDataLabelEvent( chart, visibleSeriesRenderer, position); } }, @@ -2239,22 +2347,22 @@ class _ContainerArea extends StatelessWidget { onPanDown: (DragDownDetails details) { _performPanDown(details); }, - onVerticalDragUpdate: isUserInteractionEnabled + onVerticalDragUpdate: isXYPanMode || isYPan ? (DragUpdateDetails details) { _performPanUpdate(details); } : null, - onVerticalDragEnd: isUserInteractionEnabled + onVerticalDragEnd: isXYPanMode || isYPan ? (DragEndDetails details) { _performPanEnd(details); } : null, - onHorizontalDragUpdate: isUserInteractionEnabled + onHorizontalDragUpdate: isXYPanMode || isXPan ? (DragUpdateDetails details) { _performPanUpdate(details); } : null, - onHorizontalDragEnd: isUserInteractionEnabled + onHorizontalDragEnd: isXYPanMode || isXPan ? (DragEndDetails details) { _performPanEnd(details); } @@ -2268,9 +2376,9 @@ class _ContainerArea extends StatelessWidget { }); } - /// To initialise chart + /// To initialize chart Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - // chart._chartState._tooltipBehaviorRenderer = TooltipBehaviorRenderer(chart.tooltipBehavior); + // chart._stateProperties.tooltipBehaviorRenderer = TooltipBehaviorRenderer(chart.tooltipBehavior); _calculateContainerSize(constraints); _calculateBounds(); @@ -2283,27 +2391,32 @@ class _ContainerArea extends StatelessWidget { void _calculateContainerSize(BoxConstraints constraints) { final double width = constraints.maxWidth; final double height = constraints.maxHeight; - _renderingDetails.chartContainerRect = Rect.fromLTWH(0, 0, width, height); + _stateProperties.renderingDetails.chartContainerRect = + Rect.fromLTWH(0, 0, width, height); } /// Calculate container bounds void _calculateBounds() { - _chartState._chartSeries._processData(); - _chartState._chartAxis._measureAxesBounds(); - _chartState._rangeChangeBySlider = false; - _chartState._rangeChangedByChart = false; + _stateProperties.chartSeries.processData(); + _stateProperties.chartAxis.measureAxesBounds(); + _stateProperties.rangeChangeBySlider = false; + _stateProperties.rangeChangedByChart = false; } /// To calculate the trendline region - void _calculateTrendlineRegion( - SfCartesianChartState _chartState, XyDataSeriesRenderer seriesRenderer) { - if (seriesRenderer._series.trendlines != null) { + void _calculateTrendlineRegion(CartesianStateProperties _stateProperties, + XyDataSeriesRenderer seriesRenderer) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.series.trendlines != null) { TrendlineRenderer trendlineRenderer; - for (int i = 0; i < seriesRenderer._series.trendlines!.length; i++) { - trendlineRenderer = seriesRenderer._trendlineRenderer[i]; - if (trendlineRenderer._isNeedRender) { + for (int i = 0; + i < seriesRendererDetails.series.trendlines!.length; + i++) { + trendlineRenderer = seriesRendererDetails.trendlineRenderer[i]; + if (trendlineRenderer.isNeedRender) { trendlineRenderer.calculateTrendlinePoints( - seriesRenderer, _chartState); + seriesRendererDetails, _stateProperties); } } } @@ -2311,8 +2424,8 @@ class _ContainerArea extends StatelessWidget { /// To render chart widgets Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _renderingDetails.chartWidgets = []; - _chartState._renderDatalabelRegions = []; + _stateProperties.renderingDetails.chartWidgets = []; + _stateProperties.renderDatalabelRegions = []; _bindAxisWidgets('outside'); _bindPlotBandWidgets(true); _bindSeriesWidgets(); @@ -2324,33 +2437,33 @@ class _ContainerArea extends StatelessWidget { _bindInteractionWidgets(constraints, context); _bindLoadMoreIndicatorWidget(); renderBox = context.findRenderObject()! as RenderBox; - _chartState._containerArea = this; - _chartState._legendRefresh = false; + _stateProperties.containerArea = this; + _stateProperties.legendRefresh = false; return Container( child: Stack( textDirection: TextDirection.ltr, - children: _renderingDetails.chartWidgets!)); + children: _stateProperties.renderingDetails.chartWidgets!)); } void _bindLoadMoreIndicatorWidget() { - _renderingDetails.chartWidgets!.add(StatefulBuilder( + _stateProperties.renderingDetails.chartWidgets!.add(StatefulBuilder( builder: (BuildContext context, StateSetter stateSetter) { Widget renderWidget; - _chartState._loadMoreViewStateSetter = stateSetter; - renderWidget = _chartState._isLoadMoreIndicator + _stateProperties.loadMoreViewStateSetter = stateSetter; + renderWidget = _stateProperties.isLoadMoreIndicator ? Center( - child: _chartState._chart.loadMoreIndicatorBuilder!( - context, _chartState._swipeDirection)) + child: _stateProperties.chart.loadMoreIndicatorBuilder!( + context, _stateProperties.swipeDirection)) : renderWidget = Container(); return renderWidget; })); } void _bindPlotBandWidgets(bool shouldRenderAboveSeries) { - _renderingDetails.chartWidgets!.add(RepaintBoundary( + _stateProperties.renderingDetails.chartWidgets!.add(RepaintBoundary( child: CustomPaint( - painter: _PlotBandPainter( - chartState: _chartState, + painter: getPlotBandPainter( + stateProperties: _stateProperties, shouldRenderAboveSeries: shouldRenderAboveSeries)))); } @@ -2359,68 +2472,83 @@ class _ContainerArea extends StatelessWidget { final Map> trendlineAnimations = >{}; for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < _stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i] + _seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i] as XyDataSeriesRenderer; - _series = _seriesRenderer._series; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(_seriesRenderer); + _series = seriesRendererDetails.series; // ignore: unnecessary_null_comparison if (_seriesRenderer != null && - _seriesRenderer._visible! && + seriesRendererDetails.visible! == true && _series.trendlines != null) { isTrendline = true; - for (int j = 0; j < _seriesRenderer._trendlineRenderer.length; j++) { + for (int j = 0; + j < seriesRendererDetails.trendlineRenderer.length; + j++) { final TrendlineRenderer trendlineRenderer = - _seriesRenderer._trendlineRenderer[j]; + seriesRendererDetails.trendlineRenderer[j]; final Trendline trendline = _series.trendlines![j]; if (trendline.animationDuration > 0 && (_renderingDetails.oldDeviceOrientation == null || - _renderingDetails.oldDeviceOrientation == - _renderingDetails.deviceOrientation) && - _seriesRenderer._needsAnimation && - _seriesRenderer._oldSeries == null) { - trendlineRenderer._animationController.duration = - Duration(milliseconds: trendline.animationDuration.toInt()); + _stateProperties.renderingDetails.oldDeviceOrientation == + _stateProperties.renderingDetails.deviceOrientation) && + seriesRendererDetails.needsAnimation == true && + seriesRendererDetails.oldSeries == null) { + final int totalAnimationDuration = + trendline.animationDuration.toInt() + + trendline.animationDelay!.toInt(); + trendlineRenderer.animationController.duration = + Duration(milliseconds: totalAnimationDuration); + const double maxSeriesInterval = 0.8; + double minSeriesInterval = 0.1; + minSeriesInterval = trendline.animationDelay!.toInt() / + totalAnimationDuration * + (maxSeriesInterval - minSeriesInterval) + + minSeriesInterval; trendlineAnimations['$i-$j'] = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: trendlineRenderer._animationController, - curve: const Interval(0.1, 0.8, curve: Curves.decelerate), + parent: trendlineRenderer.animationController, + curve: Interval(minSeriesInterval, maxSeriesInterval, + curve: Curves.decelerate), )); - trendlineRenderer._animationController.forward(from: 0.0); + trendlineRenderer.animationController.forward(from: 0.0); } } } } if (isTrendline) { - _renderingDetails.chartWidgets!.add(RepaintBoundary( + _stateProperties.renderingDetails.chartWidgets!.add(RepaintBoundary( child: CustomPaint( - painter: _TrendlinePainter( - chartState: _chartState, + painter: TrendlinePainter( + stateProperties: _stateProperties, trendlineAnimations: trendlineAnimations, - notifier: _chartState._repaintNotifiers['trendline']!)), + notifier: _stateProperties.repaintNotifiers['trendline']!)), )); } } /// To bind the widget for data label void _bindDataLabelWidgets() { - _chartState._renderDataLabel = _DataLabelRenderer( - cartesianChartState: _chartState, - show: _renderingDetails.animateCompleted); - _renderingDetails.chartWidgets!.add(_chartState._renderDataLabel!); + _stateProperties.renderDataLabel = DataLabelRenderer( + stateProperties: _stateProperties, + show: _stateProperties.renderingDetails.animateCompleted); + _stateProperties.renderingDetails.chartWidgets! + .add(_stateProperties.renderDataLabel!); } /// To render a template void _renderTemplates() { - _chartState._annotationRegions = []; - _renderingDetails.templates = <_ChartTemplateInfo>[]; + _stateProperties.annotationRegions = []; + _stateProperties.renderingDetails.templates = []; _renderDataLabelTemplates(); if (chart.annotations != null && chart.annotations!.isNotEmpty) { for (int i = 0; i < chart.annotations!.length; i++) { final CartesianChartAnnotation annotation = chart.annotations![i]; - final _ChartLocation location = - _getAnnotationLocation(annotation, _chartState); - final _ChartTemplateInfo chartTemplateInfo = _ChartTemplateInfo( + final ChartLocation location = + getAnnotationLocation(annotation, _stateProperties); + final ChartTemplateInfo chartTemplateInfo = ChartTemplateInfo( key: GlobalKey(), animationDuration: 200, widget: annotation.widget!, @@ -2430,22 +2558,26 @@ class _ContainerArea extends StatelessWidget { verticalAlignment: annotation.verticalAlignment, horizontalAlignment: annotation.horizontalAlignment, clipRect: annotation.region == AnnotationRegion.chart - ? _renderingDetails.chartContainerRect - : _chartState._chartAxis._axisClipRect, + ? _stateProperties.renderingDetails.chartContainerRect + : _stateProperties.chartAxis.axisClipRect, location: Offset(location.x.toDouble(), location.y.toDouble())); - _renderingDetails.templates.add(chartTemplateInfo); + _stateProperties.renderingDetails.templates.add(chartTemplateInfo); } } if (_renderingDetails.templates.isNotEmpty) { - final int templateLength = _renderingDetails.templates.length; - for (int i = 0; i < _renderingDetails.templates.length; i++) { - final _ChartTemplateInfo templateInfo = _renderingDetails.templates[i]; - _renderingDetails.chartWidgets!.add(_RenderTemplate( + final int templateLength = + _stateProperties.renderingDetails.templates.length; + for (int i = 0; + i < _stateProperties.renderingDetails.templates.length; + i++) { + final ChartTemplateInfo templateInfo = + _stateProperties.renderingDetails.templates[i]; + _stateProperties.renderingDetails.chartWidgets!.add(RenderTemplate( template: templateInfo, templateIndex: i, templateLength: templateLength, - chartState: _chartState)); + stateProperties: _stateProperties)); } } } @@ -2454,18 +2586,21 @@ class _ContainerArea extends StatelessWidget { void _renderDataLabelTemplates() { Widget? labelWidget; CartesianChartPoint point; - _renderingDetails.dataLabelTemplateRegions = []; + _stateProperties.renderingDetails.dataLabelTemplateRegions = []; for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < _stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; + _stateProperties.chartSeries.visibleSeriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; num padding; - if (series.dataLabelSettings.isVisible && seriesRenderer._visible!) { - for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { - point = seriesRenderer._dataPoints[j]; + if (series.dataLabelSettings.isVisible && + seriesRendererDetails.visible! == true) { + for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { + point = seriesRendererDetails.dataPoints[j]; if (point.isVisible && !point.isGap) { labelWidget = (series.dataLabelSettings.builder != null) ? series.dataLabelSettings.builder!( @@ -2476,7 +2611,7 @@ class _ContainerArea extends StatelessWidget { i) : null; if (labelWidget != null) { - final String seriesType = seriesRenderer._seriesType; + final String seriesType = seriesRendererDetails.seriesType; final List dataLabelTemplateYValues = (seriesType.contains('range') || (seriesType.contains('hilo') && @@ -2497,30 +2632,30 @@ class _ContainerArea extends StatelessWidget { for (int k = 0; k < dataLabelTemplateYValues.length; k++) { padding = (k == 0 && dataLabelTemplateYValues.length > 1 && - !_chartState._requireInvertedAxis) + !_stateProperties.requireInvertedAxis) ? 20 : 0; - final _ChartLocation location = _calculatePoint( + final ChartLocation location = calculatePoint( point.xValue, dataLabelTemplateYValues[k], - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + _stateProperties.requireInvertedAxis, series, - _chartState._chartAxis._axisClipRect); + _stateProperties.chartAxis.axisClipRect); - final _ChartTemplateInfo templateInfo = _ChartTemplateInfo( + final ChartTemplateInfo templateInfo = ChartTemplateInfo( key: GlobalKey(), templateType: 'DataLabel', pointIndex: j, seriesIndex: i, needMeasure: true, - clipRect: _chartState._chartAxis._axisClipRect, + clipRect: _stateProperties.chartAxis.axisClipRect, animationDuration: (series.animationDuration + 1000.0).floor(), widget: labelWidget, location: Offset(location.x, location.y + padding)); - _renderingDetails.templates.add(templateInfo); + _stateProperties.renderingDetails.templates.add(templateInfo); } } } @@ -2531,65 +2666,74 @@ class _ContainerArea extends StatelessWidget { /// To bind a series of widgets for all series void _bindSeriesWidgets() { - _chartState._painterKeys = <_PainterKey>[]; - _chartState._animationCompleteCount = 0; - _renderingDetails.animateCompleted = false; + _stateProperties.painterKeys = []; + _stateProperties.animationCompleteCount = 0; + _stateProperties.renderingDetails.animateCompleted = false; for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < _stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i] + _seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i] as XyDataSeriesRenderer; - _seriesRenderer._animationCompleted = false; - _series = _seriesRenderer._series; - final String _seriesType = _seriesRenderer._seriesType; - if (_seriesRenderer._isIndicator) { - _seriesRenderer._repaintNotifier = ValueNotifier(0); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(_seriesRenderer); + seriesRendererDetails.animationCompleted = false; + _series = seriesRendererDetails.series; + final String _seriesType = seriesRendererDetails.seriesType; + if (seriesRendererDetails.isIndicator == true) { + seriesRendererDetails.repaintNotifier = ValueNotifier(0); if (_seriesRenderer is XyDataSeriesRenderer) { - _seriesRenderer._animationController = - AnimationController(vsync: _chartState) - ..addListener(_seriesRenderer._repaintSeriesElement); - _chartState._controllerList[_seriesRenderer._animationController] = - _seriesRenderer._repaintSeriesElement; - _seriesRenderer._animationController - .addStatusListener(_seriesRenderer._animationStatusListener); + seriesRendererDetails.animationController = + AnimationController(vsync: _stateProperties.chartState) + ..addListener(seriesRendererDetails.repaintSeriesElement); + _stateProperties + .controllerList[seriesRendererDetails.animationController] = + seriesRendererDetails.repaintSeriesElement; + seriesRendererDetails.animationController + .addStatusListener(seriesRendererDetails.animationStatusListener); } } // ignore: unnecessary_null_comparison - if (_seriesRenderer != null && _seriesRenderer._visible!) { - _calculateTrendlineRegion(_chartState, _seriesRenderer); - _series.selectionBehavior._chartState = _chartState; - _seriesRenderer._selectionBehavior = _series.selectionBehavior; - final dynamic selectionBehavior = _seriesRenderer._selectionBehavior; - _seriesRenderer._selectionBehaviorRenderer = - SelectionBehaviorRenderer(selectionBehavior, chart, _chartState); + if (_seriesRenderer != null && seriesRendererDetails.visible! == true) { + _calculateTrendlineRegion(_stateProperties, _seriesRenderer); + seriesRendererDetails.selectionBehavior = _series.selectionBehavior; + final dynamic selectionBehavior = + seriesRendererDetails.selectionBehavior; + seriesRendererDetails.selectionBehaviorRenderer = + SelectionBehaviorRenderer( + selectionBehavior, chart, _stateProperties); final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _seriesRenderer._selectionBehaviorRenderer; + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.setSelectionBehaviorRenderer( + _series.selectionBehavior, selectionBehaviorRenderer!); if (selectionBehaviorRenderer != null) { - selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer!.chart = chart; - selectionBehaviorRenderer._selectionRenderer!._chartState = - _chartState; - selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = - _seriesRenderer; - _series = _seriesRenderer._series; + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); + selectionDetails.selectionRenderer ??= SelectionRenderer(); + selectionDetails.selectionRenderer!.chart = chart; + selectionDetails.selectionRenderer!.stateProperties = + _stateProperties; + selectionDetails.selectionRenderer!.seriesRendererDetails = + seriesRendererDetails; + _series = seriesRendererDetails.series; if (selectionBehavior.selectionController != null) { - selectionBehaviorRenderer._selectRange(); + selectionDetails.selectRange(); } - selectionBehaviorRenderer._selectionRenderer!.selectedSegments = - _chartState._selectedSegments; - selectionBehaviorRenderer._selectionRenderer!.unselectedSegments = - _chartState._unselectedSegments; + selectionDetails.selectionRenderer!.selectedSegments = + _stateProperties.selectedSegments; + selectionDetails.selectionRenderer!.unselectedSegments = + _stateProperties.unselectedSegments; //To determine whether initialSelectedDataIndexes collection is updated dynamically bool isSelecetedIndexesUpdated = false; if (_series.initialSelectedDataIndexes != null && _series.initialSelectedDataIndexes!.isNotEmpty && - _seriesRenderer._oldSelectedIndexes != null && - _seriesRenderer._oldSelectedIndexes!.isNotEmpty && - _seriesRenderer._oldSelectedIndexes!.length == + seriesRendererDetails.oldSelectedIndexes != null && + seriesRendererDetails.oldSelectedIndexes!.isNotEmpty == true && + seriesRendererDetails.oldSelectedIndexes!.length == _series.initialSelectedDataIndexes!.length) { for (final int index in _series.initialSelectedDataIndexes!) { isSelecetedIndexesUpdated = - !_seriesRenderer._oldSelectedIndexes!.contains(index); + seriesRendererDetails.oldSelectedIndexes!.contains(index) == + false; if (isSelecetedIndexesUpdated) { break; } @@ -2601,126 +2745,145 @@ class _ContainerArea extends StatelessWidget { int totalSelectedSegment = 0; int? selectedSeriesIndex; if (selectionBehavior.enable == true && - _chartState._selectedSegments.isNotEmpty && - _chartState._unselectedSegments.isNotEmpty) { - for (int j = 0; j < _chartState._selectedSegments.length; j++) { - if (_chartState._selectedSegments[j]._seriesIndex == i) { + _stateProperties.selectedSegments.isNotEmpty && + _stateProperties.unselectedSegments.isNotEmpty) { + for (int j = 0; j < _stateProperties.selectedSegments.length; j++) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties( + _stateProperties.selectedSegments[j]); + if (segmentProperties.seriesIndex == i) { totalSelectedSegment += 1; - selectedSeriesIndex = - _chartState._selectedSegments[j]._seriesIndex; + selectedSeriesIndex = segmentProperties.seriesIndex; } } - for (int k = 0; k < _chartState._unselectedSegments.length; k++) { - if (_chartState._unselectedSegments[k]._seriesIndex == i) { + for (int k = 0; + k < _stateProperties.unselectedSegments.length; + k++) { + if (SegmentHelper.getSegmentProperties( + _stateProperties.unselectedSegments[k]) + .seriesIndex == + i) { totalSelectedSegment += 1; } } } - if (_chartState._isRangeSelectionSlider == false && + if (_stateProperties.isRangeSelectionSlider == false && selectionBehavior.enable == true && (isSelecetedIndexesUpdated || (!_renderingDetails.initialRender! && (totalSelectedSegment != 0 && (totalSelectedSegment < - _chartState - ._seriesRenderers[i]._dataPoints.length))))) { - int segmentLength = _seriesRenderer._dataPoints.length; - - if (_isLineTypeSeries(_seriesRenderer._seriesType) || - _seriesRenderer._seriesType.contains('boxandwhisker')) { - segmentLength = _seriesRenderer._dataPoints.length - 1; + SeriesHelper.getSeriesRendererDetails( + _stateProperties.seriesRenderers[i]) + .dataPoints + .length))))) { + int segmentLength = seriesRendererDetails.dataPoints.length; + + if (isLineTypeSeries(seriesRendererDetails.seriesType) || + seriesRendererDetails.seriesType.contains('boxandwhisker') == + true) { + segmentLength = seriesRendererDetails.dataPoints.length - 1; } for (int j = 0; j < segmentLength; j++) { final ChartSegment segment = ColumnSegment(); + SegmentHelper.setSegmentProperties( + segment, SegmentProperties(_stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); segment.currentSegmentIndex = j; - segment._seriesIndex = i; - segment._currentPoint = _seriesRenderer._dataPoints[j]; + segmentProperties.seriesIndex = i; + segmentProperties.currentPoint = + seriesRendererDetails.dataPoints[j]; ((_series.initialSelectedDataIndexes! .contains(segment.currentSegmentIndex) && isSelecetedIndexesUpdated) || chart.selectionType == SelectionType.series && selectedSeriesIndex == i) - ? selectionBehaviorRenderer - ._selectionRenderer!.selectedSegments + ? SelectionHelper.getRenderingDetails( + selectionBehaviorRenderer) + .selectionRenderer! + .selectedSegments .add(segment) - : selectionBehaviorRenderer - ._selectionRenderer!.unselectedSegments! + : SelectionHelper.getRenderingDetails( + selectionBehaviorRenderer) + .selectionRenderer! + .unselectedSegments! .add(segment); } - _seriesRenderer._oldSelectedIndexes = [] + seriesRendererDetails.oldSelectedIndexes = [] //ignore: prefer_spread_collections ..addAll(_series.initialSelectedDataIndexes!); } } // ignore: unnecessary_null_comparison - if (_seriesRenderer._animationController != null && + if (seriesRendererDetails.animationController != null && _series.animationDuration > 0 && !_renderingDetails.didSizeChange && (_renderingDetails.oldDeviceOrientation == null || - _chartState._legendRefresh || - _renderingDetails.oldDeviceOrientation == - _renderingDetails.deviceOrientation) && + _stateProperties.legendRefresh || + _stateProperties.renderingDetails.oldDeviceOrientation == + _stateProperties.renderingDetails.deviceOrientation) && (_renderingDetails.initialRender! || - _chartState._legendRefresh || + _stateProperties.legendRefresh || ((_seriesType == 'column' || _seriesType == 'bar') && - _chartState._legendToggling) || - (!_chartState._legendToggling && - _seriesRenderer._needsAnimation && - _renderingDetails.widgetNeedUpdate))) { + _stateProperties.legendToggling) || + (!_stateProperties.legendToggling && + seriesRendererDetails.needsAnimation == true && + _stateProperties.renderingDetails.widgetNeedUpdate))) { if ((_seriesType == 'column' || _seriesType == 'bar') && - _chartState._legendToggling) { - _seriesRenderer._needAnimateSeriesElements = true; + _stateProperties.legendToggling) { + seriesRendererDetails.needAnimateSeriesElements = true; } - _chartState._forwardAnimation(_seriesRenderer); + _stateProperties.forwardAnimation(seriesRendererDetails); } else { - _seriesRenderer._animationController.duration = + seriesRendererDetails.animationController.duration = const Duration(milliseconds: 0); - _seriesRenderer._seriesAnimation = + seriesRendererDetails.seriesAnimation = Tween(begin: 1, end: 1.0).animate(CurvedAnimation( - parent: _seriesRenderer._animationController, + parent: seriesRendererDetails.animationController, curve: const Interval(0.1, 0.8, curve: Curves.decelerate), )); - _seriesRenderer._seriesElementAnimation = + seriesRendererDetails.seriesElementAnimation = Tween(begin: 1, end: 1.0).animate(CurvedAnimation( - parent: _seriesRenderer._animationController, + parent: seriesRendererDetails.animationController, curve: const Interval(1.0, 1.0, curve: Curves.decelerate), )); - _chartState._animationCompleteCount++; - _seriesRenderer._animationCompleted = true; - _setAnimationStatus(_chartState); + _stateProperties.animationCompleteCount++; + seriesRendererDetails.animationCompleted = true; + setAnimationStatus(_stateProperties); } } - _renderingDetails.chartWidgets!.add(Container( + _stateProperties.renderingDetails.chartWidgets!.add(Container( child: RepaintBoundary( child: CustomPaint( painter: _getSeriesPainter( - i, _seriesRenderer._animationController, _seriesRenderer), + i, seriesRendererDetails.animationController, _seriesRenderer), )))); } - _renderingDetails.chartWidgets!.add(Container( + _stateProperties.renderingDetails.chartWidgets!.add(Container( color: Colors.red, child: RepaintBoundary( child: CustomPaint( - painter: _ZoomRectPainter( + painter: ZoomRectPainter( isRepaint: true, - chartState: _chartState, - notifier: _chartState._repaintNotifiers['zoom']))))); - _chartState._legendToggling = false; + stateProperties: _stateProperties, + notifier: _stateProperties.repaintNotifiers['zoom']))))); + _stateProperties.legendToggling = false; } /// Bind the axis widgets void _bindAxisWidgets(String renderType) { // ignore: unnecessary_null_comparison - if (_chartState._chartAxis._axisRenderersCollection != null && - _chartState._chartAxis._axisRenderersCollection.isNotEmpty && - _chartState._chartAxis._axisRenderersCollection.length > 1) { - final _CartesianAxisRenderer axisWidget = _CartesianAxisRenderer( - chartState: _chartState, renderType: renderType); + if (_stateProperties.chartAxis.axisRenderersCollection != null && + _stateProperties.chartAxis.axisRenderersCollection.isNotEmpty && + _stateProperties.chartAxis.axisRenderersCollection.length > 1) { + final CartesianAxisWidget axisWidget = CartesianAxisWidget( + stateProperties: _stateProperties, renderType: renderType); renderType == 'outside' - ? _chartState._renderOutsideAxis = axisWidget - : _chartState._renderInsideAxis = axisWidget; - _renderingDetails.chartWidgets!.add(axisWidget); + ? _stateProperties.renderOutsideAxis = axisWidget + : _stateProperties.renderInsideAxis = axisWidget; + _stateProperties.renderingDetails.chartWidgets!.add(axisWidget); } } @@ -2729,11 +2892,13 @@ class _ContainerArea extends StatelessWidget { CartesianSeriesRenderer? seriesRenderer; SelectionBehaviorRenderer? selectionBehaviorRenderer; outerLoop: - for (int i = _chartState._chartSeries.visibleSeriesRenderers.length - 1; + for (int i = _stateProperties.chartSeries.visibleSeriesRenderers.length - 1; i >= 0; i--) { - seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - final String _seriesType = seriesRenderer._seriesType; + seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final String _seriesType = seriesRendererDetails.seriesType; if (_seriesType == 'column' || _seriesType == 'bar' || _seriesType == 'scatter' || @@ -2744,37 +2909,47 @@ class _ContainerArea extends StatelessWidget { _seriesType.contains('stackedbar') || _seriesType.contains('range') || _seriesType == 'histogram' || - _seriesType == 'waterfall') { - for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { - if (seriesRenderer._dataPoints[j].region != null && - seriesRenderer._dataPoints[j].region!.contains(position)) { - seriesRenderer._isOuterRegion = false; + _seriesType == 'waterfall' || + _seriesType == 'errorbar') { + for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { + if (seriesRendererDetails.dataPoints[j].region != null && + seriesRendererDetails.dataPoints[j].region!.contains(position) == + true) { + seriesRendererDetails.isOuterRegion = false; break outerLoop; } else { - seriesRenderer._isOuterRegion = true; + seriesRendererDetails.isOuterRegion = true; } } } else { - selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; + selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; bool isSelect = false; - seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - for (int k = _chartState._chartSeries.visibleSeriesRenderers.length - 1; + seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[i]; + for (int k = + _stateProperties.chartSeries.visibleSeriesRenderers.length - 1; k >= 0; k--) { - isSelect = seriesRenderer._isSelectionEnable && - seriesRenderer._visible! && - selectionBehaviorRenderer!._selectionRenderer! - ._isSeriesContainsPoint( - _chartState._chartSeries.visibleSeriesRenderers[i], - position); + isSelect = seriesRendererDetails.isSelectionEnable == true && + seriesRendererDetails.visible! == true && + (SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer! + .isSeriesContainsPoint( + SeriesHelper.getSeriesRendererDetails(_stateProperties + .chartSeries.visibleSeriesRenderers[i]), + position) == + true); if (isSelect) { - return _chartState._chartSeries.visibleSeriesRenderers[i]; - } else if (seriesRenderer._visible! && - selectionBehaviorRenderer!._selectionRenderer! - ._isSeriesContainsPoint( - _chartState._chartSeries.visibleSeriesRenderers[i], - position)) { - return _chartState._chartSeries.visibleSeriesRenderers[i]; + return _stateProperties.chartSeries.visibleSeriesRenderers[i]; + } else if (seriesRendererDetails.visible! == true && + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer! + .isSeriesContainsPoint( + SeriesHelper.getSeriesRendererDetails(_stateProperties + .chartSeries.visibleSeriesRenderers[i]), + position) == + true) { + return _stateProperties.chartSeries.visibleSeriesRenderers[i]; } } } @@ -2784,110 +2959,142 @@ class _ContainerArea extends StatelessWidget { /// To perform the pointer down event void _performPointerDown(PointerDownEvent event) { - _chartState._canSetRangeController = true; - _renderingDetails.tooltipBehaviorRenderer._isHovering = false; + _stateProperties.canSetRangeController = true; + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; _tapDownDetails = event.position; if (chart.zoomPanBehavior.enablePinching == true) { ZoomPanArgs? zoomStartArgs; - if (_chartState._touchStartPositions.length < 2) { - _chartState._touchStartPositions.add(event); + if (_stateProperties.touchStartPositions.length < 2) { + _stateProperties.touchStartPositions.add(event); } - if (_chartState._touchStartPositions.length == 2) { + if (_stateProperties.touchStartPositions.length == 2) { for (int axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < + _stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(_stateProperties + .chartAxis.axisRenderersCollection[axisIndex]); + if (chart.onZoomStart != null) { zoomStartArgs = - _bindZoomEvent(chart, axisRenderer, chart.onZoomStart!); - axisRenderer._zoomFactor = zoomStartArgs.currentZoomFactor; - axisRenderer._zoomPosition = zoomStartArgs.currentZoomPosition; + bindZoomEvent(chart, axisDetails, chart.onZoomStart!); + axisDetails.zoomFactor = zoomStartArgs.currentZoomFactor; + axisDetails.zoomPosition = zoomStartArgs.currentZoomPosition; } - _chartState._zoomPanBehaviorRenderer.onPinchStart( - axisRenderer._axis, - _chartState._touchStartPositions[0].position.dx, - _chartState._touchStartPositions[0].position.dy, - _chartState._touchStartPositions[1].position.dx, - _chartState._touchStartPositions[1].position.dy, - axisRenderer._zoomFactor); + _stateProperties.zoomPanBehaviorRenderer.onPinchStart( + axisDetails.axis, + _stateProperties.touchStartPositions[0].position.dx, + _stateProperties.touchStartPositions[0].position.dy, + _stateProperties.touchStartPositions[1].position.dx, + _stateProperties.touchStartPositions[1].position.dy, + axisDetails.zoomFactor); } } } final Offset position = renderBox.globalToLocal(event.position); _touchPosition = position; + + final CartesianSeriesRenderer? cartesianSeriesRenderer = + _findSeries(position); // ignore: unnecessary_null_comparison if (chart.trackballBehavior != null && chart.trackballBehavior.enable && - chart.trackballBehavior.activationMode == ActivationMode.singleTap) { + chart.trackballBehavior.activationMode == ActivationMode.singleTap && + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series + is! ErrorBarSeries) { if (chart.trackballBehavior.builder != null) { - _chartState._trackballBehaviorRenderer._isMoving = true; - _chartState._trackballBehaviorRenderer._showTemplateTrackball(position); + trackballRenderingDetails.isMoving = true; + trackballRenderingDetails.showTemplateTrackball(position); } else { - _chartState._trackballBehaviorRenderer + _stateProperties.trackballBehaviorRenderer .onTouchDown(position.dx, position.dy); } } // ignore: unnecessary_null_comparison if (chart.crosshairBehavior != null && chart.crosshairBehavior.enable && - chart.crosshairBehavior.activationMode == ActivationMode.singleTap) { - _chartState._crosshairBehaviorRenderer + chart.crosshairBehavior.activationMode == ActivationMode.singleTap && + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series + is! ErrorBarSeries) { + _stateProperties.crosshairBehaviorRenderer .onTouchDown(position.dx, position.dy); } } /// To perform the pointer move event void _performPointerMove(PointerMoveEvent event) { - _renderingDetails.tooltipBehaviorRenderer._isHovering = false; + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); if (chart.zoomPanBehavior.enablePinching == true && - _chartState._touchStartPositions.length == 2) { - _chartState._zoomPanBehaviorRenderer._isPinching = true; + _stateProperties.touchStartPositions.length == 2) { + zoomingBehaviorDetails.isPinching = true; final int pointerID = event.pointer; bool addPointer = true; - for (int i = 0; i < _chartState._touchMovePositions.length; i++) { - if (_chartState._touchMovePositions[i].pointer == pointerID) { + for (int i = 0; i < _stateProperties.touchMovePositions.length; i++) { + if (_stateProperties.touchMovePositions[i].pointer == pointerID) { addPointer = false; } } - if (_chartState._touchMovePositions.length < 2 && addPointer) { - _chartState._touchMovePositions.add(event); + if (_stateProperties.touchMovePositions.length < 2 && addPointer) { + _stateProperties.touchMovePositions.add(event); } - if (_chartState._touchMovePositions.length == 2 && - _chartState._touchStartPositions.length == 2) { - if (_chartState._touchMovePositions[0].pointer == event.pointer) { - _chartState._touchMovePositions[0] = event; + if (_stateProperties.touchMovePositions.length == 2 && + _stateProperties.touchStartPositions.length == 2) { + if (_stateProperties.touchMovePositions[0].pointer == event.pointer) { + _stateProperties.touchMovePositions[0] = event; } - if (_chartState._touchMovePositions[1].pointer == event.pointer) { - _chartState._touchMovePositions[1] = event; + if (_stateProperties.touchMovePositions[1].pointer == event.pointer) { + _stateProperties.touchMovePositions[1] = event; } - _chartState._zoomPanBehaviorRenderer._performPinchZooming( - _chartState._touchStartPositions, _chartState._touchMovePositions); + zoomingBehaviorDetails.performPinchZooming( + _stateProperties.touchStartPositions, + _stateProperties.touchMovePositions); } } } /// To perform the pointer up event void _performPointerUp(PointerUpEvent event) { - if (_chartState._touchStartPositions.length == 2 && - _chartState._touchMovePositions.length == 2 && - _chartState._zoomPanBehaviorRenderer._isPinching == true) { + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + if (_stateProperties.touchStartPositions.length == 2 && + _stateProperties.touchMovePositions.length == 2 && + zoomingBehaviorDetails.isPinching == true) { _calculatePinchZoomingArgs(); } - _chartState._touchStartPositions = []; - _chartState._touchMovePositions = []; - _chartState._zoomPanBehaviorRenderer._isPinching = false; - _chartState._zoomPanBehaviorRenderer._delayRedraw = false; - _renderingDetails.tooltipBehaviorRenderer._isHovering = false; + + _stateProperties.touchStartPositions = []; + _stateProperties.touchMovePositions = []; + zoomingBehaviorDetails.isPinching = false; + zoomingBehaviorDetails.delayRedraw = false; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.isHovering = false; final Offset position = renderBox.globalToLocal(event.position); + final CartesianSeriesRenderer? cartesianSeriesRenderer = + _findSeries(position); // ignore: unnecessary_null_comparison if ((chart.trackballBehavior != null && chart.trackballBehavior.enable && !chart.trackballBehavior.shouldAlwaysShow && + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) + .series is! ErrorBarSeries && chart.trackballBehavior.activationMode != ActivationMode.doubleTap && - _chartState._zoomPanBehaviorRenderer._isPinching != true) || + zoomingBehaviorDetails.isPinching != true) || // ignore: unnecessary_null_comparison (chart.zoomPanBehavior != null && ((chart.zoomPanBehavior.enableDoubleTapZooming || @@ -2895,10 +3102,10 @@ class _ContainerArea extends StatelessWidget { chart.zoomPanBehavior.enablePinching || chart.zoomPanBehavior.enableSelectionZooming) && !chart.trackballBehavior.shouldAlwaysShow))) { - _chartState._trackballBehaviorRenderer + _stateProperties.trackballBehaviorRenderer .onTouchUp(position.dx, position.dy); - _chartState._trackballBehaviorRenderer._isLongPressActivated = false; + trackballRenderingDetails.isLongPressActivated = false; } // ignore: unnecessary_null_comparison if ((chart.crosshairBehavior != null && @@ -2906,7 +3113,10 @@ class _ContainerArea extends StatelessWidget { !chart.crosshairBehavior.shouldAlwaysShow && chart.crosshairBehavior.activationMode != ActivationMode.doubleTap && - _chartState._zoomPanBehaviorRenderer._isPinching != true) || + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) + .series is! ErrorBarSeries && + zoomingBehaviorDetails.isPinching != true) || // ignore: unnecessary_null_comparison (chart.zoomPanBehavior != null && ((chart.zoomPanBehavior.enableDoubleTapZooming || @@ -2914,18 +3124,19 @@ class _ContainerArea extends StatelessWidget { chart.zoomPanBehavior.enablePinching || chart.zoomPanBehavior.enableSelectionZooming) && !chart.crosshairBehavior.shouldAlwaysShow))) { - _chartState._crosshairBehaviorRenderer + _stateProperties.crosshairBehaviorRenderer .onTouchUp(position.dx, position.dy); - _chartState._crosshairBehaviorRenderer._isLongPressActivated = false; + CrosshairHelper.getRenderingDetails( + _stateProperties.crosshairBehaviorRenderer) + .isLongPressActivated = false; } if (chart.tooltipBehavior.enable && chart.tooltipBehavior.activationMode == ActivationMode.singleTap || - _shouldShowAxisTooltip(_chartState)) { - _renderingDetails.tooltipBehaviorRenderer._isInteraction = true; + shouldShowAxisTooltip(_stateProperties)) { + tooltipRenderingDetails.isInteraction = true; chart.tooltipBehavior.builder != null - ? _renderingDetails.tooltipBehaviorRenderer - ._showTemplateTooltip(position) - : _renderingDetails.tooltipBehaviorRenderer + ? tooltipRenderingDetails.showTemplateTooltip(position) + : _stateProperties.renderingDetails.tooltipBehaviorRenderer .onTouchUp(position.dx, position.dy); } } @@ -2936,9 +3147,10 @@ class _ContainerArea extends StatelessWidget { if (_mousePointerDetails != null) { final Offset position = renderBox.globalToLocal(event.position); if (chart.zoomPanBehavior.enableMouseWheelZooming && - _chartState._chartAxis._axisClipRect.contains(position)) { - _chartState._zoomPanBehaviorRenderer - ._performMouseWheelZooming(event, position.dx, position.dy); + _stateProperties.chartAxis.axisClipRect.contains(position)) { + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer) + .performMouseWheelZooming(event, position.dx, position.dy); } } } @@ -2949,36 +3161,38 @@ class _ContainerArea extends StatelessWidget { bool resetFlag = false; int axisIndex; for (axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < _stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[axisIndex]); if (chart.onZoomEnd != null) { - zoomEndArgs = _bindZoomEvent(chart, axisRenderer, chart.onZoomEnd!); - axisRenderer._zoomFactor = zoomEndArgs.currentZoomFactor; - axisRenderer._zoomPosition = zoomEndArgs.currentZoomPosition; + zoomEndArgs = bindZoomEvent(chart, axisDetails, chart.onZoomEnd!); + axisDetails.zoomFactor = zoomEndArgs.currentZoomFactor; + axisDetails.zoomPosition = zoomEndArgs.currentZoomPosition; } - if (axisRenderer._zoomFactor.toInt() == 1 && - axisRenderer._zoomPosition.toInt() == 0 && + if (axisDetails.zoomFactor.toInt() == 1 && + axisDetails.zoomPosition.toInt() == 0 && chart.onZoomReset != null) { resetFlag = true; } - _chartState._zoomAxes = <_ZoomAxisRange>[]; - _chartState._zoomPanBehaviorRenderer.onPinchEnd( - axisRenderer._axis, - _chartState._touchMovePositions[0].position.dx, - _chartState._touchMovePositions[0].position.dy, - _chartState._touchMovePositions[1].position.dx, - _chartState._touchMovePositions[1].position.dy, - axisRenderer._zoomFactor); + _stateProperties.zoomAxes = []; + _stateProperties.zoomPanBehaviorRenderer.onPinchEnd( + axisDetails.axis, + _stateProperties.touchMovePositions[0].position.dx, + _stateProperties.touchMovePositions[0].position.dy, + _stateProperties.touchMovePositions[1].position.dx, + _stateProperties.touchMovePositions[1].position.dy, + axisDetails.zoomFactor); } if (resetFlag) { for (int index = 0; - index < _chartState._chartAxis._axisRenderersCollection.length; + index < _stateProperties.chartAxis.axisRenderersCollection.length; index++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[index]; - _bindZoomEvent(chart, axisRenderer, chart.onZoomReset!); + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[index]); + bindZoomEvent(chart, axisDetails, chart.onZoomReset!); } } } @@ -2986,12 +3200,15 @@ class _ContainerArea extends StatelessWidget { /// To perform long press move update void _performLongPressMoveUpdate(LongPressMoveUpdateDetails details) { final Offset? position = renderBox.globalToLocal(details.globalPosition); - if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + if (zoomingBehaviorDetails.isPinching != true) { if (chart.zoomPanBehavior.enableSelectionZooming && position != null && _zoomStartPosition != null) { - _chartState._zoomPanBehaviorRenderer._canPerformSelection = true; - _chartState._zoomPanBehaviorRenderer.onDrawSelectionZoomRect( + zoomingBehaviorDetails.canPerformSelection = true; + _stateProperties.zoomPanBehaviorRenderer.onDrawSelectionZoomRect( position.dx, position.dy, _zoomStartPosition!.dx, @@ -3002,22 +3219,23 @@ class _ContainerArea extends StatelessWidget { if (chart.trackballBehavior != null && chart.trackballBehavior.enable && // ignore: unnecessary_null_comparison - _chartState != null && + _renderingDetails != null && chart.trackballBehavior.activationMode != ActivationMode.doubleTap && - position != null) { + position != null && + _findSeries(position) != null && + SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series + is! ErrorBarSeries) { if (chart.trackballBehavior.activationMode == ActivationMode.singleTap) { chart.trackballBehavior.builder != null - ? _chartState._trackballBehaviorRenderer - ._showTemplateTrackball(position) - : _chartState._trackballBehaviorRenderer + ? trackballRenderingDetails.showTemplateTrackball(position) + : _stateProperties.trackballBehaviorRenderer .onTouchMove(position.dx, position.dy); } if (chart.trackballBehavior.activationMode == ActivationMode.longPress && - _chartState._trackballBehaviorRenderer._isLongPressActivated) { + trackballRenderingDetails.isLongPressActivated == true) { chart.trackballBehavior.builder != null - ? _chartState._trackballBehaviorRenderer - ._showTemplateTrackball(position) - : _chartState._trackballBehaviorRenderer + ? trackballRenderingDetails.showTemplateTrackball(position) + : _stateProperties.trackballBehaviorRenderer .onTouchMove(position.dx, position.dy); } } @@ -3025,17 +3243,22 @@ class _ContainerArea extends StatelessWidget { if (chart.crosshairBehavior != null && chart.crosshairBehavior.enable && chart.crosshairBehavior.activationMode != ActivationMode.doubleTap && - position != null) { + position != null && + SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series + is! ErrorBarSeries) { if (chart.crosshairBehavior.activationMode == ActivationMode.singleTap) { - _chartState._crosshairBehaviorRenderer + _stateProperties.crosshairBehaviorRenderer .onTouchMove(position.dx, position.dy); // ignore: unnecessary_null_comparison } else if ((chart.crosshairBehavior != null && chart.crosshairBehavior.activationMode == ActivationMode.longPress && - _chartState._crosshairBehaviorRenderer._isLongPressActivated) && + CrosshairHelper.getRenderingDetails( + _stateProperties.crosshairBehaviorRenderer) + .isLongPressActivated == + true) && !chart.zoomPanBehavior.enableSelectionZooming) { - _chartState._crosshairBehaviorRenderer + _stateProperties.crosshairBehaviorRenderer .onTouchMove(position.dx, position.dy); } } @@ -3043,15 +3266,17 @@ class _ContainerArea extends StatelessWidget { /// To perform long press end void _performLongPressEnd() { - if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { - _chartState._zoomPanBehaviorRenderer._canPerformSelection = false; + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + if (zoomingBehaviorDetails.isPinching != true) { + zoomingBehaviorDetails.canPerformSelection = false; if (chart.zoomPanBehavior.enableSelectionZooming && - _chartState._zoomPanBehaviorRenderer._zoomingRect.width != 0) { - _chartState._zoomPanBehaviorRenderer._doSelectionZooming( - _chartState._zoomPanBehaviorRenderer._zoomingRect); - if (_chartState._zoomPanBehaviorRenderer._canPerformSelection != true) { - _chartState._zoomPanBehaviorRenderer._zoomingRect = - const Rect.fromLTRB(0, 0, 0, 0); + zoomingBehaviorDetails.zoomingRect.width != 0) { + zoomingBehaviorDetails + .doSelectionZooming(zoomingBehaviorDetails.zoomingRect); + if (zoomingBehaviorDetails.canPerformSelection != true) { + zoomingBehaviorDetails.zoomingRect = const Rect.fromLTRB(0, 0, 0, 0); } } } @@ -3059,12 +3284,16 @@ class _ContainerArea extends StatelessWidget { /// To perform pan down void _performPanDown(DragDownDetails details) { - _chartState._startOffset = renderBox.globalToLocal(details.globalPosition); - if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { + _stateProperties.startOffset = + renderBox.globalToLocal(details.globalPosition); + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + if (zoomingBehaviorDetails.isPinching != true) { _zoomStartPosition = renderBox.globalToLocal(details.globalPosition); if (chart.zoomPanBehavior.enablePanning == true) { - _chartState._zoomPanBehaviorRenderer._isPanning = true; - _chartState._zoomPanBehaviorRenderer._previousMovedPosition = null; + zoomingBehaviorDetails.isPanning = true; + zoomingBehaviorDetails.previousMovedPosition = null; } } } @@ -3074,46 +3303,61 @@ class _ContainerArea extends StatelessWidget { Offset? position; if (_tapDownDetails != null) { position = renderBox.globalToLocal(_tapDownDetails!); - if (_findSeries(position)!._series.onPointLongPress != null) { - _calculatePointSeriesIndex( - chart, _chartState, position, null, ActivationMode.longPress); + final CartesianSeriesRenderer? cartesianSeriesRenderer = + _findSeries(position); + if (cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) + .series + .onPointLongPress != + null) { + calculatePointSeriesIndex( + chart, _stateProperties, position, null, ActivationMode.longPress); } if (chart.tooltipBehavior.enable && chart.tooltipBehavior.activationMode == ActivationMode.longPress || - _shouldShowAxisTooltip(_chartState)) { - _renderingDetails.tooltipBehaviorRenderer._isInteraction = true; + shouldShowAxisTooltip(_stateProperties)) { + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.isInteraction = true; chart.tooltipBehavior.builder != null - ? _renderingDetails.tooltipBehaviorRenderer - ._showTemplateTooltip(position) - : _renderingDetails.tooltipBehaviorRenderer + ? tooltipRenderingDetails.showTemplateTooltip(position) + : _stateProperties.renderingDetails.tooltipBehaviorRenderer .onLongPress(position.dx, position.dy); } } // ignore: unnecessary_null_comparison - if (_chartState._chartSeries.visibleSeriesRenderers != null && + if (_stateProperties.chartSeries.visibleSeriesRenderers != null && position != null && chart.selectionGesture == ActivationMode.longPress) { final CartesianSeriesRenderer selectionSeriesRenderer = _findSeries(position)!; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(selectionSeriesRenderer); final SelectionBehaviorRenderer selectionBehaviorRenderer = - selectionSeriesRenderer._selectionBehaviorRenderer!; - selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = - selectionSeriesRenderer; + seriesRendererDetails.selectionBehaviorRenderer!; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer) + .selectionRenderer! + .seriesRendererDetails = seriesRendererDetails; selectionBehaviorRenderer.onLongPress(position.dx, position.dy); } + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); // ignore: unnecessary_null_comparison if ((chart.trackballBehavior != null && chart.trackballBehavior.enable == true && chart.trackballBehavior.activationMode == ActivationMode.longPress) && + SeriesHelper.getSeriesRendererDetails(_findSeries(position!)!).series + is! ErrorBarSeries && position != null && - _chartState._zoomPanBehaviorRenderer._isPinching != true) { - _chartState._trackballBehaviorRenderer._isLongPressActivated = true; + zoomingBehaviorDetails.isPinching != true) { + trackballRenderingDetails.isLongPressActivated = true; chart.trackballBehavior.builder != null - ? _chartState._trackballBehaviorRenderer - ._showTemplateTrackball(position) - : _chartState._trackballBehaviorRenderer + ? trackballRenderingDetails.showTemplateTrackball(position) + : _stateProperties.trackballBehaviorRenderer .onTouchDown(position.dx, position.dy); } // ignore: unnecessary_null_comparison @@ -3121,11 +3365,15 @@ class _ContainerArea extends StatelessWidget { chart.crosshairBehavior.enable == true && chart.crosshairBehavior.activationMode == ActivationMode.longPress) && + SeriesHelper.getSeriesRendererDetails(_findSeries(position!)!).series + is! ErrorBarSeries && !chart.zoomPanBehavior.enableSelectionZooming && - _chartState._zoomPanBehaviorRenderer._isPinching != true && + zoomingBehaviorDetails.isPinching != true && position != null) { - _chartState._crosshairBehaviorRenderer._isLongPressActivated = true; - _chartState._crosshairBehaviorRenderer + CrosshairHelper.getRenderingDetails( + _stateProperties.crosshairBehaviorRenderer) + .isLongPressActivated = true; + _stateProperties.crosshairBehaviorRenderer .onTouchDown(position.dx, position.dy); } } @@ -3134,59 +3382,76 @@ class _ContainerArea extends StatelessWidget { void _performDoubleTap() { if (_tapDownDetails != null) { final Offset position = renderBox.globalToLocal(_tapDownDetails!); - if (_findSeries(position)!._series.onPointDoubleTap != null) { - _calculatePointSeriesIndex( - chart, _chartState, position, null, ActivationMode.doubleTap); + final CartesianSeriesRenderer? cartesianSeriesRenderer = + _findSeries(position); + if (cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer) + .series + .onPointDoubleTap != + null) { + calculatePointSeriesIndex( + chart, _stateProperties, position, null, ActivationMode.doubleTap); } // ignore: unnecessary_null_comparison if (chart.trackballBehavior != null && chart.trackballBehavior.enable && + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series + is! ErrorBarSeries && chart.trackballBehavior.activationMode == ActivationMode.doubleTap) { chart.trackballBehavior.builder != null - ? _chartState._trackballBehaviorRenderer - ._showTemplateTrackball(position) - : _chartState._trackballBehaviorRenderer + ? trackballRenderingDetails.showTemplateTrackball(position) + : _stateProperties.trackballBehaviorRenderer .onDoubleTap(position.dx, position.dy); - _chartState._enableDoubleTap = true; - _chartState._isTouchUp = true; - _chartState._trackballBehaviorRenderer + _stateProperties.enableDoubleTap = true; + _stateProperties.isTouchUp = true; + _stateProperties.trackballBehaviorRenderer .onTouchUp(position.dx, position.dy); - _chartState._isTouchUp = false; - _chartState._enableDoubleTap = false; + _stateProperties.isTouchUp = false; + _stateProperties.enableDoubleTap = false; } // ignore: unnecessary_null_comparison if (chart.crosshairBehavior != null && chart.crosshairBehavior.enable && + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series + is! ErrorBarSeries && chart.crosshairBehavior.activationMode == ActivationMode.doubleTap) { - _chartState._crosshairBehaviorRenderer + _stateProperties.crosshairBehaviorRenderer .onDoubleTap(position.dx, position.dy); - _chartState._enableDoubleTap = true; - _chartState._isTouchUp = true; - _chartState._crosshairBehaviorRenderer + _stateProperties.enableDoubleTap = true; + _stateProperties.isTouchUp = true; + _stateProperties.crosshairBehaviorRenderer .onTouchUp(position.dx, position.dy); - _chartState._isTouchUp = false; - _chartState._enableDoubleTap = false; + _stateProperties.isTouchUp = false; + _stateProperties.enableDoubleTap = false; } + if (chart.tooltipBehavior.enable && chart.tooltipBehavior.activationMode == ActivationMode.doubleTap || - _shouldShowAxisTooltip(_chartState)) { - _renderingDetails.tooltipBehaviorRenderer._isInteraction = true; + shouldShowAxisTooltip(_stateProperties)) { + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.isInteraction = true; chart.tooltipBehavior.builder != null - ? _renderingDetails.tooltipBehaviorRenderer - ._showTemplateTooltip(position) - : _renderingDetails.tooltipBehaviorRenderer + ? tooltipRenderingDetails.showTemplateTooltip(position) + : _stateProperties.renderingDetails.tooltipBehaviorRenderer .onDoubleTap(position.dx, position.dy); } // ignore: unnecessary_null_comparison - if (_chartState._chartSeries.visibleSeriesRenderers != null && + if (_stateProperties.chartSeries.visibleSeriesRenderers != null && chart.selectionGesture == ActivationMode.doubleTap) { final CartesianSeriesRenderer selectionSeriesRenderer = _findSeries(position)!; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(selectionSeriesRenderer); final SelectionBehaviorRenderer selectionBehaviorRenderer = - selectionSeriesRenderer._selectionBehaviorRenderer!; - selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = - selectionSeriesRenderer; + seriesRendererDetails.selectionBehaviorRenderer!; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer) + .selectionRenderer! + .seriesRendererDetails = seriesRendererDetails; selectionBehaviorRenderer.onDoubleTap(position.dx, position.dy); } } @@ -3195,8 +3460,12 @@ class _ContainerArea extends StatelessWidget { final Offset? doubleTapPosition = _touchPosition; final Offset? position = doubleTapPosition; if (position != null) { - _chartState._zoomPanBehaviorRenderer.onDoubleTap(position.dx, - position.dy, _chartState._zoomPanBehaviorRenderer._zoomFactor); + _stateProperties.zoomPanBehaviorRenderer.onDoubleTap( + position.dx, + position.dy, + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer) + .zoomFactor); } } } @@ -3204,44 +3473,52 @@ class _ContainerArea extends StatelessWidget { /// Update the details for pan void _performPanUpdate(DragUpdateDetails details) { Offset? position; - _chartState._currentPosition = + _stateProperties.currentPosition = renderBox.globalToLocal(details.globalPosition); - if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + if (zoomingBehaviorDetails.isPinching != true) { position = renderBox.globalToLocal(details.globalPosition); - if (_chartState._zoomPanBehaviorRenderer._isPanning == true && + if (zoomingBehaviorDetails.isPanning == true && chart.zoomPanBehavior.enablePanning && - _chartState._zoomPanBehaviorRenderer._previousMovedPosition != null && - !_chartState._isLoadMoreIndicator) { - _chartState._zoomPanBehaviorRenderer.onPan(position.dx, position.dy); + zoomingBehaviorDetails.previousMovedPosition != null && + !_stateProperties.isLoadMoreIndicator) { + _stateProperties.zoomPanBehaviorRenderer + .onPan(position.dx, position.dy); } - _chartState._zoomPanBehaviorRenderer._previousMovedPosition = position; + zoomingBehaviorDetails.previousMovedPosition = position; } final bool panInProgress = chart.zoomPanBehavior.enablePanning && - _chartState._zoomPanBehaviorRenderer._previousMovedPosition != null; + zoomingBehaviorDetails.previousMovedPosition != null; // ignore: unnecessary_null_comparison if (chart.trackballBehavior != null && chart.trackballBehavior.enable && position != null && + _findSeries(position) != null && + SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series + is! ErrorBarSeries && !panInProgress && chart.trackballBehavior.activationMode != ActivationMode.doubleTap) { if (chart.trackballBehavior.activationMode == ActivationMode.singleTap) { if (chart.trackballBehavior.builder != null) { - _chartState._trackballBehaviorRenderer._isMoving = true; - _chartState._trackballBehaviorRenderer - ._showTemplateTrackball(position); + trackballRenderingDetails.isMoving = true; + trackballRenderingDetails.showTemplateTrackball(position); } else { - _chartState._trackballBehaviorRenderer + _stateProperties.trackballBehaviorRenderer .onTouchMove(position.dx, position.dy); } // ignore: unnecessary_null_comparison } else if (chart.trackballBehavior != null && chart.trackballBehavior.activationMode == ActivationMode.longPress && - _chartState._trackballBehaviorRenderer._isLongPressActivated == - true) { + position != null && + _findSeries(position) != null && + SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series + is! ErrorBarSeries && + trackballRenderingDetails.isLongPressActivated == true) { chart.trackballBehavior.builder != null - ? _chartState._trackballBehaviorRenderer - ._showTemplateTrackball(position) - : _chartState._trackballBehaviorRenderer + ? trackballRenderingDetails.showTemplateTrackball(position) + : _stateProperties.trackballBehaviorRenderer .onTouchMove(position.dx, position.dy); } } @@ -3250,15 +3527,21 @@ class _ContainerArea extends StatelessWidget { chart.crosshairBehavior.enable && chart.crosshairBehavior.activationMode != ActivationMode.doubleTap && position != null && + _findSeries(position) != null && + SeriesHelper.getSeriesRendererDetails(_findSeries(position)!).series + is! ErrorBarSeries && !panInProgress) { if (chart.crosshairBehavior.activationMode == ActivationMode.singleTap) { - _chartState._crosshairBehaviorRenderer + _stateProperties.crosshairBehaviorRenderer .onTouchMove(position.dx, position.dy); // ignore: unnecessary_null_comparison } else if (chart.crosshairBehavior != null && chart.crosshairBehavior.activationMode == ActivationMode.longPress && - _chartState._crosshairBehaviorRenderer._isLongPressActivated) { - _chartState._crosshairBehaviorRenderer + CrosshairHelper.getRenderingDetails( + _stateProperties.crosshairBehaviorRenderer) + .isLongPressActivated == + true) { + _stateProperties.crosshairBehaviorRenderer .onTouchMove(position.dx, position.dy); } } @@ -3266,216 +3549,266 @@ class _ContainerArea extends StatelessWidget { /// Method for the pan end event void _performPanEnd(DragEndDetails details) { - if (_chartState._zoomPanBehaviorRenderer._isPinching != true) { - _chartState._zoomPanBehaviorRenderer._isPanning = false; - _chartState._zoomPanBehaviorRenderer._previousMovedPosition = null; + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + if (zoomingBehaviorDetails.isPinching != true) { + zoomingBehaviorDetails.isPanning = false; + zoomingBehaviorDetails.previousMovedPosition = null; } if (chart.trackballBehavior.enable && !chart.trackballBehavior.shouldAlwaysShow && chart.trackballBehavior.activationMode != ActivationMode.doubleTap && - _touchPosition != null) { - _chartState._isTouchUp = true; - _chartState._trackballBehaviorRenderer + _touchPosition != null && + SeriesHelper.getSeriesRendererDetails(_findSeries(_touchPosition!)!) + .series is! ErrorBarSeries) { + _stateProperties.isTouchUp = true; + _stateProperties.trackballBehaviorRenderer .onTouchUp(_touchPosition!.dx, _touchPosition!.dy); - _chartState._isTouchUp = false; - _chartState._trackballBehaviorRenderer._isLongPressActivated = false; + _stateProperties.isTouchUp = false; + trackballRenderingDetails.isLongPressActivated = false; } if (chart.crosshairBehavior.enable && !chart.crosshairBehavior.shouldAlwaysShow && _touchPosition != null && + SeriesHelper.getSeriesRendererDetails(_findSeries(_touchPosition!)!) + .series is! ErrorBarSeries && chart.crosshairBehavior.activationMode != ActivationMode.doubleTap) { - _chartState._isTouchUp = true; - _chartState._crosshairBehaviorRenderer + _stateProperties.isTouchUp = true; + _stateProperties.crosshairBehaviorRenderer .onTouchUp(_touchPosition!.dx, _touchPosition!.dy); - _chartState._isTouchUp = false; - _chartState._crosshairBehaviorRenderer._isLongPressActivated = false; + _stateProperties.isTouchUp = false; + CrosshairHelper.getRenderingDetails( + _stateProperties.crosshairBehaviorRenderer) + .isLongPressActivated = false; } /// Pagination/Swiping feature if (chart.onPlotAreaSwipe != null && - _chartState._zoomedState != true && - _chartState._startOffset != null && - _chartState._currentPosition != null && - _chartState._chartAxis._axisClipRect - .contains(_chartState._startOffset!) && - _chartState._chartAxis._axisClipRect - .contains(_chartState._currentPosition!)) { + _stateProperties.zoomedState != true && + _stateProperties.startOffset != null && + _stateProperties.currentPosition != null && + _stateProperties.chartAxis.axisClipRect + .contains(_stateProperties.startOffset!) && + _stateProperties.chartAxis.axisClipRect + .contains(_stateProperties.currentPosition!)) { //swipe configuration options const double swipeMaxDistanceThreshold = 50.0; - final double swipeMinDisplacement = (_chartState._requireInvertedAxis - ? _chartState._chartAxis._axisClipRect.height - : _chartState._chartAxis._axisClipRect.width) * + final double swipeMinDisplacement = (_stateProperties.requireInvertedAxis + ? _stateProperties.chartAxis.axisClipRect.height + : _stateProperties.chartAxis.axisClipRect.width) * 0.1; final double swipeMinVelocity = - _chartState._pointerDeviceKind == PointerDeviceKind.mouse ? 0.0 : 240; + _stateProperties.pointerDeviceKind == PointerDeviceKind.mouse + ? 0.0 + : 240; ChartSwipeDirection swipeDirection; - final double dx = - (_chartState._currentPosition!.dx - _chartState._startOffset!.dx) - .abs(); - final double dy = - (_chartState._currentPosition!.dy - _chartState._startOffset!.dy) - .abs(); + final double dx = (_stateProperties.currentPosition!.dx - + _stateProperties.startOffset!.dx) + .abs(); + final double dy = (_stateProperties.currentPosition!.dy - + _stateProperties.startOffset!.dy) + .abs(); final double velocity = details.primaryVelocity!; - if (_chartState._requireInvertedAxis && + if (_stateProperties.requireInvertedAxis && dx <= swipeMaxDistanceThreshold && dy >= swipeMinDisplacement && velocity.abs() >= swipeMinVelocity) { ///vertical - swipeDirection = _chartState._pointerDeviceKind == - PointerDeviceKind.mouse - ? (_chartState._currentPosition!.dy > _chartState._startOffset!.dy - ? ChartSwipeDirection.end - : ChartSwipeDirection.start) - : (velocity < 0 - ? ChartSwipeDirection.start - : ChartSwipeDirection.end); + swipeDirection = + _stateProperties.pointerDeviceKind == PointerDeviceKind.mouse + ? (_stateProperties.currentPosition!.dy > + _stateProperties.startOffset!.dy + ? ChartSwipeDirection.end + : ChartSwipeDirection.start) + : (velocity < 0 + ? ChartSwipeDirection.start + : ChartSwipeDirection.end); chart.onPlotAreaSwipe!(swipeDirection); - } else if (!_chartState._requireInvertedAxis && + } else if (!_stateProperties.requireInvertedAxis && dx >= swipeMinDisplacement && dy <= swipeMaxDistanceThreshold && velocity.abs() >= swipeMinVelocity) { ///horizontal - swipeDirection = _chartState._pointerDeviceKind == - PointerDeviceKind.mouse - ? (_chartState._currentPosition!.dx > _chartState._startOffset!.dx - ? ChartSwipeDirection.start - : ChartSwipeDirection.end) - : (velocity > 0 - ? ChartSwipeDirection.start - : ChartSwipeDirection.end); + swipeDirection = + _stateProperties.pointerDeviceKind == PointerDeviceKind.mouse + ? (_stateProperties.currentPosition!.dx > + _stateProperties.startOffset!.dx + ? ChartSwipeDirection.start + : ChartSwipeDirection.end) + : (velocity > 0 + ? ChartSwipeDirection.start + : ChartSwipeDirection.end); chart.onPlotAreaSwipe!(swipeDirection); } } ///Load More feature if (chart.loadMoreIndicatorBuilder != null && - _chartState._startOffset != null && - _chartState._currentPosition != null) { - final bool verticallyDragging = - (_chartState._currentPosition!.dy - _chartState._startOffset!.dy) - .abs() > - (_chartState._currentPosition!.dx - _chartState._startOffset!.dx) - .abs(); - if ((!verticallyDragging && !_chartState._requireInvertedAxis) || - (verticallyDragging && _chartState._requireInvertedAxis)) { + _stateProperties.startOffset != null && + _stateProperties.currentPosition != null) { + final bool verticallyDragging = (_stateProperties.currentPosition!.dy - + _stateProperties.startOffset!.dy) + .abs() > + (_stateProperties.currentPosition!.dx - + _stateProperties.startOffset!.dx) + .abs(); + if ((!verticallyDragging && !_stateProperties.requireInvertedAxis) || + (verticallyDragging && _stateProperties.requireInvertedAxis)) { bool loadMore = false; - final ChartSwipeDirection direction = _chartState._requireInvertedAxis - ? (_chartState._currentPosition!.dy > _chartState._startOffset!.dy - ? ChartSwipeDirection.end - : ChartSwipeDirection.start) - : (_chartState._currentPosition!.dx > _chartState._startOffset!.dx - ? ChartSwipeDirection.start - : ChartSwipeDirection.end); + final ChartAxisRendererDetails primaryXAxisDetails = + _stateProperties.chartAxis.primaryXAxisDetails; + // Here, direction is set accordingly based on the axis transposed value + // and primary x-axis inversed value. + final ChartSwipeDirection direction = + _stateProperties.requireInvertedAxis + ? (_stateProperties.currentPosition!.dy > + _stateProperties.startOffset!.dy + ? primaryXAxisDetails.axis.isInversed + ? ChartSwipeDirection.start + : ChartSwipeDirection.end + : primaryXAxisDetails.axis.isInversed + ? ChartSwipeDirection.end + : ChartSwipeDirection.start) + : (_stateProperties.currentPosition!.dx > + _stateProperties.startOffset!.dx + ? primaryXAxisDetails.axis.isInversed + ? ChartSwipeDirection.end + : ChartSwipeDirection.start + : primaryXAxisDetails.axis.isInversed + ? ChartSwipeDirection.start + : ChartSwipeDirection.end); for (int axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < + _stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(_stateProperties + .chartAxis.axisRenderersCollection[axisIndex]); if (((!verticallyDragging && - axisRenderer._orientation == - AxisOrientation.horizontal) || + axisDetails.orientation == AxisOrientation.horizontal) || (verticallyDragging && - axisRenderer._orientation == AxisOrientation.vertical)) && - axisRenderer._actualRange != null && - ((axisRenderer._actualRange!.minimum.round() == - axisRenderer._visibleRange!.minimum.round() && + axisDetails.orientation == AxisOrientation.vertical)) && + axisDetails.actualRange != null && + ((axisDetails.actualRange!.minimum.round() == + axisDetails.visibleRange!.minimum.round() && direction == ChartSwipeDirection.start) || - (axisRenderer._actualRange!.maximum.round() == - axisRenderer._visibleRange!.maximum.round() && + (axisDetails.actualRange!.maximum.round() == + axisDetails.visibleRange!.maximum.round() && direction == ChartSwipeDirection.end))) { loadMore = true; break; } } - if (loadMore && !_chartState._isLoadMoreIndicator) { - _chartState._isLoadMoreIndicator = true; - _chartState._loadMoreViewStateSetter(() { - _chartState._swipeDirection = direction; + if (loadMore && !_stateProperties.isLoadMoreIndicator) { + _stateProperties.isLoadMoreIndicator = true; + _stateProperties.loadMoreViewStateSetter(() { + _stateProperties.swipeDirection = direction; }); } else { - _chartState._isLoadMoreIndicator = false; + _stateProperties.isLoadMoreIndicator = false; } } } - _chartState._startOffset = null; - _chartState._currentPosition = null; + _stateProperties.startOffset = null; + _stateProperties.currentPosition = null; } /// To perform mouse hover event void _performMouseHover(PointerEvent event) { - _renderingDetails.tooltipBehaviorRenderer._isHovering = true; - _renderingDetails.tooltipBehaviorRenderer._isInteraction = true; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.isHovering = true; + tooltipRenderingDetails.isInteraction = true; final Offset position = renderBox.globalToLocal(event.position); + final CartesianSeriesRenderer? cartesianSeriesRenderer = + _findSeries(position); if ((chart.tooltipBehavior.enable && chart.tooltipBehavior.activationMode == ActivationMode.singleTap) || - _shouldShowAxisTooltip(_chartState)) { + shouldShowAxisTooltip(_stateProperties)) { chart.tooltipBehavior.builder != null - ? _renderingDetails.tooltipBehaviorRenderer - ._showTemplateTooltip(position) - : _renderingDetails.tooltipBehaviorRenderer + ? tooltipRenderingDetails.showTemplateTooltip(position) + : _stateProperties.renderingDetails.tooltipBehaviorRenderer .onEnter(position.dx, position.dy); } if (chart.trackballBehavior.enable && + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series + is! ErrorBarSeries && chart.trackballBehavior.activationMode == ActivationMode.singleTap) { chart.trackballBehavior.builder != null - ? _chartState._trackballBehaviorRenderer - ._showTemplateTrackball(position) - : _chartState._trackballBehaviorRenderer + ? trackballRenderingDetails.showTemplateTrackball(position) + : _stateProperties.trackballBehaviorRenderer .onEnter(position.dx, position.dy); } if (chart.crosshairBehavior.enable && + cartesianSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(cartesianSeriesRenderer).series + is! ErrorBarSeries && chart.crosshairBehavior.activationMode == ActivationMode.singleTap) { - _chartState._crosshairBehaviorRenderer.onEnter(position.dx, position.dy); + _stateProperties.crosshairBehaviorRenderer + .onEnter(position.dx, position.dy); } } /// To perform the mouse exit event void _performMouseExit(PointerEvent event) { - _renderingDetails.tooltipBehaviorRenderer._isHovering = false; + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; final Offset position = renderBox.globalToLocal(event.position); - if (chart.tooltipBehavior.enable || _shouldShowAxisTooltip(_chartState)) { - _renderingDetails.tooltipBehaviorRenderer + if (chart.tooltipBehavior.enable || + shouldShowAxisTooltip(_stateProperties)) { + _stateProperties.renderingDetails.tooltipBehaviorRenderer .onExit(position.dx, position.dy); } if (chart.crosshairBehavior.enable) { - _chartState._crosshairBehaviorRenderer.onExit(position.dx, position.dy); + _stateProperties.crosshairBehaviorRenderer + .onExit(position.dx, position.dy); } if (chart.trackballBehavior.enable) { - _chartState._trackballBehaviorRenderer.onExit(position.dx, position.dy); + _stateProperties.trackballBehaviorRenderer + .onExit(position.dx, position.dy); } } /// To bind the interaction widgets void _bindInteractionWidgets( BoxConstraints constraints, BuildContext context) { - _TrackballPainter trackballPainter; - _CrosshairPainter crosshairPainter; + TrackballPainter trackballPainter; + CrosshairPainter crosshairPainter; final List userInteractionWidgets = []; - final _ZoomRectPainter zoomRectPainter = - _ZoomRectPainter(chartState: _chartState); - _chartState._zoomPanBehaviorRenderer._painter = zoomRectPainter; - chart.trackballBehavior._chartState = chart.tooltipBehavior._chartState = - chart.zoomPanBehavior._chartState = - chart.crosshairBehavior._chartState = _chartState; + final ZoomRectPainter zoomRectPainter = + ZoomRectPainter(stateProperties: _stateProperties); + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + zoomingBehaviorDetails.painter = zoomRectPainter; + CrosshairHelper.setStateProperties( + chart.crosshairBehavior, _stateProperties); + TooltipHelper.setStateProperties(chart.tooltipBehavior, _stateProperties); + TrackballHelper.setStateProperties( + chart.trackballBehavior, _stateProperties); + ZoomPanBehaviorHelper.setStateProperties( + chart.zoomPanBehavior, _stateProperties); // ignore: unnecessary_null_comparison if (chart.trackballBehavior != null && chart.trackballBehavior.enable) { if (chart.trackballBehavior.builder != null) { - _chartState._trackballBehaviorRenderer._trackballTemplate = - _TrackballTemplate( - key: GlobalKey>(), - trackballBehavior: chart.trackballBehavior, - chartState: _chartState); + trackballRenderingDetails.trackballTemplate = TrackballTemplate( + key: GlobalKey>(), + trackballBehavior: chart.trackballBehavior, + stateProperties: _stateProperties); userInteractionWidgets - .add(_chartState._trackballBehaviorRenderer._trackballTemplate!); + .add(trackballRenderingDetails.trackballTemplate!); } else { - trackballPainter = _TrackballPainter( - chartState: _chartState, - valueNotifier: _chartState._repaintNotifiers['trackball']!); - _chartState._trackballBehaviorRenderer._trackballPainter = - trackballPainter; + trackballPainter = TrackballPainter( + stateProperties: _stateProperties, + valueNotifier: _stateProperties.repaintNotifiers['trackball']!); + trackballRenderingDetails.trackballPainter = trackballPainter; userInteractionWidgets.add(Container( height: constraints.maxHeight, width: constraints.maxWidth, @@ -3485,11 +3818,12 @@ class _ContainerArea extends StatelessWidget { } // ignore: unnecessary_null_comparison if (chart.crosshairBehavior != null && chart.crosshairBehavior.enable) { - crosshairPainter = _CrosshairPainter( - chartState: _chartState, - valueNotifier: _chartState._repaintNotifiers['crosshair']!); - _chartState._crosshairBehaviorRenderer._crosshairPainter = - crosshairPainter; + crosshairPainter = CrosshairPainter( + stateProperties: _stateProperties, + valueNotifier: _stateProperties.repaintNotifiers['crosshair']!); + CrosshairHelper.getRenderingDetails( + _stateProperties.crosshairBehaviorRenderer) + .crosshairPainter = crosshairPainter; userInteractionWidgets.add(Container( height: constraints.maxHeight, width: constraints.maxWidth, @@ -3497,10 +3831,14 @@ class _ContainerArea extends StatelessWidget { child: CustomPaint(painter: crosshairPainter))); } final TooltipBehavior tooltip = chart.tooltipBehavior; - if (chart.tooltipBehavior.enable || _shouldShowAxisTooltip(_chartState)) { - _renderingDetails.tooltipBehaviorRenderer._prevTooltipValue = - _renderingDetails.tooltipBehaviorRenderer._currentTooltipValue = null; - _renderingDetails.tooltipBehaviorRenderer._chartTooltip = SfTooltip( + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _renderingDetails.tooltipBehaviorRenderer); + if (chart.tooltipBehavior.enable || + shouldShowAxisTooltip(_stateProperties)) { + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue = null; + tooltipRenderingDetails.chartTooltip = SfTooltip( color: tooltip.color ?? _renderingDetails.chartTheme.tooltipColor, key: GlobalKey(), textStyle: tooltip.textStyle, @@ -3522,33 +3860,32 @@ class _ContainerArea extends StatelessWidget { format: tooltip.format, shadowColor: tooltip.shadowColor, onTooltipRender: chart.onTooltipRender != null - ? _renderingDetails.tooltipBehaviorRenderer._tooltipRenderingEvent + ? tooltipRenderingDetails.tooltipRenderingEvent : null); _renderingDetails.chartWidgets! - .add(_renderingDetails.tooltipBehaviorRenderer._chartTooltip!); + .add(tooltipRenderingDetails.chartTooltip!); } final Widget uiWidget = IgnorePointer( ignoring: chart.annotations != null, child: Stack(children: userInteractionWidgets)); - _renderingDetails.chartWidgets!.add(uiWidget); + _stateProperties.renderingDetails.chartWidgets!.add(uiWidget); } /// Triggering onAxisLabelTapped event void _triggerAxisLabelEvent(Offset position) { for (int i = 0; - i < _chartState._chartAxis._axisRenderersCollection.length; + i < _stateProperties.chartAxis.axisRenderersCollection.length; i++) { - final List labels = - _chartState._chartAxis._axisRenderersCollection[i]._visibleLabels; + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[i]); + final List labels = axisDetails.visibleLabels; for (int k = 0; k < labels.length; k++) { - if (_chartState - ._chartAxis._axisRenderersCollection[i]._axis.isVisible && - labels[k]._labelRegion != null && - labels[k]._labelRegion!.contains(position)) { + if (axisDetails.axis.isVisible && + AxisHelper.getLabelRegion(labels[k]) != null && + AxisHelper.getLabelRegion(labels[k])!.contains(position)) { AxisLabelTapArgs labelArgs; - labelArgs = AxisLabelTapArgs( - _chartState._chartAxis._axisRenderersCollection[i]._axis, - _chartState._chartAxis._axisRenderersCollection[i]._name!); + labelArgs = AxisLabelTapArgs(axisDetails.axis, axisDetails.name!); labelArgs.text = labels[k].text; labelArgs.value = labels[k].value; chart.onAxisLabelTapped!(labelArgs); @@ -3557,322 +3894,362 @@ class _ContainerArea extends StatelessWidget { } } - /// Getter method of the series painter + /// Gets method of the series painter CustomPainter _getSeriesPainter(int value, AnimationController controller, CartesianSeriesRenderer seriesRenderer) { CustomPainter? customPainter; - final _PainterKey painterKey = _PainterKey( + final PainterKey painterKey = PainterKey( index: value, name: 'series $value', isRenderCompleted: false); - _chartState._painterKeys.add(painterKey); - switch (seriesRenderer._seriesType) { + _stateProperties.painterKeys.add(painterKey); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + switch (seriesRendererDetails.seriesType) { case 'line': - customPainter = _LineChartPainter( - chartState: _chartState, + customPainter = LineChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as LineSeriesRenderer, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), painterKey: painterKey, animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'spline': - customPainter = _SplineChartPainter( - chartState: _chartState, + customPainter = SplineChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as SplineSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'column': - customPainter = _ColumnChartPainter( - chartState: _chartState, + customPainter = ColumnChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as ColumnSeriesRenderer, - isRepaint: !(_chartState._zoomedState != null) || - _chartState._zoomedAxisRendererStates.isNotEmpty, + isRepaint: !(_stateProperties.zoomedState != null) || + _stateProperties.zoomedAxisRendererStates.isNotEmpty, painterKey: painterKey, animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'scatter': - customPainter = _ScatterChartPainter( - chartState: _chartState, + customPainter = ScatterChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as ScatterSeriesRenderer, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), painterKey: painterKey, animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stepline': - customPainter = _StepLineChartPainter( - chartState: _chartState, + customPainter = StepLineChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StepLineSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'area': - customPainter = _AreaChartPainter( - chartState: _chartState, + customPainter = AreaChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as AreaSeriesRenderer, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), painterKey: painterKey, animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'bubble': - customPainter = _BubbleChartPainter( - chartState: _chartState, + customPainter = BubbleChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as BubbleSeriesRenderer, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), painterKey: painterKey, animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'bar': - customPainter = _BarChartPainter( - chartState: _chartState, + customPainter = BarChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as BarSeriesRenderer, - isRepaint: ((_chartState._zoomedState != null) == false) || - _chartState._zoomedAxisRendererStates.isNotEmpty, + isRepaint: ((_stateProperties.zoomedState != null) == false) || + _stateProperties.zoomedAxisRendererStates.isNotEmpty, painterKey: painterKey, animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'fastline': - customPainter = _FastLineChartPainter( - chartState: _chartState, + customPainter = FastLineChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as FastLineSeriesRenderer, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), painterKey: painterKey, animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'rangecolumn': - customPainter = _RangeColumnChartPainter( - chartState: _chartState, + customPainter = RangeColumnChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as RangeColumnSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'rangearea': - customPainter = _RangeAreaChartPainter( - chartState: _chartState, + customPainter = RangeAreaChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as RangeAreaSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'steparea': - customPainter = _StepAreaChartPainter( - chartState: _chartState, + customPainter = StepAreaChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StepAreaSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'splinearea': - customPainter = _SplineAreaChartPainter( - chartState: _chartState, + customPainter = SplineAreaChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as SplineAreaSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'splinerangearea': - customPainter = _SplineRangeAreaChartPainter( - chartState: _chartState, + customPainter = SplineRangeAreaChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as SplineRangeAreaSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedarea': - customPainter = _StackedAreaChartPainter( - chartState: _chartState, + customPainter = StackedAreaChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedAreaSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedbar': - customPainter = _StackedBarChartPainter( - chartState: _chartState, + customPainter = StackedBarChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedBarSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedcolumn': - customPainter = _StackedColummnChartPainter( - chartState: _chartState, + customPainter = StackedColummnChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedColumnSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedline': - customPainter = _StackedLineChartPainter( - chartState: _chartState, + customPainter = StackedLineChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedLineSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedarea100': - customPainter = _StackedArea100ChartPainter( - chartState: _chartState, + customPainter = StackedArea100ChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedArea100SeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedbar100': - customPainter = _StackedBar100ChartPainter( - chartState: _chartState, + customPainter = StackedBar100ChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedBar100SeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedcolumn100': - customPainter = _StackedColumn100ChartPainter( - chartState: _chartState, + customPainter = StackedColumn100ChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedColumn100SeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'stackedline100': - customPainter = _StackedLine100ChartPainter( - chartState: _chartState, + customPainter = StackedLine100ChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as StackedLine100SeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'hilo': - customPainter = _HiloPainter( - chartState: _chartState, + customPainter = HiloPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as HiloSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'hiloopenclose': - customPainter = _HiloOpenClosePainter( - chartState: _chartState, + customPainter = HiloOpenClosePainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as HiloOpenCloseSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'candle': - customPainter = _CandlePainter( - chartState: _chartState, + customPainter = CandlePainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as CandleSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'histogram': - customPainter = _HistogramChartPainter( - chartState: _chartState, + customPainter = HistogramChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as HistogramSeriesRenderer, - chartSeries: _chartState._chartSeries, + chartSeries: _stateProperties.chartSeries, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'boxandwhisker': - customPainter = _BoxAndWhiskerPainter( - chartState: _chartState, + customPainter = BoxAndWhiskerPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as BoxAndWhiskerSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; case 'waterfall': - customPainter = _WaterfallChartPainter( - chartState: _chartState, + customPainter = WaterfallChartPainter( + stateProperties: _stateProperties, seriesRenderer: seriesRenderer as WaterfallSeriesRenderer, painterKey: painterKey, - isRepaint: _chartState._zoomedState != null - ? _chartState._zoomedAxisRendererStates.isNotEmpty - : (_chartState._legendToggling || seriesRenderer._needsRepaint), + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), + animationController: controller, + notifier: seriesRendererDetails.repaintNotifier); + break; + case 'errorbar': + customPainter = ErrorBarChartPainter( + stateProperties: _stateProperties, + seriesRenderer: seriesRenderer as ErrorBarSeriesRenderer, + painterKey: painterKey, + isRepaint: _stateProperties.zoomedState != null + ? _stateProperties.zoomedAxisRendererStates.isNotEmpty + : (_stateProperties.legendToggling || + seriesRendererDetails.needsRepaint == true), animationController: controller, - notifier: seriesRenderer._repaintNotifier); + notifier: seriesRendererDetails.repaintNotifier); break; } return customPainter!; diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart index 137674469..51400df58 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/base/series_base.dart @@ -1,27 +1,79 @@ -part of charts; +import 'dart:math' as math; -class _ChartSeries { - _ChartSeries(this._chartState); - final SfCartesianChartState _chartState; - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfCartesianChart get chart => _chartState._chart; - _RenderingDetails get _renderingDetails => _chartState._renderingDetails; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; + +import '../../common/utils/typedef.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/chart_base.dart'; +import '../chart_series/histogram_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../series_painter/bubble_painter.dart'; +import '../series_painter/fastline_painter.dart'; +import '../series_painter/histogram_painter.dart'; +import '../series_painter/spline_painter.dart'; +import '../series_painter/waterfall_painter.dart'; +import '../technical_indicators/accumulation_distribution_indicator.dart'; +import '../technical_indicators/atr_indicator.dart'; +import '../technical_indicators/bollinger_bands_indicator.dart'; +import '../technical_indicators/ema_indicator.dart'; +import '../technical_indicators/macd_indicator.dart'; +import '../technical_indicators/momentum_indicator.dart'; +import '../technical_indicators/rsi_indicator.dart'; +import '../technical_indicators/sma_indicator.dart'; +import '../technical_indicators/stochastic_indicator.dart'; +import '../technical_indicators/technical_indicator.dart'; +import '../technical_indicators/tma_indicator.dart'; +import '../trendlines/trendlines.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the chart series panel class +class ChartSeriesPanel { + /// Creates an instance of chart series panel + ChartSeriesPanel(this.stateProperties); + + /// Specifies the value of state properties + final CartesianStateProperties stateProperties; + + /// Here, we are using get keyword in order to get the proper & updated instance of chart widget + /// When we initialize chart widget as a property to other classes like ChartSeries, the chart widget is not updated properly and by using get we can rectify this. + SfCartesianChart get chart => stateProperties.chart; + + /// Specifies whether the series is stacked 100 bool isStacked100 = false; + + /// Specifies the palette index value int paletteIndex = 0; + + /// Holds the sum of Y-values num sumOfYvalues = 0; + + /// Holds the list of YValues List yValues = []; /// Contains the visible series for chart List visibleSeriesRenderers = []; - List<_ClusterStackedItemInfo> clusterStackedItemInfo = - <_ClusterStackedItemInfo>[]; - bool _needAxisRangeAnimation = false; + /// Holds the list of cluster stacked item info + List clusterStackedItemInfo = + []; + + /// Specifies whether axis range animation is required + bool needAxisRangeAnimation = false; /// To get data and process data for rendering chart - void _processData() { + void processData() { final List seriesRendererList = visibleSeriesRenderers; isStacked100 = false; @@ -30,84 +82,95 @@ class _ChartSeries { if (chart.indicators.isNotEmpty) { _populateDataPoints(seriesRendererList); _calculateIndicators(); - _chartState._chartAxis._calculateVisibleAxes(); + stateProperties.chartAxis.calculateVisibleAxes(); _findMinMax(seriesRendererList); _renderTrendline(); } else { - _chartState._chartAxis._calculateVisibleAxes(); + stateProperties.chartAxis.calculateVisibleAxes(); _populateDataPoints(seriesRendererList); } - _calculateStackedValues(_findSeriesCollection(_chartState)); + calculateStackedValues(findSeriesCollection(stateProperties)); _renderTrendline(); } ///check whether axis animation applicable or not bool _needAxisAnimation(CartesianSeriesRenderer seriesRenderer, CartesianSeriesRenderer oldSeriesRenderer) { - final dynamic oldAxis = oldSeriesRenderer._xAxisRenderer!._axis; - final dynamic axis = seriesRenderer._xAxisRenderer!._axis; - final bool needAnimation = seriesRenderer._series.animationDuration > 0 && - seriesRenderer._yAxisRenderer!.runtimeType == - oldSeriesRenderer._yAxisRenderer!.runtimeType && - seriesRenderer._xAxisRenderer!.runtimeType == - oldSeriesRenderer._xAxisRenderer!.runtimeType && - ((oldAxis.visibleMinimum != null && - oldAxis.visibleMinimum != axis.visibleMinimum) || - (oldAxis.visibleMaximum != null && - oldAxis.visibleMaximum != axis.visibleMaximum)); - _needAxisRangeAnimation = needAnimation; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final SeriesRendererDetails oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); + final dynamic oldAxis = oldSeriesRendererDetails.xAxisDetails!.axis; + final dynamic axis = seriesRendererDetails.xAxisDetails!.axis; + final bool needAnimation = + seriesRendererDetails.series.animationDuration > 0 == true && + seriesRendererDetails.yAxisDetails!.runtimeType == + oldSeriesRendererDetails.yAxisDetails!.runtimeType && + seriesRendererDetails.xAxisDetails!.runtimeType == + oldSeriesRendererDetails.xAxisDetails!.runtimeType && + ((oldAxis.visibleMinimum != null && + oldAxis.visibleMinimum != axis.visibleMinimum) || + (oldAxis.visibleMaximum != null && + oldAxis.visibleMaximum != axis.visibleMaximum)); + needAxisRangeAnimation = needAnimation; return needAnimation; } /// Find the data points for each series void _populateDataPoints(List seriesRendererList) { - _chartState._totalAnimatingSeries = 0; + stateProperties.totalAnimatingSeries = 0; bool isSelectionRangeChangeByEvent = false; for (final CartesianSeriesRenderer seriesRenderer in seriesRendererList) { - final CartesianSeries series = seriesRenderer._series; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final CartesianSeries series = + seriesRendererDetails.series; final ChartIndexedValueMapper? _bubbleSize = series.sizeValueMapper; - seriesRenderer._minimumX = seriesRenderer._minimumY = - seriesRenderer._minDelta = - seriesRenderer._maximumX = seriesRenderer._maximumY = null; + seriesRendererDetails.minimumX = seriesRendererDetails.minimumY = + seriesRendererDetails.minDelta = seriesRendererDetails.maximumX = + seriesRendererDetails.maximumY = null; if (seriesRenderer is BubbleSeriesRenderer) { - seriesRenderer._maxSize = seriesRenderer._minSize = null; + seriesRendererDetails.maxSize = seriesRendererDetails.minSize = null; } - seriesRenderer._needAnimateSeriesElements = false; - seriesRenderer._needsAnimation = false; - seriesRenderer._reAnimate = false; + seriesRendererDetails.needAnimateSeriesElements = false; + seriesRendererDetails.needsAnimation = false; + seriesRendererDetails.reAnimate = false; CartesianChartPoint? currentPoint; yValues = []; sumOfYvalues = 0; - seriesRenderer._dataPoints = >[]; + seriesRendererDetails.dataPoints = >[]; if (seriesRenderer is FastLineSeriesRenderer) { - seriesRenderer._overallDataPoints = >[]; + seriesRendererDetails.overallDataPoints = + >[]; } - seriesRenderer._xValues = []; - if (!isStacked100 && seriesRenderer._seriesType.contains('100')) { + seriesRendererDetails.xValues = []; + if (!isStacked100 && + seriesRendererDetails.seriesType.contains('100') == true) { isStacked100 = true; } - if (seriesRenderer._visible!) { - _chartState._totalAnimatingSeries++; + if (seriesRendererDetails.visible! == true) { + stateProperties.totalAnimatingSeries++; } if (seriesRenderer is HistogramSeriesRenderer) { final HistogramSeries series = - seriesRenderer._series as HistogramSeries; + seriesRendererDetails.series as HistogramSeries; for (int pointIndex = 0; pointIndex < series.dataSource.length; pointIndex++) { yValues.add(series.yValueMapper!(pointIndex) ?? 0); sumOfYvalues += yValues[pointIndex]; } - seriesRenderer._processData(series, yValues, sumOfYvalues); - seriesRenderer._histogramValues.minValue = - seriesRenderer._histogramValues.yValues!.reduce(min); - seriesRenderer._histogramValues.binWidth = series.binInterval ?? - ((3.5 * seriesRenderer._histogramValues.sDValue!) / + seriesRendererDetails.processData(series, yValues, sumOfYvalues); + seriesRendererDetails.histogramValues.minValue = + seriesRendererDetails.histogramValues.yValues!.reduce(math.min); + seriesRendererDetails.histogramValues.binWidth = series.binInterval ?? + ((3.5 * seriesRendererDetails.histogramValues.sDValue!) / math.pow( - seriesRenderer._histogramValues.yValues!.length, 1 / 3)) + seriesRendererDetails.histogramValues.yValues!.length, + 1 / 3)) .round(); } - final String seriesType = seriesRenderer._seriesType; + final String seriesType = seriesRendererDetails.seriesType; final bool needSorting = series.sortingOrder != SortingOrder.none && series.sortFieldValueMapper != null; // ignore: unnecessary_null_comparison @@ -116,9 +179,10 @@ class _ChartSeries { dynamic yVal; num? low, high; num maxYValue = 0; - seriesRenderer._overAllDataPoints = ?>[]; + seriesRendererDetails.overAllDataPoints = + ?>[]; for (int pointIndex = 0; pointIndex < series.dataSource.length;) { - currentPoint = _getChartPoint( + currentPoint = getChartPoint( seriesRenderer, series.dataSource[pointIndex], pointIndex); xVal = currentPoint?.x; yVal = currentPoint?.y; @@ -126,7 +190,7 @@ class _ChartSeries { low = currentPoint?.low; currentPoint?.overallDataPointIndex = pointIndex; - seriesRenderer._overAllDataPoints.add(currentPoint); + seriesRendererDetails.overAllDataPoints.add(currentPoint); if (seriesRenderer is WaterfallSeriesRenderer) { yVal ??= 0; maxYValue += yVal; @@ -135,8 +199,8 @@ class _ChartSeries { if (xVal != null) { num bubbleSize; - final dynamic xAxis = seriesRenderer._xAxisRenderer?._axis; - final dynamic yAxis = seriesRenderer._yAxisRenderer?._axis; + final dynamic xAxis = seriesRendererDetails.xAxisDetails?.axis; + final dynamic yAxis = seriesRendererDetails.yAxisDetails?.axis; dynamic xMin = xAxis?.visibleMinimum; dynamic xMax = xAxis?.visibleMaximum; final dynamic yMin = yAxis?.visibleMinimum; @@ -167,30 +231,33 @@ class _ChartSeries { yMin != null || yMax != null) && // ignore: unnecessary_null_comparison - _chartState._oldSeriesRenderers != null && - _chartState._oldSeriesRenderers.isNotEmpty) { - final int seriesIndex = _chartState - ._chartSeries.visibleSeriesRenderers + stateProperties.oldSeriesRenderers != null && + stateProperties.oldSeriesRenderers.isNotEmpty) { + final int seriesIndex = stateProperties + .chartSeries.visibleSeriesRenderers .indexOf(seriesRenderer); final CartesianSeriesRenderer? _oldSeriesRenderer = - _chartState._oldSeriesRenderers.length - 1 >= seriesIndex - ? _chartState._oldSeriesRenderers[seriesIndex] + stateProperties.oldSeriesRenderers.length - 1 >= seriesIndex + ? stateProperties.oldSeriesRenderers[seriesIndex] : null; if (_oldSeriesRenderer != null && - (_chartState._chart.onSelectionChanged != null || + (stateProperties.chart.onSelectionChanged != null || _needAxisAnimation(seriesRenderer, _oldSeriesRenderer))) { + final SeriesRendererDetails oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(_oldSeriesRenderer); isSelectionRangeChangeByEvent = - _oldSeriesRenderer._minimumX != xMin || - _oldSeriesRenderer._maximumX != xMax || - _oldSeriesRenderer._minimumY != yMin || - _oldSeriesRenderer._maximumY != yMax; + oldSeriesRendererDetails.minimumX != xMin || + oldSeriesRendererDetails.maximumX != xMax || + oldSeriesRendererDetails.minimumY != yMin || + oldSeriesRendererDetails.maximumY != yMax; } } if (!(!(isSelectionRangeChangeByEvent || - _chartState._rangeChangeBySlider || - _chartState._zoomedState == true || - _chartState._zoomProgress) && + stateProperties.rangeChangeBySlider || + stateProperties.zoomedState == true || + stateProperties.renderingDetails.didSizeChange || + stateProperties.zoomProgress) && (xMin != null || xMax != null || yMin != null || @@ -216,18 +283,22 @@ class _ChartSeries { true) { _isXVisibleRange = true; _isYVisibleRange = true; - seriesRenderer._dataPoints.add(currentPoint!); - seriesRenderer._xValues!.add(xVal); + seriesRendererDetails.dataPoints.add(currentPoint!); + seriesRendererDetails.xValues!.add(xVal); if (seriesRenderer is BubbleSeriesRenderer) { bubbleSize = series.sizeValueMapper == null ? 4 : _bubbleSize!(pointIndex) ?? 4; currentPoint.bubbleSize = bubbleSize.toDouble(); - seriesRenderer._maxSize ??= currentPoint.bubbleSize!.toDouble(); - seriesRenderer._minSize ??= currentPoint.bubbleSize!.toDouble(); - seriesRenderer._maxSize = math.max(seriesRenderer._maxSize!, + seriesRendererDetails.maxSize ??= + currentPoint.bubbleSize!.toDouble(); + seriesRendererDetails.minSize ??= + currentPoint.bubbleSize!.toDouble(); + seriesRendererDetails.maxSize = math.max( + seriesRendererDetails.maxSize!, currentPoint.bubbleSize!.toDouble()); - seriesRenderer._minSize = math.min(seriesRenderer._minSize!, + seriesRendererDetails.minSize = math.min( + seriesRendererDetails.minSize!, currentPoint.bubbleSize!.toDouble()); } @@ -256,7 +327,7 @@ class _ChartSeries { } } - /// Below lines for changing high, low values based on input + // Below lines for changing high, low values based on input if ((seriesType.contains('range') || seriesType.contains('hilo') || seriesType.contains('candle') || @@ -275,34 +346,37 @@ class _ChartSeries { } } //determines whether the data source has been changed in-order to perform dynamic animation - if (!seriesRenderer._needsAnimation && - _needAxisRangeAnimation != true) { - if (seriesRenderer._oldSeries == null || - seriesRenderer._oldDataPoints!.length < - seriesRenderer._dataPoints.length) { - seriesRenderer._needAnimateSeriesElements = true; - seriesRenderer._needsAnimation = seriesRenderer._visible!; + if (seriesRendererDetails.needsAnimation == false && + needAxisRangeAnimation != true) { + if (seriesRendererDetails.oldSeries == null || + seriesRendererDetails.oldDataPoints!.length < + seriesRendererDetails.dataPoints.length == + true) { + seriesRendererDetails.needAnimateSeriesElements = true; + seriesRendererDetails.needsAnimation = + seriesRendererDetails.visible!; } else { - seriesRenderer._needsAnimation = - (seriesRenderer._dataPoints.length <= - seriesRenderer._oldDataPoints!.length) - ? seriesRenderer._visible! && - _findChangesInPoint( - currentPoint, - seriesRenderer._oldDataPoints![ - seriesRenderer._dataPoints.length - 1], - seriesRenderer, - ) - : seriesRenderer._visible!; + seriesRendererDetails.needsAnimation = (seriesRendererDetails + .dataPoints.length <= + seriesRendererDetails.oldDataPoints!.length) == + true + ? seriesRendererDetails.visible! == true && + findChangesInPoint( + currentPoint, + seriesRendererDetails.oldDataPoints![ + seriesRendererDetails.dataPoints.length - 1], + seriesRendererDetails, + ) + : seriesRendererDetails.visible!; } } } - if (seriesRenderer._xAxisRenderer != null && - seriesRenderer._yAxisRenderer != null && + if (seriesRendererDetails.xAxisDetails != null && + seriesRendererDetails.yAxisDetails != null && !needSorting && chart.indicators.isEmpty) { _findMinMaxValue( - seriesRenderer._xAxisRenderer!, + seriesRendererDetails.xAxisDetails!.axisRenderer, seriesRenderer, currentPoint!, pointIndex, @@ -312,31 +386,33 @@ class _ChartSeries { } if (seriesRenderer is SplineSeriesRenderer && !needSorting) { if (pointIndex == 0) { - seriesRenderer._xValueList.clear(); - seriesRenderer._yValueList.clear(); + seriesRendererDetails.xValueList.clear(); + seriesRendererDetails.yValueList.clear(); } if (!currentPoint!.isDrop) { - seriesRenderer._xValueList.add(currentPoint.xValue); - seriesRenderer._yValueList.add(currentPoint.yValue); + seriesRendererDetails.xValueList.add(currentPoint.xValue); + seriesRendererDetails.yValueList.add(currentPoint.yValue); } } } - pointIndex = seriesRenderer._seriesType != 'histogram' + pointIndex = seriesRendererDetails.seriesType != 'histogram' ? pointIndex + 1 : pointIndex + yVal as int; } - if (seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) { - _sortDateTimeCategoryDetails(seriesRenderer); + if (seriesRendererDetails.xAxisDetails + is DateTimeCategoryAxisRenderer) { + _sortDateTimeCategoryDetails(seriesRendererDetails); } if (needSorting) { - _sortDataSource(seriesRenderer); + _sortDataSource(seriesRendererDetails); if (chart.indicators.isEmpty) { - _findSeriesMinMax(seriesRenderer); + findSeriesMinMax(seriesRendererDetails); } } } if (seriesRenderer is FastLineSeriesRenderer) { - seriesRenderer._overallDataPoints.addAll(seriesRenderer._dataPoints); + seriesRendererDetails.overallDataPoints + .addAll(seriesRendererDetails.dataPoints); } } } @@ -350,51 +426,56 @@ class _ChartSeries { int dataLength, [bool? isXVisibleRange, bool? isYVisibleRange]) { - if (seriesRenderer._visible!) { - if (axisRenderer is NumericAxisRenderer) { - axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.visible! == true) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + if (axisDetails is NumericAxisDetails) { + axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisRenderer is CategoryAxisRenderer) { - axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, + } else if (axisDetails is CategoryAxisDetails) { + axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisRenderer is DateTimeAxisRenderer) { - axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, + } else if (axisDetails is DateTimeAxisDetails) { + axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisRenderer is LogarithmicAxisRenderer) { - axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, + } else if (axisDetails is LogarithmicAxisDetails) { + axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange); - } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - axisRenderer._findAxisMinMaxValues(seriesRenderer, currentPoint, + } else if (axisDetails is DateTimeCategoryAxisDetails) { + axisDetails.findAxisMinMaxValues(seriesRendererDetails, currentPoint, pointIndex, dataLength, isXVisibleRange, isYVisibleRange); } } } /// To find minimum and maximum series values - void _findSeriesMinMax(CartesianSeriesRenderer seriesRenderer) { - final ChartAxisRenderer axisRenderer = seriesRenderer._xAxisRenderer!; - if (seriesRenderer._visible!) { - if (seriesRenderer is SplineSeriesRenderer) { - seriesRenderer._xValueList.clear(); - seriesRenderer._yValueList.clear(); + void findSeriesMinMax(SeriesRendererDetails seriesRendererDetails) { + final ChartAxisRenderer axisRenderer = + seriesRendererDetails.xAxisDetails!.axisRenderer; + if (seriesRendererDetails.visible! == true) { + if (seriesRendererDetails.renderer is SplineSeriesRenderer) { + seriesRendererDetails.xValueList.clear(); + seriesRendererDetails.yValueList.clear(); } for (int pointIndex = 0; - pointIndex < seriesRenderer._dataPoints.length; + pointIndex < seriesRendererDetails.dataPoints.length; pointIndex++) { _findMinMaxValue( axisRenderer, - seriesRenderer, - seriesRenderer._dataPoints[pointIndex], + seriesRendererDetails.renderer, + seriesRendererDetails.dataPoints[pointIndex], pointIndex, - seriesRenderer._dataPoints.length, + seriesRendererDetails.dataPoints.length, true, true); - if (seriesRenderer is SplineSeriesRenderer) { - if (!seriesRenderer._dataPoints[pointIndex].isDrop) { - seriesRenderer._xValueList - .add(seriesRenderer._dataPoints[pointIndex].xValue); - seriesRenderer._yValueList - .add(seriesRenderer._dataPoints[pointIndex].yValue); + if (seriesRendererDetails.renderer is SplineSeriesRenderer) { + if (seriesRendererDetails.dataPoints[pointIndex].isDrop == false) { + seriesRendererDetails.xValueList + .add(seriesRendererDetails.dataPoints[pointIndex].xValue); + seriesRendererDetails.yValueList + .add(seriesRendererDetails.dataPoints[pointIndex].yValue); } } } @@ -403,10 +484,13 @@ class _ChartSeries { /// To find minimum and maximum in series collection void _findMinMax(List seriesCollection) { + SeriesRendererDetails seriesRendererDetails; for (int seriesIndex = 0; seriesIndex < seriesCollection.length; seriesIndex++) { - _findSeriesMinMax(seriesCollection[seriesIndex]); + seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesCollection[seriesIndex]); + findSeriesMinMax(seriesRendererDetails); } } @@ -414,29 +498,34 @@ class _ChartSeries { void _renderTrendline() { for (final CartesianSeriesRenderer seriesRenderer in visibleSeriesRenderers) { - if (seriesRenderer._series.trendlines != null) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.series.trendlines != null) { TrendlineRenderer trendlineRenderer; Trendline trendline; - for (int i = 0; i < seriesRenderer._series.trendlines!.length; i++) { - trendline = seriesRenderer._series.trendlines![i]; - trendlineRenderer = seriesRenderer._trendlineRenderer[i]; - trendlineRenderer._isNeedRender = trendlineRenderer._visible == - true && - seriesRenderer._visible! && + for (int i = 0; + i < seriesRendererDetails.series.trendlines!.length; + i++) { + trendline = seriesRendererDetails.series.trendlines![i]; + trendlineRenderer = seriesRendererDetails.trendlineRenderer[i]; + trendlineRenderer.isNeedRender = trendlineRenderer.visible == true && + seriesRendererDetails.visible! == true && (trendline.type == TrendlineType.polynomial ? (trendline.polynomialOrder >= 2 && trendline.polynomialOrder <= 6) : !(trendline.type == TrendlineType.movingAverage) || (trendline.period >= 2 && trendline.period <= - seriesRenderer._series.dataSource.length - 1)); - trendlineRenderer._animationController = - AnimationController(vsync: _chartState) - ..addListener(_chartState._repaintTrendlines); - _chartState._controllerList[trendlineRenderer._animationController] = - _chartState._repaintTrendlines; - if (trendlineRenderer._isNeedRender) { - trendlineRenderer._setDataSource(seriesRenderer, chart); + seriesRendererDetails.series.dataSource.length - + 1)); + trendlineRenderer.animationController = + AnimationController(vsync: stateProperties.chartState) + ..addListener(stateProperties.repaintTrendlines); + stateProperties + .controllerList[trendlineRenderer.animationController] = + stateProperties.repaintTrendlines; + if (trendlineRenderer.isNeedRender) { + trendlineRenderer.setDataSource(seriesRendererDetails, chart); } } } @@ -444,12 +533,12 @@ class _ChartSeries { } /// Sort the dataSource - void _sortDataSource(CartesianSeriesRenderer seriesRenderer) { - seriesRenderer._dataPoints.sort( + void _sortDataSource(SeriesRendererDetails seriesRendererDetails) { + seriesRendererDetails.dataPoints.sort( // ignore: missing_return (CartesianChartPoint firstPoint, CartesianChartPoint secondPoint) { - if (seriesRenderer._series.sortingOrder == SortingOrder.ascending) { + if (seriesRendererDetails.series.sortingOrder == SortingOrder.ascending) { return ((firstPoint.sortValue == null) ? -1 : (secondPoint.sortValue == null @@ -460,7 +549,7 @@ class _ChartSeries { .compareTo(secondPoint.sortValue.toLowerCase()) : firstPoint.sortValue .compareTo(secondPoint.sortValue)))) as int; - } else if (seriesRenderer._series.sortingOrder == + } else if (seriesRendererDetails.series.sortingOrder == SortingOrder.descending) { return ((firstPoint.sortValue == null) ? 1 @@ -479,68 +568,77 @@ class _ChartSeries { } /// To calculate stacked values of a stacked series - void _calculateStackedValues( + void calculateStackedValues( List seriesRendererCollection) { - _StackedItemInfo stackedItemInfo; - _ClusterStackedItemInfo clusterStackedItemInfo; + StackedItemInfo stackedItemInfo; + ClusterStackedItemInfo clusterStackedItemInfo; String groupName = ''; - List<_StackingInfo>? positiveValues; - List<_StackingInfo>? negativeValues; + List? positiveValues; + List? negativeValues; CartesianSeriesRenderer seriesRenderer; if (isStacked100) { _calculateStackingPercentage(seriesRendererCollection); } - _chartState._chartSeries.clusterStackedItemInfo = - <_ClusterStackedItemInfo>[]; + stateProperties.chartSeries.clusterStackedItemInfo = + []; for (int i = 0; i < seriesRendererCollection.length; i++) { seriesRenderer = seriesRendererCollection[i]; - if (seriesRenderer is _StackedSeriesRenderer && - seriesRenderer._series is _StackedSeriesBase) { - final _StackedSeriesBase stackedSeriesBase = - seriesRenderer._series as _StackedSeriesBase; - if (seriesRenderer._dataPoints.isNotEmpty) { - groupName = (seriesRenderer._seriesType.contains('stackedarea')) - ? 'stackedareagroup' - // ignore: unnecessary_null_comparison - : stackedSeriesBase.groupName; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRenderer is StackedSeriesRenderer && + seriesRendererDetails.series is StackedSeriesBase) { + final StackedSeriesBase stackedSeriesBase = + seriesRendererDetails.series as StackedSeriesBase; + if (seriesRendererDetails.dataPoints.isNotEmpty == true) { + groupName = + (seriesRendererDetails.seriesType.contains('stackedarea') == true) + ? 'stackedareagroup' + // ignore: unnecessary_null_comparison + : stackedSeriesBase.groupName; // : (stackedSeriesBase.groupName ?? ('series ' + i.toString())); - stackedItemInfo = _StackedItemInfo(i, seriesRenderer); - if (_chartState._chartSeries.clusterStackedItemInfo.isNotEmpty) { + stackedItemInfo = StackedItemInfo(i, seriesRenderer); + if (stateProperties.chartSeries.clusterStackedItemInfo.isNotEmpty) { for (int k = 0; - k < _chartState._chartSeries.clusterStackedItemInfo.length; + k < stateProperties.chartSeries.clusterStackedItemInfo.length; k++) { clusterStackedItemInfo = - _chartState._chartSeries.clusterStackedItemInfo[k]; + stateProperties.chartSeries.clusterStackedItemInfo[k]; if (clusterStackedItemInfo.stackName == groupName) { clusterStackedItemInfo.stackedItemInfo.add(stackedItemInfo); break; } else if (k == - _chartState._chartSeries.clusterStackedItemInfo.length - 1) { - _chartState._chartSeries.clusterStackedItemInfo.add( - _ClusterStackedItemInfo( - groupName, <_StackedItemInfo>[stackedItemInfo])); + stateProperties.chartSeries.clusterStackedItemInfo.length - + 1) { + stateProperties.chartSeries.clusterStackedItemInfo.add( + ClusterStackedItemInfo( + groupName, [stackedItemInfo])); break; } } } else { - _chartState._chartSeries.clusterStackedItemInfo.add( - _ClusterStackedItemInfo( - groupName, <_StackedItemInfo>[stackedItemInfo])); + stateProperties.chartSeries.clusterStackedItemInfo.add( + ClusterStackedItemInfo( + groupName, [stackedItemInfo])); } - seriesRenderer._stackingValues = <_StackedValues>[]; - _StackingInfo? currentPositiveStackInfo; + seriesRendererDetails.stackingValues = []; + StackingInfo? currentPositiveStackInfo; if (positiveValues == null || negativeValues == null) { - positiveValues = <_StackingInfo>[]; - currentPositiveStackInfo = _StackingInfo(groupName, []); + positiveValues = []; + currentPositiveStackInfo = StackingInfo(groupName, []); positiveValues.add(currentPositiveStackInfo); - negativeValues = <_StackingInfo>[]; - negativeValues.add(_StackingInfo(groupName, [])); + negativeValues = []; + negativeValues.add(StackingInfo(groupName, [])); } - _addStackingValues(seriesRenderer, isStacked100, positiveValues, - negativeValues, currentPositiveStackInfo, groupName); + _addStackingValues( + seriesRendererDetails, + isStacked100, + positiveValues, + negativeValues, + currentPositiveStackInfo, + groupName); } } } @@ -548,19 +646,19 @@ class _ChartSeries { /// To add the values of stacked series void _addStackingValues( - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, bool isStacked100, - List<_StackingInfo> positiveValues, - List<_StackingInfo> negativeValues, - _StackingInfo? currentPositiveStackInfo, + List positiveValues, + List negativeValues, + StackingInfo? currentPositiveStackInfo, String groupName) { num lastValue, value; CartesianChartPoint point; - _StackingInfo? currentNegativeStackInfo; + StackingInfo? currentNegativeStackInfo; final List startValues = []; final List endValues = []; - for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { - point = seriesRenderer._dataPoints[j]; + for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { + point = seriesRendererDetails.dataPoints[j]; value = point.y; if (positiveValues.isNotEmpty) { for (int k = 0; k < positiveValues.length; k++) { @@ -568,7 +666,7 @@ class _ChartSeries { currentPositiveStackInfo = positiveValues[k]; break; } else if (k == positiveValues.length - 1) { - currentPositiveStackInfo = _StackingInfo(groupName, []); + currentPositiveStackInfo = StackingInfo(groupName, []); positiveValues.add(currentPositiveStackInfo); } } @@ -579,34 +677,36 @@ class _ChartSeries { currentNegativeStackInfo = negativeValues[k]; break; } else if (k == negativeValues.length - 1) { - currentNegativeStackInfo = _StackingInfo(groupName, []); + currentNegativeStackInfo = StackingInfo(groupName, []); negativeValues.add(currentNegativeStackInfo); } } } - if (currentPositiveStackInfo?._stackingValues != null) { - final int length = currentPositiveStackInfo!._stackingValues!.length; + if (currentPositiveStackInfo?.stackingValues != null) { + final int length = currentPositiveStackInfo!.stackingValues!.length; if (length == 0 || j > length - 1) { - currentPositiveStackInfo._stackingValues!.add(0); + currentPositiveStackInfo.stackingValues!.add(0); } } - if (currentNegativeStackInfo?._stackingValues != null) { - final int length = currentNegativeStackInfo!._stackingValues!.length; + if (currentNegativeStackInfo?.stackingValues != null) { + final int length = currentNegativeStackInfo!.stackingValues!.length; if (length == 0 || j > length - 1) { - currentNegativeStackInfo._stackingValues!.add(0); + currentNegativeStackInfo.stackingValues!.add(0); } } - if (isStacked100 && seriesRenderer is _StackedSeriesRenderer) { - value = value / seriesRenderer._percentageValues[j] * 100; + if (isStacked100 && + seriesRendererDetails.renderer is StackedSeriesRenderer) { + value = value / seriesRendererDetails.percentageValues[j] * 100; value = value.isNaN ? 0 : value; } - if (seriesRenderer._seriesType.contains('stackedarea') || value >= 0) { - lastValue = currentPositiveStackInfo!._stackingValues![j]; - currentPositiveStackInfo._stackingValues![j] = + if (seriesRendererDetails.seriesType.contains('stackedarea') == true || + value >= 0) { + lastValue = currentPositiveStackInfo!.stackingValues![j]; + currentPositiveStackInfo.stackingValues![j] = (lastValue + value).toDouble(); } else { - lastValue = currentNegativeStackInfo!._stackingValues![j]; - currentNegativeStackInfo._stackingValues![j] = + lastValue = currentNegativeStackInfo!.stackingValues![j]; + currentNegativeStackInfo.stackingValues![j] = (lastValue + value).toDouble(); } startValues.add(lastValue.toDouble()); @@ -614,53 +714,59 @@ class _ChartSeries { if (isStacked100 && endValues[j] > 100) { endValues[j] = 100; } - point.cumulativeValue = !seriesRenderer._seriesType.contains('100') - ? endValues[j] - : endValues[j].truncateToDouble(); + point.cumulativeValue = + seriesRendererDetails.seriesType.contains('100') == false + ? endValues[j] + : endValues[j].truncateToDouble(); } - if (seriesRenderer is _StackedSeriesRenderer) { - seriesRenderer._stackingValues - .add(_StackedValues(startValues, endValues)); + if (seriesRendererDetails.renderer is StackedSeriesRenderer) { + seriesRendererDetails.stackingValues + .add(StackedValues(startValues, endValues)); } - seriesRenderer._minimumY = startValues.reduce(min); - seriesRenderer._maximumY = endValues.reduce(max); + seriesRendererDetails.minimumY = startValues.reduce(math.min); + seriesRendererDetails.maximumY = endValues.reduce(math.max); - if (seriesRenderer._minimumY! > endValues.reduce(min)) { - seriesRenderer._minimumY = isStacked100 ? -100 : endValues.reduce(min); + if (seriesRendererDetails.minimumY! > endValues.reduce(math.min) == true) { + seriesRendererDetails.minimumY = + isStacked100 ? -100 : endValues.reduce(math.min); } - if (seriesRenderer._maximumY! < startValues.reduce(max)) { - seriesRenderer._maximumY = 0; + if (seriesRendererDetails.maximumY! < startValues.reduce(math.max) == + true) { + seriesRendererDetails.maximumY = 0; } } /// To find the percentage of stacked series void _calculateStackingPercentage( List seriesRendererCollection) { - List<_StackingInfo>? percentageValues; + List? percentageValues; CartesianSeriesRenderer seriesRenderer; + SeriesRendererDetails seriesRendererDetails; String groupName; - _StackingInfo? stackingInfo; + StackingInfo? stackingInfo; int length; num lastValue, value; CartesianChartPoint point; for (int i = 0; i < seriesRendererCollection.length; i++) { seriesRenderer = seriesRendererCollection[i]; - seriesRenderer._yAxisRenderer!._isStack100 = true; - if (seriesRenderer is _StackedSeriesRenderer && - seriesRenderer._series is _StackedSeriesBase) { - final _StackedSeriesBase stackedSeriesBase = - seriesRenderer._series as _StackedSeriesBase; - if (seriesRenderer._dataPoints.isNotEmpty) { - groupName = (seriesRenderer._seriesType == 'stackedarea100') + seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + seriesRendererDetails.yAxisDetails!.isStack100 = true; + if (seriesRenderer is StackedSeriesRenderer && + seriesRendererDetails.series is StackedSeriesBase) { + final StackedSeriesBase stackedSeriesBase = + seriesRendererDetails.series as StackedSeriesBase; + if (seriesRendererDetails.dataPoints.isNotEmpty == true) { + groupName = (seriesRendererDetails.seriesType == 'stackedarea100') ? 'stackedareagroup' : stackedSeriesBase.groupName; if (percentageValues == null) { - percentageValues = <_StackingInfo>[]; - stackingInfo = _StackingInfo(groupName, []); + percentageValues = []; + stackingInfo = StackingInfo(groupName, []); } - for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { - point = seriesRenderer._dataPoints[j]; + for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { + point = seriesRendererDetails.dataPoints[j]; value = point.y; if (percentageValues.isNotEmpty) { for (int k = 0; k < percentageValues.length; k++) { @@ -668,40 +774,41 @@ class _ChartSeries { stackingInfo = percentageValues[k]; break; } else if (k == percentageValues.length - 1) { - stackingInfo = _StackingInfo(groupName, []); + stackingInfo = StackingInfo(groupName, []); percentageValues.add(stackingInfo); } } } - if (stackingInfo?._stackingValues != null) { - length = stackingInfo!._stackingValues!.length; + if (stackingInfo?.stackingValues != null) { + length = stackingInfo!.stackingValues!.length; if (length == 0 || j > length - 1) { - stackingInfo._stackingValues!.add(0); + stackingInfo.stackingValues!.add(0); } } - if (seriesRenderer._seriesType.contains('stackedarea') || + if (seriesRendererDetails.seriesType.contains('stackedarea') == + true || value >= 0) { - lastValue = stackingInfo!._stackingValues![j]; - stackingInfo._stackingValues![j] = (lastValue + value).toDouble(); + lastValue = stackingInfo!.stackingValues![j]; + stackingInfo.stackingValues![j] = (lastValue + value).toDouble(); } else { - lastValue = stackingInfo!._stackingValues![j]; - stackingInfo._stackingValues![j] = (lastValue - value).toDouble(); + lastValue = stackingInfo!.stackingValues![j]; + stackingInfo.stackingValues![j] = (lastValue - value).toDouble(); } - if (j == seriesRenderer._dataPoints.length - 1) { + if (j == seriesRendererDetails.dataPoints.length - 1) { percentageValues.add(stackingInfo); } } } if (percentageValues != null) { for (int i = 0; i < percentageValues.length; i++) { - if (seriesRenderer._seriesType == 'stackedarea100') { - seriesRenderer._percentageValues = - percentageValues[i]._stackingValues!; + if (seriesRendererDetails.seriesType == 'stackedarea100') { + seriesRendererDetails.percentageValues = + percentageValues[i].stackingValues!; } else { if (stackedSeriesBase.groupName == percentageValues[i].groupName) { - seriesRenderer._percentageValues = - percentageValues[i]._stackingValues!; + seriesRendererDetails.percentageValues = + percentageValues[i].stackingValues!; } } } @@ -722,76 +829,23 @@ class _ChartSeries { /// To find and set the series type void _setSeriesType(CartesianSeriesRenderer seriesRenderer, int index) { - if (seriesRenderer._series.color == null) { - seriesRenderer._seriesColor = + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.series.color == null) { + seriesRendererDetails.seriesColor = chart.palette[paletteIndex % chart.palette.length]; paletteIndex++; } else { - seriesRenderer._seriesColor = seriesRenderer._series.color; - } - if (seriesRenderer is AreaSeriesRenderer) { - seriesRenderer._seriesType = 'area'; - } else if (seriesRenderer is BarSeriesRenderer) { - seriesRenderer._seriesType = 'bar'; - } else if (seriesRenderer is BubbleSeriesRenderer) { - seriesRenderer._seriesType = 'bubble'; - } else if (seriesRenderer is ColumnSeriesRenderer) { - seriesRenderer._seriesType = 'column'; - } else if (seriesRenderer is FastLineSeriesRenderer) { - seriesRenderer._seriesType = 'fastline'; - } else if (seriesRenderer is LineSeriesRenderer) { - seriesRenderer._seriesType = 'line'; - } else if (seriesRenderer is ScatterSeriesRenderer) { - seriesRenderer._seriesType = 'scatter'; - } else if (seriesRenderer is SplineSeriesRenderer) { - seriesRenderer._seriesType = 'spline'; - } else if (seriesRenderer is StepLineSeriesRenderer) { - seriesRenderer._seriesType = 'stepline'; - } else if (seriesRenderer is StackedColumnSeriesRenderer) { - seriesRenderer._seriesType = 'stackedcolumn'; - } else if (seriesRenderer is StackedBarSeriesRenderer) { - seriesRenderer._seriesType = 'stackedbar'; - } else if (seriesRenderer is StackedAreaSeriesRenderer) { - seriesRenderer._seriesType = 'stackedarea'; - } else if (seriesRenderer is StackedArea100SeriesRenderer) { - seriesRenderer._seriesType = 'stackedarea100'; - } else if (seriesRenderer is StackedLineSeriesRenderer) { - seriesRenderer._seriesType = 'stackedline'; - } else if (seriesRenderer is StackedLine100SeriesRenderer) { - seriesRenderer._seriesType = 'stackedline100'; - } else if (seriesRenderer is RangeColumnSeriesRenderer) { - seriesRenderer._seriesType = 'rangecolumn'; - } else if (seriesRenderer is RangeAreaSeriesRenderer) { - seriesRenderer._seriesType = 'rangearea'; - } else if (seriesRenderer is StackedColumn100SeriesRenderer) { - seriesRenderer._seriesType = 'stackedcolumn100'; - } else if (seriesRenderer is StackedBar100SeriesRenderer) { - seriesRenderer._seriesType = 'stackedbar100'; - } else if (seriesRenderer is SplineAreaSeriesRenderer) { - seriesRenderer._seriesType = 'splinearea'; - } else if (seriesRenderer is StepAreaSeriesRenderer) { - seriesRenderer._seriesType = 'steparea'; - } else if (seriesRenderer is HiloSeriesRenderer) { - seriesRenderer._seriesType = 'hilo'; - } else if (seriesRenderer is HiloOpenCloseSeriesRenderer) { - seriesRenderer._seriesType = 'hiloopenclose'; - } else if (seriesRenderer is CandleSeriesRenderer) { - seriesRenderer._seriesType = 'candle'; - } else if (seriesRenderer is HistogramSeriesRenderer) { - seriesRenderer._seriesType = 'histogram'; - } else if (seriesRenderer is SplineRangeAreaSeriesRenderer) { - seriesRenderer._seriesType = 'splinerangearea'; - } else if (seriesRenderer is BoxAndWhiskerSeriesRenderer) { - seriesRenderer._seriesType = 'boxandwhisker'; - } else if (seriesRenderer is WaterfallSeriesRenderer) { - seriesRenderer._seriesType = 'waterfall'; + seriesRendererDetails.seriesColor = seriesRendererDetails.series.color; } + seriesRendererDetails.seriesType = getSeriesType(seriesRenderer); + if (index == 0) { - _chartState._requireInvertedAxis = (!chart.isTransposed && - seriesRenderer._seriesType.toLowerCase().contains('bar')) || - (chart.isTransposed && - !seriesRenderer._seriesType.toLowerCase().contains('bar')); + final String seriesType = seriesRendererDetails.seriesType; + stateProperties.requireInvertedAxis = chart.isTransposed ^ + ((seriesType.toLowerCase().contains('bar')) && + (!seriesType.toLowerCase().contains('errorbar'))); } } @@ -809,9 +863,9 @@ class _ChartSeries { final TechnicalIndicators indicator = chart.indicators[i]; technicalIndicatorsRenderer = - _chartState._technicalIndicatorRenderer[i]; - _setIndicatorType(indicator, technicalIndicatorsRenderer); - textCollection.add(technicalIndicatorsRenderer._indicatorType); + stateProperties.technicalIndicatorRenderer[i]; + setIndicatorType(indicator, technicalIndicatorsRenderer); + textCollection.add(technicalIndicatorsRenderer.indicatorType); } //ignore: prefer_collection_literals _map = Map(); @@ -824,21 +878,21 @@ class _ChartSeries { for (int i = 0; i < chart.indicators.length; i++) { indicator = chart.indicators[i]; technicalIndicatorsRenderer = - _chartState._technicalIndicatorRenderer[i]; - technicalIndicatorsRenderer._dataPoints = + stateProperties.technicalIndicatorRenderer[i]; + technicalIndicatorsRenderer.dataPoints = >[]; - technicalIndicatorsRenderer._index = i; + technicalIndicatorsRenderer.index = i; if (!chart.legend.isVisible!) { final int count = indicatorTextCollection - .contains(technicalIndicatorsRenderer._indicatorType) - ? _getIndicatorId(indicatorTextCollection, - technicalIndicatorsRenderer._indicatorType) + .contains(technicalIndicatorsRenderer.indicatorType) + ? getIndicatorId(indicatorTextCollection, + technicalIndicatorsRenderer.indicatorType) : 0; indicatorTextCollection - .add(technicalIndicatorsRenderer._indicatorType); - technicalIndicatorsRenderer._name = indicator.name ?? - (technicalIndicatorsRenderer._indicatorType + - (_map[technicalIndicatorsRenderer._indicatorType] == 1 + .add(technicalIndicatorsRenderer.indicatorType); + technicalIndicatorsRenderer.name = indicator.name ?? + (technicalIndicatorsRenderer.indicatorType + + (_map[technicalIndicatorsRenderer.indicatorType] == 1 ? '' : ' ' + count.toString())); } @@ -847,9 +901,9 @@ class _ChartSeries { (indicator.dataSource != null || indicator.seriesName != null)) { if (indicator.dataSource != null && indicator.dataSource.isNotEmpty == true) { - existField = technicalIndicatorsRenderer._indicatorType == 'SMA' || - technicalIndicatorsRenderer._indicatorType == 'TMA' || - technicalIndicatorsRenderer._indicatorType == 'EMA'; + existField = technicalIndicatorsRenderer.indicatorType == 'SMA' || + technicalIndicatorsRenderer.indicatorType == 'TMA' || + technicalIndicatorsRenderer.indicatorType == 'EMA'; final String valueField = existField ? _getFieldType(indicator).toLowerCase() : ''; CartesianChartPoint currentPoint; @@ -859,26 +913,26 @@ class _ChartSeries { if (indicator.xValueMapper != null) { final dynamic xVal = indicator.xValueMapper(pointIndex); num? highValue, lowValue, openValue, closeValue, volumeValue; - technicalIndicatorsRenderer._dataPoints! + technicalIndicatorsRenderer.dataPoints! .add(CartesianChartPoint(xVal, null)); - currentPoint = technicalIndicatorsRenderer._dataPoints![ - technicalIndicatorsRenderer._dataPoints!.length - 1]; + currentPoint = technicalIndicatorsRenderer.dataPoints![ + technicalIndicatorsRenderer.dataPoints!.length - 1]; if (indicator.highValueMapper != null) { highValue = indicator.highValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints![ - technicalIndicatorsRenderer._dataPoints!.length - 1] + .dataPoints![ + technicalIndicatorsRenderer.dataPoints!.length - 1] .high = highValue; } if (indicator.lowValueMapper != null) { lowValue = indicator.lowValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints![ - technicalIndicatorsRenderer._dataPoints!.length - 1] + .dataPoints![ + technicalIndicatorsRenderer.dataPoints!.length - 1] .low = lowValue; } - ///changing high,low value + // changing high,low value if (currentPoint.high != null && currentPoint.low != null) { final num high = currentPoint.high; final num low = currentPoint.low; @@ -888,23 +942,23 @@ class _ChartSeries { if (indicator.openValueMapper != null) { openValue = indicator.openValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints![ - technicalIndicatorsRenderer._dataPoints!.length - 1] + .dataPoints![ + technicalIndicatorsRenderer.dataPoints!.length - 1] .open = openValue; } if (indicator.closeValueMapper != null) { closeValue = indicator.closeValueMapper(pointIndex); technicalIndicatorsRenderer - ._dataPoints![ - technicalIndicatorsRenderer._dataPoints!.length - 1] + .dataPoints![ + technicalIndicatorsRenderer.dataPoints!.length - 1] .close = closeValue; } if (indicator is AccumulationDistributionIndicator && indicator.volumeValueMapper != null) { volumeValue = indicator.volumeValueMapper!(pointIndex); technicalIndicatorsRenderer - ._dataPoints![ - technicalIndicatorsRenderer._dataPoints!.length - 1] + .dataPoints![ + technicalIndicatorsRenderer.dataPoints!.length - 1] .volume = volumeValue; } @@ -912,61 +966,65 @@ class _ChartSeries { (!existField || valueField == 'close')) || (highValue == null && (valueField == 'high' || - technicalIndicatorsRenderer._indicatorType == - 'AD' || - technicalIndicatorsRenderer._indicatorType == + technicalIndicatorsRenderer.indicatorType == 'AD' || + technicalIndicatorsRenderer.indicatorType == 'ATR' || - technicalIndicatorsRenderer._indicatorType == + technicalIndicatorsRenderer.indicatorType == 'RSI' || - technicalIndicatorsRenderer._indicatorType == + technicalIndicatorsRenderer.indicatorType == 'Stochastic')) || (lowValue == null && (valueField == 'low' || - technicalIndicatorsRenderer._indicatorType == - 'AD' || - technicalIndicatorsRenderer._indicatorType == + technicalIndicatorsRenderer.indicatorType == 'AD' || + technicalIndicatorsRenderer.indicatorType == 'ATR' || - technicalIndicatorsRenderer._indicatorType == + technicalIndicatorsRenderer.indicatorType == 'RSI' || - technicalIndicatorsRenderer._indicatorType == + technicalIndicatorsRenderer.indicatorType == 'Stochastic')) || (openValue == null && valueField == 'open') || (volumeValue == null && - technicalIndicatorsRenderer._indicatorType == 'AD')) { - technicalIndicatorsRenderer._dataPoints!.removeAt( - technicalIndicatorsRenderer._dataPoints!.length - 1); + technicalIndicatorsRenderer.indicatorType == 'AD')) { + technicalIndicatorsRenderer.dataPoints!.removeAt( + technicalIndicatorsRenderer.dataPoints!.length - 1); } } } } else if (indicator.seriesName != null) { CartesianSeriesRenderer? seriesRenderer; + for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { if (indicator.seriesName == - _chartState - ._chartSeries.visibleSeriesRenderers[i]._series.name) { + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]) + .series + .name) { seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; + stateProperties.chartSeries.visibleSeriesRenderers[i]; break; } } - technicalIndicatorsRenderer._dataPoints = (seriesRenderer != null && - (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType == 'boxandwhisker')) - ? seriesRenderer._dataPoints + final SeriesRendererDetails? seriesRendererDetails = + seriesRenderer != null + ? SeriesHelper.getSeriesRendererDetails(seriesRenderer) + : null; + technicalIndicatorsRenderer.dataPoints = (seriesRendererDetails != + null && + (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType == 'boxandwhisker')) + ? seriesRendererDetails.dataPoints : null; } - if (technicalIndicatorsRenderer._dataPoints != null && - technicalIndicatorsRenderer._dataPoints!.isNotEmpty) { - indicator._initSeriesCollection( - indicator, chart, technicalIndicatorsRenderer); - indicator._initDataSource( + if (technicalIndicatorsRenderer.dataPoints != null && + technicalIndicatorsRenderer.dataPoints!.isNotEmpty) { + technicalIndicatorsRenderer.initDataSource( indicator, technicalIndicatorsRenderer, chart); - if (technicalIndicatorsRenderer._renderPoints.isNotEmpty) { - _chartState._chartSeries.visibleSeriesRenderers - .addAll(technicalIndicatorsRenderer._targetSeriesRenderers); + if (technicalIndicatorsRenderer.renderPoints.isNotEmpty) { + stateProperties.chartSeries.visibleSeriesRenderers + .addAll(technicalIndicatorsRenderer.targetSeriesRenderers); } } } @@ -988,7 +1046,7 @@ class _ChartSeries { } /// To return the indicator id - int _getIndicatorId(List list, String str) { + int getIndicatorId(List list, String str) { int count = 0; for (int i = 0; i < list.length; i++) { if (list[i] == str) { @@ -999,47 +1057,48 @@ class _ChartSeries { } /// Setting indicator type - void _setIndicatorType(TechnicalIndicators indicator, + void setIndicatorType(TechnicalIndicators indicator, TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { if (indicator is AtrIndicator) { - technicalIndicatorsRenderer._indicatorType = 'ATR'; + technicalIndicatorsRenderer.indicatorType = 'ATR'; } else if (indicator is AccumulationDistributionIndicator) { - technicalIndicatorsRenderer._indicatorType = 'AD'; + technicalIndicatorsRenderer.indicatorType = 'AD'; } else if (indicator is BollingerBandIndicator) { - technicalIndicatorsRenderer._indicatorType = 'Bollinger'; + technicalIndicatorsRenderer.indicatorType = 'Bollinger'; } else if (indicator is EmaIndicator) { - technicalIndicatorsRenderer._indicatorType = 'EMA'; + technicalIndicatorsRenderer.indicatorType = 'EMA'; } else if (indicator is MacdIndicator) { - technicalIndicatorsRenderer._indicatorType = 'MACD'; + technicalIndicatorsRenderer.indicatorType = 'MACD'; } else if (indicator is MomentumIndicator) { - technicalIndicatorsRenderer._indicatorType = 'Momentum'; + technicalIndicatorsRenderer.indicatorType = 'Momentum'; } else if (indicator is RsiIndicator) { - technicalIndicatorsRenderer._indicatorType = 'RSI'; + technicalIndicatorsRenderer.indicatorType = 'RSI'; } else if (indicator is SmaIndicator) { - technicalIndicatorsRenderer._indicatorType = 'SMA'; + technicalIndicatorsRenderer.indicatorType = 'SMA'; } else if (indicator is StochasticIndicator) { - technicalIndicatorsRenderer._indicatorType = 'Stochastic'; + technicalIndicatorsRenderer.indicatorType = 'Stochastic'; } else if (indicator is TmaIndicator) { - technicalIndicatorsRenderer._indicatorType = 'TMA'; + technicalIndicatorsRenderer.indicatorType = 'TMA'; } } - //this function sorts the details available based on the x which is of datetime type - void _sortDateTimeCategoryDetails(CartesianSeriesRenderer seriesRenderer) { - final DateTimeCategoryAxisRenderer axisRenderer = - seriesRenderer._xAxisRenderer as DateTimeCategoryAxisRenderer; - seriesRenderer._dataPoints.sort((CartesianChartPoint point1, + /// This function sorts the details available based on the x which is of date time type + void _sortDateTimeCategoryDetails( + SeriesRendererDetails seriesRendererDetails) { + final DateTimeCategoryAxisDetails axisDetails = + seriesRendererDetails.xAxisDetails as DateTimeCategoryAxisDetails; + seriesRendererDetails.dataPoints.sort((CartesianChartPoint point1, CartesianChartPoint point2) { return point2.x.isAfter(point1.x) == true ? -1 : 1; }); - axisRenderer._labels.sort((String first, String second) { + axisDetails.labels.sort((String first, String second) { return int.parse(first) < int.parse(second) ? -1 : 1; }); - seriesRenderer._xValues?.sort(); + seriesRendererDetails.xValues?.sort(); for (final CartesianChartPoint point - in seriesRenderer._dataPoints) { - point.xValue = axisRenderer._labels - .indexOf(point.x.microsecondsSinceEpoch.toString()); + in seriesRendererDetails.dataPoints) { + point.xValue = + axisDetails.labels.indexOf(point.x.microsecondsSinceEpoch.toString()); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/chart_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/chart_behavior.dart index 4f8662cd1..746b96281 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/chart_behavior.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/chart_behavior.dart @@ -1,4 +1,6 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; /// Holds the gestures for chart. /// diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/selection_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/selection_behavior.dart index d2b06001e..bf7821b7d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/selection_behavior.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/selection_behavior.dart @@ -1,4 +1,8 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../circular_chart/renderer/common.dart'; +import '../chart_segment/chart_segment.dart'; /// Customizes the selection in chart. /// @@ -27,7 +31,7 @@ abstract class ChartSelectionBehavior { /// void onLongPress(double xPos, double yPos); - /// It gets the fill property of the seleted item in the series + /// It gets the fill property of the selected item in the series /// /// * paint - Paint value of the selected item. /// * seriesIndex - Series index of the selected point @@ -37,7 +41,7 @@ abstract class ChartSelectionBehavior { Paint getSelectedItemFill(Paint paint, int seriesIndex, int pointIndex, List selectedSegments); - /// It gets the fill property of the unseleted item in the series + /// It gets the fill property of the unselected item in the series /// /// * paint - Paint value of the unselected item. /// * seriesIndex - Series index of the unselected point @@ -47,7 +51,7 @@ abstract class ChartSelectionBehavior { Paint getUnselectedItemFill(Paint paint, int seriesIndex, int pointIndex, List unselectedSegments); - /// It gets the border property of the seleted item in the series + /// It gets the border property of the selected item in the series /// /// * paint - Paint value of the selected item. /// * seriesIndex - Series index of the selected point @@ -57,7 +61,7 @@ abstract class ChartSelectionBehavior { Paint getSelectedItemBorder(Paint paint, int seriesIndex, int pointIndex, List selectedSegments); - /// It gets the border property of the seleted item in the series + /// It gets the border property of the selected item in the series /// /// * paint - Paint value of the unselected item. /// * seriesIndex - Series index of the unselected point @@ -67,17 +71,17 @@ abstract class ChartSelectionBehavior { Paint getUnselectedItemBorder(Paint paint, int seriesIndex, int pointIndex, List unselectedSegments); - /// It gets the fill property of the seleted item in the series + /// It gets the fill property of the selected item in the series /// /// * color - Color value of the selected item. /// * seriesIndex - Series index of the selected point /// * pointIndex - Point index of the selected point in the series. - /// * selectedregion - Value of Selected region in the series. + /// * selected region - Value of Selected region in the series. /// Color getCircularSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List<_Region> selectedRegions); + int pointIndex, List selectedRegions); - /// It gets the circular fill property of the unseleted item in the series + /// It gets the circular fill property of the unselected item in the series /// /// * color - Color value of the unselected item. /// * seriesIndex - Series index of the unselected point @@ -85,7 +89,7 @@ abstract class ChartSelectionBehavior { /// * unselectedRegion - Value of unSelected region in the series. /// Color getCircularUnSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List<_Region> unselectedRegions); + int pointIndex, List unselectedRegions); /// It gets the circular border property of the seleted item in the series /// @@ -95,9 +99,9 @@ abstract class ChartSelectionBehavior { /// * selectedRegion - Value of Selected region of the series. /// Color getCircularSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List<_Region> selectedRegions); + int pointIndex, List selectedRegions); - /// It gets the circular border property of the unseleted item in the series + /// It gets the circular border property of the unselected item in the series /// /// * color - Color value of the unselected item. /// * seriesIndex - Series index of the unselected point @@ -105,5 +109,5 @@ abstract class ChartSelectionBehavior { /// * unselectedRegion - Value of UnSelected region in the series. /// Color getCircularUnSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List<_Region> unselectedRegions); + int pointIndex, List unselectedRegions); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/zoom_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/zoom_behavior.dart index bd5e034e9..4b5465dd3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/zoom_behavior.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_behavior/zoom_behavior.dart @@ -1,8 +1,11 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../axis/axis.dart'; /// Holds the zooming gestures. /// -/// You can zoom in and zoom out using Zoombehavior. It can be used to customize the +/// You can zoom in and zoom out using Zoom behavior. It can be used to customize the /// DoubleTap zooming, selection zooming and zoomPinching. abstract class ZoomBehavior { ///Hits while double tapping on the chart. @@ -13,7 +16,7 @@ abstract class ZoomBehavior { ///Hits while double tapping on the chart. ///* xPos - X value of the double tap position. ///* yPos - Y value of the double tap position. - ///* zoomFactor - zoomin and zoom out position + ///* zoomFactor - zoom in and zoom out position void onDoubleTap(double xPos, double yPos, double zoomFactor); /// To paint in the chart plot area. @@ -23,8 +26,8 @@ abstract class ZoomBehavior { /// Chart can be zoomed by a rectangular selecting region on the plot area. /// - /// * startX - start position of selectionzooming in X axis. - /// * startY - start position of selectionzooming in Y axis. + /// * startX - start position of selection zooming in X axis. + /// * startY - start position of selection zooming in Y axis. /// * currentX - end position of the selection zooming in X axis. /// * currentY - end position of the selection zooming in Y axis. void onDrawSelectionZoomRect( @@ -54,6 +57,6 @@ abstract class ZoomBehavior { /// ///* position - which position have to zoom in the axis. /// scaleFacator - scale factor is a number which scales some quantity in charts. - void onPinch( - ChartAxisRenderer axisRenderer, double position, double scaleFactor); + void onPinch(ChartAxisRendererDetails axisDetails, double position, + double scaleFactor); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart index bf095f296..5b27c02e1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/area_segment.dart @@ -1,4 +1,13 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/series.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for area series. /// @@ -8,55 +17,62 @@ part of charts; /// It gets the path, stroke color and fill color from the `series` to render the segment. /// class AreaSegment extends ChartSegment { - late Path _path; - Path? _strokePath; - Rect? _pathRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); fillPaint = Paint(); - if (_series.gradient == null) { - if (_color != null) { - fillPaint!.color = _color!; + if (_segmentProperties.series.gradient == null) { + if (_segmentProperties.color != null) { + fillPaint!.color = _segmentProperties.color!; fillPaint!.style = PaintingStyle.fill; } } else { - fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient!, _pathRect!, - _seriesRenderer._chartState!._requireInvertedAxis) + fillPaint = (_segmentProperties.pathRect != null) + ? getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.pathRect!, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis) : fillPaint; } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the area series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the area series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); final Paint _strokePaint = Paint(); _strokePaint ..style = PaintingStyle.stroke - ..strokeWidth = _series.borderWidth; - if (_series.borderGradient != null) { - _strokePaint.shader = - _series.borderGradient!.createShader(_strokePath!.getBounds()); - } else if (_strokeColor != null) { - _strokePaint.color = _series.borderColor; + ..strokeWidth = _segmentProperties.series.borderWidth; + if (_segmentProperties.series.borderGradient != null) { + _strokePaint.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.strokePath!.getBounds()); + } else if (_segmentProperties.strokeColor != null) { + _strokePaint.color = _segmentProperties.series.borderColor; } - _series.borderWidth == 0 + _segmentProperties.series.borderWidth == 0 ? _strokePaint.color = Colors.transparent : _strokePaint.color; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + _segmentProperties.defaultStrokeColor = _strokePaint; return _strokePaint; } @@ -67,11 +83,25 @@ class AreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _pathRect = _path.getBounds(); + _setSegmentProperties(); + _segmentProperties.pathRect = _segmentProperties.path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); - if (strokePaint!.color != Colors.transparent && _strokePath != null) { - _drawDashedLine(canvas, _series.dashArray, strokePaint!, _strokePath!); + _segmentProperties.path, + (_segmentProperties.series.gradient == null) + ? fillPaint! + : getFillPaint()); + if (strokePaint!.color != Colors.transparent && + _segmentProperties.strokePath != null) { + drawDashedLine(canvas, _segmentProperties.series.dashArray, strokePaint!, + _segmentProperties.strokePath!); + } + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart index a14aefd68..7d5f39e9c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bar_segment.dart @@ -1,4 +1,14 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import '../chart_series/bar_series.dart'; +import '../chart_series/series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for bar series. /// @@ -8,88 +18,67 @@ part of charts; /// It gets the path, stroke color and fill color from the `series` to render the bar segment. /// class BarSegment extends ChartSegment { - late RRect _trackBarRect; - - Paint? _trackerFillPaint, _trackerStrokePaint; - late Path _path; - /// Rectangle of the segment this could be used to render the segment while overriding this segment late RRect segmentRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { - if (_series.gradient == null) { + _setSegmentProperties(); + if (_segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!) + ..color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.color + : (_segmentProperties.currentPoint!.pointColorMapper ?? + _segmentProperties.color!) ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.currentPoint!.region!, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the the bar series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the the bar series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - (_series.borderGradient != null) - ? strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!) - : strokePaint!.color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _strokeColor!; - _series.borderWidth == 0 + ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderWidth + : _segmentProperties.strokeWidth!; + (_segmentProperties.series.borderGradient != null) + ? strokePaint!.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.currentPoint!.region!) + : strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderColor + : _segmentProperties.strokeColor!; + _segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; return strokePaint!; } - /// Method to get series tracker fill. - Paint _getTrackerFillPaint() { - if (_series is BarSeries) { - final BarSeries barSeries = - _series as BarSeries; - _trackerFillPaint = Paint() - ..color = barSeries.trackColor - ..style = PaintingStyle.fill; - } - return _trackerFillPaint!; - } - - /// Method to get series tracker stroke color. - Paint _getTrackerStrokePaint() { - final BarSeries barSeries = - _series as BarSeries; - _trackerStrokePaint = Paint() - ..color = barSeries.trackBorderColor - ..strokeWidth = barSeries.trackBorderWidth - ..style = PaintingStyle.stroke; - barSeries.trackBorderWidth == 0 - ? _trackerStrokePaint!.color = Colors.transparent - : _trackerStrokePaint!.color; - return _trackerStrokePaint!; - } - /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() {} @@ -97,39 +86,54 @@ class BarSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); final BarSeries barSeries = - _series as BarSeries; - if (_trackerFillPaint != null && barSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackBarRect, _trackerFillPaint!); + _segmentProperties.series as BarSeries; + if (_segmentProperties.trackerFillPaint != null && + barSeries.isTrackVisible) { + _drawSegmentRect(canvas, _segmentProperties.trackBarRect, + _segmentProperties.trackerFillPaint!); } - if (_trackerStrokePaint != null && barSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackBarRect, _trackerStrokePaint!); + if (_segmentProperties.trackerStrokePaint != null && + barSeries.isTrackVisible) { + _drawSegmentRect(canvas, _segmentProperties.trackBarRect, + _segmentProperties.trackerStrokePaint!); } if (fillPaint != null) { _drawSegmentRect(canvas, segmentRect, fillPaint!); } if (strokePaint != null) { - (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path) + (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0) + ? drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, _segmentProperties.path) : _drawSegmentRect(canvas, segmentRect, strokePaint!); } } /// To draw Segment rect of bar segment void _drawSegmentRect(Canvas canvas, RRect segmentRect, Paint paint) { - (_series.animationDuration > 0) - ? _animateRectSeries( + (_segmentProperties.series.animationDuration > 0 == true) + ? animateRectSeries( canvas, - _seriesRenderer, + _segmentProperties.seriesRenderer, paint, segmentRect, - _currentPoint!.yValue, + _segmentProperties.currentPoint!.yValue, animationFactor, - _oldPoint?.region ?? _oldRegion, - _oldPoint?.yValue, - _oldSeriesVisible) + _segmentProperties.oldPoint?.region ?? _segmentProperties.oldRegion, + _segmentProperties.oldPoint?.yValue, + _segmentProperties.oldSeriesVisible) : canvas.drawRRect(segmentRect, paint); } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart index fffc7a8cf..329af2545 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/box_and_whisker_segment.dart @@ -1,4 +1,18 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; + +import '../chart_series/box_and_whisker_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for box and whisker series. /// @@ -8,155 +22,183 @@ part of charts; /// Gets the path and fill color from the `series` to render the box and whisker segment. /// class BoxAndWhiskerSegment extends ChartSegment { - late double _x, - _min, - _max, - _maxY, - _lowerX, + late double _maxY, _lowerY, - _upperX, _upperY, _centerMax, _centerMin, _minY, _centersY, - _topRectY, _topLineY, - _bottomRectY, _bottomLineY, _medianX, _medianY; - late Path _path; + late Paint _meanPaint; - // ignore: unused_field - Color? _pointColorMapper; late bool _isTransposed; - late _ChartLocation _minPoint, _maxPoint, _centerMinPoint, _centerMaxPoint; + late ChartLocation _centerMinPoint, _centerMaxPoint; late BoxAndWhiskerSeries _boxAndWhiskerSeries; + late SegmentProperties _segmentProperties; + + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); + /// Get and set the paint options for box and whisker series. - if (_series.gradient == null) { + if (_segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!) + ..color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.color + : (_segmentProperties.currentPoint!.pointColorMapper ?? + _segmentProperties.color!) ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.currentPoint!.region!, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the box plot series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the box plot series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; + ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderWidth + : _segmentProperties.strokeWidth!; _meanPaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - if (_series.borderGradient != null) { - strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!); - _meanPaint.shader = - _series.borderGradient!.createShader(_currentPoint!.region!); + ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderWidth + : _segmentProperties.strokeWidth!; + if (_segmentProperties.series.borderGradient != null) { + strokePaint!.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.currentPoint!.region!); + _meanPaint.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.currentPoint!.region!); } else { - strokePaint!.color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _strokeColor!; - _meanPaint.color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _strokeColor!; + strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderColor + : _segmentProperties.strokeColor!; + _meanPaint.color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderColor + : _segmentProperties.strokeColor!; } - _series.borderWidth == 0 + _segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; return strokePaint!; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _boxAndWhiskerSeries = _series as BoxAndWhiskerSeries; - _x = _max = double.nan; - _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; - _minPoint = _currentPoint!.minimumPoint!; - _maxPoint = _currentPoint!.maximumPoint!; - _centerMinPoint = _currentPoint!.centerMinimumPoint!; - _centerMaxPoint = _currentPoint!.centerMaximumPoint!; - _x = _minPoint.x; - _min = _minPoint.y; - _max = _maxPoint.y; + _setSegmentProperties(); + _boxAndWhiskerSeries = + _segmentProperties.series as BoxAndWhiskerSeries; + _segmentProperties.x = _segmentProperties.max = double.nan; + _isTransposed = + SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis; + _segmentProperties.minPoint = + _segmentProperties.currentPoint!.minimumPoint!; + _segmentProperties.maxPoint = + _segmentProperties.currentPoint!.maximumPoint!; + _centerMinPoint = _segmentProperties.currentPoint!.centerMinimumPoint!; + _centerMaxPoint = _segmentProperties.currentPoint!.centerMaximumPoint!; + _segmentProperties.x = _segmentProperties.minPoint.x; + _segmentProperties.min = _segmentProperties.minPoint.y; + _segmentProperties.max = _segmentProperties.maxPoint.y; _centerMax = _centerMaxPoint.x; _maxY = _centerMaxPoint.y; _centerMin = _centerMinPoint.x; _minY = _centerMinPoint.y; - _lowerX = _currentPoint!.lowerQuartilePoint!.x; - _lowerY = _currentPoint!.lowerQuartilePoint!.y; - _upperX = _currentPoint!.upperQuartilePoint!.x; - _upperY = _currentPoint!.upperQuartilePoint!.y; - _medianX = _currentPoint!.medianPoint!.x; - _medianY = _currentPoint!.medianPoint!.y; + _segmentProperties.lowerX = + _segmentProperties.currentPoint!.lowerQuartilePoint!.x; + _lowerY = _segmentProperties.currentPoint!.lowerQuartilePoint!.y; + _segmentProperties.upperX = + _segmentProperties.currentPoint!.upperQuartilePoint!.x; + _upperY = _segmentProperties.currentPoint!.upperQuartilePoint!.y; + _medianX = _segmentProperties.currentPoint!.medianPoint!.x; + _medianY = _segmentProperties.currentPoint!.medianPoint!.y; _centersY = (_lowerY > _upperY) ? (_upperY + ((_upperY - _lowerY).abs() / 2)) : (_lowerY + ((_lowerY - _upperY).abs() / 2)); - _topRectY = _centersY - ((_centersY - _upperY).abs() * 1); - _bottomRectY = _centersY + ((_centersY - _lowerY).abs() * 1); + _segmentProperties.topRectY = _centersY - ((_centersY - _upperY).abs() * 1); + _segmentProperties.bottomRectY = + _centersY + ((_centersY - _lowerY).abs() * 1); } /// To draw rect path of box and whisker segments void _drawRectPath() { - _path.moveTo(!_isTransposed ? _lowerX : _topRectY, - !_isTransposed ? _topRectY : _upperY); - _path.lineTo(!_isTransposed ? _upperX : _topRectY, - !_isTransposed ? _topRectY : _lowerY); - _path.lineTo(!_isTransposed ? _upperX : _bottomRectY, - !_isTransposed ? _bottomRectY : _lowerY); - _path.lineTo(!_isTransposed ? _lowerX : _bottomRectY, - !_isTransposed ? _bottomRectY : _upperY); - _path.lineTo(!_isTransposed ? _lowerX : _topRectY, - !_isTransposed ? _topRectY : _upperY); - _path.close(); + _segmentProperties.path.moveTo( + !_isTransposed + ? _segmentProperties.lowerX + : _segmentProperties.topRectY, + !_isTransposed ? _segmentProperties.topRectY : _upperY); + _segmentProperties.path.lineTo( + !_isTransposed + ? _segmentProperties.upperX + : _segmentProperties.topRectY, + !_isTransposed ? _segmentProperties.topRectY : _lowerY); + _segmentProperties.path.lineTo( + !_isTransposed + ? _segmentProperties.upperX + : _segmentProperties.bottomRectY, + !_isTransposed ? _segmentProperties.bottomRectY : _lowerY); + _segmentProperties.path.lineTo( + !_isTransposed + ? _segmentProperties.lowerX + : _segmentProperties.bottomRectY, + !_isTransposed ? _segmentProperties.bottomRectY : _upperY); + _segmentProperties.path.lineTo( + !_isTransposed + ? _segmentProperties.lowerX + : _segmentProperties.topRectY, + !_isTransposed ? _segmentProperties.topRectY : _upperY); + _segmentProperties.path.close(); } /// To draw line path of box and whisker segments void _drawLine(Canvas canvas) { - canvas.drawLine( - Offset(_lowerX, _topLineY), Offset(_upperX, _topLineY), strokePaint!); - canvas.drawLine(Offset(_centerMax, _topRectY), + canvas.drawLine(Offset(_segmentProperties.lowerX, _topLineY), + Offset(_segmentProperties.upperX, _topLineY), strokePaint!); + canvas.drawLine(Offset(_centerMax, _segmentProperties.topRectY), Offset(_centerMax, _topLineY), strokePaint!); - canvas.drawLine( - Offset(_lowerX, _medianY), Offset(_upperX, _medianY), strokePaint!); - canvas.drawLine(Offset(_centerMax, _bottomRectY), + canvas.drawLine(Offset(_segmentProperties.lowerX, _medianY), + Offset(_segmentProperties.upperX, _medianY), strokePaint!); + canvas.drawLine(Offset(_centerMax, _segmentProperties.bottomRectY), Offset(_centerMax, _bottomLineY), strokePaint!); - canvas.drawLine(Offset(_lowerX, _bottomLineY), - Offset(_upperX, _bottomLineY), strokePaint!); + canvas.drawLine(Offset(_segmentProperties.lowerX, _bottomLineY), + Offset(_segmentProperties.upperX, _bottomLineY), strokePaint!); } /// To draw mean line path of box and whisker segments @@ -164,11 +206,15 @@ class BoxAndWhiskerSegment extends ChartSegment { Canvas canvas, Offset position, Size size, bool isTransposed) { final double x = !isTransposed ? position.dx : position.dy; final double y = !isTransposed ? position.dy : position.dx; - if (_series.animationDuration <= 0 || - animationFactor >= _seriesRenderer._chartState!._seriesDurationFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); + if (_segmentProperties.series.animationDuration <= 0 == true || + animationFactor >= + seriesRendererDetails.stateProperties.seriesDurationFactor) { /// `0.15` is animation duration of mean point value, as like marker. final double opacity = (animationFactor - - _seriesRenderer._chartState!._seriesDurationFactor) * + seriesRendererDetails.stateProperties.seriesDurationFactor) * (1 / 0.15); _meanPaint.color = Color.fromRGBO(_meanPaint.color.red, _meanPaint.color.green, _meanPaint.color.blue, opacity); @@ -181,22 +227,26 @@ class BoxAndWhiskerSegment extends ChartSegment { /// To draw line path of box and whisker segments void _drawFillLine(Canvas canvas) { - final bool isOpen = - _currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!; + final bool isOpen = _segmentProperties.currentPoint!.lowerQuartile! > + _segmentProperties.currentPoint!.upperQuartile!; canvas.drawLine( - Offset(_topRectY, _maxY), + Offset(_segmentProperties.topRectY, _maxY), Offset( - _topRectY + - ((isOpen ? (_lowerX - _centerMax) : (_upperX - _centerMax)) + _segmentProperties.topRectY + + ((isOpen + ? (_segmentProperties.lowerX - _centerMax) + : (_segmentProperties.upperX - _centerMax)) .abs() * animationFactor), _maxY), strokePaint!); canvas.drawLine( - Offset(_bottomRectY, _maxY), + Offset(_segmentProperties.bottomRectY, _maxY), Offset( - _bottomRectY - - ((isOpen ? (_upperX - _centerMin) : (_lowerX - _centerMin)) + _segmentProperties.bottomRectY - + ((isOpen + ? (_segmentProperties.upperX - _centerMin) + : (_segmentProperties.lowerX - _centerMin)) .abs() * animationFactor), _maxY), @@ -206,101 +256,132 @@ class BoxAndWhiskerSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - if (fillPaint != null && _seriesRenderer._reAnimate || - (!(_seriesRenderer._renderingDetails!.widgetNeedUpdate && - _oldSeriesRenderer != null && - !_seriesRenderer._renderingDetails!.isLegendToggled))) { - _path = Path(); + _setSegmentProperties(); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); + if (fillPaint != null && seriesRendererDetails.reAnimate == true || + (!(seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + _segmentProperties.oldSeriesRenderer != null && + seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false))) { + _segmentProperties.path = Path(); if (!_isTransposed && - _currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!) { + _segmentProperties.currentPoint!.lowerQuartile! > + _segmentProperties.currentPoint!.upperQuartile! == + true) { final double temp = _upperY; _upperY = _lowerY; _lowerY = temp; } - if (_seriesRenderer._renderingDetails!.isLegendToggled) { + if (seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + true) { animationFactor = 1; } if (_lowerY > _upperY) { _centersY = _upperY + ((_upperY - _lowerY).abs() / 2); - _topRectY = _centersY - ((_centersY - _upperY).abs() * animationFactor); - _bottomRectY = + _segmentProperties.topRectY = + _centersY - ((_centersY - _upperY).abs() * animationFactor); + _segmentProperties.bottomRectY = _centersY + ((_centersY - _lowerY).abs() * animationFactor); } else { _centersY = _lowerY + ((_lowerY - _upperY).abs() / 2); - _topRectY = _centersY - ((_centersY - _upperY).abs() * animationFactor); - _bottomRectY = + _segmentProperties.topRectY = + _centersY - ((_centersY - _upperY).abs() * animationFactor); + _segmentProperties.bottomRectY = _centersY + ((_centersY - _lowerY).abs() * animationFactor); } - _topLineY = _topRectY - ((_topRectY - _maxY).abs() * animationFactor); - _bottomLineY = - _bottomRectY + ((_bottomRectY - _minY).abs() * animationFactor); + _topLineY = _segmentProperties.topRectY - + ((_segmentProperties.topRectY - _maxY).abs() * animationFactor); + _bottomLineY = _segmentProperties.bottomRectY + + ((_segmentProperties.bottomRectY - _minY).abs() * animationFactor); _bottomLineY = _minY < _lowerY - ? _bottomRectY + ((_upperY - _maxY).abs() * animationFactor) + ? _segmentProperties.bottomRectY + + ((_upperY - _maxY).abs() * animationFactor) : _bottomLineY; _topLineY = _maxY > _upperY - ? _topRectY - ((_lowerY - _minY).abs() * animationFactor) + ? _segmentProperties.topRectY - + ((_lowerY - _minY).abs() * animationFactor) : _topLineY; if (_isTransposed) { - if (_lowerX > _upperX) { - _centersY = _upperX + ((_lowerX - _upperX).abs() / 2); - _topRectY = - _centersY + ((_centersY - _lowerX).abs() * animationFactor); - _bottomRectY = - _centersY - ((_centersY - _upperX).abs() * animationFactor); + if (_segmentProperties.lowerX > _segmentProperties.upperX == true) { + _centersY = _segmentProperties.upperX + + ((_segmentProperties.lowerX - _segmentProperties.upperX).abs() / + 2); + _segmentProperties.topRectY = _centersY + + ((_centersY - _segmentProperties.lowerX).abs() * animationFactor); + _segmentProperties.bottomRectY = _centersY - + ((_centersY - _segmentProperties.upperX).abs() * animationFactor); } else { - _centersY = _lowerX + (_upperX - _lowerX).abs() / 2; - _topRectY = - _centersY + ((_centersY - _upperX).abs() * animationFactor); - _bottomRectY = - _centersY - ((_centersY - _lowerX).abs() * animationFactor); + _centersY = _segmentProperties.lowerX + + (_segmentProperties.upperX - _segmentProperties.lowerX).abs() / 2; + _segmentProperties.topRectY = _centersY + + ((_centersY - _segmentProperties.upperX).abs() * animationFactor); + _segmentProperties.bottomRectY = _centersY - + ((_centersY - _segmentProperties.lowerX).abs() * animationFactor); } - _path.moveTo(_centerMax, _upperY); - _path.lineTo(_centerMax, _lowerY); - if (_centerMax < _upperX) { - _path.moveTo(_bottomRectY, _maxY); - _path.lineTo( - _topRectY - ((_lowerX - _centerMax).abs() * animationFactor), + _segmentProperties.path.moveTo(_centerMax, _upperY); + _segmentProperties.path.lineTo(_centerMax, _lowerY); + if (_centerMax < _segmentProperties.upperX) { + _segmentProperties.path.moveTo(_segmentProperties.bottomRectY, _maxY); + _segmentProperties.path.lineTo( + _segmentProperties.topRectY - + ((_segmentProperties.lowerX - _centerMax).abs() * + animationFactor), _maxY); } else { - _path.moveTo(_topRectY, _maxY); - _path.lineTo( - _topRectY + ((_upperX - _centerMax).abs() * animationFactor), + _segmentProperties.path.moveTo(_segmentProperties.topRectY, _maxY); + _segmentProperties.path.lineTo( + _segmentProperties.topRectY + + ((_segmentProperties.upperX - _centerMax).abs() * + animationFactor), _maxY); } - _path.moveTo(_medianX, _upperY); - _path.lineTo(_medianX, _lowerY); - if (_centerMin > _lowerX) { - _path.moveTo(_topRectY, _maxY); - _path.lineTo( - _bottomRectY + ((_upperX - _centerMin).abs() * animationFactor), + _segmentProperties.path.moveTo(_medianX, _upperY); + _segmentProperties.path.lineTo(_medianX, _lowerY); + if (_centerMin > _segmentProperties.lowerX) { + _segmentProperties.path.moveTo(_segmentProperties.topRectY, _maxY); + _segmentProperties.path.lineTo( + _segmentProperties.bottomRectY + + ((_segmentProperties.upperX - _centerMin).abs() * + animationFactor), _maxY); } else { - _path.moveTo(_bottomRectY, _maxY); - _path.lineTo( - _bottomRectY - ((_lowerX - _centerMin).abs() * animationFactor), + _segmentProperties.path.moveTo(_segmentProperties.bottomRectY, _maxY); + _segmentProperties.path.lineTo( + _segmentProperties.bottomRectY - + ((_segmentProperties.lowerX - _centerMin).abs() * + animationFactor), _maxY); } - _path.moveTo(_centerMin, _upperY); - _path.lineTo(_centerMin, _lowerY); + _segmentProperties.path.moveTo(_centerMin, _upperY); + _segmentProperties.path.lineTo(_centerMin, _lowerY); if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint!.centerMeanPoint!.y, - _currentPoint!.centerMeanPoint!.x), - Size(_series.markerSettings.width, _series.markerSettings.height), + Offset(_segmentProperties.currentPoint!.centerMeanPoint!.y, + _segmentProperties.currentPoint!.centerMeanPoint!.x), + Size(_segmentProperties.series.markerSettings.width, + _segmentProperties.series.markerSettings.height), _isTransposed); } - _lowerX == _upperX - ? canvas.drawLine( - Offset(_lowerX, _lowerY), Offset(_upperX, _upperY), fillPaint!) + _segmentProperties.lowerX == _segmentProperties.upperX + ? canvas.drawLine(Offset(_segmentProperties.lowerX, _lowerY), + Offset(_segmentProperties.upperX, _upperY), fillPaint!) : _drawRectPath(); } else { - if (_currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!) { + if (_segmentProperties.currentPoint!.lowerQuartile! > + _segmentProperties.currentPoint!.upperQuartile! == + true) { final double temp = _upperY; _upperY = _lowerY; _lowerY = temp; @@ -309,38 +390,42 @@ class BoxAndWhiskerSegment extends ChartSegment { if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint!.centerMeanPoint!.x, - _currentPoint!.centerMeanPoint!.y), - Size(_series.markerSettings.width, _series.markerSettings.height), + Offset(_segmentProperties.currentPoint!.centerMeanPoint!.x, + _segmentProperties.currentPoint!.centerMeanPoint!.y), + Size(_segmentProperties.series.markerSettings.width, + _segmentProperties.series.markerSettings.height), _isTransposed); } _lowerY == _upperY - ? canvas.drawLine( - Offset(_lowerX, _lowerY), Offset(_upperX, _upperY), fillPaint!) + ? canvas.drawLine(Offset(_segmentProperties.lowerX, _lowerY), + Offset(_segmentProperties.upperX, _upperY), fillPaint!) : _drawRectPath(); } - if (_series.dashArray[0] != 0 && - _series.dashArray[1] != 0 && - _series.animationDuration <= 0) { - canvas.drawPath(_path, fillPaint!); - _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); + if (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0 && + _segmentProperties.series.animationDuration <= 0 == true) { + canvas.drawPath(_segmentProperties.path, fillPaint!); + drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, _segmentProperties.path); } else { - canvas.drawPath(_path, fillPaint!); - canvas.drawPath(_path, strokePaint!); + canvas.drawPath(_segmentProperties.path, fillPaint!); + canvas.drawPath(_segmentProperties.path, strokePaint!); } if (fillPaint!.style == PaintingStyle.fill) { if (_isTransposed) { - if (_currentPoint!.lowerQuartile! > _currentPoint!.upperQuartile!) { + if (_segmentProperties.currentPoint!.lowerQuartile! > + _segmentProperties.currentPoint!.upperQuartile! == + true) { _drawFillLine(canvas); } if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint!.centerMeanPoint!.y, - _currentPoint!.centerMeanPoint!.x), - Size(_series.markerSettings.width, - _series.markerSettings.height), + Offset(_segmentProperties.currentPoint!.centerMeanPoint!.y, + _segmentProperties.currentPoint!.centerMeanPoint!.x), + Size(_segmentProperties.series.markerSettings.width, + _segmentProperties.series.markerSettings.height), _isTransposed); } } else { @@ -348,50 +433,64 @@ class BoxAndWhiskerSegment extends ChartSegment { if (_boxAndWhiskerSeries.showMean) { _drawMeanLine( canvas, - Offset(_currentPoint!.centerMeanPoint!.x, - _currentPoint!.centerMeanPoint!.y), - Size(_series.markerSettings.width, - _series.markerSettings.height), + Offset(_segmentProperties.currentPoint!.centerMeanPoint!.x, + _segmentProperties.currentPoint!.centerMeanPoint!.y), + Size(_segmentProperties.series.markerSettings.width, + _segmentProperties.series.markerSettings.height), _isTransposed); } } } - } else if (!_seriesRenderer._renderingDetails!.isLegendToggled) { - final BoxAndWhiskerSegment currentSegment = _seriesRenderer - ._segments[currentSegmentIndex!] as BoxAndWhiskerSegment; - final BoxAndWhiskerSegment? oldSegment = (currentSegment - ._oldSeriesRenderer!._segments.isNotEmpty && - currentSegment._oldSeriesRenderer!._segments[0] - is BoxAndWhiskerSegment && - currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex!) - ? currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] - as BoxAndWhiskerSegment? - : null; - _animateBoxSeries( + } else if (seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false) { + final BoxAndWhiskerSegment currentSegment = seriesRendererDetails + .segments[currentSegmentIndex!] as BoxAndWhiskerSegment; + final SegmentProperties chartSegmentPropeties = + SegmentHelper.getSegmentProperties(currentSegment); + final SeriesRendererDetails oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + chartSegmentPropeties.oldSeriesRenderer!); + final BoxAndWhiskerSegment? oldSegment = + (oldSeriesRendererDetails.segments.isNotEmpty == true && + oldSeriesRendererDetails.segments[0] + is BoxAndWhiskerSegment && + oldSeriesRendererDetails.segments.length - 1 >= + currentSegmentIndex! == + true) + ? oldSeriesRendererDetails.segments[currentSegmentIndex!] + as BoxAndWhiskerSegment? + : null; + SegmentProperties? oldSegmentProperties; + if (oldSegment != null) { + oldSegmentProperties = SegmentHelper.getSegmentProperties(oldSegment); + } + + animateBoxSeries( _boxAndWhiskerSeries.showMean, - Offset(_currentPoint!.centerMeanPoint!.x, - _currentPoint!.centerMeanPoint!.y), - Offset(_currentPoint!.centerMeanPoint!.y, - _currentPoint!.centerMeanPoint!.x), - Size(_series.markerSettings.width, _series.markerSettings.height), - _max, + Offset(_segmentProperties.currentPoint!.centerMeanPoint!.x, + _segmentProperties.currentPoint!.centerMeanPoint!.y), + Offset(_segmentProperties.currentPoint!.centerMeanPoint!.y, + _segmentProperties.currentPoint!.centerMeanPoint!.x), + Size(_segmentProperties.series.markerSettings.width, + _segmentProperties.series.markerSettings.height), + _segmentProperties.max, _isTransposed, - _currentPoint!.lowerQuartile!, - _currentPoint!.upperQuartile!, + _segmentProperties.currentPoint!.lowerQuartile!, + _segmentProperties.currentPoint!.upperQuartile!, _minY, _maxY, oldSegment?._minY, oldSegment?._maxY, - _lowerX, + _segmentProperties.lowerX, _lowerY, - _upperX, + _segmentProperties.upperX, _upperY, _centerMin, _centerMax, - oldSegment?._lowerX, + oldSegmentProperties?.lowerX, oldSegment?._lowerY, - oldSegment?._upperX, + oldSegmentProperties?.upperX, oldSegment?._upperY, oldSegment?._centerMin, oldSegment?._centerMax, @@ -401,7 +500,16 @@ class BoxAndWhiskerSegment extends ChartSegment { fillPaint!, strokePaint!, canvas, - _seriesRenderer); + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer)); + } + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart index 19b1a1034..4067b1550 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/bubble_segment.dart @@ -1,4 +1,16 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../series_painter/bubble_painter.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for bubble series. /// @@ -12,143 +24,155 @@ class BubbleSegment extends ChartSegment { ///Center position of the bubble and size late double _centerX, _centerY, _radius, _size; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { - final bool hasPointColor = _series.pointColorMapper != null; - if (_series.gradient == null) { - if (_color != null) { + _setSegmentProperties(); + final SegmentProperties bubbleSegmentProperties = _segmentProperties; + final bool hasPointColor = + bubbleSegmentProperties.series.pointColorMapper != null; + if (bubbleSegmentProperties.series.gradient == null) { + if (bubbleSegmentProperties.color != null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.color - : ((hasPointColor && _currentPoint!.pointColorMapper != null) - ? _currentPoint!.pointColorMapper! - : _color!) + ..color = bubbleSegmentProperties.currentPoint!.isEmpty == true + ? bubbleSegmentProperties.series.emptyPointSettings.color + : ((hasPointColor && + bubbleSegmentProperties.currentPoint!.pointColorMapper != + null) + ? bubbleSegmentProperties.currentPoint!.pointColorMapper! + : bubbleSegmentProperties.color!) ..style = PaintingStyle.fill; } } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + bubbleSegmentProperties.series.gradient!, + bubbleSegmentProperties.currentPoint!.region!, + SeriesHelper.getSeriesRendererDetails( + bubbleSegmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(bubbleSegmentProperties.series.opacity >= 0, 'The opacity value of the bubble series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(bubbleSegmentProperties.series.opacity <= 1, 'The opacity value of the bubble series should be less than or equal to 1.'); if (fillPaint?.color != null) { - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; + fillPaint!.color = (bubbleSegmentProperties.series.opacity < 1 && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(bubbleSegmentProperties.series.opacity) + : fillPaint!.color; } - _defaultFillColor = fillPaint; + bubbleSegmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); + final SegmentProperties bubbleSegmentProperties = _segmentProperties; final Paint _strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - _series.borderGradient != null - ? _strokePaint.shader = - _series.borderGradient!.createShader(_currentPoint!.region!) - : _strokePaint.color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _strokeColor!; - _series.borderWidth == 0 + ..strokeWidth = bubbleSegmentProperties.currentPoint!.isEmpty == true + ? bubbleSegmentProperties.series.emptyPointSettings.borderWidth + : bubbleSegmentProperties.strokeWidth!; + bubbleSegmentProperties.series.borderGradient != null + ? _strokePaint.shader = bubbleSegmentProperties.series.borderGradient! + .createShader(bubbleSegmentProperties.currentPoint!.region!) + : _strokePaint.color = + bubbleSegmentProperties.currentPoint!.isEmpty == true + ? bubbleSegmentProperties.series.emptyPointSettings.borderColor + : bubbleSegmentProperties.strokeColor!; + bubbleSegmentProperties.series.borderWidth == 0 ? _strokePaint.color = Colors.transparent : _strokePaint.color; - _defaultStrokeColor = _strokePaint; + bubbleSegmentProperties.defaultStrokeColor = _strokePaint; return _strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { + _setSegmentProperties(); + + final SegmentProperties bubbleSegmentProperties = _segmentProperties; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + bubbleSegmentProperties.seriesRenderer); _centerX = _centerY = double.nan; - final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - final _ChartLocation localtion = _calculatePoint( - _currentPoint!.xValue, - _currentPoint!.yValue, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final Rect rect = calculatePlotOffset( + seriesRendererDetails.stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + final ChartLocation location = calculatePoint( + bubbleSegmentProperties.currentPoint!.xValue, + bubbleSegmentProperties.currentPoint!.yValue, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + bubbleSegmentProperties.series, rect); - _centerX = localtion.x; - _centerY = localtion.y; - if (_seriesRenderer is BubbleSeriesRenderer) - _radius = _calculateBubbleRadius(_seriesRenderer as BubbleSeriesRenderer); - _currentPoint!.region = Rect.fromLTRB( - localtion.x - 2 * _radius, - localtion.y - 2 * _radius, - localtion.x + 2 * _radius, - localtion.y + 2 * _radius); - _size = _radius = _currentPoint!.region!.width / 2; - } - - /// To calculate and return the bubble size - double _calculateBubbleRadius(BubbleSeriesRenderer _seriesRenderer) { - final BubbleSeries bubbleSeries = - _series as BubbleSeries; - num bubbleRadius, sizeRange, radiusRange, maxSize, minSize; - maxSize = _seriesRenderer._maxSize!; - minSize = _seriesRenderer._minSize!; - sizeRange = maxSize - minSize; - final double bubbleSize = ((_currentPoint!.bubbleSize) ?? 4).toDouble(); - assert(bubbleSeries.minimumRadius >= 0 && bubbleSeries.maximumRadius >= 0, - 'The min radius and max radius of the bubble should be greater than or equal to 0.'); - if (bubbleSeries.sizeValueMapper == null) { - // ignore: unnecessary_null_comparison - bubbleSeries.minimumRadius != null - ? bubbleRadius = bubbleSeries.minimumRadius - : bubbleRadius = bubbleSeries.maximumRadius; - } else { - if (sizeRange == 0) { - bubbleRadius = bubbleSize == 0 - ? bubbleSeries.minimumRadius - : bubbleSeries.maximumRadius; - } else { - radiusRange = - (bubbleSeries.maximumRadius - bubbleSeries.minimumRadius) * 2; - bubbleRadius = - (((bubbleSize.abs() - minSize) * radiusRange) / sizeRange) + - bubbleSeries.minimumRadius; - } - } - return bubbleRadius.toDouble(); + _centerX = location.x; + _centerY = location.y; + if (bubbleSegmentProperties.seriesRenderer is BubbleSeriesRenderer) + _radius = calculateBubbleRadius( + seriesRendererDetails, + bubbleSegmentProperties.series, + bubbleSegmentProperties.currentPoint!); + bubbleSegmentProperties.currentPoint!.region = Rect.fromLTRB( + location.x - 2 * _radius, + location.y - 2 * _radius, + location.x + 2 * _radius, + location.y + 2 * _radius); + _size = _radius = bubbleSegmentProperties.currentPoint!.region!.width / 2; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _segmentRect = RRect.fromRectAndRadius(_currentPoint!.region!, Radius.zero); - if (_seriesRenderer._renderingDetails!.widgetNeedUpdate && - !_seriesRenderer._reAnimate && - !_seriesRenderer._renderingDetails!.isLegendToggled && - _seriesRenderer._chartState!._oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderer != null && - _oldSeriesRenderer!._segments.isNotEmpty && - _oldSeriesRenderer!._segments[0] is BubbleSegment && - _series.animationDuration > 0 && - _oldPoint != null) { + _setSegmentProperties(); + + final SegmentProperties bubbleSegmentProperties = _segmentProperties; + bubbleSegmentProperties.segmentRect = RRect.fromRectAndRadius( + bubbleSegmentProperties.currentPoint!.region!, Radius.zero); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + bubbleSegmentProperties.seriesRenderer); + if (seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == true && + seriesRendererDetails.reAnimate == false && + seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + seriesRendererDetails.stateProperties.oldSeriesRenderers.isNotEmpty == + true && + bubbleSegmentProperties.oldSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails( + bubbleSegmentProperties.oldSeriesRenderer!) + .segments + .isNotEmpty == + true && + SeriesHelper.getSeriesRendererDetails( + bubbleSegmentProperties.oldSeriesRenderer!) + .segments[0] is BubbleSegment && + bubbleSegmentProperties.series.animationDuration > 0 && + bubbleSegmentProperties.oldPoint != null) { final BubbleSegment currentSegment = - _seriesRenderer._segments[currentSegmentIndex!] as BubbleSegment; + seriesRendererDetails.segments[currentSegmentIndex!] as BubbleSegment; + final SegmentProperties seriesSegmentProperties = + SegmentHelper.getSegmentProperties(currentSegment); + final SeriesRendererDetails oldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + seriesSegmentProperties.oldSeriesRenderer!); final BubbleSegment? oldSegment = - (currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex!) - ? currentSegment._oldSeriesRenderer! - ._segments[currentSegmentIndex!] as BubbleSegment? + (oldSeriesDetails.segments.length - 1 >= currentSegmentIndex! == true) + ? oldSeriesDetails.segments[currentSegmentIndex!] + as BubbleSegment? : null; - _animateBubbleSeries( + animateBubbleSeries( canvas, _centerX, _centerY, @@ -159,7 +183,7 @@ class BubbleSegment extends ChartSegment { _radius, strokePaint!, fillPaint!, - _seriesRenderer); + bubbleSegmentProperties.seriesRenderer); } else { canvas.drawCircle( Offset(_centerX, _centerY), _radius * animationFactor, fillPaint!); @@ -167,4 +191,12 @@ class BubbleSegment extends ChartSegment { Offset(_centerX, _centerY), _radius * animationFactor, strokePaint!); } } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart index 4301d34e9..7989bae69 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/candle_segment.dart @@ -1,4 +1,16 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/financial_series_base.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import 'chart_segment.dart'; /// Creates the segments for bubble series. /// @@ -9,109 +21,123 @@ part of charts; /// class CandleSegment extends ChartSegment { // ignore: unused_field - late double _x, - _low, - _high, - _highY, - _openX, - _openY, - _closeX, - _closeY, + late double _highY, _centerHigh, _centerLow, _lowY, _centersY, - _topRectY, _topLineY, - _bottomRectY, _bottomLineY; // ignore: unused_field - late Path _path, _linePath; - Color? _pointColorMapper; - - //ignore: prefer_final_fields - late bool _isSolid = false, _isTransposed, _isBull = false, _showSameValue; - late _ChartLocation _lowPoint, _highPoint, _centerLowPoint, _centerHighPoint; + late Path _linePath; + late bool _isTransposed, _showSameValue; + late ChartLocation _centerLowPoint, _centerHighPoint; - late CandleSeries _candleSeries; late CandleSegment _currentSegment; CandleSegment? _oldSegment; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); fillPaint = Paint() - ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!); - assert(_series.opacity >= 0, + ..color = _segmentProperties.currentPoint!.isEmpty != null && + _segmentProperties.currentPoint!.isEmpty! == true + ? _segmentProperties.series.emptyPointSettings.color + : (_segmentProperties.currentPoint!.pointColorMapper ?? + _segmentProperties.color!); + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the candle series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the candle series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - fillPaint!.strokeWidth = _strokeWidth!; - fillPaint!.style = _isSolid ? PaintingStyle.fill : PaintingStyle.stroke; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + fillPaint!.strokeWidth = _segmentProperties.strokeWidth!; + fillPaint!.style = _segmentProperties.isSolid == true + ? PaintingStyle.fill + : PaintingStyle.stroke; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); + final SegmentProperties candleSegmentProperties = _segmentProperties; final Paint _strokePaint = Paint(); - if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor!; - _strokePaint.color = - (_series.opacity < 1 && _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(_series.opacity) - : _strokePaint.color; + if (candleSegmentProperties.strokeColor != null) { + _strokePaint.color = _segmentProperties.pointColorMapper ?? + candleSegmentProperties.strokeColor!; + _strokePaint.color = (candleSegmentProperties.series.opacity < 1 && + _strokePaint.color != Colors.transparent) + ? _strokePaint.color + .withOpacity(candleSegmentProperties.series.opacity) + : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth!; + _strokePaint.strokeWidth = candleSegmentProperties.strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + candleSegmentProperties.defaultStrokeColor = _strokePaint; return _strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _candleSeries = _series as CandleSeries; - _isBull = _currentPoint!.open < _currentPoint!.close; - _x = _high = _low = double.nan; - _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; - _lowPoint = _currentPoint!.lowPoint!; - _highPoint = _currentPoint!.highPoint!; - _centerLowPoint = _currentPoint!.centerLowPoint!; - _centerHighPoint = _currentPoint!.centerHighPoint!; - _x = _lowPoint.x; - _low = _lowPoint.y; - _high = _highPoint.y; + _setSegmentProperties(); + _segmentProperties.isBull = _segmentProperties.currentPoint!.open < + _segmentProperties.currentPoint!.close; + _segmentProperties.x = + _segmentProperties.high = _segmentProperties.low = double.nan; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); + _isTransposed = seriesRendererDetails.stateProperties.requireInvertedAxis; + _segmentProperties.lowPoint = _segmentProperties.currentPoint!.lowPoint!; + _segmentProperties.highPoint = _segmentProperties.currentPoint!.highPoint!; + _centerLowPoint = _segmentProperties.currentPoint!.centerLowPoint!; + _centerHighPoint = _segmentProperties.currentPoint!.centerHighPoint!; + _segmentProperties.x = _segmentProperties.lowPoint.x; + _segmentProperties.low = _segmentProperties.lowPoint.y; + _segmentProperties.high = _segmentProperties.highPoint.y; _centerHigh = _centerHighPoint.x; _highY = _centerHighPoint.y; _centerLow = _centerLowPoint.x; _lowY = _centerLowPoint.y; - _openX = _currentPoint!.openPoint!.x; - _openY = _currentPoint!.openPoint!.y; - _closeX = _currentPoint!.closePoint!.x; - _closeY = _currentPoint!.closePoint!.y; + _segmentProperties.openX = _segmentProperties.currentPoint!.openPoint!.x; + _segmentProperties.openY = _segmentProperties.currentPoint!.openPoint!.y; + _segmentProperties.closeX = _segmentProperties.currentPoint!.closePoint!.x; + _segmentProperties.closeY = _segmentProperties.currentPoint!.closePoint!.y; - _showSameValue = _candleSeries.showIndicationForSameValues && - (!_seriesRenderer._chartState!._requireInvertedAxis - ? _centerHighPoint.y == _centerLowPoint.y - : _centerHighPoint.x == _centerLowPoint.x); + _showSameValue = + (_segmentProperties.series as FinancialSeriesBase) + .showIndicationForSameValues == + true && + (seriesRendererDetails.stateProperties.requireInvertedAxis == false + ? _centerHighPoint.y == _centerLowPoint.y + : _centerHighPoint.x == _centerLowPoint.x); - _x = _lowPoint.x = - (_showSameValue && _isTransposed) ? _lowPoint.x - 2 : _lowPoint.x; - _highPoint.x = - (_showSameValue && _isTransposed) ? _highPoint.x + 2 : _highPoint.x; - _low = _lowPoint.y = - (_showSameValue && !_isTransposed) ? _lowPoint.y - 2 : _lowPoint.y; - _high = _highPoint.y = - (_showSameValue && !_isTransposed) ? _highPoint.y + 2 : _highPoint.y; + _segmentProperties.x = _segmentProperties.lowPoint.x = + (_showSameValue && _isTransposed) + ? _segmentProperties.lowPoint.x - 2 + : _segmentProperties.lowPoint.x; + _segmentProperties.highPoint.x = (_showSameValue && _isTransposed) + ? _segmentProperties.highPoint.x + 2 + : _segmentProperties.highPoint.x; + _segmentProperties.low = _segmentProperties.lowPoint.y = + (_showSameValue && !_isTransposed) + ? _segmentProperties.lowPoint.y - 2 + : _segmentProperties.lowPoint.y; + _segmentProperties.high = _segmentProperties.highPoint.y = + (_showSameValue && !_isTransposed) + ? _segmentProperties.highPoint.y + 2 + : _segmentProperties.highPoint.y; _centerHigh = _centerHighPoint.x = (_showSameValue && _isTransposed) ? _centerHighPoint.x + 2 : _centerHighPoint.x; @@ -124,49 +150,78 @@ class CandleSegment extends ChartSegment { _lowY = _centerLowPoint.y = (_showSameValue && !_isTransposed) ? _centerLowPoint.y - 2 : _centerLowPoint.y; - _centersY = _closeY + ((_closeY - _openY).abs() / 2); - _topRectY = _centersY - ((_centersY - _closeY).abs() * 1); - _bottomRectY = _centersY + ((_centersY - _openY).abs() * 1); + _centersY = _segmentProperties.closeY + + ((_segmentProperties.closeY - _segmentProperties.openY).abs() / 2); + _segmentProperties.topRectY = + _centersY - ((_centersY - _segmentProperties.closeY).abs() * 1); + _segmentProperties.bottomRectY = + _centersY + ((_centersY - _segmentProperties.openY).abs() * 1); } /// To draw rect path of candle segments void _drawRectPath() { - _path.moveTo(!_isTransposed ? _openX : _topRectY, - !_isTransposed ? _topRectY : _closeY); - _path.lineTo(!_isTransposed ? _closeX : _topRectY, - !_isTransposed ? _topRectY : _openY); - _path.lineTo(!_isTransposed ? _closeX : _bottomRectY, - !_isTransposed ? _bottomRectY : _openY); - _path.lineTo(!_isTransposed ? _openX : _bottomRectY, - !_isTransposed ? _bottomRectY : _closeY); - _path.lineTo(!_isTransposed ? _openX : _topRectY, - !_isTransposed ? _topRectY : _closeY); - _path.close(); + _segmentProperties.path.moveTo( + !_isTransposed ? _segmentProperties.openX : _segmentProperties.topRectY, + !_isTransposed + ? _segmentProperties.topRectY + : _segmentProperties.closeY); + _segmentProperties.path.lineTo( + !_isTransposed + ? _segmentProperties.closeX + : _segmentProperties.topRectY, + !_isTransposed + ? _segmentProperties.topRectY + : _segmentProperties.openY); + _segmentProperties.path.lineTo( + !_isTransposed + ? _segmentProperties.closeX + : _segmentProperties.bottomRectY, + !_isTransposed + ? _segmentProperties.bottomRectY + : _segmentProperties.openY); + _segmentProperties.path.lineTo( + !_isTransposed + ? _segmentProperties.openX + : _segmentProperties.bottomRectY, + !_isTransposed + ? _segmentProperties.bottomRectY + : _segmentProperties.closeY); + _segmentProperties.path.lineTo( + !_isTransposed ? _segmentProperties.openX : _segmentProperties.topRectY, + !_isTransposed + ? _segmentProperties.topRectY + : _segmentProperties.closeY); + _segmentProperties.path.close(); } void _drawLine(Canvas canvas) { - canvas.drawLine(Offset(_centerHigh, _topRectY), + canvas.drawLine(Offset(_centerHigh, _segmentProperties.topRectY), Offset(_centerHigh, _topLineY), fillPaint!); - canvas.drawLine(Offset(_centerHigh, _bottomRectY), + canvas.drawLine(Offset(_centerHigh, _segmentProperties.bottomRectY), Offset(_centerHigh, _bottomLineY), fillPaint!); } void _drawFillLine(Canvas canvas) { - final bool isOpen = _currentPoint!.open > _currentPoint!.close; + final bool isOpen = _segmentProperties.currentPoint!.open > + _segmentProperties.currentPoint!.close; canvas.drawLine( - Offset(_topRectY, _highY), + Offset(_segmentProperties.topRectY, _highY), Offset( - _topRectY + - ((isOpen ? (_openX - _centerHigh) : (_closeX - _centerHigh)) + _segmentProperties.topRectY + + ((isOpen + ? (_segmentProperties.openX - _centerHigh) + : (_segmentProperties.closeX - _centerHigh)) .abs() * animationFactor), _highY), fillPaint!); canvas.drawLine( - Offset(_bottomRectY, _highY), + Offset(_segmentProperties.bottomRectY, _highY), Offset( - _bottomRectY - - ((isOpen ? (_closeX - _centerLow) : (_openX - _centerLow)) + _segmentProperties.bottomRectY - + ((isOpen + ? (_segmentProperties.closeX - _centerLow) + : (_segmentProperties.openX - _centerLow)) .abs() * animationFactor), _highY), @@ -175,97 +230,135 @@ class CandleSegment extends ChartSegment { void _calculateCandlePositions(num openX, num closeX) { _centersY = closeX + ((openX - closeX).abs() / 2); - _topRectY = _centersY + ((_centersY - openX).abs() * animationFactor); - _bottomRectY = _centersY - ((_centersY - closeX).abs() * animationFactor); + _segmentProperties.topRectY = + _centersY + ((_centersY - openX).abs() * animationFactor); + _segmentProperties.bottomRectY = + _centersY - ((_centersY - closeX).abs() * animationFactor); } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); if (fillPaint != null && - (_seriesRenderer._reAnimate || - !(_seriesRenderer._renderingDetails!.widgetNeedUpdate && - !_seriesRenderer._renderingDetails!.isLegendToggled))) { - _path = Path(); + (seriesRendererDetails.reAnimate == true || + !(seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false))) { + _segmentProperties.path = Path(); _linePath = Path(); if (!_isTransposed && - _currentPoint!.open > _currentPoint!.close == true) { - final double temp = _closeY; - _closeY = _openY; - _openY = temp; + _segmentProperties.currentPoint!.open > + _segmentProperties.currentPoint!.close == + true) { + final double temp = _segmentProperties.closeY; + _segmentProperties.closeY = _segmentProperties.openY; + _segmentProperties.openY = temp; } - if (_seriesRenderer._renderingDetails!.isLegendToggled) { + if (seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + true) { animationFactor = 1; } - _centersY = _closeY + ((_closeY - _openY).abs() / 2); - _topRectY = _centersY - ((_centersY - _closeY).abs() * animationFactor); - _topLineY = _topRectY - ((_topRectY - _highY).abs() * animationFactor); - _bottomRectY = _centersY + ((_centersY - _openY).abs() * animationFactor); - _bottomLineY = - _bottomRectY + ((_bottomRectY - _lowY).abs() * animationFactor); + _centersY = _segmentProperties.closeY + + ((_segmentProperties.closeY - _segmentProperties.openY).abs() / 2); + _segmentProperties.topRectY = _centersY - + ((_centersY - _segmentProperties.closeY).abs() * animationFactor); + _topLineY = _segmentProperties.topRectY - + ((_segmentProperties.topRectY - _highY).abs() * animationFactor); + _segmentProperties.bottomRectY = _centersY + + ((_centersY - _segmentProperties.openY).abs() * animationFactor); + _bottomLineY = _segmentProperties.bottomRectY + + ((_segmentProperties.bottomRectY - _lowY).abs() * animationFactor); - _bottomLineY = _lowY < _openY - ? _bottomRectY - ((_openY - _lowY).abs() * animationFactor) + _bottomLineY = _lowY < _segmentProperties.openY + ? _segmentProperties.bottomRectY - + ((_segmentProperties.openY - _lowY).abs() * animationFactor) : _bottomLineY; - _topLineY = _highY > _closeY - ? _topRectY + ((_closeY - _highY).abs() * animationFactor) + _topLineY = _highY > _segmentProperties.closeY + ? _segmentProperties.topRectY + + ((_segmentProperties.closeY - _highY).abs() * animationFactor) : _topLineY; if (_isTransposed) { - _currentPoint!.open > _currentPoint!.close == true - ? _calculateCandlePositions(_openX, _closeX) - : _calculateCandlePositions(_closeX, _openX); + _segmentProperties.currentPoint!.open > + _segmentProperties.currentPoint!.close == + true + ? _calculateCandlePositions( + _segmentProperties.openX, _segmentProperties.closeX) + : _calculateCandlePositions( + _segmentProperties.closeX, _segmentProperties.openX); if (_showSameValue) { canvas.drawLine(Offset(_centerHighPoint.x, _centerHighPoint.y), Offset(_centerLowPoint.x, _centerHighPoint.y), fillPaint!); } else { - _path.moveTo(_topRectY, _highY); - _centerHigh < _closeX - ? _path.lineTo( - _topRectY - ((_closeX - _centerHigh).abs() * animationFactor), + _segmentProperties.path.moveTo(_segmentProperties.topRectY, _highY); + _centerHigh < _segmentProperties.closeX + ? _segmentProperties.path.lineTo( + _segmentProperties.topRectY - + ((_segmentProperties.closeX - _centerHigh).abs() * + animationFactor), _highY) - : _path.lineTo( - _topRectY + ((_closeX - _centerHigh).abs() * animationFactor), + : _segmentProperties.path.lineTo( + _segmentProperties.topRectY + + ((_segmentProperties.closeX - _centerHigh).abs() * + animationFactor), _highY); - _path.moveTo(_bottomRectY, _highY); - _centerLow > _openX - ? _path.lineTo( - _bottomRectY + - ((_openX - _centerLow).abs() * animationFactor), + _segmentProperties.path + .moveTo(_segmentProperties.bottomRectY, _highY); + _centerLow > _segmentProperties.openX + ? _segmentProperties.path.lineTo( + _segmentProperties.bottomRectY + + ((_segmentProperties.openX - _centerLow).abs() * + animationFactor), _highY) - : _path.lineTo( - _bottomRectY - - ((_openX - _centerLow).abs() * animationFactor), + : _segmentProperties.path.lineTo( + _segmentProperties.bottomRectY - + ((_segmentProperties.openX - _centerLow).abs() * + animationFactor), _highY); - _linePath = _path; + _linePath = _segmentProperties.path; } - _openX == _closeX + _segmentProperties.openX == _segmentProperties.closeX ? canvas.drawLine( - Offset(_openX, _openY), Offset(_closeX, _closeY), fillPaint!) + Offset(_segmentProperties.openX, _segmentProperties.openY), + Offset(_segmentProperties.closeX, _segmentProperties.closeY), + fillPaint!) : _drawRectPath(); } else { _showSameValue - ? canvas.drawLine(Offset(_centerHighPoint.x, _highPoint.y), - Offset(_centerHighPoint.x, _lowPoint.y), fillPaint!) + ? canvas.drawLine( + Offset(_centerHighPoint.x, _segmentProperties.highPoint.y), + Offset(_centerHighPoint.x, _segmentProperties.lowPoint.y), + fillPaint!) : _drawLine(canvas); - _openY == _closeY + _segmentProperties.openY == _segmentProperties.closeY ? canvas.drawLine( - Offset(_openX, _openY), Offset(_closeX, _closeY), fillPaint!) + Offset(_segmentProperties.openX, _segmentProperties.openY), + Offset(_segmentProperties.closeX, _segmentProperties.closeY), + fillPaint!) : _drawRectPath(); } - if (_series.dashArray[0] != 0 && - _series.dashArray[1] != 0 && + if (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0 && fillPaint!.style != PaintingStyle.fill && - _series.animationDuration <= 0) { - _drawDashedLine(canvas, _series.dashArray, fillPaint!, _path); + _segmentProperties.series.animationDuration <= 0 == true) { + drawDashedLine(canvas, _segmentProperties.series.dashArray, fillPaint!, + _segmentProperties.path); } else { - canvas.drawPath(_path, fillPaint!); + canvas.drawPath(_segmentProperties.path, fillPaint!); if (fillPaint!.style == PaintingStyle.fill) { _isTransposed ? _showSameValue @@ -275,50 +368,74 @@ class CandleSegment extends ChartSegment { fillPaint!) : _drawFillLine(canvas) : _showSameValue - ? canvas.drawLine(Offset(_centerHighPoint.x, _highPoint.y), - Offset(_centerHighPoint.x, _lowPoint.y), fillPaint!) + ? canvas.drawLine( + Offset( + _centerHighPoint.x, _segmentProperties.highPoint.y), + Offset(_centerHighPoint.x, _segmentProperties.lowPoint.y), + fillPaint!) : _drawLine(canvas); } } - } else if (!_seriesRenderer._renderingDetails!.isLegendToggled) { + } else if (seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false) { _currentSegment = - _seriesRenderer._segments[currentSegmentIndex!] as CandleSegment; - _oldSegment = !_seriesRenderer._reAnimate && - (_currentSegment._oldSeriesRenderer != null && - _currentSegment._oldSeriesRenderer!._segments.isNotEmpty && - _currentSegment._oldSeriesRenderer!._segments[0] - is CandleSegment && - _currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex!) - ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] - as CandleSegment? + seriesRendererDetails.segments[currentSegmentIndex!] as CandleSegment; + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(_currentSegment); + SegmentProperties? oldSegmentProperties; + final SeriesRendererDetails? oldRendererDetails = + currentSegmentProperties.oldSeriesRenderer != null + ? SeriesHelper.getSeriesRendererDetails( + currentSegmentProperties.oldSeriesRenderer!) + : null; + _oldSegment = seriesRendererDetails.reAnimate == false && + (oldRendererDetails != null && + oldRendererDetails.segments.isNotEmpty == true && + oldRendererDetails.segments[0] is CandleSegment && + oldRendererDetails.segments.length - 1 >= + currentSegmentIndex! == + true) + ? oldRendererDetails.segments[currentSegmentIndex!] as CandleSegment? : null; - _animateCandleSeries( + if (_oldSegment != null) { + oldSegmentProperties = SegmentHelper.getSegmentProperties(_oldSegment!); + } + animateCandleSeries( _showSameValue, - _high, + _segmentProperties.high, _isTransposed, - _currentPoint!.open!.toDouble(), - _currentPoint!.close!.toDouble(), + _segmentProperties.currentPoint!.open!.toDouble(), + _segmentProperties.currentPoint!.close!.toDouble(), _lowY, _highY, _oldSegment?._lowY, _oldSegment?._highY, - _openX, - _openY, - _closeX, - _closeY, + _segmentProperties.openX, + _segmentProperties.openY, + _segmentProperties.closeX, + _segmentProperties.closeY, _centerLow, _centerHigh, - _oldSegment?._openX, - _oldSegment?._openY, - _oldSegment?._closeX, - _oldSegment?._closeY, + oldSegmentProperties?.openX, + oldSegmentProperties?.openY, + oldSegmentProperties?.closeX, + oldSegmentProperties?.closeY, _oldSegment?._centerLow, _oldSegment?._centerHigh, animationFactor, fillPaint!, canvas, - _seriesRenderer); + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer)); + } + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart index cda1cbf0c..c2d1bfbd2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/chart_segment.dart @@ -1,4 +1,7 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; /// Creates the segments for chart series. /// @@ -22,56 +25,39 @@ abstract class ChartSegment { ///Draws segment in series bounds. void onPaint(Canvas canvas); - ///Color of the segment - Color? _color, _strokeColor; - - ///Border width of the segment - double? _strokeWidth; - ///Fill paint of the segment Paint? fillPaint; ///Stroke paint of the segment Paint? strokePaint; - ///Chart series - late XyDataSeries _series; - XyDataSeries? _oldSeries; - - ///Chart series renderer - late CartesianSeriesRenderer _seriesRenderer; - CartesianSeriesRenderer? _oldSeriesRenderer; - ///Animation factor value late double animationFactor; - /// Rectangle of the segment - RRect? _segmentRect; - ///Current point offset value List points = []; - /// Default fill color & stroke color - Paint? _defaultFillColor, _defaultStrokeColor; - /// Current index value. int? currentSegmentIndex; - int? _oldSegmentIndex; - late int _seriesIndex; - - CartesianChartPoint? _currentPoint, _point, _oldPoint, _nextPoint; - - /// Old series visibility property. - bool? _oldSeriesVisible; - /// Old rect region. - Rect? _oldRegion; + /// Represents the segment properties + SegmentProperties? _segmentProperties; - /// Cartesian chart properties - late SfCartesianChart _chart; - - /// Cartesian chart state properties - late SfCartesianChartState _chartState; + /// To dispose the objects. + void dispose() { + _segmentProperties = null; + } +} - _RenderingDetails get _renderingDetails => _chartState._renderingDetails; +// ignore: avoid_classes_with_only_static_members +/// Helper class to get the private fields of chart segment +class SegmentHelper { + /// Method to get the segment properties of corresponding chart segment + static SegmentProperties getSegmentProperties(ChartSegment chartSegment) => + chartSegment._segmentProperties!; + + /// Method to set the segment properties of corresponding chart segment + static void setSegmentProperties( + ChartSegment segment, SegmentProperties segmentProperties) => + segment._segmentProperties = segmentProperties; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart index 347c56683..eaea9e240 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/column_segment.dart @@ -1,4 +1,14 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import '../chart_series/column_series.dart'; +import '../chart_series/series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for column series. /// @@ -8,87 +18,69 @@ part of charts; /// It gets the path, stroke color and fill color from the `series` to render the column segment. /// class ColumnSegment extends ChartSegment { - /// Render path. - late Path _path; - late RRect _trackRect; - Paint? _trackerFillPaint, _trackerStrokePaint; - /// Rectangle of the segment this could be used to render the segment while overriding this segment late RRect segmentRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); + /// Get and set the paint options for column series. - if (_series.gradient == null) { + if (_segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!) + ..color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.color + : (_segmentProperties.currentPoint!.pointColorMapper ?? + _segmentProperties.color!) ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.currentPoint!.region!, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the column series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the column series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - _series.borderGradient != null - ? strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!) - : strokePaint!.color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _strokeColor!; - _series.borderWidth == 0 + ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderWidth + : _segmentProperties.strokeWidth!; + _segmentProperties.series.borderGradient != null + ? strokePaint!.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.currentPoint!.region!) + : strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderColor + : _segmentProperties.strokeColor!; + _segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; return strokePaint!; } - /// Method to get series tracker fill. - Paint _getTrackerFillPaint() { - final ColumnSeries columnSeries = - _series as ColumnSeries; - _trackerFillPaint = Paint() - ..color = columnSeries.trackColor - ..style = PaintingStyle.fill; - return _trackerFillPaint!; - } - - /// Method to getseries tracker stroke color. - Paint _getTrackerStrokePaint() { - final ColumnSeries columnSeries = - _series as ColumnSeries; - _trackerStrokePaint = Paint() - ..color = columnSeries.trackBorderColor - ..strokeWidth = columnSeries.trackBorderWidth - ..style = PaintingStyle.stroke; - columnSeries.trackBorderWidth == 0 - ? _trackerStrokePaint!.color = Colors.transparent - : _trackerStrokePaint!.color; - return _trackerStrokePaint!; - } - /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() {} @@ -96,40 +88,57 @@ class ColumnSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); final ColumnSeries columnSeries = - _series as ColumnSeries; + _segmentProperties.series as ColumnSeries; - if (_trackerFillPaint != null && columnSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackRect, _trackerFillPaint!); + if (_segmentProperties.trackerFillPaint != null && + columnSeries.isTrackVisible) { + _drawSegmentRect(canvas, _segmentProperties.trackRect, + _segmentProperties.trackerFillPaint!); } - if (_trackerStrokePaint != null && columnSeries.isTrackVisible) { - _drawSegmentRect(canvas, _trackRect, _trackerStrokePaint!); + if (_segmentProperties.trackerStrokePaint != null && + columnSeries.isTrackVisible) { + _drawSegmentRect(canvas, _segmentProperties.trackRect, + _segmentProperties.trackerStrokePaint!); } if (fillPaint != null) { _drawSegmentRect(canvas, segmentRect, fillPaint!); } if (strokePaint != null) { - (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path) + (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0) + ? drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, _segmentProperties.path) : _drawSegmentRect(canvas, segmentRect, strokePaint!); } } - /// To draw sgement rect for column segment + /// To draw segment rect for column segment void _drawSegmentRect(Canvas canvas, RRect segmentRect, Paint paint) { - (_series.animationDuration > 0) - ? _animateRectSeries( + (_segmentProperties.series.animationDuration > 0 == true) + ? animateRectSeries( canvas, - _seriesRenderer, + _segmentProperties.seriesRenderer, paint, segmentRect, - _currentPoint!.yValue, + _segmentProperties.currentPoint!.yValue, animationFactor, - _oldPoint != null ? _oldPoint!.region : _oldRegion, - _oldPoint?.yValue, - _oldSeriesVisible) + _segmentProperties.oldPoint != null + ? _segmentProperties.oldPoint!.region + : _segmentProperties.oldRegion, + _segmentProperties.oldPoint?.yValue, + _segmentProperties.oldSeriesVisible) : canvas.drawRRect(segmentRect, paint); } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/error_bar_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/error_bar_segment.dart new file mode 100644 index 000000000..6a2032606 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/error_bar_segment.dart @@ -0,0 +1,256 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../../../charts.dart'; +import '../chart_series/error_bar_series.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import 'chart_segment.dart'; + +/// Segment class for error bar +class ErrorBarSegment extends ChartSegment { + final double _effectiveAnimationFactor = 0.05; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + late double _errorBarMidPointX; + late double _errorBarMidPointY; + late double _capPointValue; + + /// Gets the color of the series. + @override + Paint getFillPaint() { + return fillPaint!; + } + + /// Gets the border color of the series. + @override + Paint getStrokePaint() { + _setSegmentProperties(); + strokePaint = Paint() + ..color = _segmentProperties.color! + .withOpacity(_segmentProperties.series.opacity) + ..style = PaintingStyle.stroke + ..strokeWidth = _segmentProperties.series.width!; + _segmentProperties.defaultStrokeColor = strokePaint; + setShader(_segmentProperties, strokePaint!); + return strokePaint!; + } + + /// Calculates the rendering bounds of a segment. + @override + void calculateSegmentPoints() {} + + /// Draws segment in series bounds. + @override + void onPaint(Canvas canvas) { + _setSegmentProperties(); + final ErrorBarSeries errorBarSeries = + _segmentProperties.series as ErrorBarSeries; + _animateErrorBar( + canvas, + strokePaint!, + _segmentProperties.currentPoint!, + animationFactor, + _segmentProperties.seriesRenderer, + errorBarSeries.capLength!, + errorBarSeries.dashArray); + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } + + /// To animate error bar + void _animateErrorBar( + Canvas canvas, + Paint errorBarPaint, + CartesianChartPoint point, + double animationFactor, + CartesianSeriesRenderer seriesRenderer, + double capLength, + List dashArray) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (_segmentProperties.series.animationDuration > 0 == true && + animationFactor != 0) { + animationFactor = (!seriesRendererDetails.reAnimate && + !seriesRendererDetails + .stateProperties.renderingDetails.initialRender! && + seriesRendererDetails.series.key != null && + seriesRendererDetails.stateProperties.oldSeriesKeys + .contains(seriesRendererDetails.series.key)) + ? 1 + : animationFactor; + + final bool isTransposedChart = seriesRendererDetails.chart.isTransposed; + _errorBarMidPointX = point.currentPoint!.x; + _errorBarMidPointY = point.currentPoint!.y; + + double animatingPoint; + if (point.verticalPositiveErrorPoint != null) { + animatingPoint = isTransposedChart + ? _errorBarMidPointX + + ((point.verticalPositiveErrorPoint!.x - _errorBarMidPointX) * + _effectiveAnimationFactor) + : _errorBarMidPointY - + ((_errorBarMidPointY - point.verticalPositiveErrorPoint!.y) * + _effectiveAnimationFactor); + + _capPointValue = animatingPoint - + ((animatingPoint - + (isTransposedChart + ? point.verticalPositiveErrorPoint!.x + : point.verticalPositiveErrorPoint!.y)) * + animationFactor); + + _verticalErrorBarRendering(canvas, errorBarPaint, _capPointValue, + animationFactor, capLength, dashArray, isTransposedChart); + } + if (point.verticalNegativeErrorPoint != null) { + animatingPoint = isTransposedChart + ? _errorBarMidPointX + + ((point.verticalNegativeErrorPoint!.x - _errorBarMidPointX) * + _effectiveAnimationFactor) + : _errorBarMidPointY + + ((point.verticalNegativeErrorPoint!.y - _errorBarMidPointY) * + _effectiveAnimationFactor); + + _capPointValue = animatingPoint + + (((isTransposedChart + ? point.verticalNegativeErrorPoint!.x + : point.verticalNegativeErrorPoint!.y) - + animatingPoint) * + animationFactor); + + _verticalErrorBarRendering(canvas, errorBarPaint, _capPointValue, + animationFactor, capLength, dashArray, isTransposedChart); + } + if (point.horizontalPositiveErrorPoint != null) { + animatingPoint = isTransposedChart + ? _errorBarMidPointY - + ((_errorBarMidPointY - point.horizontalPositiveErrorPoint!.y) * + _effectiveAnimationFactor) + : _errorBarMidPointX + + ((point.horizontalPositiveErrorPoint!.x - _errorBarMidPointX) * + _effectiveAnimationFactor); + + _capPointValue = animatingPoint + + (((isTransposedChart + ? point.horizontalPositiveErrorPoint!.y + : point.horizontalPositiveErrorPoint!.x) - + animatingPoint) * + animationFactor); + + _horizontalErrorBarRendering(canvas, errorBarPaint, _capPointValue, + animationFactor, capLength, dashArray, isTransposedChart); + } + if (point.horizontalNegativeErrorPoint != null) { + animatingPoint = isTransposedChart + ? _errorBarMidPointY + + ((point.horizontalNegativeErrorPoint!.y - _errorBarMidPointY) * + _effectiveAnimationFactor) + : _errorBarMidPointX - + ((_errorBarMidPointX - point.horizontalNegativeErrorPoint!.x) * + _effectiveAnimationFactor); + + _capPointValue = animatingPoint - + ((animatingPoint - + (isTransposedChart + ? point.horizontalNegativeErrorPoint!.y + : point.horizontalNegativeErrorPoint!.x)) * + animationFactor); + + _horizontalErrorBarRendering(canvas, errorBarPaint, _capPointValue, + animationFactor, capLength, dashArray, isTransposedChart); + } + } + } + + /// Animating vertical error bar. + void _verticalErrorBarRendering( + Canvas canvas, + Paint errorBarPaint, + double capPointValue, + double animationFactor, + double capLength, + List dashArray, + bool isTransposedChart) { + final Path verticalPath = Path(); + final Path capPath = Path(); + if (isTransposedChart) { + verticalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); + verticalPath.lineTo(capPointValue, _errorBarMidPointY); + capPath.moveTo(capPointValue, _errorBarMidPointY - (capLength / 2)); + capPath.lineTo(capPointValue, _errorBarMidPointY + (capLength / 2)); + } else { + verticalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); + verticalPath.lineTo(_errorBarMidPointX, capPointValue); + capPath.moveTo(_errorBarMidPointX - (capLength / 2), capPointValue); + capPath.lineTo(_errorBarMidPointX + (capLength / 2), capPointValue); + } + verticalPath.close(); + capPath.close(); + + if (dashArray != null) { + // Draws vertical line of the error bar. + drawDashedLine(canvas, dashArray, errorBarPaint, verticalPath); + // Draws cap of the error bar. + drawDashedLine(canvas, dashArray, errorBarPaint, capPath); + } else { + canvas.drawPath(verticalPath, errorBarPaint); + canvas.drawPath(capPath, errorBarPaint); + } + } + + /// Animating horizontal error bar. + void _horizontalErrorBarRendering( + Canvas canvas, + Paint errorBarPaint, + double capPointValue, + double animationFactor, + double capLength, + List dashArray, + bool isTransposedChart) { + final Path horizontalPath = Path(); + final Path horizontalCapPath = Path(); + if (isTransposedChart) { + horizontalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); + horizontalPath.lineTo(_errorBarMidPointX, capPointValue); + horizontalCapPath.moveTo( + _errorBarMidPointX - (capLength / 2), capPointValue); + horizontalCapPath.lineTo( + _errorBarMidPointX + (capLength / 2), capPointValue); + } else { + horizontalPath.moveTo(_errorBarMidPointX, _errorBarMidPointY); + horizontalPath.lineTo(capPointValue, _errorBarMidPointY); + horizontalCapPath.moveTo( + capPointValue, + _errorBarMidPointY - (capLength / 2), + ); + horizontalCapPath.lineTo( + capPointValue, + _errorBarMidPointY + (capLength / 2), + ); + } + horizontalPath.close(); + horizontalCapPath.close(); + + if (dashArray != null) { + // Draws horizontal line of the error bar. + drawDashedLine(canvas, dashArray, errorBarPaint, horizontalPath); + // Draws cap of the error bar. + drawDashedLine(canvas, dashArray, errorBarPaint, horizontalCapPath); + } else { + canvas.drawPath(horizontalPath, errorBarPaint); + canvas.drawPath(horizontalCapPath, errorBarPaint); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart index 231507a68..6ef216294 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/fastline_segment.dart @@ -1,4 +1,13 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/series.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import 'chart_segment.dart'; /// Creates the segments for fast line series. /// @@ -7,59 +16,84 @@ part of charts; /// /// Gets the path and color from the `series`. class FastLineSegment extends ChartSegment { + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); + final Paint _fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the fast line series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the fast line series should be less than or equal to 1.'); - if (_color != null) { - _fillPaint.color = _color!.withOpacity(_series.opacity); + if (_segmentProperties.color != null) { + _fillPaint.color = _segmentProperties.color! + .withOpacity(_segmentProperties.series.opacity); } _fillPaint.style = PaintingStyle.fill; - _defaultFillColor = _fillPaint; + _segmentProperties.defaultFillColor = _fillPaint; return _fillPaint; } /// Gets the stroke color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); + final Paint _strokePaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the fast line series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the fast line series should be less than or equal to 1.'); - if (_series.gradient == null) { - if (_strokeColor != null) { - _strokePaint.color = - (_series.opacity < 1 && _strokeColor != Colors.transparent) - ? _strokeColor!.withOpacity(_series.opacity) - : _strokeColor!; + if (_segmentProperties.series.gradient == null) { + if (_segmentProperties.strokeColor != null) { + _strokePaint.color = (_segmentProperties.series.opacity < 1 == true && + _segmentProperties.strokeColor != Colors.transparent) + ? _segmentProperties.strokeColor! + .withOpacity(_segmentProperties.series.opacity) + : _segmentProperties.strokeColor!; } } else { - _strokePaint.shader = _series.gradient! - .createShader(_seriesRenderer._segmentPath!.getBounds()); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); + _strokePaint.shader = _segmentProperties.series.gradient! + .createShader(seriesRendererDetails.segmentPath!.getBounds()); } - _strokePaint.strokeWidth = _strokeWidth!; + _strokePaint.strokeWidth = _segmentProperties.strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + _segmentProperties.defaultStrokeColor = _strokePaint; + setShader(_segmentProperties, _strokePaint); return _strokePaint; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); // ignore: unnecessary_null_comparison - _series.dashArray != null - ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, - _seriesRenderer._segmentPath!) - : canvas.drawPath(_seriesRenderer._segmentPath!, strokePaint!); + _segmentProperties.series.dashArray != null + ? drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, seriesRendererDetails.segmentPath!) + : canvas.drawPath(seriesRendererDetails.segmentPath!, strokePaint!); } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() {} + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart index 110fa5cba..a2100e346 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hilo_segment.dart @@ -1,4 +1,14 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import '../chart_series/hilo_series.dart'; +import '../chart_series/series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import 'chart_segment.dart'; /// Creates the segments for Hilo series. /// @@ -7,88 +17,98 @@ part of charts; /// /// Gets the path and color from the `series`. class HiloSegment extends ChartSegment { - /// Current point X value - //ignore: unused_field - late double _x, _low, _high, _centerX, _highX, _lowX, _centerY, _highY, _lowY; - Color? _pointColorMapper; + late double _centerX, _highX, _lowX, _centerY, _highY, _lowY; - /// Stores _low point location - late _ChartLocation _lowPoint, _highPoint; - - /// Render path. - late Path _path; late bool _showSameValue, _isTransposed; late HiloSeries _hiloSeries; late HiloSegment _currentSegment; HiloSegment? _oldSegment; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); + final Paint _fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the Hilo series will not accept negative numbers.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the Hilo series must be less than or equal to 1.'); - if (_color != null) { - _fillPaint.color = - _pointColorMapper ?? _color!.withOpacity(_series.opacity); + if (_segmentProperties.color != null) { + _fillPaint.color = _segmentProperties.pointColorMapper ?? + _segmentProperties.color! + .withOpacity(_segmentProperties.series.opacity); } - _fillPaint.strokeWidth = _strokeWidth!; + _fillPaint.strokeWidth = _segmentProperties.strokeWidth!; _fillPaint.style = PaintingStyle.fill; - _defaultFillColor = _fillPaint; + _segmentProperties.defaultFillColor = _fillPaint; return _fillPaint; } /// Gets the stroke color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); final Paint _strokePaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the Hilo series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the Hilo series should be less than or equal to 1.'); - if (_strokeColor != null) { - _strokePaint.color = - _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.color - : _pointColorMapper ?? _strokeColor!; - _strokePaint.color = - (_series.opacity < 1 && _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(_series.opacity) - : _strokePaint.color; + if (_segmentProperties.strokeColor != null) { + _strokePaint.color = _segmentProperties.currentPoint!.isEmpty != null && + _segmentProperties.currentPoint!.isEmpty! == true + ? _segmentProperties.series.emptyPointSettings.color + : _segmentProperties.pointColorMapper ?? + _segmentProperties.strokeColor!; + _strokePaint.color = (_segmentProperties.series.opacity < 1 == true && + _strokePaint.color != Colors.transparent) + ? _strokePaint.color.withOpacity(_segmentProperties.series.opacity) + : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth!; + _strokePaint.strokeWidth = _segmentProperties.strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + _segmentProperties.defaultStrokeColor = _strokePaint; + setShader(_segmentProperties, _strokePaint); return _strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _hiloSeries = _series as HiloSeries; - _x = _high = _low = double.nan; - _lowPoint = _currentPoint!.lowPoint!; - _highPoint = _currentPoint!.highPoint!; + _setSegmentProperties(); + _hiloSeries = _segmentProperties.series as HiloSeries; + _segmentProperties.x = + _segmentProperties.high = _segmentProperties.low = double.nan; + _segmentProperties.lowPoint = _segmentProperties.currentPoint!.lowPoint!; + _segmentProperties.highPoint = _segmentProperties.currentPoint!.highPoint!; - _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; + _isTransposed = + SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis; - _x = _lowPoint.x; - _low = _lowPoint.y; - _high = _highPoint.y; + _segmentProperties.x = _segmentProperties.lowPoint.x; + _segmentProperties.low = _segmentProperties.lowPoint.y; + _segmentProperties.high = _segmentProperties.highPoint.y; _showSameValue = _hiloSeries.showIndicationForSameValues && - (!_isTransposed ? _low == _high : _lowPoint.x == _highPoint.x); + (!_isTransposed + ? _segmentProperties.low == _segmentProperties.high + : _segmentProperties.lowPoint.x == _segmentProperties.highPoint.x); if (_showSameValue) { if (_isTransposed) { - _x = _lowPoint.x = _lowPoint.x - 2; - _highPoint.x = _highPoint.x + 2; + _segmentProperties.x = + _segmentProperties.lowPoint.x = _segmentProperties.lowPoint.x - 2; + _segmentProperties.highPoint.x = _segmentProperties.highPoint.x + 2; } else { - _low = _lowPoint.y = _lowPoint.y - 2; - _high = _highPoint.y = _highPoint.y + 2; + _segmentProperties.low = + _segmentProperties.lowPoint.y = _segmentProperties.lowPoint.y - 2; + _segmentProperties.high = + _segmentProperties.highPoint.y = _segmentProperties.highPoint.y + 2; } } } @@ -96,59 +116,88 @@ class HiloSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - if (_series.animationDuration > 0 && - !_seriesRenderer._renderingDetails!.isLegendToggled) { - if (!_seriesRenderer._renderingDetails!.widgetNeedUpdate || - _seriesRenderer._reAnimate) { + _setSegmentProperties(); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); + if (_segmentProperties.series.animationDuration > 0 == true && + seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false) { + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + false || + seriesRendererDetails.reAnimate == true) { if (_isTransposed) { - _lowX = _lowPoint.x; - _highX = _highPoint.x; + _lowX = _segmentProperties.lowPoint.x; + _highX = _segmentProperties.highPoint.x; _centerX = _highX + ((_lowX - _highX) / 2); _highX = _centerX + ((_centerX - _highX).abs() * animationFactor); _lowX = _centerX - ((_lowX - _centerX).abs() * animationFactor); - canvas.drawLine(Offset(_lowX, _lowPoint.y), - Offset(_highX, _highPoint.y), strokePaint!); + canvas.drawLine(Offset(_lowX, _segmentProperties.lowPoint.y), + Offset(_highX, _segmentProperties.highPoint.y), strokePaint!); } else { - _centerY = _high + ((_low - _high) / 2); - _highY = _centerY - ((_centerY - _high) * animationFactor); - _lowY = _centerY + ((_low - _centerY) * animationFactor); - canvas.drawLine(Offset(_lowPoint.x, _highY), - Offset(_highPoint.x, _lowY), strokePaint!); + _centerY = _segmentProperties.high + + ((_segmentProperties.low - _segmentProperties.high) / 2); + _highY = _centerY - + ((_centerY - _segmentProperties.high) * animationFactor); + _lowY = _centerY + + ((_segmentProperties.low - _centerY) * animationFactor); + canvas.drawLine(Offset(_segmentProperties.lowPoint.x, _highY), + Offset(_segmentProperties.highPoint.x, _lowY), strokePaint!); } } else { _currentSegment = - _seriesRenderer._segments[currentSegmentIndex!] as HiloSegment; - _oldSegment = !_seriesRenderer._reAnimate && - (_currentSegment._oldSeriesRenderer != null && - _currentSegment._oldSeriesRenderer!._segments.isNotEmpty && - _currentSegment._oldSeriesRenderer!._segments[0] - is HiloSegment && - _currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex!) - ? _currentSegment._oldSeriesRenderer! - ._segments[currentSegmentIndex!] as HiloSegment? + seriesRendererDetails.segments[currentSegmentIndex!] as HiloSegment; + final SeriesRendererDetails? oldSeriesDetails = + _currentSegment._segmentProperties.oldSeriesRenderer != null + ? SeriesHelper.getSeriesRendererDetails( + _currentSegment._segmentProperties.oldSeriesRenderer!) + : null; + _oldSegment = seriesRendererDetails.reAnimate == false && + (oldSeriesDetails != null && + oldSeriesDetails.segments.isNotEmpty == true && + oldSeriesDetails.segments[0] is HiloSegment && + oldSeriesDetails.segments.length - 1 >= + currentSegmentIndex! == + true) + ? oldSeriesDetails.segments[currentSegmentIndex!] as HiloSegment? : null; - _animateHiloSeries( + animateHiloSeres( _isTransposed, - _lowPoint, - _highPoint, - _oldSegment?._lowPoint, - _oldSegment?._highPoint, + _segmentProperties.lowPoint, + _segmentProperties.highPoint, + _oldSegment?._segmentProperties.lowPoint, + _oldSegment?._segmentProperties.highPoint, animationFactor, strokePaint!, canvas, - _seriesRenderer); + _segmentProperties.seriesRenderer); } } else { - if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _path = Path(); - _path.moveTo(_lowPoint.x, _high); - _path.lineTo(_highPoint.x, _low); - _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); + if (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0) { + _segmentProperties.path = Path(); + _segmentProperties.path + .moveTo(_segmentProperties.lowPoint.x, _segmentProperties.high); + _segmentProperties.path + .lineTo(_segmentProperties.highPoint.x, _segmentProperties.low); + drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, _segmentProperties.path); } else { - canvas.drawLine(Offset(_lowPoint.x, _high), Offset(_highPoint.x, _low), + canvas.drawLine( + Offset(_segmentProperties.lowPoint.x, _segmentProperties.high), + Offset(_segmentProperties.highPoint.x, _segmentProperties.low), strokePaint!); } } } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart index dff22e210..7053c903c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/hiloopenclose_segment.dart @@ -1,4 +1,15 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import '../chart_series/hiloopenclose_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import 'chart_segment.dart'; /// Creates the segments for HiloOpenClose series. /// @@ -7,98 +18,99 @@ part of charts; /// /// Gets the path and color from the `series`. class HiloOpenCloseSegment extends ChartSegment { - //ignore: unused_field - late double _x, - _low, - _high, - _centerY, + late double _centerY, _highY, _centerX, _lowX, _highX, - _openX, - _openY, - _closeX, - _closeY, _centerHigh, _centerLow, _lowY; - ///Render path. - late Path _path; - - Color? _pointColorMapper; - late _ChartLocation _centerLowPoint, _centerHighPoint, _lowPoint, _highPoint; - late bool _showSameValue, _isTransposed, _isBull = false; + late ChartLocation _centerLowPoint, _centerHighPoint; + late bool _showSameValue, _isTransposed; late HiloOpenCloseSegment _currentSegment; HiloOpenCloseSegment? _oldSegment; late HiloOpenCloseSeries _hiloOpenCloseSeries; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); final Paint fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the Hilo open-close series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the Hilo open-close series should be less than or equal to 1.'); - if (_color != null) { - fillPaint.color = - _pointColorMapper ?? _color!.withOpacity(_series.opacity); + if (_segmentProperties.color != null) { + fillPaint.color = _segmentProperties.pointColorMapper ?? + _segmentProperties.color! + .withOpacity(_segmentProperties.series.opacity); } - fillPaint.strokeWidth = _strokeWidth!; + fillPaint.strokeWidth = _segmentProperties.strokeWidth!; fillPaint.style = PaintingStyle.fill; - _defaultFillColor = fillPaint; + _segmentProperties.defaultFillColor = fillPaint; return fillPaint; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); final Paint strokePaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the Hilo open-close series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the Hilo open-close series should be less than or equal to 1.'); - if (_strokeColor != null) { - strokePaint.color = - _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.color - : _pointColorMapper ?? _strokeColor!; - strokePaint.color = - (_series.opacity < 1 && strokePaint.color != Colors.transparent) - ? strokePaint.color.withOpacity(_series.opacity) - : strokePaint.color; + if (_segmentProperties.strokeColor != null) { + strokePaint.color = _segmentProperties.currentPoint!.isEmpty != null && + _segmentProperties.currentPoint!.isEmpty! == true + ? _segmentProperties.series.emptyPointSettings.color + : _segmentProperties.pointColorMapper ?? + _segmentProperties.strokeColor!; + strokePaint.color = (_segmentProperties.series.opacity < 1 == true && + strokePaint.color != Colors.transparent) + ? strokePaint.color.withOpacity(_segmentProperties.series.opacity) + : strokePaint.color; } - strokePaint.strokeWidth = _strokeWidth!; + strokePaint.strokeWidth = _segmentProperties.strokeWidth!; strokePaint.style = PaintingStyle.stroke; strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; + setShader(_segmentProperties, strokePaint); return strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _hiloOpenCloseSeries = _series as HiloOpenCloseSeries; - _isTransposed = _seriesRenderer._chartState!._requireInvertedAxis; - _isBull = _currentPoint!.open < _currentPoint!.close; - _lowPoint = _currentPoint!.lowPoint!; - _highPoint = _currentPoint!.highPoint!; - _centerLowPoint = _currentPoint!.centerLowPoint!; - _centerHighPoint = _currentPoint!.centerHighPoint!; - _x = _lowX = _lowPoint.x; - _low = _lowPoint.y; - _high = _highPoint.y; - _highX = _highPoint.x; + _setSegmentProperties(); + _hiloOpenCloseSeries = + _segmentProperties.series as HiloOpenCloseSeries; + _isTransposed = + SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) + .stateProperties + .requireInvertedAxis; + _segmentProperties.isBull = _segmentProperties.currentPoint!.open < + _segmentProperties.currentPoint!.close; + _segmentProperties.lowPoint = _segmentProperties.currentPoint!.lowPoint!; + _segmentProperties.highPoint = _segmentProperties.currentPoint!.highPoint!; + _centerLowPoint = _segmentProperties.currentPoint!.centerLowPoint!; + _centerHighPoint = _segmentProperties.currentPoint!.centerHighPoint!; + _segmentProperties.x = _lowX = _segmentProperties.lowPoint.x; + _segmentProperties.low = _segmentProperties.lowPoint.y; + _segmentProperties.high = _segmentProperties.highPoint.y; + _highX = _segmentProperties.highPoint.x; _centerHigh = _centerHighPoint.x; _highY = _centerHighPoint.y; _centerLow = _centerLowPoint.x; _lowY = _centerLowPoint.y; - _openX = _currentPoint!.openPoint!.x; - _openY = _currentPoint!.openPoint!.y; - _closeX = _currentPoint!.closePoint!.x; - _closeY = _currentPoint!.closePoint!.y; + _segmentProperties.openX = _segmentProperties.currentPoint!.openPoint!.x; + _segmentProperties.openY = _segmentProperties.currentPoint!.openPoint!.y; + _segmentProperties.closeX = _segmentProperties.currentPoint!.closePoint!.x; + _segmentProperties.closeY = _segmentProperties.currentPoint!.closePoint!.y; _showSameValue = _hiloOpenCloseSeries.showIndicationForSameValues && (!_isTransposed @@ -107,13 +119,16 @@ class HiloOpenCloseSegment extends ChartSegment { if (_showSameValue) { if (_isTransposed) { - _x = _lowPoint.x = _lowPoint.x - 2; - _highPoint.x = _highPoint.x + 2; + _segmentProperties.x = + _segmentProperties.lowPoint.x = _segmentProperties.lowPoint.x - 2; + _segmentProperties.highPoint.x = _segmentProperties.highPoint.x + 2; _centerHigh = _centerHighPoint.x = _centerHighPoint.x + 2; _centerLow = _centerLowPoint.x = _centerLowPoint.x - 2; } else { - _low = _lowPoint.y = _lowPoint.y - 2; - _high = _highPoint.y = _highPoint.y + 2; + _segmentProperties.low = + _segmentProperties.lowPoint.y = _segmentProperties.lowPoint.y - 2; + _segmentProperties.high = + _segmentProperties.highPoint.y = _segmentProperties.highPoint.y + 2; _highY = _centerHighPoint.y = _centerHighPoint.y + 2; _lowY = _centerLowPoint.y = _centerLowPoint.y - 2; } @@ -125,103 +140,138 @@ class HiloOpenCloseSegment extends ChartSegment { canvas.drawLine( Offset(_centerHigh, _highY), Offset(_centerLow, _lowY), strokePaint!); canvas.drawLine( - Offset(_openX, _openY), - Offset(_isTransposed ? _openX : _centerHigh, - _isTransposed ? _highY : _openY), + Offset(_segmentProperties.openX, _segmentProperties.openY), + Offset(_isTransposed ? _segmentProperties.openX : _centerHigh, + _isTransposed ? _highY : _segmentProperties.openY), strokePaint!); canvas.drawLine( - Offset(_closeX, _closeY), - Offset(_isTransposed ? _closeX : _centerLow, - _isTransposed ? _highY : _closeY), + Offset(_segmentProperties.closeX, _segmentProperties.closeY), + Offset(_isTransposed ? _segmentProperties.closeX : _centerLow, + _isTransposed ? _highY : _segmentProperties.closeY), strokePaint!); } /// To draw dashed hilo open close path Path _drawDashedHiloOpenClosePath(Canvas canvas) { - _path.moveTo(_centerHigh, _highY); - _path.lineTo(_centerLow, _lowY); - _path.moveTo(_openX, _openY); - _path.lineTo( - _isTransposed ? _openX : _centerHigh, _isTransposed ? _highY : _openY); - _path.moveTo( - _isTransposed ? _closeX : _centerLow, _isTransposed ? _highY : _closeY); - _path.lineTo(_closeX, _closeY); - return _path; + _segmentProperties.path.moveTo(_centerHigh, _highY); + _segmentProperties.path.lineTo(_centerLow, _lowY); + _segmentProperties.path + .moveTo(_segmentProperties.openX, _segmentProperties.openY); + _segmentProperties.path.lineTo( + _isTransposed ? _segmentProperties.openX : _centerHigh, + _isTransposed ? _highY : _segmentProperties.openY); + _segmentProperties.path.moveTo( + _isTransposed ? _segmentProperties.closeX : _centerLow, + _isTransposed ? _highY : _segmentProperties.closeY); + _segmentProperties.path + .lineTo(_segmentProperties.closeX, _segmentProperties.closeY); + return _segmentProperties.path; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); if (strokePaint != null) { - _path = Path(); - if (_series.animationDuration > 0 && - !_seriesRenderer._renderingDetails!.isLegendToggled) { - if (!_seriesRenderer._renderingDetails!.widgetNeedUpdate || - _seriesRenderer._reAnimate) { + _segmentProperties.path = Path(); + if (_segmentProperties.series.animationDuration > 0 == true && + _segmentProperties.stateProperties.renderingDetails.isLegendToggled == + false) { + if (_segmentProperties + .stateProperties.renderingDetails.widgetNeedUpdate == + false || + seriesRendererDetails.reAnimate == true) { if (_isTransposed) { _centerX = _highX + ((_lowX - _highX) / 2); - _openX = _centerX - - ((_centerX - _currentPoint!.openPoint!.x) * animationFactor); - _closeX = _centerX + - ((_currentPoint!.closePoint!.x - _centerX) * animationFactor); + _segmentProperties.openX = _centerX - + ((_centerX - _segmentProperties.currentPoint!.openPoint!.x) * + animationFactor); + _segmentProperties.closeX = _centerX + + ((_segmentProperties.currentPoint!.closePoint!.x - _centerX) * + animationFactor); _highX = _centerX + ((_centerX - _highX).abs() * animationFactor); _lowX = _centerX - ((_lowX - _centerX).abs() * animationFactor); canvas.drawLine(Offset(_lowX, _centerLowPoint.y), Offset(_highX, _centerHighPoint.y), strokePaint!); canvas.drawLine( - Offset(_openX, _openY), Offset(_openX, _highY), strokePaint!); + Offset(_segmentProperties.openX, _segmentProperties.openY), + Offset(_segmentProperties.openX, _highY), + strokePaint!); canvas.drawLine( - Offset(_closeX, _lowY), Offset(_closeX, _closeY), strokePaint!); + Offset(_segmentProperties.closeX, _lowY), + Offset(_segmentProperties.closeX, _segmentProperties.closeY), + strokePaint!); } else { - _centerY = _high + ((_low - _high) / 2); - _openY = _centerY - - ((_centerY - _currentPoint!.openPoint!.y) * animationFactor); - _closeY = _centerY + - ((_currentPoint!.closePoint!.y - _centerY) * animationFactor); - _highY = _centerY - ((_centerY - _high) * animationFactor); - _lowY = _centerY + ((_low - _centerY) * animationFactor); + _centerY = _segmentProperties.high + + ((_segmentProperties.low - _segmentProperties.high) / 2); + _segmentProperties.openY = _centerY - + ((_centerY - _segmentProperties.currentPoint!.openPoint!.y) * + animationFactor); + _segmentProperties.closeY = _centerY + + ((_segmentProperties.currentPoint!.closePoint!.y - _centerY) * + animationFactor); + _highY = _centerY - + ((_centerY - _segmentProperties.high) * animationFactor); + _lowY = _centerY + + ((_segmentProperties.low - _centerY) * animationFactor); canvas.drawLine(Offset(_centerHigh, _highY), Offset(_centerLow, _lowY), strokePaint!); - canvas.drawLine(Offset(_openX, _openY), Offset(_centerHigh, _openY), + canvas.drawLine( + Offset(_segmentProperties.openX, _segmentProperties.openY), + Offset(_centerHigh, _segmentProperties.openY), + strokePaint!); + canvas.drawLine( + Offset(_centerLow, _segmentProperties.closeY), + Offset(_segmentProperties.closeX, _segmentProperties.closeY), strokePaint!); - canvas.drawLine(Offset(_centerLow, _closeY), - Offset(_closeX, _closeY), strokePaint!); } } else { - _currentSegment = _seriesRenderer._segments[currentSegmentIndex!] + _currentSegment = seriesRendererDetails.segments[currentSegmentIndex!] as HiloOpenCloseSegment; - _oldSegment = !_seriesRenderer._reAnimate && - (_currentSegment._oldSeriesRenderer != null && - _currentSegment - ._oldSeriesRenderer!._segments.isNotEmpty && - _currentSegment._oldSeriesRenderer!._segments[0] - is HiloOpenCloseSegment && - _currentSegment._oldSeriesRenderer!._segments.length - - 1 >= - currentSegmentIndex!) - ? _currentSegment._oldSeriesRenderer! - ._segments[currentSegmentIndex!] as HiloOpenCloseSegment? + final SeriesRendererDetails? oldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + _currentSegment._segmentProperties.oldSeriesRenderer!); + _oldSegment = seriesRendererDetails.reAnimate == false && + (_currentSegment._segmentProperties.oldSeriesRenderer != + null && + oldSeriesDetails!.segments.isNotEmpty == true && + oldSeriesDetails.segments[0] is HiloOpenCloseSegment && + oldSeriesDetails.segments.length - 1 >= + currentSegmentIndex! == + true) + ? oldSeriesDetails.segments[currentSegmentIndex!] + as HiloOpenCloseSegment? : null; - _animateHiloOpenCloseSeries( + animateHiloOpenCloseSeries( _isTransposed, - _isTransposed ? _lowPoint.x : _low, - _isTransposed ? _highPoint.x : _high, _isTransposed - ? (_oldSegment != null ? _oldSegment!._lowPoint.x : null) - : _oldSegment?._low, + ? _segmentProperties.lowPoint.x + : _segmentProperties.low, _isTransposed - ? (_oldSegment != null ? _oldSegment!._highPoint.x : null) - : _oldSegment?._high, - _openX, - _openY, - _closeX, - _closeY, + ? _segmentProperties.highPoint.x + : _segmentProperties.high, + _isTransposed + ? (_oldSegment != null + ? _oldSegment!._segmentProperties.lowPoint.x + : null) + : _oldSegment?._segmentProperties.low, + _isTransposed + ? (_oldSegment != null + ? _oldSegment!._segmentProperties.highPoint.x + : null) + : _oldSegment?._segmentProperties.high, + _segmentProperties.openX, + _segmentProperties.openY, + _segmentProperties.closeX, + _segmentProperties.closeY, _isTransposed ? _centerLowPoint.y : _centerLow, _isTransposed ? _centerHighPoint.y : _centerHigh, - _oldSegment?._openX, - _oldSegment?._openY, - _oldSegment?._closeX, - _oldSegment?._closeY, + _oldSegment?._segmentProperties.openX, + _oldSegment?._segmentProperties.openY, + _oldSegment?._segmentProperties.closeX, + _oldSegment?._segmentProperties.closeY, _isTransposed ? (_oldSegment != null ? _oldSegment!._centerLowPoint.y @@ -235,14 +285,24 @@ class HiloOpenCloseSegment extends ChartSegment { animationFactor, strokePaint!, canvas, - _seriesRenderer); + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer)); } } else { - (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, - _drawDashedHiloOpenClosePath(canvas)) + (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0) + ? drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, _drawDashedHiloOpenClosePath(canvas)) : drawHiloOpenClosePath(canvas); } } } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart index 108f7829e..f6500972b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/histogram_segment.dart @@ -1,4 +1,13 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import '../chart_series/histogram_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for column series. /// @@ -8,99 +17,74 @@ part of charts; /// It gets the path, stroke color and fill color from the `series` to render the column segment. /// class HistogramSegment extends ChartSegment { - /// Render path. - late Path _path; - late RRect _trackRect; - Paint? _trackerFillPaint, _trackerStrokePaint; - //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. /// Rectangle of the segment late RRect segmentRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); // final bool hasPointColor = series.pointColorMapper != null ? true : false; /// Get and set the paint options for column series. - if (_series.gradient == null) { - if (_color != null) { + if (_segmentProperties.series.gradient == null) { + if (_segmentProperties.color != null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.color - : ((_currentPoint!.pointColorMapper != null) - ? _currentPoint!.pointColorMapper! - : _color!) + ..color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.color + : ((_segmentProperties.currentPoint!.pointColorMapper != null) + ? _segmentProperties.currentPoint!.pointColorMapper! + : _segmentProperties.color!) ..style = PaintingStyle.fill; } } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.currentPoint!.region!, + _segmentProperties.stateProperties.requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the histogram series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the histogram series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - if (_series.borderGradient != null) { - strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!); - } else if (_strokeColor != null) { - strokePaint!.color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _strokeColor!; + ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderWidth + : _segmentProperties.strokeWidth!; + if (_segmentProperties.series.borderGradient != null) { + strokePaint!.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.currentPoint!.region!); + } else if (_segmentProperties.strokeColor != null) { + strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderColor + : _segmentProperties.strokeColor!; } - _defaultStrokeColor = strokePaint; - _series.borderWidth == 0 + _segmentProperties.defaultStrokeColor = strokePaint; + _segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; return strokePaint!; } - /// Method to get series tracker fill. - Paint _getTrackerFillPaint() { - final HistogramSeries histogramSeries = - _series as HistogramSeries; - if (_color != null) { - _trackerFillPaint = Paint() - ..color = histogramSeries.trackColor - ..style = PaintingStyle.fill; - } - return _trackerFillPaint!; - } - - /// Method to get series tracker stroke color. - Paint _getTrackerStrokePaint() { - final HistogramSeries histogramSeries = - _series as HistogramSeries; - _trackerStrokePaint = Paint() - ..color = histogramSeries.trackBorderColor - ..strokeWidth = histogramSeries.trackBorderWidth - ..style = PaintingStyle.stroke; - histogramSeries.trackBorderWidth == 0 - ? _trackerStrokePaint!.color = Colors.transparent - : _trackerStrokePaint!.color; - return _trackerStrokePaint!; - } - /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() {} @@ -108,44 +92,66 @@ class HistogramSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); final HistogramSeries histogramSeries = - _series as HistogramSeries; + _segmentProperties.series as HistogramSeries; - if (_trackerFillPaint != null && histogramSeries.isTrackVisible) { - _drawSegmentRect(_trackerFillPaint!, canvas, _trackRect); + if (_segmentProperties.trackerFillPaint != null && + histogramSeries.isTrackVisible) { + _drawSegmentRect(_segmentProperties.trackerFillPaint!, canvas, + _segmentProperties.trackRect); } - if (_trackerStrokePaint != null && histogramSeries.isTrackVisible) { - _drawSegmentRect(_trackerStrokePaint!, canvas, _trackRect); + if (_segmentProperties.trackerStrokePaint != null && + histogramSeries.isTrackVisible) { + _drawSegmentRect(_segmentProperties.trackerStrokePaint!, canvas, + _segmentProperties.trackRect); } if (fillPaint != null) { _drawSegmentRect(fillPaint!, canvas, segmentRect); } if (strokePaint != null) { - (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path) + (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0) + ? drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, _segmentProperties.path) : _drawSegmentRect(strokePaint!, canvas, segmentRect); } } /// To draw the rect of a given segment void _drawSegmentRect(Paint getPaint, Canvas canvas, RRect getRect) { - ((_renderingDetails.initialRender! || - _renderingDetails.isLegendToggled || - (_series.key != null && - _chartState._oldSeriesKeys.contains(_series.key))) && - _series.animationDuration > 0) - ? _animateRectSeries( + ((_segmentProperties.stateProperties.renderingDetails.initialRender! == + true || + _segmentProperties + .stateProperties.renderingDetails.isLegendToggled == + true || + (_segmentProperties.series.key != null && + _segmentProperties.stateProperties.oldSeriesKeys + .contains(_segmentProperties.series.key) == + true)) && + _segmentProperties.series.animationDuration > 0 == true) + ? animateRectSeries( canvas, - _seriesRenderer, + _segmentProperties.seriesRenderer, getPaint, getRect, - _currentPoint!.yValue, + _segmentProperties.currentPoint!.yValue, animationFactor, - _oldPoint != null ? _oldPoint!.region : _oldRegion, - _oldPoint?.yValue, - _oldSeriesVisible) + _segmentProperties.oldPoint != null + ? _segmentProperties.oldPoint!.region + : _segmentProperties.oldRegion, + _segmentProperties.oldPoint?.yValue, + _segmentProperties.oldSeriesVisible) : canvas.drawRRect(getRect, getPaint); } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart index 8bb715dcf..6f905afe0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/line_segment.dart @@ -1,4 +1,16 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import '../axis/axis.dart'; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for line series. /// @@ -11,178 +23,217 @@ part of charts; /// _Note:_ This is only applicable for [SfCartesianChart]. class LineSegment extends ChartSegment { /// segment points & old segment points - late double _x1, _y1, _x2, _y2; double? _oldX1, _oldY1, _oldX2, _oldY2; - /// Render path - late Path _path; - - Color? _pointColorMapper; - late bool _needAnimate, _newlyAddedSegment = false; late Rect _axisClipRect; - late _ChartLocation _first, - _second, - _currentPointLocation, - _nextPointLocation; + late ChartLocation _first, _second, _currentPointLocation, _nextPointLocation; - late ChartAxisRenderer _xAxisRenderer, _yAxisRenderer; + late ChartAxisRendererDetails _xAxisRenderer, _yAxisRenderer; ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; late LineSegment _currentSegment; LineSegment? _oldSegment; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); + final Paint _fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the line series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the line series should be less than or equal to 1.'); - if (_color != null) { - _fillPaint.color = - _pointColorMapper ?? _color!.withOpacity(_series.opacity); + if (_segmentProperties.color != null) { + _fillPaint.color = _segmentProperties.pointColorMapper ?? + _segmentProperties.color! + .withOpacity(_segmentProperties.series.opacity); } - _fillPaint.strokeWidth = _strokeWidth!; + _fillPaint.strokeWidth = _segmentProperties.strokeWidth!; _fillPaint.style = PaintingStyle.fill; - _defaultFillColor = _fillPaint; + _segmentProperties.defaultFillColor = _fillPaint; return _fillPaint; } /// Gets the stroke color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); + final Paint strokePaint = Paint(); - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the line series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the line series should be less than or equal to 1.'); - if (_strokeColor != null) { - strokePaint.color = _pointColorMapper ?? _strokeColor!; - strokePaint.color = - (_series.opacity < 1 && strokePaint.color != Colors.transparent) - ? strokePaint.color.withOpacity(_series.opacity) - : strokePaint.color; + if (_segmentProperties.strokeColor != null) { + strokePaint.color = _segmentProperties.pointColorMapper ?? + _segmentProperties.strokeColor!; + strokePaint.color = (_segmentProperties.series.opacity < 1 == true && + strokePaint.color != Colors.transparent) + ? strokePaint.color.withOpacity(_segmentProperties.series.opacity) + : strokePaint.color; } - strokePaint.strokeWidth = _strokeWidth!; + strokePaint.strokeWidth = _segmentProperties.strokeWidth!; strokePaint.style = PaintingStyle.stroke; strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; + setShader(_segmentProperties, strokePaint); return strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _xAxisRenderer = _seriesRenderer._xAxisRenderer!; - _yAxisRenderer = _seriesRenderer._yAxisRenderer!; - _axisClipRect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - _currentPointLocation = _calculatePoint( - _currentPoint!.xValue, - _currentPoint!.yValue, + _setSegmentProperties(); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); + _xAxisRenderer = seriesRendererDetails.xAxisDetails!; + _yAxisRenderer = seriesRendererDetails.yAxisDetails!; + _axisClipRect = calculatePlotOffset( + _segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + _currentPointLocation = calculatePoint( + _segmentProperties.currentPoint!.xValue, + _segmentProperties.currentPoint!.yValue, _xAxisRenderer, _yAxisRenderer, - _chartState._requireInvertedAxis, - _series, + _segmentProperties.stateProperties.requireInvertedAxis, + _segmentProperties.series, _axisClipRect); - _x1 = _currentPointLocation.x; - _y1 = _currentPointLocation.y; - _nextPointLocation = _calculatePoint( - _nextPoint!.xValue, - _nextPoint!.yValue, + _segmentProperties.x1 = _currentPointLocation.x; + _segmentProperties.y1 = _currentPointLocation.y; + _nextPointLocation = calculatePoint( + _segmentProperties.nextPoint!.xValue, + _segmentProperties.nextPoint!.yValue, _xAxisRenderer, _yAxisRenderer, - _chartState._requireInvertedAxis, - _series, + _segmentProperties.stateProperties.requireInvertedAxis, + _segmentProperties.series, _axisClipRect); - _x2 = _nextPointLocation.x; - _y2 = _nextPointLocation.y; + _segmentProperties.x2 = _nextPointLocation.x; + _segmentProperties.y2 = _nextPointLocation.y; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); double? prevX, prevY; LineSegment? prevSegment; - final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - _path = Path(); - if (_series.animationDuration > 0 && - !_seriesRenderer._reAnimate && - _oldSeriesRenderer != null && - _oldSeriesRenderer!._segments.isNotEmpty && - _oldSeriesRenderer!._segments[0] is LineSegment && - _seriesRenderer._chartState!._oldSeriesRenderers.length - 1 >= - _seriesRenderer._segments[currentSegmentIndex!]._seriesIndex && - _seriesRenderer._segments[currentSegmentIndex!]._oldSeriesRenderer! - ._segments.isNotEmpty) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer); + + final Rect rect = calculatePlotOffset( + _segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + _segmentProperties.path = Path(); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[currentSegmentIndex!]); + if (_segmentProperties.series.animationDuration > 0 == true && + seriesRendererDetails.reAnimate == false && + _segmentProperties.oldSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.oldSeriesRenderer!) + .segments + .isNotEmpty == + true && + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.oldSeriesRenderer!) + .segments[0] is LineSegment && + _segmentProperties.stateProperties.oldSeriesRenderers.length - 1 >= + segmentProperties.seriesIndex == + true && + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!) + .segments + .isNotEmpty == + true) { _currentSegment = - _seriesRenderer._segments[currentSegmentIndex!] as LineSegment; - _oldSegment = (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex!) - ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] - as LineSegment? - : null; + seriesRendererDetails.segments[currentSegmentIndex!] as LineSegment; + final SeriesRendererDetails oldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + SegmentHelper.getSegmentProperties(_currentSegment) + .oldSeriesRenderer!); + _oldSegment = + (oldSeriesDetails.segments.length - 1 >= currentSegmentIndex! == true) + ? oldSeriesDetails.segments[currentSegmentIndex!] as LineSegment? + : null; if (currentSegmentIndex! > 0) { prevSegment = - (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex! - 1) - ? _currentSegment._oldSeriesRenderer! - ._segments[currentSegmentIndex! - 1] as LineSegment? + (oldSeriesDetails.segments.length - 1 >= currentSegmentIndex! - 1 == + true) + ? oldSeriesDetails.segments[currentSegmentIndex! - 1] + as LineSegment? : null; } - _oldX1 = _oldSegment?._x1; - _oldY1 = _oldSegment?._y1; - _oldX2 = _oldSegment?._x2; - _oldY2 = _oldSegment?._y2; - if (_oldSegment == null && _renderingDetails.widgetNeedUpdate) { + _oldX1 = _oldSegment?._segmentProperties.x1; + _oldY1 = _oldSegment?._segmentProperties.y1; + _oldX2 = _oldSegment?._segmentProperties.x2; + _oldY2 = _oldSegment?._segmentProperties.y2; + if (_oldSegment == null && + _segmentProperties + .stateProperties.renderingDetails.widgetNeedUpdate == + true) { _newlyAddedSegment = true; - prevX = prevSegment?._x2; - prevY = prevSegment?._y2; + prevX = prevSegment?._segmentProperties.x2; + prevY = prevSegment?._segmentProperties.y2; } else { _newlyAddedSegment = false; } if (_oldSegment != null && (_oldX1!.isNaN || _oldX2!.isNaN) && - _seriesRenderer._chartState!._oldAxisRenderers.isNotEmpty) { - _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer!, - _seriesRenderer._chartState!._oldAxisRenderers); - _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._oldAxisRenderers); + _segmentProperties.stateProperties.oldAxisRenderers.isNotEmpty == + true) { + _oldXAxisRenderer = getOldAxisRenderer( + seriesRendererDetails.xAxisDetails!.axisRenderer, + _segmentProperties.stateProperties.oldAxisRenderers); + _oldYAxisRenderer = getOldAxisRenderer( + seriesRendererDetails.yAxisDetails!.axisRenderer, + _segmentProperties.stateProperties.oldAxisRenderers); if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - _needAnimate = _oldYAxisRenderer!._visibleRange!.minimum != - _seriesRenderer._yAxisRenderer!._visibleRange!.minimum || - _oldYAxisRenderer!._visibleRange!.maximum != - _seriesRenderer._yAxisRenderer!._visibleRange!.maximum || - _oldXAxisRenderer!._visibleRange!.minimum != - _seriesRenderer._xAxisRenderer!._visibleRange!.minimum || - _oldXAxisRenderer!._visibleRange!.maximum != - _seriesRenderer._xAxisRenderer!._visibleRange!.maximum; + final ChartAxisRendererDetails _oldXAxisDetails = + AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); + final ChartAxisRendererDetails _oldYAxisDetails = + AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); + _needAnimate = _oldYAxisDetails.visibleRange!.minimum != + seriesRendererDetails.yAxisDetails!.visibleRange!.minimum || + _oldYAxisDetails.visibleRange!.maximum != + seriesRendererDetails.yAxisDetails!.visibleRange!.maximum || + _oldXAxisDetails.visibleRange!.minimum != + seriesRendererDetails.xAxisDetails!.visibleRange!.minimum || + _oldXAxisDetails.visibleRange!.maximum != + seriesRendererDetails.xAxisDetails!.visibleRange!.maximum; } if (_needAnimate) { - _first = _calculatePoint( - _currentPoint!.xValue, - _currentPoint!.yValue, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartAxisRendererDetails _oldXAxisDetails = + AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); + final ChartAxisRendererDetails _oldYAxisDetails = + AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); + _first = calculatePoint( + _segmentProperties.currentPoint!.xValue, + _segmentProperties.currentPoint!.yValue, + _oldXAxisDetails, + _oldYAxisDetails, + _segmentProperties.stateProperties.requireInvertedAxis, + _segmentProperties.series, rect); - _second = _calculatePoint( - _nextPoint!.xValue, - _nextPoint!.yValue, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + _second = calculatePoint( + _segmentProperties.nextPoint!.xValue, + _segmentProperties.nextPoint!.yValue, + _oldXAxisDetails, + _oldYAxisDetails, + _segmentProperties.stateProperties.requireInvertedAxis, + _segmentProperties.series, rect); _oldX1 = _first.x; _oldX2 = _second.x; @@ -191,39 +242,54 @@ class LineSegment extends ChartSegment { } } _newlyAddedSegment - ? _animateToPoint( + ? animateToPoint( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer), strokePaint!, animationFactor, - _currentSegment._x1, - _currentSegment._y1, - _currentSegment._x2, - _currentSegment._y2, + _currentSegment._segmentProperties.x1, + _currentSegment._segmentProperties.y1, + _currentSegment._segmentProperties.x2, + _currentSegment._segmentProperties.y2, prevX, prevY) - : _animateLineTypeSeries( + : animateLineTypeSeries( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer), strokePaint!, animationFactor, - _currentSegment._x1, - _currentSegment._y1, - _currentSegment._x2, - _currentSegment._y2, + _currentSegment._segmentProperties.x1, + _currentSegment._segmentProperties.y1, + _currentSegment._segmentProperties.x2, + _currentSegment._segmentProperties.y2, _oldX1, _oldY1, _oldX2, _oldY2, ); } else { - if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _path.moveTo(_x1, _y1); - _path.lineTo(_x2, _y2); - _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); + if (_segmentProperties.series.dashArray[0] != 0 && + _segmentProperties.series.dashArray[1] != 0) { + _segmentProperties.path + .moveTo(_segmentProperties.x1, _segmentProperties.y1); + _segmentProperties.path + .lineTo(_segmentProperties.x2, _segmentProperties.y2); + drawDashedLine(canvas, _segmentProperties.series.dashArray, + strokePaint!, _segmentProperties.path); } else { - canvas.drawLine(Offset(_x1, _y1), Offset(_x2, _y2), strokePaint!); + canvas.drawLine(Offset(_segmentProperties.x1, _segmentProperties.y1), + Offset(_segmentProperties.x2, _segmentProperties.y2), strokePaint!); } } } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart index bbd724132..5774f1a82 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_area_segment.dart @@ -1,63 +1,76 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/range_area_series.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for range area series. /// -/// Generates the range area series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the range area series points and has the [calculateSegmentPoints] method overrides to customize /// the range area segment point calculation. /// /// Gets the path and color from the `series`. class RangeAreaSegment extends ChartSegment { - late Path _path; - Path? _borderPath; - - ///For storing the path in RangeAreaBorderMode.excludeSides mode - Rect? _pathRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); fillPaint = Paint(); - if (_series.gradient == null) { - if (_color != null) { - fillPaint!.color = _color!; + if (_segmentProperties.series.gradient == null) { + if (_segmentProperties.color != null) { + fillPaint!.color = _segmentProperties.color!; fillPaint!.style = PaintingStyle.fill; } } else { - fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient!, _pathRect!, - _seriesRenderer._chartState!._requireInvertedAxis) + fillPaint = (_segmentProperties.pathRect != null) + ? getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.pathRect!, + _segmentProperties.stateProperties.requireInvertedAxis) : fillPaint; } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the range area series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the range area series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); final Paint _strokePaint = Paint(); _strokePaint ..style = PaintingStyle.stroke - ..strokeWidth = _series.borderWidth; - if (_series.borderGradient != null && _borderPath != null) { - _strokePaint.shader = - _series.borderGradient!.createShader(_borderPath!.getBounds()); - } else if (_strokeColor != null) { - _strokePaint.color = _series.borderColor; + ..strokeWidth = _segmentProperties.series.borderWidth; + if (_segmentProperties.series.borderGradient != null && + _segmentProperties.borderPath != null) { + _strokePaint.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.borderPath!.getBounds()); + } else if (_segmentProperties.strokeColor != null) { + _strokePaint.color = _segmentProperties.series.borderColor; } - _series.borderWidth == 0 + _segmentProperties.series.borderWidth == 0 ? _strokePaint.color = Colors.transparent : _strokePaint.color; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + _segmentProperties.defaultStrokeColor = _strokePaint; return _strokePaint; } @@ -68,20 +81,29 @@ class RangeAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); final RangeAreaSeries _series = - this._series as RangeAreaSeries; - _pathRect = _path.getBounds(); - canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); + _segmentProperties.series as RangeAreaSeries; + _segmentProperties.pathRect = _segmentProperties.path.getBounds(); + canvas.drawPath(_segmentProperties.path, + (_series.gradient == null) ? fillPaint! : getFillPaint()); strokePaint = getStrokePaint(); if (strokePaint!.color != Colors.transparent) { - _drawDashedLine( + drawDashedLine( canvas, _series.dashArray, strokePaint!, _series.borderDrawMode == RangeAreaBorderMode.all - ? _path - : _borderPath!); + ? _segmentProperties.path + : _segmentProperties.borderPath!); + } + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart index 5aed9bce0..7b6de40d4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/range_column_segment.dart @@ -1,104 +1,88 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import '../chart_series/range_column_series.dart'; +import '../chart_series/series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for range column series. /// -/// Generates the range column series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the range column series points and has the [calculateSegmentPoints] method overrides to customize /// the range column segment point calculation. /// /// Gets the path and color from the `series`. class RangeColumnSegment extends ChartSegment { - //ignore: unused_field - late double _x1, _low1, _high1; - - ///Path of the series - late Path _path; - late RRect _trackRect; - Paint? _trackerFillPaint, _trackerStrokePaint; - //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. /// Rectangle of the segment late RRect segmentRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { - final bool hasPointColor = _series.pointColorMapper != null; + _setSegmentProperties(); + final bool hasPointColor = + _segmentProperties.series.pointColorMapper != null; /// Get and set the paint options for range column series. - if (_series.gradient == null) { + if (_segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.color - : ((hasPointColor && _currentPoint!.pointColorMapper != null) - ? _currentPoint!.pointColorMapper - : _color)! + ..color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.color + : ((hasPointColor && + _segmentProperties.currentPoint!.pointColorMapper != null) + ? _segmentProperties.currentPoint!.pointColorMapper + : _segmentProperties.color)! ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.currentPoint!.region!, + _segmentProperties.stateProperties.requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the range column series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the range column series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint!; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint!; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - _defaultStrokeColor = strokePaint; - _series.borderGradient != null - ? strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!) - : strokePaint!.color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _strokeColor!; - _series.borderWidth == 0 + ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderWidth + : _segmentProperties.strokeWidth!; + _segmentProperties.defaultStrokeColor = strokePaint; + _segmentProperties.series.borderGradient != null + ? strokePaint!.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.currentPoint!.region!) + : strokePaint!.color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderColor + : _segmentProperties.strokeColor!; + _segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; return strokePaint!; } - /// Method to get series tracker fill. - Paint _getTrackerFillPaint() { - final RangeColumnSeries _series = - this._series as RangeColumnSeries; - - _trackerFillPaint = Paint() - ..color = _series.trackColor - ..style = PaintingStyle.fill; - - return _trackerFillPaint!; - } - - /// Method to get series tracker stroke color. - Paint _getTrackerStrokePaint() { - final RangeColumnSeries _series = - this._series as RangeColumnSeries; - _trackerStrokePaint = Paint() - ..color = _series.trackBorderColor - ..strokeWidth = _series.trackBorderWidth - ..style = PaintingStyle.stroke; - _series.trackBorderWidth == 0 - ? _trackerStrokePaint!.color = Colors.transparent - : _trackerStrokePaint!.color; - return _trackerStrokePaint!; - } - /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() {} @@ -106,42 +90,65 @@ class RangeColumnSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); final RangeColumnSeries _series = - this._series as RangeColumnSeries; + _segmentProperties.series as RangeColumnSeries; - if (_trackerFillPaint != null && _series.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerFillPaint!); + if (_segmentProperties.trackerFillPaint != null && _series.isTrackVisible) { + canvas.drawRRect( + _segmentProperties.trackRect, _segmentProperties.trackerFillPaint!); } - if (_trackerStrokePaint != null && _series.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerStrokePaint!); + if (_segmentProperties.trackerStrokePaint != null && + _series.isTrackVisible) { + canvas.drawRRect( + _segmentProperties.trackRect, _segmentProperties.trackerStrokePaint!); } if (fillPaint != null) { (_series.animationDuration > 0 && - !_seriesRenderer._renderingDetails!.isLegendToggled) - ? _animateRangeColumn( + _segmentProperties + .stateProperties.renderingDetails.isLegendToggled == + false) + ? animateRangeColumn( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer), fillPaint!, segmentRect, - _oldPoint != null ? _oldPoint!.region : _oldRegion, + _segmentProperties.oldPoint != null + ? _segmentProperties.oldPoint!.region + : _segmentProperties.oldRegion, animationFactor) : canvas.drawRRect(segmentRect, fillPaint!); } if (strokePaint != null) { (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path) + ? drawDashedLine( + canvas, _series.dashArray, strokePaint!, _segmentProperties.path) : (_series.animationDuration > 0 && - !_seriesRenderer._renderingDetails!.isLegendToggled) - ? _animateRangeColumn( + _segmentProperties + .stateProperties.renderingDetails.isLegendToggled == + false) + ? animateRangeColumn( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer), strokePaint!, segmentRect, - _oldPoint != null ? _oldPoint!.region : _oldRegion, + _segmentProperties.oldPoint != null + ? _segmentProperties.oldPoint!.region + : _segmentProperties.oldRegion, animationFactor) : canvas.drawRRect(segmentRect, strokePaint!); } } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart index 9f4651b57..53c14646e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/scatter_segment.dart @@ -1,64 +1,86 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../series_painter/scatter_painter.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for scatter series. /// -/// Generates the scatter series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the scatter series points and has the [calculateSegmentPoints] method overrides to customize /// the scatter segment point calculation. /// /// Gets the path and color from the `series`. class ScatterSegment extends ChartSegment { + late SegmentProperties _segmentProperties; + bool _isInitialize = false; + /// Gets the color of the series. @override Paint getFillPaint() { - final bool hasPointColor = _series.pointColorMapper != null; - if (_series.gradient == null) { - if (_color != null) { + _setSegmentProperties(); + final bool hasPointColor = + _segmentProperties.series.pointColorMapper != null; + if (_segmentProperties.series.gradient == null) { + if (_segmentProperties.color != null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.color - : ((hasPointColor && _currentPoint!.pointColorMapper != null) - ? _currentPoint!.pointColorMapper - : _color)! + ..color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.color + : ((hasPointColor && + _segmentProperties.currentPoint!.pointColorMapper != null) + ? _segmentProperties.currentPoint!.pointColorMapper + : _segmentProperties.color)! ..style = PaintingStyle.fill; } } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.currentPoint!.region!, + _segmentProperties.stateProperties.requireInvertedAxis); } - _defaultFillColor = fillPaint; - assert(_series.opacity >= 0, + _segmentProperties.defaultFillColor = fillPaint; + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the the scatter series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the the scatter series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); final ScatterSeriesRenderer _scatterRenderer = - _seriesRenderer as ScatterSeriesRenderer; + _segmentProperties.seriesRenderer as ScatterSeriesRenderer; final Paint strokePaint = Paint() - ..color = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderColor - : _series.markerSettings.isVisible - ? _series.markerSettings.borderColor ?? - _seriesRenderer._seriesColor! - : _strokeColor! + ..color = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderColor + : _segmentProperties.series.markerSettings.isVisible == true + ? _segmentProperties.series.markerSettings.borderColor ?? + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer) + .seriesColor! + : _segmentProperties.strokeColor! ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty == true - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - (strokePaint.strokeWidth == 0 && !_scatterRenderer._isLineType) + ..strokeWidth = _segmentProperties.currentPoint!.isEmpty == true + ? _segmentProperties.series.emptyPointSettings.borderWidth + : _segmentProperties.strokeWidth!; + (strokePaint.strokeWidth == 0 && + SeriesHelper.getSeriesRendererDetails(_scatterRenderer) + .isLineType == + false) ? strokePaint.color = Colors.transparent : strokePaint.color; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; return strokePaint; } @@ -69,27 +91,39 @@ class ScatterSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); if (fillPaint != null) { - _series.animationDuration > 0 && - !_seriesRenderer._renderingDetails!.isLegendToggled - ? _animateScatterSeries( - _seriesRenderer, - _point!, - _oldPoint, + _segmentProperties.series.animationDuration > 0 == true && + _segmentProperties + .stateProperties.renderingDetails.isLegendToggled == + false + ? animateScatterSeries( + SeriesHelper.getSeriesRendererDetails( + _segmentProperties.seriesRenderer), + _segmentProperties.point!, + _segmentProperties.oldPoint, animationFactor, canvas, fillPaint!, strokePaint!, currentSegmentIndex!, this) - : _seriesRenderer.drawDataMarker( + : _segmentProperties.seriesRenderer.drawDataMarker( currentSegmentIndex!, canvas, fillPaint!, strokePaint!, - _point!.markerPoint!.x, - _point!.markerPoint!.y, - _seriesRenderer); + _segmentProperties.point!.markerPoint!.x, + _segmentProperties.point!.markerPoint!.y, + _segmentProperties.seriesRenderer); + } + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart index a411d78ed..ddd0946c5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_area_segment.dart @@ -1,4 +1,12 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for spline area series. /// @@ -7,54 +15,59 @@ part of charts; /// /// Gets the path and color from the `series`. class SplineAreaSegment extends ChartSegment { - late Path _path, _strokePath; - Rect? _pathRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); fillPaint = Paint(); - if (_series.gradient == null) { - if (_color != null) { - fillPaint!.color = _color!; + if (_segmentProperties.series.gradient == null) { + if (_segmentProperties.color != null) { + fillPaint!.color = _segmentProperties.color!; fillPaint!.style = PaintingStyle.fill; } } else { - fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient!, _pathRect!, - _seriesRenderer._chartState!._requireInvertedAxis) + fillPaint = (_segmentProperties.pathRect != null) + ? getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.pathRect!, + _segmentProperties.stateProperties.requireInvertedAxis) : fillPaint; } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the spline area series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the spline area series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); final Paint strokePaint = Paint(); strokePaint ..style = PaintingStyle.stroke - ..strokeWidth = _series.borderWidth; - if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient!.createShader(_strokePath.getBounds()); - } else if (_strokeColor != null) { - strokePaint.color = _series.borderColor; + ..strokeWidth = _segmentProperties.series.borderWidth; + if (_segmentProperties.series.borderGradient != null) { + strokePaint.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.strokePath!.getBounds()); + } else if (_segmentProperties.strokeColor != null) { + strokePaint.color = _segmentProperties.series.borderColor; } - _series.borderWidth == 0 + _segmentProperties.series.borderWidth == 0 ? strokePaint.color = Colors.transparent : strokePaint.color; strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; return strokePaint; } @@ -65,9 +78,22 @@ class SplineAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _pathRect = _path.getBounds(); + _setSegmentProperties(); + _segmentProperties.pathRect = _segmentProperties.path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); - _drawDashedLine(canvas, _series.dashArray, strokePaint!, _strokePath); + _segmentProperties.path, + (_segmentProperties.series.gradient == null) + ? fillPaint! + : getFillPaint()); + drawDashedLine(canvas, _segmentProperties.series.dashArray, strokePaint!, + _segmentProperties.strokePath!); + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; + } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart index 865fa8a9e..ab303833c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_range_area_segment.dart @@ -1,63 +1,77 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/series.dart'; +import '../chart_series/spline_range_area_series.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for spline area series. /// -/// Generates the spline area series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the spline area series points and has the [calculateSegmentPoints] method overrides to customize /// the spline area segment point calculation. /// /// Gets the path and color from the `series`. class SplineRangeAreaSegment extends ChartSegment { - /// Path _borderPath; - late Path _path, _strokePath; - - ///For storing the path in RangeAreaBorderMode.excludeSides mode - Rect? _pathRect; + late SegmentProperties _segmentProperties; + bool _isInitialize = false; /// Gets the color of the series. @override Paint getFillPaint() { + _setSegmentProperties(); fillPaint = Paint(); - if (_series.gradient == null) { - if (_color != null) { - fillPaint!.color = _color!; + if (_segmentProperties.series.gradient == null) { + if (_segmentProperties.color != null) { + fillPaint!.color = _segmentProperties.color!; fillPaint!.style = PaintingStyle.fill; } } else { - fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient!, _pathRect!, - _seriesRenderer._chartState!._requireInvertedAxis) + fillPaint = (_segmentProperties.pathRect != null) + ? getLinearGradientPaint( + _segmentProperties.series.gradient!, + _segmentProperties.pathRect!, + _segmentProperties.stateProperties.requireInvertedAxis) : fillPaint; } - assert(_series.opacity >= 0, + assert(_segmentProperties.series.opacity >= 0 == true, 'The opacity value of the spline range area series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(_segmentProperties.series.opacity <= 1 == true, 'The opacity value of the spline range area series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (_segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(_segmentProperties.series.opacity) + : fillPaint!.color; + _segmentProperties.defaultFillColor = fillPaint; + setShader(_segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + _setSegmentProperties(); + final Paint strokePaint = Paint(); strokePaint ..style = PaintingStyle.stroke - ..strokeWidth = _series.borderWidth; - if (_series.borderGradient != null) { - strokePaint.shader = - _series.borderGradient!.createShader(_strokePath.getBounds()); - } else if (_strokeColor != null) { - strokePaint.color = _series.borderColor; + ..strokeWidth = _segmentProperties.series.borderWidth; + if (_segmentProperties.series.borderGradient != null) { + strokePaint.shader = _segmentProperties.series.borderGradient! + .createShader(_segmentProperties.strokePath!.getBounds()); + } else if (_segmentProperties.strokeColor != null) { + strokePaint.color = _segmentProperties.series.borderColor; } - _series.borderWidth == 0 + _segmentProperties.series.borderWidth == 0 ? strokePaint.color = Colors.transparent : strokePaint.color; strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = strokePaint; + _segmentProperties.defaultStrokeColor = strokePaint; return strokePaint; } @@ -68,19 +82,32 @@ class SplineRangeAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + _setSegmentProperties(); final SplineRangeAreaSeries splineRangeAreaSeries = - _seriesRenderer._series as SplineRangeAreaSeries; - _pathRect = _path.getBounds(); + SeriesHelper.getSeriesRendererDetails(_segmentProperties.seriesRenderer) + .series as SplineRangeAreaSeries; + _segmentProperties.pathRect = _segmentProperties.path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); + _segmentProperties.path, + (_segmentProperties.series.gradient == null) + ? fillPaint! + : getFillPaint()); if (strokePaint!.color != Colors.transparent) { - _drawDashedLine( + drawDashedLine( canvas, - _series.dashArray, + _segmentProperties.series.dashArray, strokePaint!, splineRangeAreaSeries.borderDrawMode == RangeAreaBorderMode.all - ? _path - : _strokePath); + ? _segmentProperties.path + : _segmentProperties.strokePath!); + } + } + + /// Method to set segment properties + void _setSegmentProperties() { + if (!_isInitialize) { + _segmentProperties = SegmentHelper.getSegmentProperties(this); + _isInitialize = true; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart index c324dd804..0c37a2659 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/spline_segment.dart @@ -1,13 +1,27 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for spline series. /// -/// Generates the spline series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the spline series points and has the [calculateSegmentPoints] method overrides to customize /// the spline segment point calculation. /// /// Gets the path and color from the `series`. class SplineSegment extends ChartSegment { - late double _x1, _y1, _x2, _y2; double? _oldX1, _oldY1, _oldX2, _oldY2, _oldX3, _oldY3, _oldX4, _oldY4; /// Start point X value @@ -22,132 +36,178 @@ class SplineSegment extends ChartSegment { /// End point Y value double? endControlY; - Color? _pointColorMapper; - late _ChartLocation _currentPointLocation, _nextPointLocation; - late ChartAxisRenderer _xAxisRenderer, _yAxisRenderer; + ChartLocation? _currentPointLocation, _nextPointLocation; + late ChartAxisRendererDetails _xAxisRenderer, _yAxisRenderer; ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; late Rect _axisClipRect; - late SplineSegment _currentSegment; + SplineSegment? _currentSegment; SplineSegment? _oldSegment; late bool _needAnimate; /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the spline series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the spline series should be less than or equal to 1.'); - if (_strokeColor != null) { - _fillPaint.color = _strokeColor!.withOpacity(_series.opacity); + if (segmentProperties.strokeColor != null) { + _fillPaint.color = segmentProperties.strokeColor! + .withOpacity(segmentProperties.series.opacity); } - _fillPaint.strokeWidth = _strokeWidth!; + _fillPaint.strokeWidth = segmentProperties.strokeWidth!; _fillPaint.style = PaintingStyle.stroke; - _defaultFillColor = _fillPaint; + segmentProperties.defaultFillColor = _fillPaint; return _fillPaint; } /// Gets the stroke color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _strokePaint = Paint(); - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the spline series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the spline series should be less than or equal to 1.'); - if (_strokeColor != null) { - _strokePaint.color = - _pointColorMapper ?? _strokeColor!.withOpacity(_series.opacity); - _strokePaint.color = - (_series.opacity < 1 && _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(_series.opacity) - : _strokePaint.color; + if (segmentProperties.strokeColor != null) { + _strokePaint.color = segmentProperties.pointColorMapper ?? + segmentProperties.strokeColor! + .withOpacity(segmentProperties.series.opacity); + _strokePaint.color = (segmentProperties.series.opacity < 1 == true && + _strokePaint.color != Colors.transparent) + ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) + : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth!; + _strokePaint.strokeWidth = segmentProperties.strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + segmentProperties.defaultStrokeColor = _strokePaint; + setShader(segmentProperties, _strokePaint); return _strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - _xAxisRenderer = _seriesRenderer._xAxisRenderer!; - _yAxisRenderer = _seriesRenderer._yAxisRenderer!; - _axisClipRect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - _currentPointLocation = _calculatePoint( - _currentPoint!.xValue, - _currentPoint!.yValue, + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + final SeriesRendererDetails segmentSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + _xAxisRenderer = segmentSeriesRendererDetails.xAxisDetails!; + _yAxisRenderer = segmentSeriesRendererDetails.yAxisDetails!; + _axisClipRect = calculatePlotOffset( + segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, + segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); + _currentPointLocation = calculatePoint( + segmentProperties.currentPoint!.xValue, + segmentProperties.currentPoint!.yValue, _xAxisRenderer, _yAxisRenderer, - _chartState._requireInvertedAxis, - _series, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, _axisClipRect); - _x1 = _currentPointLocation.x; - _y1 = _currentPointLocation.y; - _nextPointLocation = _calculatePoint( - _nextPoint!.xValue, - _nextPoint!.yValue, + segmentProperties.x1 = _currentPointLocation!.x; + segmentProperties.y1 = _currentPointLocation!.y; + _nextPointLocation = calculatePoint( + segmentProperties.nextPoint!.xValue, + segmentProperties.nextPoint!.yValue, _xAxisRenderer, _yAxisRenderer, - _chartState._requireInvertedAxis, - _series, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, _axisClipRect); - _x2 = _nextPointLocation.x; - _y2 = _nextPointLocation.y; + segmentProperties.x2 = _nextPointLocation!.x; + segmentProperties.y2 = _nextPointLocation!.y; - startControlX = _currentPoint!.startControl!.x; - startControlY = _currentPoint!.startControl!.y; - endControlX = _currentPoint!.endControl!.x; - endControlY = _currentPoint!.endControl!.y; + startControlX = segmentProperties.currentPoint!.startControl!.x; + startControlY = segmentProperties.currentPoint!.startControl!.y; + endControlX = segmentProperties.currentPoint!.endControl!.x; + endControlY = segmentProperties.currentPoint!.endControl!.y; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - if (_seriesRenderer._isSelectionEnable) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + final SeriesRendererDetails segmentSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (segmentSeriesRendererDetails.isSelectionEnable == true) { final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _seriesRenderer._segments[currentSegmentIndex!], - _seriesRenderer._chart); + segmentSeriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + segmentSeriesRendererDetails.segments[currentSegmentIndex!], + segmentSeriesRendererDetails.chart); } - final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - - /// Draw spline series - if (_series.animationDuration > 0 && - !_seriesRenderer._reAnimate && - _seriesRenderer._renderingDetails!.widgetNeedUpdate && - !_seriesRenderer._renderingDetails!.isLegendToggled && - _seriesRenderer._chartState!._oldSeriesRenderers.isNotEmpty && - _oldSeries != null && - _oldSeriesRenderer!._segments.isNotEmpty && - _oldSeriesRenderer!._segments[0] is SplineSegment && - _seriesRenderer._chartState!._oldSeriesRenderers.length - 1 >= - _seriesRenderer._segments[currentSegmentIndex!]._seriesIndex && - _seriesRenderer._segments[currentSegmentIndex!]._oldSeriesRenderer! - ._segments.isNotEmpty && - _currentPoint!.isGap != true && - _nextPoint!.isGap != true) { - _currentSegment = - _seriesRenderer._segments[currentSegmentIndex!] as SplineSegment; - _oldSegment = (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex!) - ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] + late Rect rect; + if (segmentSeriesRendererDetails.xAxisDetails != null && + segmentSeriesRendererDetails.yAxisDetails != null) { + rect = calculatePlotOffset( + segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, + segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); + } + + // Draw spline series + if (segmentProperties.series.animationDuration > 0 == true && + segmentSeriesRendererDetails.reAnimate == false && + segmentProperties.stateProperties.renderingDetails.widgetNeedUpdate == + true && + segmentProperties.stateProperties.renderingDetails.isLegendToggled == + false && + segmentProperties.stateProperties.oldSeriesRenderers.isNotEmpty == + true && + segmentProperties.oldSeries != null && + SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) + .segments + .isNotEmpty == + true && + SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) + .segments[0] is SplineSegment && + segmentProperties.stateProperties.oldSeriesRenderers.length - 1 >= + SegmentHelper.getSegmentProperties(segmentSeriesRendererDetails + .segments[currentSegmentIndex!]) + .seriesIndex == + true && + SeriesHelper.getSeriesRendererDetails(SegmentHelper.getSegmentProperties( + segmentSeriesRendererDetails + .segments[currentSegmentIndex!]) + .oldSeriesRenderer!) + .segments + .isNotEmpty == + true && + segmentProperties.currentPoint!.isGap != true && + segmentProperties.nextPoint!.isGap != true) { + _currentSegment = segmentSeriesRendererDetails + .segments[currentSegmentIndex!] as SplineSegment; + final SegmentProperties _currentSegmentProperties = + SegmentHelper.getSegmentProperties(_currentSegment!); + SegmentProperties? _oldSegmentProperties; + final SeriesRendererDetails _currentOldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _currentSegmentProperties.oldSeriesRenderer!); + _oldSegment = (_currentOldSeriesRendererDetails.segments.length - 1 >= + currentSegmentIndex! == + true) + ? _currentOldSeriesRendererDetails.segments[currentSegmentIndex!] as SplineSegment? : null; - _oldX1 = _oldSegment?._x1; - _oldY1 = _oldSegment?._y1; - _oldX2 = _oldSegment?._x2; - _oldY2 = _oldSegment?._y2; + if (_oldSegment != null) { + _oldSegmentProperties = + SegmentHelper.getSegmentProperties(_oldSegment!); + } + _oldX1 = _oldSegmentProperties?.x1; + _oldY1 = _oldSegmentProperties?.y1; + _oldX2 = _oldSegmentProperties?.x2; + _oldY2 = _oldSegmentProperties?.y2; _oldX3 = _oldSegment?.startControlX; _oldY3 = _oldSegment?.startControlY; _oldX4 = _oldSegment?.endControlX; @@ -155,95 +215,121 @@ class SplineSegment extends ChartSegment { if (_oldSegment != null && (_oldX1!.isNaN || _oldX2!.isNaN) && - _seriesRenderer._chartState!._oldAxisRenderers.isNotEmpty) { - _ChartLocation _oldPoint; - _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer!, - _seriesRenderer._chartState!._oldAxisRenderers); - _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._oldAxisRenderers); + segmentProperties.stateProperties.oldAxisRenderers.isNotEmpty == + true) { + ChartLocation _oldPoint; + _oldXAxisRenderer = getOldAxisRenderer( + segmentSeriesRendererDetails.xAxisDetails!.axisRenderer, + segmentProperties.stateProperties.oldAxisRenderers); + _oldYAxisRenderer = getOldAxisRenderer( + segmentSeriesRendererDetails.yAxisDetails!.axisRenderer, + segmentProperties.stateProperties.oldAxisRenderers); if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - _needAnimate = _oldYAxisRenderer!._visibleRange!.minimum != - _seriesRenderer._yAxisRenderer!._visibleRange!.minimum || - _oldYAxisRenderer!._visibleRange!.maximum != - _seriesRenderer._yAxisRenderer!._visibleRange!.maximum || - _oldXAxisRenderer!._visibleRange!.minimum != - _seriesRenderer._xAxisRenderer!._visibleRange!.minimum || - _oldXAxisRenderer!._visibleRange!.maximum != - _seriesRenderer._xAxisRenderer!._visibleRange!.maximum; + final ChartAxisRendererDetails _oldXAxisDetails = + AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); + final ChartAxisRendererDetails _oldYAxisDetails = + AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); + _needAnimate = _oldYAxisDetails.visibleRange!.minimum != + segmentSeriesRendererDetails + .yAxisDetails!.visibleRange!.minimum || + _oldYAxisDetails.visibleRange!.maximum != + segmentSeriesRendererDetails + .yAxisDetails!.visibleRange!.maximum || + _oldXAxisDetails.visibleRange!.minimum != + segmentSeriesRendererDetails + .xAxisDetails!.visibleRange!.minimum || + _oldXAxisDetails.visibleRange!.maximum != + segmentSeriesRendererDetails + .xAxisDetails!.visibleRange!.maximum; } if (_needAnimate) { - _oldPoint = _calculatePoint( - _currentPoint!.xValue, - _currentPoint!.yValue, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartAxisRendererDetails _oldXAxisDetails = + AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); + final ChartAxisRendererDetails _oldYAxisDetails = + AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); + _oldPoint = calculatePoint( + segmentProperties.currentPoint!.xValue, + segmentProperties.currentPoint!.yValue, + _oldXAxisDetails, + _oldYAxisDetails, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); _oldX1 = _oldPoint.x; _oldY1 = _oldPoint.y; - _oldPoint = _calculatePoint( - _nextPoint!.xValue, - _nextPoint!.xValue, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + _oldPoint = calculatePoint( + segmentProperties.nextPoint!.xValue, + segmentProperties.nextPoint!.xValue, + _oldXAxisDetails, + _oldYAxisDetails, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); _oldX2 = _oldPoint.x; _oldY2 = _oldPoint.y; - _oldPoint = _calculatePoint( + _oldPoint = calculatePoint( startControlX!, startControlY!, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + _oldXAxisDetails, + _oldYAxisDetails, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); _oldX3 = _oldPoint.x; _oldY3 = _oldPoint.y; - _oldPoint = _calculatePoint( + _oldPoint = calculatePoint( endControlX!, endControlY!, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + _oldXAxisDetails, + _oldYAxisDetails, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); _oldX4 = _oldPoint.x; _oldY4 = _oldPoint.y; } } - _animateLineTypeSeries( + animateLineTypeSeries( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer), strokePaint!, animationFactor, - _currentSegment._x1, - _currentSegment._y1, - _currentSegment._x2, - _currentSegment._y2, + _currentSegmentProperties.x1, + _currentSegmentProperties.y1, + _currentSegmentProperties.x2, + _currentSegmentProperties.y2, _oldX1, _oldY1, _oldX2, _oldY2, - _currentSegment.startControlX, - _currentSegment.startControlY, + _currentSegment!.startControlX, + _currentSegment!.startControlY, _oldX3, _oldY3, - _currentSegment.endControlX, - _currentSegment.endControlY, + _currentSegment!.endControlX, + _currentSegment!.endControlY, _oldX4, _oldY4, ); } else { final Path path = Path(); - path.moveTo(_x1, _y1); - if (_currentPoint!.isGap != true && _nextPoint!.isGap != true) { + path.moveTo(segmentProperties.x1, segmentProperties.y1); + if (segmentProperties.currentPoint!.isGap != true && + segmentProperties.nextPoint!.isGap != true) { path.cubicTo(startControlX!, startControlY!, endControlX!, endControlY!, - _x2, _y2); - _drawDashedLine(canvas, _series.dashArray, strokePaint!, path); + segmentProperties.x2, segmentProperties.y2); + drawDashedLine( + canvas, segmentProperties.series.dashArray, strokePaint!, path); } } } + + @override + void dispose() { + _currentPointLocation = null; + _nextPointLocation = null; + _currentSegment = null; + _oldSegment = null; + super.dispose(); + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart index fc74b880c..6dcacdd72 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_area_segment.dart @@ -1,61 +1,72 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for stacked area series. /// -/// Generates the stacked area series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the stacked area series points and has the [calculateSegmentPoints] method overrides to customize /// the stacked area segment point calculation. /// /// Gets the path and color from the `series`. class StackedAreaSegment extends ChartSegment { - Rect? _pathRect; - late Path _path; - Path? _strokePath; - /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); fillPaint = Paint(); - if (_series.gradient == null) { - if (_color != null) { - fillPaint!.color = _color!; + if (segmentProperties.series.gradient == null) { + if (segmentProperties.color != null) { + fillPaint!.color = segmentProperties.color!; fillPaint!.style = PaintingStyle.fill; } } else { - fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient!, _pathRect!, - _seriesRenderer._chartState!._requireInvertedAxis) + fillPaint = (segmentProperties.pathRect != null) + ? getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.pathRect!, + segmentProperties.stateProperties.requireInvertedAxis) : fillPaint; } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked area series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked area series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); strokePaint = Paint(); strokePaint! ..style = PaintingStyle.stroke - ..strokeWidth = _series.borderWidth; - if (_series.borderGradient != null && _strokePath != null) { - strokePaint!.shader = - _series.borderGradient!.createShader(_strokePath!.getBounds()); - } else if (_strokeColor != null) { - strokePaint!.color = _series.borderColor; + ..strokeWidth = segmentProperties.series.borderWidth; + if (segmentProperties.series.borderGradient != null && + segmentProperties.strokePath != null) { + strokePaint!.shader = segmentProperties.series.borderGradient! + .createShader(segmentProperties.strokePath!.getBounds()); + } else if (segmentProperties.strokeColor != null) { + strokePaint!.color = segmentProperties.series.borderColor; } - _series.borderWidth == 0 + segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; strokePaint!.strokeCap = StrokeCap.round; - _defaultStrokeColor = strokePaint; + segmentProperties.defaultStrokeColor = strokePaint; return strokePaint!; } @@ -66,7 +77,9 @@ class StackedAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _drawStackedAreaPath( - _path, _strokePath!, _seriesRenderer, canvas, fillPaint!, strokePaint!); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + drawStackedAreaPath(segmentProperties.path, segmentProperties.strokePath!, + segmentProperties.seriesRenderer, canvas, fillPaint!, strokePaint!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart index def9f4fd5..4db7ea536 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_bar_segment.dart @@ -1,8 +1,16 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/stacked_bar_series.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for stacked bar series. /// -/// Generates the stacked bar series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the stacked bar series points and has the [calculateSegmentPoints] method overrides to customize /// the stacked bar segment point calculation. /// /// Gets the path and color from the `series`. @@ -10,10 +18,6 @@ class StackedBarSegment extends ChartSegment { /// Stacked values late double stackValues; - /// Render path - late Path _path; - late RRect _trackRect; - Paint? _trackerFillPaint, _trackerStrokePaint; late StackedBarSeries _stackedBarSeries; /// Rectangle of the segment this could be used to render the segment while overriding this segment @@ -22,78 +26,64 @@ class StackedBarSegment extends ChartSegment { /// Gets the color of the series. @override Paint getFillPaint() { - /// Get and set the paint options for column series. - if (_series.gradient == null) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + + // Get and set the paint options for column series. + if (segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!) + ..color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.color + : (segmentProperties.currentPoint!.pointColorMapper ?? + segmentProperties.color!) ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.currentPoint!.region!, + segmentProperties.stateProperties.requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked bar series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked bar series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - if (_series.borderGradient != null) { - strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!); - } else if (_strokeColor != null) { - strokePaint!.color = - _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderColor - : _strokeColor!; + ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderWidth + : segmentProperties.strokeWidth!; + if (segmentProperties.series.borderGradient != null) { + strokePaint!.shader = segmentProperties.series.borderGradient! + .createShader(segmentProperties.currentPoint!.region!); + } else if (segmentProperties.strokeColor != null) { + strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderColor + : segmentProperties.strokeColor!; } - _defaultStrokeColor = strokePaint; - _series.borderWidth == 0 + segmentProperties.defaultStrokeColor = strokePaint; + segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; return strokePaint!; } - /// Method to get series tracker fill. - Paint _getTrackerFillPaint() { - final StackedBarSeries columnSeries = - _series as StackedBarSeries; - _trackerFillPaint = Paint() - ..color = columnSeries.trackColor - ..style = PaintingStyle.fill; - return _trackerFillPaint!; - } - - /// Method to get _series tracker stroke color. - Paint _getTrackerStrokePaint() { - _stackedBarSeries = _series as StackedBarSeries; - _trackerStrokePaint = Paint() - ..color = _stackedBarSeries.trackBorderColor - ..strokeWidth = _stackedBarSeries.trackBorderWidth - ..style = PaintingStyle.stroke; - _stackedBarSeries.trackBorderWidth == 0 - ? _trackerStrokePaint!.color = Colors.transparent - : _trackerStrokePaint!.color; - return _trackerStrokePaint!; - } - /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() {} @@ -101,24 +91,31 @@ class StackedBarSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _stackedBarSeries = _series as StackedBarSeries; - if (_trackerFillPaint != null && _stackedBarSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerFillPaint!); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + _stackedBarSeries = + segmentProperties.series as StackedBarSeries; + if (segmentProperties.trackerFillPaint != null && + _stackedBarSeries.isTrackVisible) { + canvas.drawRRect( + segmentProperties.trackRect, segmentProperties.trackerFillPaint!); } - if (_trackerStrokePaint != null && _stackedBarSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerStrokePaint!); + if (segmentProperties.trackerStrokePaint != null && + _stackedBarSeries.isTrackVisible) { + canvas.drawRRect( + segmentProperties.trackRect, segmentProperties.trackerStrokePaint!); } - _renderStackingRectSeries( + renderStackingRectSeries( fillPaint, strokePaint, - _path, + segmentProperties.path, animationFactor, - _seriesRenderer, + segmentProperties.seriesRenderer, canvas, segmentRect, - _currentPoint!, + segmentProperties.currentPoint!, currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart index b163fcd93..0d8f90392 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_column_segment.dart @@ -1,8 +1,16 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/stacked_column_series.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for stacked column series. /// -/// Generates the stacked column series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the stacked column series points and has the [calculateSegmentPoints] method overrides to customize /// the stacked column segment point calculation. /// /// Gets the path and color from the `series`. @@ -10,10 +18,7 @@ class StackedColumnSegment extends ChartSegment { /// Stack values. late double stackValues; - /// Rendering path. - late Path _path; - late RRect _trackRect; - Paint? _trackerFillPaint, _trackerStrokePaint; + /// Represents the stacked column series late StackedColumnSeries _stackedColumnSeries; //We are using `segmentRect` to draw the histogram segment in the series. @@ -24,77 +29,64 @@ class StackedColumnSegment extends ChartSegment { /// Gets the color of the series. @override Paint getFillPaint() { - /// Get and set the paint options for column _series. - if (_series.gradient == null) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + + // Get and set the paint options for column _series. + if (segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!) + ..color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.color + : (segmentProperties.currentPoint!.pointColorMapper ?? + segmentProperties.color!) ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.currentPoint!.region!, + segmentProperties.stateProperties.requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked column series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked column series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - if (_series.borderGradient != null) { - strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!); - } else if (_strokeColor != null) { - strokePaint!.color = - _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderColor - : _strokeColor!; + ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderWidth + : segmentProperties.strokeWidth!; + if (segmentProperties.series.borderGradient != null) { + strokePaint!.shader = segmentProperties.series.borderGradient! + .createShader(segmentProperties.currentPoint!.region!); + } else if (segmentProperties.strokeColor != null) { + strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderColor + : segmentProperties.strokeColor!; } - _defaultStrokeColor = strokePaint; - _series.borderWidth == 0 + segmentProperties.defaultStrokeColor = strokePaint; + segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; return strokePaint!; } - /// Method to get series tracker fill. - Paint _getTrackerFillPaint() { - _stackedColumnSeries = _series as StackedColumnSeries; - _trackerFillPaint = Paint() - ..color = _stackedColumnSeries.trackColor - ..style = PaintingStyle.fill; - return _trackerFillPaint!; - } - - /// Method to get series tracker stroke color. - Paint _getTrackerStrokePaint() { - _stackedColumnSeries = _series as StackedColumnSeries; - _trackerStrokePaint = Paint() - ..color = _stackedColumnSeries.trackBorderColor - ..strokeWidth = _stackedColumnSeries.trackBorderWidth - ..style = PaintingStyle.stroke; - _stackedColumnSeries.trackBorderWidth == 0 - ? _trackerStrokePaint!.color = Colors.transparent - : _trackerStrokePaint!.color; - return _trackerStrokePaint!; - } - /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() {} @@ -102,22 +94,29 @@ class StackedColumnSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _stackedColumnSeries = _series as StackedColumnSeries; - if (_trackerFillPaint != null && _stackedColumnSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerFillPaint!); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + _stackedColumnSeries = + segmentProperties.series as StackedColumnSeries; + if (segmentProperties.trackerFillPaint != null && + _stackedColumnSeries.isTrackVisible) { + canvas.drawRRect( + segmentProperties.trackRect, segmentProperties.trackerFillPaint!); } - if (_trackerStrokePaint != null && _stackedColumnSeries.isTrackVisible) { - canvas.drawRRect(_trackRect, _trackerStrokePaint!); + if (segmentProperties.trackerStrokePaint != null && + _stackedColumnSeries.isTrackVisible) { + canvas.drawRRect( + segmentProperties.trackRect, segmentProperties.trackerStrokePaint!); } - _renderStackingRectSeries( + renderStackingRectSeries( fillPaint, strokePaint, - _path, + segmentProperties.path, animationFactor, - _seriesRenderer, + segmentProperties.seriesRenderer, canvas, segmentRect, - _currentPoint!, + segmentProperties.currentPoint!, currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart index 94b7dde46..a5a3c8f96 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stacked_line_segment.dart @@ -1,4 +1,15 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/series.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for 100% stacked line series. /// @@ -7,110 +18,121 @@ part of charts; /// /// Gets the path and color from the `series`. class StackedLineSegment extends ChartSegment { - late double _x1, - _y1, - _x2, - _y2, - _currentCummulativePos, - _nextCummulativePos, - _currentCummulativeValue, - _nextCummulativeValue; - Color? _pointColorMapper; - /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked line series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked line series should be less than or equal to 1.'); - if (_color != null) { - _fillPaint.color = - _pointColorMapper ?? _color!.withOpacity(_series.opacity); + if (segmentProperties.color != null) { + _fillPaint.color = segmentProperties.pointColorMapper ?? + segmentProperties.color! + .withOpacity(segmentProperties.series.opacity); } - _fillPaint.strokeWidth = _strokeWidth!; + _fillPaint.strokeWidth = segmentProperties.strokeWidth!; _fillPaint.style = PaintingStyle.fill; - _defaultFillColor = _fillPaint; + segmentProperties.defaultFillColor = _fillPaint; return _fillPaint; } /// Gets the stroke color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _strokePaint = Paint(); - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked line series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked line series should be less than or equal to 1.'); - if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor!; + if (segmentProperties.strokeColor != null) { _strokePaint.color = - (_series.opacity < 1 && _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(_series.opacity) - : _strokePaint.color; + segmentProperties.pointColorMapper ?? segmentProperties.strokeColor!; + _strokePaint.color = (segmentProperties.series.opacity < 1 == true && + _strokePaint.color != Colors.transparent) + ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) + : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth!; + _strokePaint.strokeWidth = segmentProperties.strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + segmentProperties.defaultStrokeColor = _strokePaint; + setShader(segmentProperties, _strokePaint); return _strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - final _ChartLocation currentChartPoint = _calculatePoint( - _currentPoint!.xValue, - _currentCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + final SeriesRendererDetails segmentSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + late Rect rect; + rect = calculatePlotOffset( + segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, + segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); + + final ChartLocation currentChartPoint = calculatePoint( + segmentProperties.currentPoint!.xValue, + segmentProperties.currentCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - final _ChartLocation _nextLocation = _calculatePoint( - _nextPoint!.xValue, - _nextCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartLocation _nextLocation = calculatePoint( + segmentProperties.nextPoint!.xValue, + segmentProperties.nextCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - final _ChartLocation currentCummulativePoint = _calculatePoint( - _currentPoint!.xValue, - _currentCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartLocation currentCummulativePoint = calculatePoint( + segmentProperties.currentPoint!.xValue, + segmentProperties.currentCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - final _ChartLocation nextCummulativePoint = _calculatePoint( - _nextPoint!.xValue, - _nextCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartLocation nextCummulativePoint = calculatePoint( + segmentProperties.nextPoint!.xValue, + segmentProperties.nextCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - _x1 = currentChartPoint.x; - _y1 = currentChartPoint.y; - _x2 = _nextLocation.x; - _y2 = _nextLocation.y; - _currentCummulativeValue = currentCummulativePoint.y; - _nextCummulativeValue = nextCummulativePoint.y; + segmentProperties.x1 = currentChartPoint.x; + segmentProperties.y1 = currentChartPoint.y; + segmentProperties.x2 = _nextLocation.x; + segmentProperties.y2 = _nextLocation.y; + segmentProperties.currentCummulativeValue = currentCummulativePoint.y; + segmentProperties.nextCummulativeValue = nextCummulativePoint.y; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackedLineSeries(_series as _StackedSeriesBase, - canvas, strokePaint!, _x1, _y1, _x2, _y2); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + renderStackedLineSeries( + segmentProperties.series as StackedSeriesBase, + canvas, + strokePaint!, + segmentProperties.x1, + segmentProperties.y1, + segmentProperties.x2, + segmentProperties.y2); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart index af9d93869..ab7f4bbf3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedarea100_segment.dart @@ -1,61 +1,72 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for 100% stacked area series. /// -/// Generates the stacked area100 series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the stacked area100 series points and has the [calculateSegmentPoints] method overrides to customize /// the stacked area100 segment point calculation. /// /// Gets the path and color from the `series`. class StackedArea100Segment extends ChartSegment { - Rect? _pathRect; - late Path _path; - Path? _strokePath; - /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); fillPaint = Paint(); - if (_series.gradient == null) { - if (_color != null) { - fillPaint!.color = _color!; + if (segmentProperties.series.gradient == null) { + if (segmentProperties.color != null) { + fillPaint!.color = segmentProperties.color!; fillPaint!.style = PaintingStyle.fill; } } else { - fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient!, _pathRect!, - _seriesRenderer._chartState!._requireInvertedAxis) + fillPaint = (segmentProperties.pathRect != null) + ? getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.pathRect!, + segmentProperties.stateProperties.requireInvertedAxis) : fillPaint; } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked area 100 series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked area 100 series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint strokePaint = Paint(); strokePaint ..style = PaintingStyle.stroke - ..strokeWidth = _series.borderWidth; - if (_series.borderGradient != null && _strokePath != null) { - strokePaint.shader = - _series.borderGradient!.createShader(_strokePath!.getBounds()); - } else if (_strokeColor != null) { - strokePaint.color = _series.borderColor; + ..strokeWidth = segmentProperties.series.borderWidth; + if (segmentProperties.series.borderGradient != null && + segmentProperties.strokePath != null) { + strokePaint.shader = segmentProperties.series.borderGradient! + .createShader(segmentProperties.strokePath!.getBounds()); + } else if (segmentProperties.strokeColor != null) { + strokePaint.color = segmentProperties.series.borderColor; } - _series.borderWidth == 0 + segmentProperties.series.borderWidth == 0 ? strokePaint.color = Colors.transparent : strokePaint.color; strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = strokePaint; + segmentProperties.defaultStrokeColor = strokePaint; return strokePaint; } @@ -66,7 +77,9 @@ class StackedArea100Segment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _drawStackedAreaPath( - _path, _strokePath!, _seriesRenderer, canvas, fillPaint!, strokePaint!); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + drawStackedAreaPath(segmentProperties.path, segmentProperties.strokePath!, + segmentProperties.seriesRenderer, canvas, fillPaint!, strokePaint!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart index 2755da27f..7673f3e9e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedbar100_segment.dart @@ -1,8 +1,15 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for 100% stacked bar series. /// -/// Generates the stacked bar100 series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the stacked bar100 series points and has the [calculateSegmentPoints] method overrides to customize /// the stacked bar100 segment point calculation. /// /// Gets the path and color from the `series`. @@ -10,9 +17,6 @@ class StackedBar100Segment extends ChartSegment { /// staked value of the segment late double stackValues; - ///Render path. - late Path _path; - //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. ///Rectangle of the segment @@ -21,50 +25,59 @@ class StackedBar100Segment extends ChartSegment { /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + /// Get and set the paint options for column series. - if (_series.gradient == null) { + if (segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!) + ..color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.color + : (segmentProperties.currentPoint!.pointColorMapper ?? + segmentProperties.color!) ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.currentPoint!.region!, + segmentProperties.stateProperties.requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked bar 100 series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked bar 100 series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - if (_series.borderGradient != null) { - strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!); - } else if (_strokeColor != null) { - strokePaint!.color = - _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderColor - : _strokeColor!; + ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderWidth + : segmentProperties.strokeWidth!; + if (segmentProperties.series.borderGradient != null) { + strokePaint!.shader = segmentProperties.series.borderGradient! + .createShader(segmentProperties.currentPoint!.region!); + } else if (segmentProperties.strokeColor != null) { + strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderColor + : segmentProperties.strokeColor!; } - _defaultStrokeColor = strokePaint; - _series.borderWidth == 0 + segmentProperties.defaultStrokeColor = strokePaint; + segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; return strokePaint!; @@ -77,15 +90,17 @@ class StackedBar100Segment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackingRectSeries( + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + renderStackingRectSeries( fillPaint, strokePaint, - _path, + segmentProperties.path, animationFactor, - _seriesRenderer, + segmentProperties.seriesRenderer, canvas, segmentRect, - _currentPoint!, + segmentProperties.currentPoint!, currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart index ef01500cb..859e733cb 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedcolumn100_segment.dart @@ -1,8 +1,17 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; + +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for 100% stacked column series. /// -/// Generates the stacked column100 series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the stacked column100 series points and has the [calculateSegmentPoints] method overrides to customize /// the stacked column100 segment point calculation. /// /// Gets the path and color from the `series`. @@ -10,9 +19,6 @@ class StackedColumn100Segment extends ChartSegment { /// Stacked value. late double stackValues; - /// Rendering path. - late Path _path; - //We are using `segmentRect` to draw the histogram segment in the series. //we can override this class and customize the column segment by getting `segmentRect`. /// Rectangle of the segment @@ -21,50 +27,59 @@ class StackedColumn100Segment extends ChartSegment { /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + /// Get and set the paint options for stackedcolumn100 series. - if (_series.gradient == null) { + if (segmentProperties.series.gradient == null) { fillPaint = Paint() - ..color = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.color - : (_currentPoint!.pointColorMapper ?? _color!) + ..color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.color + : (segmentProperties.currentPoint!.pointColorMapper ?? + segmentProperties.color!) ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.currentPoint!.region!, + segmentProperties.stateProperties.requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked column100 series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked column100 series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderWidth - : _strokeWidth!; - if (_series.borderGradient != null) { - strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!); - } else if (_strokeColor != null) { - strokePaint!.color = - _currentPoint!.isEmpty != null && _currentPoint!.isEmpty! - ? _series.emptyPointSettings.borderColor - : _strokeColor!; + ..strokeWidth = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderWidth + : segmentProperties.strokeWidth!; + if (segmentProperties.series.borderGradient != null) { + strokePaint!.shader = segmentProperties.series.borderGradient! + .createShader(segmentProperties.currentPoint!.region!); + } else if (segmentProperties.strokeColor != null) { + strokePaint!.color = segmentProperties.currentPoint!.isEmpty != null && + segmentProperties.currentPoint!.isEmpty! == true + ? segmentProperties.series.emptyPointSettings.borderColor + : segmentProperties.strokeColor!; } - _defaultStrokeColor = strokePaint; - _series.borderWidth == 0 + segmentProperties.defaultStrokeColor = strokePaint; + segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; return strokePaint!; @@ -77,15 +92,17 @@ class StackedColumn100Segment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackingRectSeries( + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + renderStackingRectSeries( fillPaint, strokePaint, - _path, + segmentProperties.path, animationFactor, - _seriesRenderer, + segmentProperties.seriesRenderer, canvas, segmentRect, - _currentPoint!, + segmentProperties.currentPoint!, currentSegmentIndex!); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart index cc2243ea9..84c82396e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stackedline100_segment.dart @@ -1,4 +1,15 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for 100% stacked line series. /// @@ -7,110 +18,119 @@ part of charts; /// /// Gets the path and color from the `series`. class StackedLine100Segment extends ChartSegment { - late double _x1, - _y1, - _x2, - _y2, - _currentCummulativePos, - _nextCummulativePos, - _currentCummulativeValue, - _nextCummulativeValue; - Color? _pointColorMapper; - /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked line100 series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked line100 series should be less than or equal to 1.'); - if (_color != null) { - _fillPaint.color = - _pointColorMapper ?? _color!.withOpacity(_series.opacity); + if (segmentProperties.color != null) { + _fillPaint.color = segmentProperties.pointColorMapper ?? + segmentProperties.color! + .withOpacity(segmentProperties.series.opacity); } - _fillPaint.strokeWidth = _strokeWidth!; + _fillPaint.strokeWidth = segmentProperties.strokeWidth!; _fillPaint.style = PaintingStyle.fill; - _defaultFillColor = _fillPaint; + segmentProperties.defaultFillColor = _fillPaint; return _fillPaint; } /// Gets the stroke color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _strokePaint = Paint(); - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the stacked line100 series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the stacked line100 series should be less than or equal to 1.'); - if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor!; + if (segmentProperties.strokeColor != null) { _strokePaint.color = - (_series.opacity < 1 && _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(_series.opacity) - : _strokePaint.color; + segmentProperties.pointColorMapper ?? segmentProperties.strokeColor!; + _strokePaint.color = (segmentProperties.series.opacity < 1 == true && + _strokePaint.color != Colors.transparent) + ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) + : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth!; + _strokePaint.strokeWidth = segmentProperties.strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = _strokePaint; + segmentProperties.defaultStrokeColor = _strokePaint; + setShader(segmentProperties, _strokePaint); return _strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - final Rect rect = _calculatePlotOffset( - _seriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - final _ChartLocation currentChartPoint = _calculatePoint( - _currentPoint!.xValue, - _currentCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + final SeriesRendererDetails segmentSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + final Rect rect = calculatePlotOffset( + segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset(segmentSeriesRendererDetails.xAxisDetails!.axis.plotOffset, + segmentSeriesRendererDetails.yAxisDetails!.axis.plotOffset)); + final ChartLocation currentChartPoint = calculatePoint( + segmentProperties.currentPoint!.xValue, + segmentProperties.currentCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - final _ChartLocation _nextLocation = _calculatePoint( - _nextPoint!.xValue, - _nextCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartLocation _nextLocation = calculatePoint( + segmentProperties.nextPoint!.xValue, + segmentProperties.nextCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - final _ChartLocation currentCummulativePoint = _calculatePoint( - _currentPoint!.xValue, - _currentCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartLocation currentCummulativePoint = calculatePoint( + segmentProperties.currentPoint!.xValue, + segmentProperties.currentCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - final _ChartLocation nextCummulativePoint = _calculatePoint( - _nextPoint!.xValue, - _nextCummulativePos, - _seriesRenderer._xAxisRenderer!, - _seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + final ChartLocation nextCummulativePoint = calculatePoint( + segmentProperties.nextPoint!.xValue, + segmentProperties.nextCummulativePos, + segmentSeriesRendererDetails.xAxisDetails!, + segmentSeriesRendererDetails.yAxisDetails!, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, rect); - _x1 = currentChartPoint.x; - _y1 = currentChartPoint.y; - _x2 = _nextLocation.x; - _y2 = _nextLocation.y; - _currentCummulativeValue = currentCummulativePoint.y; - _nextCummulativeValue = nextCummulativePoint.y; + segmentProperties.x1 = currentChartPoint.x; + segmentProperties.y1 = currentChartPoint.y; + segmentProperties.x2 = _nextLocation.x; + segmentProperties.y2 = _nextLocation.y; + segmentProperties.currentCummulativeValue = currentCummulativePoint.y; + segmentProperties.nextCummulativeValue = nextCummulativePoint.y; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _renderStackedLineSeries(_series as _StackedSeriesBase, - canvas, strokePaint!, _x1, _y1, _x2, _y2); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + renderStackedLineSeries( + segmentProperties.series as StackedSeriesBase, + canvas, + strokePaint!, + segmentProperties.x1, + segmentProperties.y1, + segmentProperties.x2, + segmentProperties.y2); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart index ba549fd92..be00a8490 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/step_area_segment.dart @@ -1,58 +1,72 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/common.dart'; + +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for step area series. /// -/// Generates the step area series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the step area series points and has the [calculateSegmentPoints] method overrides to customize /// the step area segment point calculation. /// /// Gets the path and color from the `series`. class StepAreaSegment extends ChartSegment { - late Path _path, _strokePath; - Rect? _pathRect; - /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); fillPaint = Paint(); - if (_series.gradient == null) { - if (_color != null) { - fillPaint!.color = _color!; + if (segmentProperties.series.gradient == null) { + if (segmentProperties.color != null) { + fillPaint!.color = segmentProperties.color!; fillPaint!.style = PaintingStyle.fill; } } else { // ignore: unnecessary_null_comparison - fillPaint = (_pathRect != null) - ? _getLinearGradientPaint(_series.gradient!, _pathRect!, - _seriesRenderer._chartState!._requireInvertedAxis) + fillPaint = (segmentProperties.pathRect != null) + ? getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.pathRect!, + segmentProperties.stateProperties.requireInvertedAxis) : fillPaint; } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the the step area series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the the step area series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint strokePaint = Paint(); - if (_strokeColor != null) { + if (segmentProperties.strokeColor != null) { strokePaint - ..color = _series.borderColor + ..color = segmentProperties.series.borderColor ..style = PaintingStyle.stroke - ..strokeWidth = _series.borderWidth; - _series.borderWidth == 0 + ..strokeWidth = segmentProperties.series.borderWidth; + segmentProperties.series.borderWidth == 0 ? strokePaint.color = Colors.transparent : strokePaint.color; } strokePaint.strokeCap = StrokeCap.round; - _defaultStrokeColor = strokePaint; + segmentProperties.defaultStrokeColor = strokePaint; return strokePaint; } @@ -63,12 +77,18 @@ class StepAreaSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - _pathRect = _path.getBounds(); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + segmentProperties.pathRect = segmentProperties.path.getBounds(); canvas.drawPath( - _path, (_series.gradient == null) ? fillPaint! : getFillPaint()); + segmentProperties.path, + (segmentProperties.series.gradient == null) + ? fillPaint! + : getFillPaint()); if (strokePaint!.color != Colors.transparent) { - _drawDashedLine(canvas, _series.dashArray, strokePaint!, _strokePath); + drawDashedLine(canvas, segmentProperties.series.dashArray, strokePaint!, + segmentProperties.strokePath!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart index 8dd93fb84..cb0f5bd37 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/stepline_segment.dart @@ -1,217 +1,326 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; +import '../axis/axis.dart'; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for step line series. /// -/// Generates the step line series points and has the [calculateSegmentPoints] method overrided to customize +/// Generates the step line series points and has the [calculateSegmentPoints] method overrides to customize /// the step line segment point calculation. /// /// Gets the path and color from the `series`. class StepLineSegment extends ChartSegment { - late double _x1, _y1, _x2, _y2, _x3, _y3; - - /// Render path - late Path _path; - late double _x1Pos, _y1Pos, _x2Pos, _y2Pos; - late num _midX, _midY; double? _oldX1, _oldY1, _oldX2, _oldY2, _oldX3, _oldY3; - Color? _pointColorMapper; late bool _needAnimate; ChartAxisRenderer? _oldXAxisRenderer, _oldYAxisRenderer; - late _ChartLocation _currentLocation, _midLocation, _nextLocation; - _ChartLocation? _oldLocation; + late ChartLocation _currentLocation, _midLocation, _nextLocation; + ChartLocation? _oldLocation; late StepLineSegment _currentSegment; StepLineSegment? _oldSegment; /// Gets the color of the series. @override Paint getFillPaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _fillPaint = Paint(); - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the step line series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the step line series should be less than or equal to 1.'); - if (_color != null) { - _fillPaint.color = _color!.withOpacity(_series.opacity); + if (segmentProperties.color != null) { + _fillPaint.color = segmentProperties.color! + .withOpacity(segmentProperties.series.opacity); } - _fillPaint.strokeWidth = _strokeWidth!; + _fillPaint.strokeWidth = segmentProperties.strokeWidth!; _fillPaint.style = PaintingStyle.stroke; - _defaultFillColor = _fillPaint; + segmentProperties.defaultFillColor = _fillPaint; return _fillPaint; } /// Gets the stroke color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final Paint _strokePaint = Paint(); - if (_strokeColor != null) { - _strokePaint.color = _pointColorMapper ?? _strokeColor!; + if (segmentProperties.strokeColor != null) { _strokePaint.color = - (_series.opacity < 1 && _strokePaint.color != Colors.transparent) - ? _strokePaint.color.withOpacity(_series.opacity) - : _strokePaint.color; + segmentProperties.pointColorMapper ?? segmentProperties.strokeColor!; + _strokePaint.color = (segmentProperties.series.opacity < 1 == true && + _strokePaint.color != Colors.transparent) + ? _strokePaint.color.withOpacity(segmentProperties.series.opacity) + : _strokePaint.color; } - _strokePaint.strokeWidth = _strokeWidth!; + _strokePaint.strokeWidth = segmentProperties.strokeWidth!; _strokePaint.style = PaintingStyle.stroke; _strokePaint.strokeCap = StrokeCap.square; - _defaultStrokeColor = _strokePaint; + segmentProperties.defaultStrokeColor = _strokePaint; + setShader(segmentProperties, _strokePaint); return _strokePaint; } /// Calculates the rendering bounds of a segment. @override void calculateSegmentPoints() { - final ChartAxisRenderer _xAxisRenderer = _seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer _yAxisRenderer = _seriesRenderer._yAxisRenderer!; - final Rect _axisClipRect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - _currentLocation = _calculatePoint( - _currentPoint!.xValue, - _currentPoint!.yValue, - _xAxisRenderer, - _yAxisRenderer, - _seriesRenderer._chartState!._requireInvertedAxis, - _seriesRenderer._series, + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + final ChartAxisRendererDetails _xAxisRendererDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .xAxisDetails!; + final ChartAxisRendererDetails _yAxisRendererDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .yAxisDetails!; + final Rect _axisClipRect = calculatePlotOffset( + segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset( + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .xAxisDetails! + .axis + .plotOffset, + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .yAxisDetails! + .axis + .plotOffset)); + _currentLocation = calculatePoint( + segmentProperties.currentPoint!.xValue, + segmentProperties.currentPoint!.yValue, + _xAxisRendererDetails, + _yAxisRendererDetails, + segmentProperties.stateProperties.requireInvertedAxis, + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .series, _axisClipRect); - _nextLocation = _calculatePoint( - _nextPoint!.xValue, - _nextPoint!.yValue, - _xAxisRenderer, - _yAxisRenderer, - _seriesRenderer._chartState!._requireInvertedAxis, - _seriesRenderer._series, + _nextLocation = calculatePoint( + segmentProperties.nextPoint!.xValue, + segmentProperties.nextPoint!.yValue, + _xAxisRendererDetails, + _yAxisRendererDetails, + segmentProperties.stateProperties.requireInvertedAxis, + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .series, _axisClipRect); - _midLocation = _calculatePoint( - _midX, - _midY, - _xAxisRenderer, - _yAxisRenderer, - _seriesRenderer._chartState!._requireInvertedAxis, - _seriesRenderer._series, + _midLocation = calculatePoint( + segmentProperties.midX, + segmentProperties.midY, + _xAxisRendererDetails, + _yAxisRendererDetails, + segmentProperties.stateProperties.requireInvertedAxis, + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .series, _axisClipRect); - _x1 = _currentLocation.x; - _y1 = _currentLocation.y; - _x2 = _nextLocation.x; - _y2 = _nextLocation.y; - _x3 = _midLocation.x; - _y3 = _midLocation.y; + segmentProperties.x1 = _currentLocation.x; + segmentProperties.y1 = _currentLocation.y; + segmentProperties.x2 = _nextLocation.x; + segmentProperties.y2 = _nextLocation.y; + segmentProperties.x3 = _midLocation.x; + segmentProperties.y3 = _midLocation.y; } /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { - final Rect _rect = _calculatePlotOffset( - _seriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(_seriesRenderer._xAxisRenderer!._axis.plotOffset, - _seriesRenderer._yAxisRenderer!._axis.plotOffset)); - _path = Path(); - if (_series.animationDuration > 0 && - !_seriesRenderer._reAnimate && - _seriesRenderer._renderingDetails!.widgetNeedUpdate && - !_seriesRenderer._renderingDetails!.isLegendToggled && - _seriesRenderer._chartState!._oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderer != null && - _oldSeriesRenderer!._segments.isNotEmpty && - _oldSeriesRenderer!._segments[0] is StepLineSegment && - _seriesRenderer._chartState!._oldSeriesRenderers.length - 1 >= - _seriesRenderer._segments[currentSegmentIndex!]._seriesIndex && - _seriesRenderer._segments[currentSegmentIndex!]._oldSeriesRenderer! - ._segments.isNotEmpty) { - _currentSegment = - _seriesRenderer._segments[currentSegmentIndex!] as StepLineSegment; - _oldSegment = (_currentSegment._oldSeriesRenderer!._segments.length - 1 >= - currentSegmentIndex!) - ? _currentSegment._oldSeriesRenderer!._segments[currentSegmentIndex!] - as StepLineSegment? + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + final Rect _rect = calculatePlotOffset( + segmentProperties.stateProperties.chartAxis.axisClipRect, + Offset( + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .xAxisDetails! + .axis + .plotOffset, + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .yAxisDetails! + .axis + .plotOffset)); + segmentProperties.path = Path(); + if (segmentProperties.series.animationDuration > 0 == true && + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer).reAnimate == + false && + segmentProperties.stateProperties.renderingDetails.widgetNeedUpdate == + true && + segmentProperties.stateProperties.renderingDetails.isLegendToggled == + false && + segmentProperties.stateProperties.oldSeriesRenderers.isNotEmpty == + true && + segmentProperties.oldSeriesRenderer != null && + SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) + .segments + .isNotEmpty == + true && + SeriesHelper.getSeriesRendererDetails(segmentProperties.oldSeriesRenderer!) + .segments[0] is StepLineSegment && + segmentProperties.stateProperties.oldSeriesRenderers.length - 1 >= + SegmentHelper.getSegmentProperties(SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer).segments[currentSegmentIndex!]) + .seriesIndex == + true && + SeriesHelper.getSeriesRendererDetails(SegmentHelper.getSegmentProperties( + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .segments[currentSegmentIndex!]) + .oldSeriesRenderer!) + .segments + .isNotEmpty == + true) { + _currentSegment = SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .segments[currentSegmentIndex!] as StepLineSegment; + final SegmentProperties _currentSegmentProperties = + SegmentHelper.getSegmentProperties(_currentSegment); + SegmentProperties? _oldSegmentProperties; + _oldSegment = (SeriesHelper.getSeriesRendererDetails( + _currentSegmentProperties.oldSeriesRenderer!) + .segments + .length - + 1 >= + currentSegmentIndex! == + true) + ? SeriesHelper.getSeriesRendererDetails( + _currentSegmentProperties.oldSeriesRenderer!) + .segments[currentSegmentIndex!] as StepLineSegment? : null; - _oldX1 = _oldSegment?._x1; - _oldY1 = _oldSegment?._y1; - _oldX2 = _oldSegment?._x2; - _oldY2 = _oldSegment?._y2; - _oldX3 = _oldSegment?._x3; - _oldY3 = _oldSegment?._y3; + if (_oldSegment != null) { + _oldSegmentProperties = + SegmentHelper.getSegmentProperties(_oldSegment!); + } + _oldX1 = _oldSegmentProperties?.x1; + _oldY1 = _oldSegmentProperties?.y1; + _oldX2 = _oldSegmentProperties?.x2; + _oldY2 = _oldSegmentProperties?.y2; + _oldX3 = _oldSegmentProperties?.x3; + _oldY3 = _oldSegmentProperties?.y3; if (_oldSegment != null && (_oldX1!.isNaN || _oldX2!.isNaN) && - _seriesRenderer._chartState!._oldAxisRenderers.isNotEmpty) { - _oldXAxisRenderer = _getOldAxisRenderer(_seriesRenderer._xAxisRenderer!, - _seriesRenderer._chartState!._oldAxisRenderers); - _oldYAxisRenderer = _getOldAxisRenderer(_seriesRenderer._yAxisRenderer!, - _seriesRenderer._chartState!._oldAxisRenderers); + segmentProperties.stateProperties.oldAxisRenderers.isNotEmpty == + false) { + _oldXAxisRenderer = getOldAxisRenderer( + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .xAxisDetails! + .axisRenderer, + segmentProperties.stateProperties.oldAxisRenderers); + _oldYAxisRenderer = getOldAxisRenderer( + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .yAxisDetails! + .axisRenderer, + segmentProperties.stateProperties.oldAxisRenderers); + final ChartAxisRendererDetails _oldXAxisDetails = + AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); + final ChartAxisRendererDetails _oldYAxisDetails = + AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); if (_oldYAxisRenderer != null && _oldXAxisRenderer != null) { - _needAnimate = _oldYAxisRenderer!._visibleRange!.minimum != - _seriesRenderer._yAxisRenderer!._visibleRange!.minimum || - _oldYAxisRenderer!._visibleRange!.maximum != - _seriesRenderer._yAxisRenderer!._visibleRange!.maximum || - _oldXAxisRenderer!._visibleRange!.minimum != - _seriesRenderer._xAxisRenderer!._visibleRange!.minimum || - _oldXAxisRenderer!._visibleRange!.maximum != - _seriesRenderer._xAxisRenderer!._visibleRange!.maximum; + _needAnimate = _oldYAxisDetails.visibleRange!.minimum != + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .yAxisDetails! + .visibleRange! + .minimum || + _oldYAxisDetails.visibleRange!.maximum != + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .yAxisDetails! + .visibleRange! + .maximum || + _oldXAxisDetails.visibleRange!.minimum != + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .xAxisDetails! + .visibleRange! + .minimum || + _oldXAxisDetails.visibleRange!.maximum != + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .xAxisDetails! + .visibleRange! + .maximum; } if (_needAnimate) { - _oldLocation = _calculatePoint( + final ChartAxisRendererDetails _oldXAxisDetails = + AxisHelper.getAxisRendererDetails(_oldXAxisRenderer!); + final ChartAxisRendererDetails _oldYAxisDetails = + AxisHelper.getAxisRendererDetails(_oldYAxisRenderer!); + _oldLocation = calculatePoint( _x1Pos, _y1Pos, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + _oldXAxisDetails, + _oldYAxisDetails, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, _rect); _oldX1 = _oldLocation!.x; _oldY1 = _oldLocation!.y; - _oldLocation = _calculatePoint( + _oldLocation = calculatePoint( _x2Pos, _y2Pos, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + _oldXAxisDetails, + _oldYAxisDetails, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, _rect); _oldX2 = _oldLocation!.x; _oldY2 = _oldLocation!.y; - _oldLocation = _calculatePoint( - _midX, - _midY, - _oldXAxisRenderer!, - _oldYAxisRenderer!, - _seriesRenderer._chartState!._requireInvertedAxis, - _series, + _oldLocation = calculatePoint( + segmentProperties.midX, + segmentProperties.midY, + _oldXAxisDetails, + _oldYAxisDetails, + segmentProperties.stateProperties.requireInvertedAxis, + segmentProperties.series, _rect); _oldX3 = _oldLocation!.x; _oldY3 = _oldLocation!.y; } } - _animateLineTypeSeries( + animateLineTypeSeries( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer), strokePaint!, animationFactor, - _currentSegment._x1, - _currentSegment._y1, - _currentSegment._x2, - _currentSegment._y2, + _currentSegmentProperties.x1, + _currentSegmentProperties.y1, + _currentSegmentProperties.x2, + _currentSegmentProperties.y2, _oldX1, _oldY1, _oldX2, _oldY2, - _currentSegment._x3, - _currentSegment._y3, + _currentSegmentProperties.x3, + _currentSegmentProperties.y3, _oldX3, _oldY3, ); } else { - if (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) { - _path.moveTo(_x1, _y1); - _path.lineTo(_x3, _y3); - _path.lineTo(_x2, _y2); - _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path); + if (segmentProperties.series.dashArray[0] != 0 && + segmentProperties.series.dashArray[1] != 0) { + segmentProperties.path + .moveTo(segmentProperties.x1, segmentProperties.y1); + segmentProperties.path + .lineTo(segmentProperties.x3, segmentProperties.y3); + segmentProperties.path + .lineTo(segmentProperties.x2, segmentProperties.y2); + drawDashedLine(canvas, segmentProperties.series.dashArray, strokePaint!, + segmentProperties.path); } else { - canvas.drawLine(Offset(_x1, _y1), Offset(_x3, _y3), strokePaint!); - canvas.drawLine(Offset(_x3, _y3), Offset(_x2, _y2), strokePaint!); + canvas.drawLine(Offset(segmentProperties.x1, segmentProperties.y1), + Offset(segmentProperties.x3, segmentProperties.y3), strokePaint!); + canvas.drawLine(Offset(segmentProperties.x3, segmentProperties.y3), + Offset(segmentProperties.x2, segmentProperties.y2), strokePaint!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart index ea6685207..93c7bda9b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_segment/waterfall_segment.dart @@ -1,4 +1,15 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; +import '../chart_series/series.dart'; +import '../chart_series/waterfall_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../utils/helper.dart'; +import 'chart_segment.dart'; /// Creates the segments for waterfall series. /// @@ -10,15 +21,9 @@ class WaterfallSegment extends ChartSegment { /// To find the x and y values of connector lines between each data point. late double _x1, _y1, _x2, _y2; - ///Path of the series - late Path _path; - - /// Get the connetor line paint + /// Get the connector line paint Paint? connectorLineStrokePaint; - /// Colors of the negative point, intermediate point and total point. - Color? _negativePointsColor, _intermediateSumColor, _totalSumColor; - /// We are using `segmentRect` to draw the bar segment in the series. /// we can override this class and customize the waterfall segment by getting `segmentRect`. late RRect segmentRect; @@ -26,63 +31,62 @@ class WaterfallSegment extends ChartSegment { /// Gets the color of the series. @override Paint getFillPaint() { - final bool hasPointColor = _series.pointColorMapper != null; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); + final CartesianChartPoint? _currentPoint = + segmentProperties.currentPoint; + final bool hasPointColor = + segmentProperties.series.pointColorMapper != null; /// Get and set the paint options for waterfall series. - if (_series.gradient == null) { + if (segmentProperties.series.gradient == null) { fillPaint = Paint() ..color = ((hasPointColor && _currentPoint!.pointColorMapper != null) - ? _currentPoint!.pointColorMapper + ? _currentPoint.pointColorMapper : _currentPoint!.isIntermediateSum! - ? _intermediateSumColor ?? _color! - : _currentPoint!.isTotalSum! - ? _totalSumColor ?? _color! - : _currentPoint!.yValue < 0 == true - ? _negativePointsColor ?? _color! - : _color!)! + ? segmentProperties.intermediateSumColor ?? + segmentProperties.color! + : _currentPoint.isTotalSum! + ? segmentProperties.totalSumColor ?? + segmentProperties.color! + : _currentPoint.yValue < 0 == true + ? segmentProperties.negativePointsColor ?? + segmentProperties.color! + : segmentProperties.color!)! ..style = PaintingStyle.fill; } else { - fillPaint = _getLinearGradientPaint( - _series.gradient!, - _currentPoint!.region!, - _seriesRenderer._chartState!._requireInvertedAxis); + fillPaint = getLinearGradientPaint( + segmentProperties.series.gradient!, + segmentProperties.currentPoint!.region!, + segmentProperties.stateProperties.requireInvertedAxis); } - assert(_series.opacity >= 0, + assert(segmentProperties.series.opacity >= 0 == true, 'The opacity value of the waterfall series should be greater than or equal to 0.'); - assert(_series.opacity <= 1, + assert(segmentProperties.series.opacity <= 1 == true, 'The opacity value of the waterfall series should be less than or equal to 1.'); - fillPaint!.color = - (_series.opacity < 1 && fillPaint!.color != Colors.transparent) - ? fillPaint!.color.withOpacity(_series.opacity) - : fillPaint!.color; - _defaultFillColor = fillPaint; + fillPaint!.color = (segmentProperties.series.opacity < 1 == true && + fillPaint!.color != Colors.transparent) + ? fillPaint!.color.withOpacity(segmentProperties.series.opacity) + : fillPaint!.color; + segmentProperties.defaultFillColor = fillPaint; + setShader(segmentProperties, fillPaint!); return fillPaint!; } - /// Get the color of connector lines. - Paint _getConnectorLineStrokePaint() { - final WaterfallSeries series = - _series as WaterfallSeries; - connectorLineStrokePaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = series.connectorLineSettings.width - ..color = series.connectorLineSettings.color ?? - _renderingDetails.chartTheme.waterfallConnectorLineColor; - return connectorLineStrokePaint!; - } - /// Gets the border color of the series. @override Paint getStrokePaint() { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); strokePaint = Paint() ..style = PaintingStyle.stroke - ..strokeWidth = _strokeWidth!; - _defaultStrokeColor = strokePaint; - _series.borderGradient != null - ? strokePaint!.shader = - _series.borderGradient!.createShader(_currentPoint!.region!) - : strokePaint!.color = _strokeColor!; - _series.borderWidth == 0 + ..strokeWidth = segmentProperties.strokeWidth!; + segmentProperties.defaultStrokeColor = strokePaint; + segmentProperties.series.borderGradient != null + ? strokePaint!.shader = segmentProperties.series.borderGradient! + .createShader(segmentProperties.currentPoint!.region!) + : strokePaint!.color = segmentProperties.strokeColor!; + segmentProperties.series.borderWidth == 0 ? strokePaint!.color = Colors.transparent : strokePaint!.color; return strokePaint!; @@ -95,58 +99,74 @@ class WaterfallSegment extends ChartSegment { /// Draws segment in series bounds. @override void onPaint(Canvas canvas) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(this); final WaterfallSeries _series = - this._series as WaterfallSeries; + segmentProperties.series as WaterfallSeries; CartesianChartPoint oldPaint; final Path linePath = Path(); if (fillPaint != null) { (_series.animationDuration > 0 && - !_seriesRenderer._renderingDetails!.isLegendToggled) - ? _animateRangeColumn( + segmentProperties + .stateProperties.renderingDetails.isLegendToggled == + false) + ? animateRangeColumn( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer), fillPaint!, segmentRect, - _oldPoint != null ? _oldPoint!.region : _oldRegion, + segmentProperties.oldPoint != null + ? segmentProperties.oldPoint!.region + : segmentProperties.oldRegion, animationFactor) : canvas.drawRRect(segmentRect, fillPaint!); } if (strokePaint != null) { (_series.dashArray[0] != 0 && _series.dashArray[1] != 0) - ? _drawDashedLine(canvas, _series.dashArray, strokePaint!, _path) + ? drawDashedLine( + canvas, _series.dashArray, strokePaint!, segmentProperties.path) : (_series.animationDuration > 0 && - !_seriesRenderer._renderingDetails!.isLegendToggled) - ? _animateRangeColumn( + segmentProperties + .stateProperties.renderingDetails.isLegendToggled == + false) + ? animateRangeColumn( canvas, - _seriesRenderer, + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer), strokePaint!, segmentRect, - _oldPoint != null ? _oldPoint!.region : _oldRegion, + segmentProperties.oldPoint != null + ? segmentProperties.oldPoint!.region + : segmentProperties.oldRegion, animationFactor) : canvas.drawRRect(segmentRect, strokePaint!); } if (connectorLineStrokePaint != null && - _currentPoint!.overallDataPointIndex! > 0) { - oldPaint = _seriesRenderer - ._dataPoints[_currentPoint!.overallDataPointIndex! - 1]; + segmentProperties.currentPoint!.overallDataPointIndex! > 0 == true) { + oldPaint = SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .dataPoints[ + segmentProperties.currentPoint!.overallDataPointIndex! - 1]; _x1 = oldPaint.endValueRightPoint!.x; _y1 = oldPaint.endValueRightPoint!.y; - if (_currentPoint!.isTotalSum! || _currentPoint!.isIntermediateSum!) { - _x2 = _currentPoint!.endValueLeftPoint!.x; - _y2 = _currentPoint!.endValueLeftPoint!.y; + if (segmentProperties.currentPoint!.isTotalSum! == true || + segmentProperties.currentPoint!.isIntermediateSum! == true) { + _x2 = segmentProperties.currentPoint!.endValueLeftPoint!.x; + _y2 = segmentProperties.currentPoint!.endValueLeftPoint!.y; } else { - _x2 = _currentPoint!.originValueLeftPoint!.x; - _y2 = _currentPoint!.originValueLeftPoint!.y; + _x2 = segmentProperties.currentPoint!.originValueLeftPoint!.x; + _y2 = segmentProperties.currentPoint!.originValueLeftPoint!.y; } if (_series.animationDuration <= 0 || animationFactor >= - _seriesRenderer._chartState!._seriesDurationFactor) { + segmentProperties.stateProperties.seriesDurationFactor) { if (_series.connectorLineSettings.dashArray![0] != 0 && _series.connectorLineSettings.dashArray![1] != 0) { linePath.moveTo(_x1, _y1); linePath.lineTo(_x2, _y2); - _drawDashedLine(canvas, _series.connectorLineSettings.dashArray!, + drawDashedLine(canvas, _series.connectorLineSettings.dashArray!, connectorLineStrokePaint!, linePath); } else { canvas.drawLine( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart index 6bbec44db..cf38b36ed 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/area_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/area_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// This class renders the area series. /// @@ -43,11 +60,13 @@ class AreaSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, this.borderDrawMode = BorderDrawMode.top, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress}) + ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader}) : super( key: key, onRendererCreated: onRendererCreated, @@ -82,7 +101,9 @@ class AreaSeries extends XyDataSeries { isVisibleInLegend: isVisibleInLegend, legendIconType: legendIconType, sortingOrder: sortingOrder, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Border type of area series. /// @@ -166,11 +187,13 @@ class AreaSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.borderDrawMode == borderDrawMode && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -206,6 +229,7 @@ class AreaSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, borderDrawMode, onRendererCreated, onPointTap, @@ -215,85 +239,3 @@ class AreaSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Area series -class AreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of AreaSeriesRenderer class. - AreaSeriesRenderer(); - - /// Creates a segment for a data point in the series. - ChartSegment _createSegments( - Path path, Path strokePath, int seriesIndex, double animateFactor, - [List? _points]) { - final AreaSegment segment = createSegment(); - final List oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - segment._series = _series as XyDataSeries; - segment.currentSegmentIndex = 0; - if (_points != null) { - segment.points = _points; - } - segment._seriesRenderer = this; - segment._seriesIndex = seriesIndex; - segment.animationFactor = animateFactor; - segment._path = path; - segment._strokePath = strokePath; - if (_renderingDetails!.widgetNeedUpdate && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldSegmentIndex = 0; - } - customizeSegment(segment); - segment._chart = _chart; - segment._chartState = _chartState!; - _segments.add(segment); - return segment; - } - - /// To draw area segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// To create area series segments - @override - AreaSegment createSegment() => AreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final AreaSegment areaSegment = segment as AreaSegment; - areaSegment._color = areaSegment._seriesRenderer._seriesColor; - areaSegment._strokeColor = areaSegment._seriesRenderer._seriesColor; - areaSegment._strokeWidth = areaSegment._series.width; - areaSegment.strokePaint = areaSegment.getStrokePaint(); - areaSegment.fillPaint = areaSegment.getFillPaint(); - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart index 015e60f66..771c2921d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bar_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/bar_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// This class has the properties of the bar series. /// @@ -50,11 +67,13 @@ class BarSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, List? dashArray, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -91,6 +110,8 @@ class BarSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, initialSelectedDataIndexes: initialSelectedDataIndexes, dashArray: dashArray); @@ -298,6 +319,7 @@ class BarSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -309,6 +331,7 @@ class BarSeries extends XyDataSeries { other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -345,6 +368,7 @@ class BarSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -361,130 +385,3 @@ class BarSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Bar series -class BarSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of BarSeriesRenderer class. - BarSeriesRenderer(); - - // Store the rect position // - late num _rectPosition; - - // Store the rect count // - late num _rectCount; - - /// To add bar segments to chart segments - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final BarSeries _barSeries = - _series as BarSeries; - final BarSegment segment = createSegment(); - final List oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - segment._series = _barSeries; - segment._chart = _chart; - segment._chartState = _chartState!; - segment._seriesRenderer = this; - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.animationFactor = animateFactor; - segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - !_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && - segment._oldSeriesRenderer!._segments[0] is BarSegment && - segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer!._dataPoints[pointIndex] - : null; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - if ((_chartState!._selectedSegments.length - 1 >= pointIndex) && - _chartState?._selectedSegments[pointIndex]._oldSegmentIndex == null) { - final ChartSegment selectedSegment = - _chartState?._selectedSegments[pointIndex] as ChartSegment; - selectedSegment._oldSeriesRenderer = - oldSeriesRenderers[selectedSegment._seriesIndex]; - selectedSegment._seriesRenderer = this; - selectedSegment._oldSegmentIndex = _getOldSegmentIndex(selectedSegment); - } - } else if (_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - _chartState!._segments != null && - _chartState!._segments.isNotEmpty) { - segment._oldSeriesVisible = - _chartState!._oldSeriesVisible[segment._seriesIndex]; - for (int i = 0; i < _chartState!._segments.length; i++) { - final BarSegment oldSegment = _chartState!._segments[i] as BarSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - oldSegment._seriesIndex == segment._seriesIndex) { - segment._oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segment._path = - _findingRectSeriesDashedBorder(currentPoint, _barSeries.borderWidth); - segment.segmentRect = - _getRRectFromRect(currentPoint.region!, _barSeries.borderRadius); - //Tracker rect - if (_barSeries.isTrackVisible) { - segment._trackBarRect = _getRRectFromRect( - currentPoint.trackerRectRegion!, _barSeries.borderRadius); - } - segment._segmentRect = segment.segmentRect; - customizeSegment(segment); - _segments.add(segment); - return segment; - } - - /// To draw bar segement - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - BarSegment createSegment() => BarSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final BarSegment barSegment = segment as BarSegment; - barSegment._color = segment._seriesRenderer._seriesColor; - barSegment._strokeColor = segment._series.borderColor; - barSegment._strokeWidth = segment._series.borderWidth; - barSegment.strokePaint = barSegment.getStrokePaint(); - barSegment.fillPaint = barSegment.getFillPaint(); - barSegment._trackerFillPaint = barSegment._getTrackerFillPaint(); - barSegment._trackerStrokePaint = barSegment._getTrackerStrokePaint(); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart index 6c92465c7..2be5e6a57 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/box_and_whisker_series.dart @@ -1,4 +1,20 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/box_and_whisker_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// This class holds the properties of the Box and Whisker series. /// @@ -42,10 +58,12 @@ class BoxAndWhiskerSeries extends XyDataSeries { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? trendlines, this.boxPlotMode = BoxPlotMode.normal, this.showMean = true}) @@ -83,6 +101,8 @@ class BoxAndWhiskerSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, trendlines: trendlines); ///To change the box plot rendering mode. @@ -219,13 +239,15 @@ class BoxAndWhiskerSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.boxPlotMode == boxPlotMode && other.showMean == showMean && other.spacing == spacing && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -261,6 +283,7 @@ class BoxAndWhiskerSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, boxPlotMode, showMean, spacing, @@ -273,8 +296,10 @@ class BoxAndWhiskerSeries extends XyDataSeries { } } -class _BoxPlotQuartileValues { - _BoxPlotQuartileValues( +/// Represents the class for box plot quartile values +class BoxPlotQuartileValues { + /// Creates an instance of box plot quartile values + BoxPlotQuartileValues( {this.minimum, this.maximum, //ignore: unused_element, avoid_unused_constructor_parameters @@ -284,238 +309,28 @@ class _BoxPlotQuartileValues { this.average, this.median, this.mean}); - num? minimum; - num? maximum; - List? outliers = []; - double? upperQuartile; - double? lowerQuartile; - num? average; - num? median; - num? mean; -} - -/// Creates series renderer for Box and Whisker series -class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of BoxAndWhiskerSeriesRenderer class. - BoxAndWhiskerSeriesRenderer(); - - late num _rectPosition; - - late num _rectCount; - - late BoxAndWhiskerSegment _segment; - - List? _oldSeriesRenderers; - - late _BoxPlotQuartileValues _boxPlotQuartileValues; - - /// To find the minimum, maximum, quartile and median value - /// of a box plot series. - void _findBoxPlotValues(List yValues, - CartesianChartPoint point, BoxPlotMode mode) { - final int yCount = yValues.length; - _boxPlotQuartileValues = _BoxPlotQuartileValues(); - _boxPlotQuartileValues.average = - //ignore: always_specify_types - (yValues.fold(0, (num x, y) => (x.toDouble()) + y!)) / yCount; - if (mode == BoxPlotMode.exclusive) { - _boxPlotQuartileValues.lowerQuartile = - _getExclusiveQuartileValue(yValues, yCount, 0.25); - _boxPlotQuartileValues.upperQuartile = - _getExclusiveQuartileValue(yValues, yCount, 0.75); - _boxPlotQuartileValues.median = - _getExclusiveQuartileValue(yValues, yCount, 0.5); - } else if (mode == BoxPlotMode.inclusive) { - _boxPlotQuartileValues.lowerQuartile = - _getInclusiveQuartileValue(yValues, yCount, 0.25); - _boxPlotQuartileValues.upperQuartile = - _getInclusiveQuartileValue(yValues, yCount, 0.75); - _boxPlotQuartileValues.median = - _getInclusiveQuartileValue(yValues, yCount, 0.5); - } else { - _boxPlotQuartileValues.median = _getMedian(yValues); - _getQuartileValues(yValues, yCount, _boxPlotQuartileValues); - } - _getMinMaxOutlier(yValues, yCount, _boxPlotQuartileValues); - point.minimum = _boxPlotQuartileValues.minimum; - point.maximum = _boxPlotQuartileValues.maximum; - point.lowerQuartile = _boxPlotQuartileValues.lowerQuartile; - point.upperQuartile = _boxPlotQuartileValues.upperQuartile; - point.median = _boxPlotQuartileValues.median; - point.outliers = _boxPlotQuartileValues.outliers; - point.mean = _boxPlotQuartileValues.average; - } - - /// To find exclusive quartile values. - double _getExclusiveQuartileValue( - List yValues, int count, num percentile) { - if (count == 0) { - return 0; - } else if (count == 1) { - return yValues[0]!.toDouble(); - } - num value = 0; - final num rank = percentile * (count + 1); - final int integerRank = (rank.abs()).floor(); - final num fractionRank = rank - integerRank; - if (integerRank == 0) { - value = yValues[0]!; - } else if (integerRank > count - 1) { - value = yValues[count - 1]!; - } else { - value = - fractionRank * (yValues[integerRank]! - yValues[integerRank - 1]!) + - yValues[integerRank - 1]!; - } - return value.toDouble(); - } - - /// To find inclusive quartile values. - double _getInclusiveQuartileValue( - List yValues, int count, num percentile) { - if (count == 0) { - return 0; - } else if (count == 1) { - return yValues[0]!.toDouble(); - } - num value = 0; - final num rank = percentile * (count - 1); - final int integerRank = (rank.abs()).floor(); - final num fractionRank = rank - integerRank; - value = fractionRank * (yValues[integerRank + 1]! - yValues[integerRank]!) + - yValues[integerRank]!; - return value.toDouble(); - } - - /// To find a midian value of each box plot point. - double _getMedian(List values) { - final int half = (values.length / 2).floor(); - return (values.length % 2 != 0 - ? values[half]! - : ((values[half - 1]! + values[half]!) / 2.0)) - .toDouble(); - } - /// To get the quartile values. - void _getQuartileValues(dynamic yValues, num count, - _BoxPlotQuartileValues _boxPlotQuartileValues) { - if (count == 1) { - _boxPlotQuartileValues.lowerQuartile = yValues[0]; - _boxPlotQuartileValues.upperQuartile = yValues[0]; - return; - } - final bool isEvenList = count % 2 == 0; - final num halfLength = count ~/ 2; - final List lowerQuartileArray = yValues.sublist(0, halfLength); - final List upperQuartileArray = - yValues.sublist(isEvenList ? halfLength : halfLength + 1, count); - _boxPlotQuartileValues.lowerQuartile = _getMedian(lowerQuartileArray); - _boxPlotQuartileValues.upperQuartile = _getMedian(upperQuartileArray); - } + /// Specifies the value of minimum + num? minimum; - /// To get the outliers values of box plot series. - void _getMinMaxOutlier(List yValues, int count, - _BoxPlotQuartileValues _boxPlotQuartileValues) { - final double interquartile = _boxPlotQuartileValues.upperQuartile! - - _boxPlotQuartileValues.lowerQuartile!; - final num rangeIQR = 1.5 * interquartile; - for (int i = 0; i < count; i++) { - if (yValues[i]! < _boxPlotQuartileValues.lowerQuartile! - rangeIQR) { - _boxPlotQuartileValues.outliers!.add(yValues[i]!); - } else { - _boxPlotQuartileValues.minimum = yValues[i]; - break; - } - } - for (int i = count - 1; i >= 0; i--) { - if (yValues[i]! > _boxPlotQuartileValues.upperQuartile! + rangeIQR) { - _boxPlotQuartileValues.outliers!.add(yValues[i]!); - } else { - _boxPlotQuartileValues.maximum = yValues[i]; - break; - } - } - } + /// Specifies the value of maximum + num? maximum; - /// Range box plot _segment is created here - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _segment = createSegment(); - _oldSeriesRenderers = _chartState!._oldSeriesRenderers; - _isRectSeries = false; - _segment._seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - _segment._seriesRenderer = this; - _segment._series = _series as XyDataSeries; - _segment.animationFactor = animateFactor; - _segment._pointColorMapper = currentPoint.pointColorMapper; - _segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - !_renderingDetails!.isLegendToggled && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers![_segment._seriesIndex]._seriesName == - _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = _oldSeriesRenderers![_segment._seriesIndex]; - _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); - } - _segment.calculateSegmentPoints(); - //stores the points for rendering box and whisker - high, low and rect points - _segment.points - ..add(Offset(currentPoint.markerPoint!.x, _segment._maxPoint.y)) - ..add(Offset(currentPoint.markerPoint!.x, _segment._minPoint.y)) - ..add(Offset(_segment._lowerX, _segment._topRectY)) - ..add(Offset(_segment._upperX, _segment._topRectY)) - ..add(Offset(_segment._upperX, _segment._bottomRectY)) - ..add(Offset(_segment._lowerX, _segment._bottomRectY)); - customizeSegment(_segment); - _segment.strokePaint = _segment.getStrokePaint(); - _segment.fillPaint = _segment.getFillPaint(); - _segments.add(_segment); - return _segment; - } + /// Specifies the list of outliers + List? outliers = []; - @override - BoxAndWhiskerSegment createSegment() => BoxAndWhiskerSegment(); + /// Specifies the value of the upper quartiles + double? upperQuartile; - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - final BoxAndWhiskerSegment boxSegment = _segment as BoxAndWhiskerSegment; - boxSegment._color = boxSegment._currentPoint!.pointColorMapper ?? - _segment._seriesRenderer._seriesColor; - boxSegment._strokeColor = _segment._series.borderColor; - boxSegment._strokeWidth = _segment._series.borderWidth; - boxSegment.strokePaint = boxSegment.getStrokePaint(); - boxSegment.fillPaint = boxSegment.getFillPaint(); - } + /// Specifies the value of lower quartiles + double? lowerQuartile; - /// To draw box plot series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment _segment) { - if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[_segment.currentSegmentIndex!], _chart); - } - _segment.onPaint(canvas); - } + /// Specifies the value of average + num? average; - ///Draws outlier with different shape and color of the appropriate - ///data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } + /// Specifies the value of median + num? median; - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); + /// Specifies the mean value + num? mean; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart index 0809aec30..689744895 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/bubble_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/bubble_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// This class holds the properties of the bubble series. /// @@ -44,10 +61,12 @@ class BubbleSeries extends XyDataSeries { SortingOrder? sortingOrder, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -85,6 +104,8 @@ class BubbleSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, initialSelectedDataIndexes: initialSelectedDataIndexes); ///Maximum radius value of the bubble in the series. @@ -178,13 +199,15 @@ class BubbleSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.maximumRadius == maximumRadius && other.minimumRadius == minimumRadius && other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -220,6 +243,7 @@ class BubbleSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, maximumRadius, minimumRadius, initialSelectedDataIndexes, @@ -231,105 +255,3 @@ class BubbleSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Bubble series -class BubbleSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of BubbleSeriesRenderer class. - BubbleSeriesRenderer(); - - // Store the maximum size // - double? _maxSize; - - // Store the minimum size // - double? _minSize; - - /// To add bubble segments to segment list - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final BubbleSegment segment = createSegment(); - final List oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment._seriesIndex = seriesIndex; - segment._series = _series as XyDataSeries; - segment.animationFactor = animateFactor; - segment._currentPoint = currentPoint; - segment._seriesRenderer = this; - if (_renderingDetails!.widgetNeedUpdate && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = - (segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer!._dataPoints[pointIndex] - : null; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - if ((_chartState!._selectedSegments.length - 1 >= pointIndex) && - _chartState?._selectedSegments[pointIndex]._oldSegmentIndex == null) { - final ChartSegment selectedSegment = - _chartState?._selectedSegments[pointIndex] as ChartSegment; - selectedSegment._oldSeriesRenderer = - oldSeriesRenderers[selectedSegment._seriesIndex]; - selectedSegment._seriesRenderer = this; - selectedSegment._oldSegmentIndex = _getOldSegmentIndex(selectedSegment); - } - } - segment.calculateSegmentPoints(); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - return segment; - } - - /// To draw bubble segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - BubbleSegment createSegment() => BubbleSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final BubbleSegment bubbleSegment = segment as BubbleSegment; - bubbleSegment._color = bubbleSegment._seriesRenderer._seriesColor; - bubbleSegment._strokeColor = bubbleSegment._series.borderColor; // ?? - // bubbleSegment._seriesRenderer._seriesColor; - bubbleSegment._strokeWidth = bubbleSegment._series.borderWidth; - bubbleSegment.strokePaint = bubbleSegment.getStrokePaint(); - bubbleSegment.fillPaint = bubbleSegment.getFillPaint(); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - if (seriesRenderer != null) { - canvas.drawPath(seriesRenderer._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart index dda6f7f7b..ec4b71c34 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/candle_series.dart @@ -1,4 +1,20 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../series_painter/candle_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'financial_series_base.dart'; /// This class holds the properties of the candle series. /// @@ -12,7 +28,7 @@ part of charts; /// /// {@youtube 560 315 https://www.youtube.com/watch?v=g5cniDExpRw} @immutable -class CandleSeries extends _FinancialSeriesBase { +class CandleSeries extends FinancialSeriesBase { /// Creating an argument constructor of CandleSeries class. CandleSeries({ ValueKey? key, @@ -45,10 +61,12 @@ class CandleSeries extends _FinancialSeriesBase { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes, bool? showIndicationForSameValues, List? trendlines, @@ -90,6 +108,8 @@ class CandleSeries extends _FinancialSeriesBase { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, bearColor: bearColor ?? Colors.red, bullColor: bullColor ?? Colors.green, enableSolidCandles: enableSolidCandles ?? false, @@ -151,12 +171,14 @@ class CandleSeries extends _FinancialSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.showIndicationForSameValues == showIndicationForSameValues && other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -192,6 +214,7 @@ class CandleSeries extends _FinancialSeriesBase { legendItemText, dashArray, opacity, + animationDelay, onRendererCreated, initialSelectedDataIndexes, showIndicationForSameValues, @@ -203,126 +226,3 @@ class CandleSeries extends _FinancialSeriesBase { return hashList(values); } } - -/// Creates series renderer for Candle series -class CandleSeriesRenderer extends _FinancialSerieBaseRenderer { - /// Calling the default constructor of CandleSeriesRenderer class. - CandleSeriesRenderer(); - - // Store the rect position // - @override - late num _rectPosition; - - // Store the rect count // - @override - late num _rectCount; - - late CandleSegment _candleSegment, _segment; - - late CandleSeries _candleSeries; - - late CandleSeriesRenderer _candelSereisRenderer; - - List? _oldSeriesRenderers; - - /// Range column _segment is created here - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _segment = createSegment(); - _oldSeriesRenderers = _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (_segment != null) { - _segment._seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - _segment._seriesRenderer = this; - _segment._series = _series as XyDataSeries; - _segment.animationFactor = animateFactor; - _segment._pointColorMapper = currentPoint.pointColorMapper; - _segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - !_renderingDetails!.isLegendToggled && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers![_segment._seriesIndex]._seriesName == - _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = - _oldSeriesRenderers![_segment._seriesIndex]; - _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); - } - _segment.calculateSegmentPoints(); - //stores the points for rendering candle - high, low and rect points - _segment.points - ..add(Offset(currentPoint.markerPoint!.x, _segment._highPoint.y)) - ..add(Offset(currentPoint.markerPoint!.x, _segment._lowPoint.y)) - ..add(Offset(_segment._openX, _segment._topRectY)) - ..add(Offset(_segment._closeX, _segment._topRectY)) - ..add(Offset(_segment._closeX, _segment._bottomRectY)) - ..add(Offset(_segment._openX, _segment._bottomRectY)); - _candleSegment = _segment; - customizeSegment(_segment); - _segment.strokePaint = _segment.getStrokePaint(); - _segment.fillPaint = _segment.getFillPaint(); - _segments.add(_segment); - } - return _segment; - } - - @override - CandleSegment createSegment() => CandleSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - _candleSeries = _series as CandleSeries; - _candelSereisRenderer = _segment._seriesRenderer as CandleSeriesRenderer; - _candleSegment = _candelSereisRenderer._candleSegment; - - if (_candleSeries.enableSolidCandles!) { - _candleSegment._isSolid = true; - _candleSegment._color = _candleSegment._isBull - ? _candleSeries.bullColor - : _candleSeries.bearColor; - } else { - _candleSegment._isSolid = !_candleSegment._isBull; - _candleSegment.currentSegmentIndex! - 1 >= 0 && - _candleSegment - ._seriesRenderer - ._dataPoints[_candleSegment.currentSegmentIndex! - 1] - .close > - _candleSegment - ._seriesRenderer - ._dataPoints[_candleSegment.currentSegmentIndex!] - .close == - true - ? _candleSegment._color = _candleSeries.bearColor - : _candleSegment._color = _candleSeries.bullColor; - } - _segment._strokeWidth = _segment._series.borderWidth; - } - - /// To draw candle series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment _segment) { - if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[_segment.currentSegmentIndex!], _chart); - } - _segment.onPaint(canvas); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) {} - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart index e9fd5c1f9..a31158e67 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/column_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/column_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// This class has the properties of the column series. /// @@ -48,12 +65,14 @@ class ColumnSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, List? dashArray, SeriesRendererCreatedCallback? onRendererCreated, List? initialSelectedDataIndexes, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, }) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -85,6 +104,8 @@ class ColumnSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, dashArray: dashArray, onRendererCreated: onRendererCreated, initialSelectedDataIndexes: initialSelectedDataIndexes, @@ -291,6 +312,7 @@ class ColumnSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -302,7 +324,8 @@ class ColumnSeries extends XyDataSeries { other.initialSelectedDataIndexes == initialSelectedDataIndexes && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -338,6 +361,7 @@ class ColumnSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -354,137 +378,3 @@ class ColumnSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Column series -class ColumnSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of ColumnSeriesRenderer class. - ColumnSeriesRenderer(); - - // Store the rect position // - late num _rectPosition; - - // Store the rect count // - late num _rectCount; - - /// To add column segments in segments list - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final ColumnSegment segment = createSegment() as ColumnSegment; - final List oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - final ColumnSeries _columnSeries = - _series as ColumnSeries; - segment._seriesRenderer = this; - segment._series = _columnSeries; - segment._chart = _chart; - segment._chartState = _chartState!; - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.animationFactor = animateFactor; - segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - _chartState!._zoomPanBehaviorRenderer._isPinching != true && - !_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && - segment._oldSeriesRenderer!._segments[0] is ColumnSegment && - segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer!._dataPoints[pointIndex] - : null; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - if ((_chartState!._selectedSegments.length - 1 >= pointIndex) && - _chartState?._selectedSegments[pointIndex]._oldSegmentIndex == null) { - final ChartSegment selectedSegment = - _chartState?._selectedSegments[pointIndex] as ChartSegment; - selectedSegment._oldSeriesRenderer = - oldSeriesRenderers[selectedSegment._seriesIndex]; - selectedSegment._seriesRenderer = this; - selectedSegment._oldSegmentIndex = _getOldSegmentIndex(selectedSegment); - } - } else if (_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - _chartState!._segments != null && - _chartState!._segments.isNotEmpty) { - segment._oldSeriesVisible = - _chartState!._oldSeriesVisible[segment._seriesIndex]; - ColumnSegment oldSegment; - for (int i = 0; i < _chartState!._segments.length; i++) { - oldSegment = _chartState!._segments[i] as ColumnSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - oldSegment._seriesIndex == segment._seriesIndex) { - segment._oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segment._path = - _findingRectSeriesDashedBorder(currentPoint, _columnSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (_columnSeries.borderRadius != null) { - segment.segmentRect = - _getRRectFromRect(currentPoint.region!, _columnSeries.borderRadius); - - //Tracker rect - if (_columnSeries.isTrackVisible) { - segment._trackRect = _getRRectFromRect( - currentPoint.trackerRectRegion!, _columnSeries.borderRadius); - } - } - segment._segmentRect = segment.segmentRect; - customizeSegment(segment); - _segments.add(segment); - return segment; - } - - /// To draw column series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - ChartSegment createSegment() => ColumnSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final ColumnSegment columnSegment = segment as ColumnSegment; - columnSegment._color = columnSegment._currentPoint!.pointColorMapper ?? - segment._seriesRenderer._seriesColor; - columnSegment._strokeColor = segment._series.borderColor; - columnSegment._strokeWidth = segment._series.borderWidth; - columnSegment.strokePaint = columnSegment.getStrokePaint(); - columnSegment.fillPaint = columnSegment.getFillPaint(); - columnSegment._trackerFillPaint = columnSegment._getTrackerFillPaint(); - columnSegment._trackerStrokePaint = columnSegment._getTrackerStrokePaint(); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/error_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/error_bar_series.dart new file mode 100644 index 000000000..3d682342e --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/error_bar_series.dart @@ -0,0 +1,444 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/utils/typedef.dart'; +import '../series_painter/error_bar_painter.dart'; +import 'xy_data_series.dart'; + +/// This class has the properties of the error bar series. +/// +/// To render a error bar chart, create an instance of [ErrorBarSeries], +/// and add it to the series collection property of [SfCartesianChart]. +/// +class ErrorBarSeries extends XyDataSeries { + /// Creating an argument constructor of ErrorBarSeries class. + ErrorBarSeries( + {ValueKey? key, + ChartSeriesRendererFactory? onCreateRenderer, + required List dataSource, + required ChartValueMapper xValueMapper, + required ChartValueMapper yValueMapper, + ChartValueMapper? sortFieldValueMapper, + ChartValueMapper? pointColorMapper, + SortingOrder? sortingOrder, + String? xAxisName, + String? yAxisName, + String? name, + Color? color, + double? width, + EmptyPointSettings? emptyPointSettings, + bool? isVisible, + double? animationDuration, + double? opacity, + double animationDelay = 1500, + List? dashArray, + SeriesRendererCreatedCallback? onRendererCreated, + String? legendItemText, + bool isVisibleInLegend = false, + LegendIconType legendIconType = LegendIconType.verticalLine, + this.type = ErrorBarType.fixed, + this.direction = Direction.both, + this.mode = RenderingMode.vertical, + this.verticalErrorValue = 3, + this.horizontalErrorValue = 1, + this.verticalPositiveErrorValue = 3, + this.horizontalPositiveErrorValue = 1, + this.verticalNegativeErrorValue = 3, + this.horizontalNegativeErrorValue = 1, + this.capLength = 10, + this.onRenderDetailsUpdate, + CartesianShaderCallback? onCreateShader}) + : super( + key: key, + onCreateRenderer: onCreateRenderer, + name: name, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper, + sortFieldValueMapper: sortFieldValueMapper, + pointColorMapper: pointColorMapper, + dataSource: dataSource, + xAxisName: xAxisName, + yAxisName: yAxisName, + color: color, + width: width ?? 2, + isVisible: isVisible, + emptyPointSettings: emptyPointSettings, + animationDuration: animationDuration, + legendItemText: legendItemText, + isVisibleInLegend: isVisibleInLegend, + legendIconType: legendIconType, + sortingOrder: sortingOrder, + opacity: opacity, + animationDelay: animationDelay, + dashArray: dashArray, + onRendererCreated: onRendererCreated, + onCreateShader: onCreateShader, + ); + + /// Type of the error bar. + /// + /// Defaults to 'ErrorBarType.fixed'. + /// + /// Other values are percentage, standardDeviation, custom, standardError. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.fixed, + /// ), + /// ], + /// )); + /// } + ///``` + final ErrorBarType? type; + + /// Direction of error bar. + /// + /// Defaults to 'Direction.both'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// direction: Direction.plus, + /// ), + /// ], + /// )); + /// } + ///``` + final Direction? direction; + + /// Mode of error bar. + /// + /// Defaults to 'RenderingMode.vertical'. + /// + /// Other values are horizontal and both. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// mode: RenderingMode.both, + /// ), + /// ], + /// )); + /// } + ///``` + final RenderingMode? mode; + + /// Vertical error value in Y direction. + /// + /// Defaults to '3'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// verticalErrorValue:2, + /// mode: RenderingMode.vertical, + /// ), + /// ], + /// )); + /// } + ///``` + final double? verticalErrorValue; + + /// Horizontal error value in X direction.. + /// + /// Defaults to '1'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// horizontalErrorValue:2, + /// mode: RenderingMode.horizontal, + /// ), + /// ], + /// )); + /// } + ///``` + final double? horizontalErrorValue; + + /// Vertical error value in positive Y direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to '3'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// verticalPositiveErrorValue:2 + /// ), + /// ], + /// )); + /// } + ///``` + final double? verticalPositiveErrorValue; + + /// Horizontal error value in positive X direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to '1'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// horizontalPositiveErrorValue:2 + /// ), + /// ], + /// )); + /// } + ///``` + final double? horizontalPositiveErrorValue; + + /// Vertical error value in negative Y direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to '3'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// verticalNegativeErrorValue:2 + /// ), + /// ], + /// )); + /// } + ///``` + final double? verticalNegativeErrorValue; + + /// Horizontal error value in negative X direction. + /// It's only applicable for 'ErrorBarType.custom'. + /// + /// Defaults to '1'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// type:ErrorBarType.custom, + /// horizontalNegativeErrorValue:2 + /// ), + /// ], + /// )); + /// } + ///``` + final double? horizontalNegativeErrorValue; + + /// Length of the error bar's cap. + /// + /// Defaults to '10'. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// capLength:20.0, + /// ), + /// ], + /// )); + /// } + ///``` + final double? capLength; + + /// Callback which gets called on error bar render. + /// + /// ```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// ErrorBarSeries( + /// onRenderDetailsUpdate: + /// (ErrorBarRenderDetails errorBarRenderDetails){ + /// print(args.pointIndex); + /// print(args.viewPortPointIndex); + /// print(args.calculatedErrorBarValues!.horizontalPositiveErrorValue); + /// print(args.calculatedErrorBarValues!.horizontalNegativeErrorValue); + /// print(args.calculatedErrorBarValues!.verticalPositiveErrorValue); + /// print(args.calculatedErrorBarValues!.verticalNegativeErrorValue); + /// }), + /// ), + /// ], + /// )); + /// } + ///``` + final ChartErrorBarRenderCallback? onRenderDetailsUpdate; + + /// Create the error bar series renderer. + ErrorBarSeriesRenderer createRenderer(ChartSeries series) { + ErrorBarSeriesRenderer seriesRenderer; + if (onCreateRenderer != null) { + seriesRenderer = onCreateRenderer!(series) as ErrorBarSeriesRenderer; + // ignore: unnecessary_null_comparison + assert(seriesRenderer != null, + 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); + return seriesRenderer; + } + return ErrorBarSeriesRenderer(); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is ErrorBarSeries && + other.key == key && + other.onCreateRenderer == onCreateRenderer && + other.dataSource == dataSource && + other.xValueMapper == xValueMapper && + other.yValueMapper == yValueMapper && + other.sortFieldValueMapper == sortFieldValueMapper && + other.pointColorMapper == pointColorMapper && + other.sortingOrder == sortingOrder && + other.xAxisName == xAxisName && + other.yAxisName == yAxisName && + other.name == name && + other.color == color && + other.width == width && + other.emptyPointSettings == emptyPointSettings && + other.isVisible == isVisible && + other.dashArray == dashArray && + other.animationDuration == animationDuration && + other.isVisibleInLegend == isVisibleInLegend && + other.legendIconType == legendIconType && + other.legendItemText == legendItemText && + other.opacity == opacity && + other.animationDelay == animationDelay && + other.onRendererCreated == onRendererCreated && + other.type == type && + other.direction == direction && + other.mode == mode && + other.verticalErrorValue == verticalErrorValue && + other.horizontalErrorValue == horizontalErrorValue && + other.verticalPositiveErrorValue == verticalPositiveErrorValue && + other.horizontalPositiveErrorValue == horizontalPositiveErrorValue && + other.verticalNegativeErrorValue == verticalNegativeErrorValue && + other.horizontalNegativeErrorValue == horizontalNegativeErrorValue && + other.capLength == capLength && + other.onCreateShader == onCreateShader && + other.onRenderDetailsUpdate == onRenderDetailsUpdate; + } + + @override + int get hashCode { + final List values = [ + key, + onCreateRenderer, + dataSource, + xValueMapper, + yValueMapper, + sortFieldValueMapper, + pointColorMapper, + sortingOrder, + xAxisName, + yAxisName, + name, + color, + width, + emptyPointSettings, + isVisible, + dashArray, + animationDuration, + isVisibleInLegend, + legendIconType, + legendItemText, + opacity, + animationDelay, + onRendererCreated, + type, + direction, + mode, + verticalErrorValue, + horizontalErrorValue, + verticalPositiveErrorValue, + verticalNegativeErrorValue, + horizontalPositiveErrorValue, + horizontalNegativeErrorValue, + capLength, + onRenderDetailsUpdate + ]; + return hashList(values); + } +} + +/// Represents the error values of error bar. +class ChartErrorValues { + /// Creates an instance of chart error values. + ChartErrorValues( + {this.errorX, this.errorY, this.customNegativeX, this.customNegativeY}); + + /// Specifies the value of x + num? errorX; + + /// Specifies the value of y + num? errorY; + + /// Specifies the value of x in custom type error bar. + num? customNegativeX; + + /// Specifies the value of y in custom type error bar. + num? customNegativeY; +} + +/// Holds the values related to standard error and standard deviation error bars. +class ErrorBarMean { + /// Creates an instance of ErrorBarMean. + ErrorBarMean( + {this.verticalSquareRoot, + this.horizontalSquareRoot, + this.verticalMean, + this.horizontalMean}); + + /// Mean's required square root value of all data points in y direction. + final num? verticalSquareRoot; + + /// Mean's required square root value of all data points in x direction. + final num? horizontalSquareRoot; + + /// Required mean value of all data points in y direction. + final num? verticalMean; + + /// Required mean value of all data points in x direction. + final num? horizontalMean; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart index 5d2a9d849..cbdb09fc8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/fastline_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/fastline_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; ///Renders the FastLineSeries. /// @@ -42,10 +59,12 @@ class FastLineSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress}) + ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -77,7 +96,9 @@ class FastLineSeries extends XyDataSeries { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); /// Create the fastline series renderer. FastLineSeriesRenderer createRenderer(ChartSeries series) { @@ -129,10 +150,12 @@ class FastLineSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -165,6 +188,7 @@ class FastLineSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, onPointTap, onPointDoubleTap, @@ -173,74 +197,3 @@ class FastLineSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Fastline series -class FastLineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of FastLineSeriesRenderer class. - FastLineSeriesRenderer(); - - //ignore: prefer_final_fields - List> _overallDataPoints = - >[]; - - ///Adds the segment to the segments list - ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, double animateFactor, - [List? _points]) { - final FastLineSegment segment = createSegment(); - segment._series = _series as XyDataSeries; - segment._seriesIndex = seriesIndex; - segment._seriesRenderer = this; - segment.animationFactor = animateFactor; - if (_points != null) { - segment.points = _points; - } - segment._oldSegmentIndex = 0; - customizeSegment(segment); - segment._chart = chart; - _segments.add(segment); - return segment; - } - - ///Renders the segment. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer - ?._checkWithSelectionState(_segments[0], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - FastLineSegment createSegment() => FastLineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final FastLineSegment fastLineSegment = segment as FastLineSegment; - fastLineSegment._color = fastLineSegment._seriesRenderer._seriesColor; - fastLineSegment._strokeColor = fastLineSegment._seriesRenderer._seriesColor; - fastLineSegment._strokeWidth = fastLineSegment._series.width; - fastLineSegment.strokePaint = fastLineSegment.getStrokePaint(); - fastLineSegment.fillPaint = fastLineSegment.getFillPaint(); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart index 3d0f75196..b9f6a0e47 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/financial_series_base.dart @@ -1,7 +1,23 @@ -part of charts; +import 'dart:ui'; -abstract class _FinancialSeriesBase extends XyDataSeries { - _FinancialSeriesBase( +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; + +/// Represents the financial series base +abstract class FinancialSeriesBase extends XyDataSeries { + /// Creates an instance of financial series base + FinancialSeriesBase( {ValueKey? key, ChartSeriesRendererFactory? onCreateRenderer, required List dataSource, @@ -39,12 +55,14 @@ abstract class _FinancialSeriesBase extends XyDataSeries { this.bearColor, this.bullColor, double? opacity, + double? animationDelay, this.showIndicationForSameValues = false, List? trendlines, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress}) + ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader}) : dashArray = dashArray ?? [0, 0], spacing = spacing ?? 0, super( @@ -77,18 +95,33 @@ abstract class _FinancialSeriesBase extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, trendlines: trendlines); + /// Specifies the volume value mapper final ChartIndexedValueMapper? volumeValueMapper; + + /// Specifies the open value mapper final ChartIndexedValueMapper? openValueMapper; + + /// Specifies the close value mapper final ChartIndexedValueMapper? closeValueMapper; + + /// Specifies the bear color final Color? bearColor; + + /// Specifies the bull color final Color? bullColor; + + /// Specifies whether the solid candles final bool? enableSolidCandles; + + /// Specifies the dash array @override final List dashArray; @@ -126,12 +159,3 @@ abstract class _FinancialSeriesBase extends XyDataSeries { ///Defaults to `false` final bool showIndicationForSameValues; } - -abstract class _FinancialSerieBaseRenderer extends XyDataSeriesRenderer { - _FinancialSerieBaseRenderer(); - - // ignore:unused_field - late num _rectPosition; - // ignore:unused_field - late num _rectCount; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart index b717bef61..38138705b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hilo_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/hilo_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'financial_series_base.dart'; /// Renders the Hilo series. /// @@ -7,7 +24,7 @@ part of charts; ///To render a HiLo chart, create an instance of HiloSeries, and add it to the series collection property of [SfCartesianChart]. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=uSsKhlRzC2Q} -class HiloSeries extends _FinancialSeriesBase { +class HiloSeries extends FinancialSeriesBase { /// Creating an argument constructor of HiloSeries class. HiloSeries({ ValueKey? key, @@ -37,6 +54,7 @@ class HiloSeries extends _FinancialSeriesBase { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, double? spacing, List? initialSelectedDataIndexes, bool? showIndicationForSameValues, @@ -45,6 +63,7 @@ class HiloSeries extends _FinancialSeriesBase { ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, }) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -74,6 +93,8 @@ class HiloSeries extends _FinancialSeriesBase { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -133,13 +154,15 @@ class HiloSeries extends _FinancialSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.spacing == spacing && other.showIndicationForSameValues == showIndicationForSameValues && other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -172,6 +195,7 @@ class HiloSeries extends _FinancialSeriesBase { legendItemText, dashArray, opacity, + animationDelay, spacing, onRendererCreated, initialSelectedDataIndexes, @@ -184,101 +208,3 @@ class HiloSeries extends _FinancialSeriesBase { return hashList(values); } } - -/// Creates series renderer for Hilo series -class HiloSeriesRenderer extends _FinancialSerieBaseRenderer { - /// Calling the default constructor of HiloSeriesRenderer class. - HiloSeriesRenderer(); - - // Store the rect position // - @override - late num _rectPosition; - - // Store the rect count // - @override - late num _rectCount; - - /// Hilo segment is created here - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _isRectSeries = false; - final HiloSegment segment = createSegment(); - final List oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.points.add( - Offset(currentPoint.markerPoint2!.x, currentPoint.markerPoint2!.y)); - segment._series = _series as XyDataSeries; - segment._seriesRenderer = this; - segment.animationFactor = animateFactor; - segment._pointColorMapper = currentPoint.pointColorMapper; - segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - !_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - } - segment.calculateSegmentPoints(); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render hilo series segments. - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - if (!((segment._currentPoint?.low == segment._currentPoint?.high) && - //ignore: always_specify_types - !(_series as HiloSeries).showIndicationForSameValues)) { - segment.onPaint(canvas); - } - } - - @override - HiloSegment createSegment() => HiloSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._seriesRenderer._seriesColor; - segment._strokeWidth = segment._series.borderWidth; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart index ed4dd7693..ef6ebc512 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/hiloopenclose_series.dart @@ -1,4 +1,20 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../series_painter/hiloopenclose_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'financial_series_base.dart'; /// Renders the HiloOpenClose series. /// @@ -8,7 +24,7 @@ part of charts; /// and add it to the series collection property of [SfCartesianChart]. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=g5cniDExpRw} -class HiloOpenCloseSeries extends _FinancialSeriesBase { +class HiloOpenCloseSeries extends FinancialSeriesBase { /// Creating an argument constructor of HiloOpenCloseSeries class. HiloOpenCloseSeries( {ValueKey? key, @@ -41,6 +57,7 @@ class HiloOpenCloseSeries extends _FinancialSeriesBase { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, double? spacing, List? initialSelectedDataIndexes, bool? showIndicationForSameValues, @@ -48,7 +65,8 @@ class HiloOpenCloseSeries extends _FinancialSeriesBase { SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress}) + ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -88,6 +106,8 @@ class HiloOpenCloseSeries extends _FinancialSeriesBase { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, bearColor: bearColor ?? Colors.red, bullColor: bullColor ?? Colors.green, initialSelectedDataIndexes: initialSelectedDataIndexes, @@ -153,13 +173,15 @@ class HiloOpenCloseSeries extends _FinancialSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.spacing == spacing && other.showIndicationForSameValues == showIndicationForSameValues && other.initialSelectedDataIndexes == other.initialSelectedDataIndexes && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -196,6 +218,7 @@ class HiloOpenCloseSeries extends _FinancialSeriesBase { legendItemText, dashArray, opacity, + animationDelay, spacing, onRendererCreated, initialSelectedDataIndexes, @@ -208,100 +231,3 @@ class HiloOpenCloseSeries extends _FinancialSeriesBase { return hashList(values); } } - -/// Creates series renderer for Hilo open close series -class HiloOpenCloseSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of HiloOpenCloseSeriesRenderer class. - HiloOpenCloseSeriesRenderer(); - - // Store the rect position // - late num _rectPosition; - - // Store the rect count // - late num _rectCount; - - late HiloOpenCloseSeries _hiloOpenCloseSeries; - - late HiloOpenCloseSegment _segment; - - List? _oldSeriesRenderers; - - /// HiloOpenClose _segment is created here - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _segment = createSegment(); - _oldSeriesRenderers = _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (_segment != null) { - _segment._seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - _segment._seriesRenderer = this; - _segment._series = _series as XyDataSeries; - _segment.animationFactor = animateFactor; - _segment._pointColorMapper = currentPoint.pointColorMapper; - _segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - !_renderingDetails!.isLegendToggled && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers![_segment._seriesIndex]._seriesName == - _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = - _oldSeriesRenderers![_segment._seriesIndex]; - _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); - } - _segment.calculateSegmentPoints(); - //stores the points for rendering Hilo open close segment, High, low, open, close - _segment.points - ..add(Offset(currentPoint.markerPoint!.x, _segment._highPoint.y)) - ..add(Offset(currentPoint.markerPoint!.x, _segment._lowPoint.y)) - ..add(Offset(_segment._openX, _segment._openY)) - ..add(Offset(_segment._closeX, _segment._closeY)); - customizeSegment(_segment); - _segment.strokePaint = _segment.getStrokePaint(); - _segment.fillPaint = _segment.getFillPaint(); - _segments.add(_segment); - } - return _segment; - } - - /// To render hilo open close series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment _segment) { - if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[_segment.currentSegmentIndex!], _chart); - } - _segment.onPaint(canvas); - } - - @override - HiloOpenCloseSegment createSegment() => HiloOpenCloseSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - _hiloOpenCloseSeries = _series as HiloOpenCloseSeries; - _segment._color = _segment._seriesRenderer._seriesColor; - _segment._strokeColor = _segment is HiloOpenCloseSegment && _segment._isBull - ? _hiloOpenCloseSeries.bullColor - : _hiloOpenCloseSeries.bearColor; - _segment._strokeWidth = _segment._series.borderWidth; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) {} - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart index 92c640194..b05046619 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/histogram_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/histogram_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// This class has the properties of the column series. /// @@ -47,6 +64,7 @@ class HistogramSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, List? dashArray, this.binInterval, this.showNormalDistributionCurve = false, @@ -56,7 +74,8 @@ class HistogramSeries extends XyDataSeries { SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress}) + ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -91,6 +110,8 @@ class HistogramSeries extends XyDataSeries { onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, dashArray: dashArray); ///Interval value by which the data points are grouped and rendered as bars, in histogram series. @@ -389,6 +410,7 @@ class HistogramSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -404,7 +426,8 @@ class HistogramSeries extends XyDataSeries { other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -440,6 +463,7 @@ class HistogramSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -461,209 +485,24 @@ class HistogramSeries extends XyDataSeries { } } -class _HistogramValues { - _HistogramValues( +/// Represents the histogram values +class HistogramValues { + /// Creates an instance of histogram values + HistogramValues( {this.sDValue, this.mean, this.binWidth, this.yValues, this.minValue}); - num? sDValue; - num? mean; - num? binWidth; - num? minValue; - List? yValues = []; -} - -/// Creates series renderer for Histogram series -class HistogramSeriesRenderer extends XyDataSeriesRenderer { - late num _rectPosition; - late num _rectCount; - late _HistogramValues _histogramValues; - late HistogramSegment _segment; - List? _oldSeriesRenderers; - late HistogramSeries _histogramSeries; - BorderRadius? _borderRadius; - /// To get the proper data for histogram series - void _processData(HistogramSeries series, List yValues, - num yValuesCount) { - _histogramValues = _HistogramValues(); - _histogramValues.yValues = yValues; - final num mean = yValuesCount / _histogramValues.yValues!.length; - _histogramValues.mean = mean; - num sumValue = 0; - num sDValue; - for (int value = 0; value < _histogramValues.yValues!.length; value++) { - sumValue += (_histogramValues.yValues![value] - _histogramValues.mean!) * - (_histogramValues.yValues![value] - _histogramValues.mean!); - } - sDValue = math.sqrt(sumValue / _histogramValues.yValues!.length - 1).isNaN - ? 0 - : (math.sqrt(sumValue / _histogramValues.yValues!.length - 1)).round(); - _histogramValues.sDValue = sDValue; - } - - /// Find the path for distribution line in the histogram - Path _findNormalDistributionPath( - HistogramSeries series, SfCartesianChart chart) { - final num min = _xAxisRenderer!._visibleRange!.minimum; - final num max = _xAxisRenderer!._visibleRange!.maximum; - num xValue, yValue; - final Path path = Path(); - _ChartLocation pointLocation; - const num pointsCount = 500; - final num del = (max - min) / (pointsCount - 1); - for (int i = 0; i < pointsCount; i++) { - xValue = min + i * del; - yValue = math.exp(-(xValue - _histogramValues.mean!) * - (xValue - _histogramValues.mean!) / - (2 * _histogramValues.sDValue! * _histogramValues.sDValue!)) / - (_histogramValues.sDValue! * math.sqrt(2 * math.pi)); - pointLocation = _calculatePoint( - xValue, - yValue * - _histogramValues.binWidth! * - _histogramValues.yValues!.length, - _xAxisRenderer!, - _yAxisRenderer!, - _chartState!._requireInvertedAxis, - series, - _chartState!._chartAxis._axisClipRect); - i == 0 - ? path.moveTo(pointLocation.x, pointLocation.y) - : path.lineTo(pointLocation.x, pointLocation.y); - } - return path; - } - - /// To add histogram segments to segments list - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - int pointIndex, - _VisibleRange sideBySideInfo, - int seriesIndex, - double animateFactor) { - _segment = createSegment(); - _oldSeriesRenderers = _chartState!._oldSeriesRenderers; - _histogramSeries = _series as HistogramSeries; - _borderRadius = _histogramSeries.borderRadius; - _segment._seriesRenderer = this; - _segment._series = _histogramSeries; - _segment._chart = _chart; - _segment._chartState = _chartState!; - _segment._seriesIndex = seriesIndex; - _segment.currentSegmentIndex = pointIndex; - _segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - _segment.animationFactor = animateFactor; - final num origin = math.max(_yAxisRenderer!._visibleRange!.minimum, 0); - currentPoint.region = _calculateRectangle( - currentPoint.xValue + sideBySideInfo.minimum, - currentPoint.yValue, - currentPoint.xValue + sideBySideInfo.maximum, - math.max(_yAxisRenderer!._visibleRange!.minimum, 0), - this, - _chartState!); - _segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - !_renderingDetails!.isLegendToggled && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers![_segment._seriesIndex]._seriesName == - _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = _oldSeriesRenderers![_segment._seriesIndex]; - _segment._oldPoint = (_segment._oldSeriesRenderer!._segments.isNotEmpty && - _segment._oldSeriesRenderer!._segments[0] is HistogramSegment && - _segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) - ? _segment._oldSeriesRenderer!._dataPoints[pointIndex] - : null; - _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); - } else if (_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - _chartState!._segments != null && - _chartState!._segments.isNotEmpty) { - _segment._oldSeriesVisible = - _chartState!._oldSeriesVisible[_segment._seriesIndex]; - for (int i = 0; i < _chartState!._segments.length; i++) { - final HistogramSegment oldSegment = - _chartState!._segments[i] as HistogramSegment; - if (oldSegment.currentSegmentIndex == _segment.currentSegmentIndex && - oldSegment._seriesIndex == _segment._seriesIndex) { - _segment._oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - _segment._path = _findingRectSeriesDashedBorder( - currentPoint, _histogramSeries.borderWidth); - if (_borderRadius != null) { - _segment.segmentRect = - _getRRectFromRect(currentPoint.region!, _borderRadius!); - } - //Tracker rect - if (_histogramSeries.isTrackVisible) { - currentPoint.trackerRectRegion = _calculateShadowRectangle( - currentPoint.xValue + sideBySideInfo.minimum, - currentPoint.yValue, - currentPoint.xValue + sideBySideInfo.maximum, - origin, - this, - _chartState!, - Offset(_segment._seriesRenderer._xAxisRenderer!._axis.plotOffset, - _segment._seriesRenderer._yAxisRenderer!._axis.plotOffset)); - if (_borderRadius != null) { - _segment._trackRect = - _getRRectFromRect(currentPoint.trackerRectRegion!, _borderRadius!); - } - } - _segment._segmentRect = _segment.segmentRect; - customizeSegment(_segment); - _segments.add(_segment); - return _segment; - } - - /// To render histogram series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } + /// Specifies the value of SD + num? sDValue; - /// Creates a segment for a data point in the series. - @override - HistogramSegment createSegment() => HistogramSegment(); + /// Specifies the value of mean + num? mean; - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final HistogramSegment histogramSegment = segment as HistogramSegment; - histogramSegment._color = - histogramSegment._currentPoint!.pointColorMapper ?? - segment._seriesRenderer._seriesColor; - histogramSegment._strokeColor = segment._series.borderColor; - histogramSegment._strokeWidth = segment._series.borderWidth; - histogramSegment.strokePaint = histogramSegment.getStrokePaint(); - histogramSegment.fillPaint = histogramSegment.getFillPaint(); - histogramSegment._trackerFillPaint = - histogramSegment._getTrackerFillPaint(); - histogramSegment._trackerStrokePaint = - histogramSegment._getTrackerStrokePaint(); - } + /// Specifies the value of bin width + num? binWidth; - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } + /// Specifies the minimum value + num? minValue; - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); + /// Specifies the list of y values + List? yValues = []; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart index 6e60852c8..51954b9d5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/line_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/line_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the line series. /// @@ -40,11 +57,13 @@ class LineSeries extends XyDataSeries { SortingOrder? sortingOrder, String? legendItemText, double? opacity, + double? animationDelay, List? initialSelectedDataIndexes, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress}) + ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader}) : super( key: key, onRendererCreated: onRendererCreated, @@ -77,6 +96,8 @@ class LineSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, initialSelectedDataIndexes: initialSelectedDataIndexes); /// Create the line series renderer. @@ -131,10 +152,12 @@ class LineSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -170,6 +193,7 @@ class LineSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, initialSelectedDataIndexes, onPointTap, @@ -179,95 +203,3 @@ class LineSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Line series -class LineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of LineSeriesRenderer class. - LineSeriesRenderer(); - - late LineSegment _lineSegment, _segment; - - List? _oldSeriesRenderers; - - /// To add line segments to segments list - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animateFactor) { - _segment = createSegment(); - _oldSeriesRenderers = _chartState!._oldSeriesRenderers; - _segment._series = _series as XyDataSeries; - _segment._seriesRenderer = this; - _segment._seriesIndex = seriesIndex; - _segment._currentPoint = currentPoint; - _segment.currentSegmentIndex = pointIndex; - _segment._nextPoint = _nextPoint; - _segment._chart = _chart; - _segment._chartState = _chartState!; - _segment.animationFactor = animateFactor; - _segment._pointColorMapper = currentPoint.pointColorMapper; - if (_renderingDetails!.widgetNeedUpdate && - _oldSeriesRenderers != null && - _oldSeriesRenderers!.isNotEmpty && - _oldSeriesRenderers!.length - 1 >= _segment._seriesIndex && - _oldSeriesRenderers![_segment._seriesIndex]._seriesName == - _segment._seriesRenderer._seriesName) { - _segment._oldSeriesRenderer = _oldSeriesRenderers![_segment._seriesIndex]; - _segment._oldSegmentIndex = _getOldSegmentIndex(_segment); - } - _segment.calculateSegmentPoints(); - _segment.points.add(Offset(_segment._x1, _segment._y1)); - _segment.points.add(Offset(_segment._x2, _segment._y2)); - customizeSegment(_segment); - _segments.add(_segment); - return _segment; - } - - /// To render line series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment _segment) { - if (_segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - _segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[_segment.currentSegmentIndex!], _chart); - } - _segment.onPaint(canvas); - } - - /// Creates a _segment for a data point in the series. - @override - LineSegment createSegment() => LineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment _segment) { - _lineSegment = _segment as LineSegment; - _lineSegment._color = _lineSegment._pointColorMapper ?? - _lineSegment._series.color ?? - _lineSegment._seriesRenderer._seriesColor; - _lineSegment._strokeColor = _lineSegment._pointColorMapper ?? - _lineSegment._series.color ?? - _lineSegment._seriesRenderer._seriesColor; - _lineSegment._strokeWidth = _lineSegment._series.width; - _lineSegment.strokePaint = _lineSegment.getStrokePaint(); - _lineSegment.fillPaint = _lineSegment.getFillPaint(); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart index 8fd215f00..712a766a6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_area_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/range_area_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the range area series. /// @@ -45,11 +62,13 @@ class RangeAreaSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, this.borderDrawMode = RangeAreaBorderMode.all, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, - ChartPointInteractionCallback? onPointLongPress}) + ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader}) : super( key: key, onCreateRenderer: onCreateRenderer, @@ -85,7 +104,9 @@ class RangeAreaSeries extends XyDataSeries { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Border type of area series. /// @@ -162,11 +183,13 @@ class RangeAreaSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.borderDrawMode == borderDrawMode && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -204,6 +227,7 @@ class RangeAreaSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, borderDrawMode, onRendererCreated, onPointTap, @@ -213,76 +237,3 @@ class RangeAreaSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Range area series -class RangeAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of RangeAreaSeriesRenderer class. - RangeAreaSeriesRenderer(); - - /// Range Area segment is created here - ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, double animateFactor, - [List? _points]) { - final RangeAreaSegment segment = createSegment(); - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.animationFactor = animateFactor; - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - if (_points != null) { - segment.points = _points; - } - segment.currentSegmentIndex = 0; - customizeSegment(segment); - segment._chart = chart; - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - segment._oldSegmentIndex = 0; - _segments.add(segment); - } - return segment; - } - - /// To render range area series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer - ?._checkWithSelectionState(_segments[0], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - RangeAreaSegment createSegment() => RangeAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._series.borderColor; - segment._strokeWidth = segment._series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart index 13c5280fa..944d5fa70 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/range_column_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/range_column_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the range column series. /// @@ -54,11 +71,13 @@ class RangeColumnSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, List? dashArray, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -92,6 +111,8 @@ class RangeColumnSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, dashArray: dashArray, onRendererCreated: onRendererCreated, onPointTap: onPointTap, @@ -295,6 +316,7 @@ class RangeColumnSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -306,6 +328,7 @@ class RangeColumnSeries extends XyDataSeries { other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -343,6 +366,7 @@ class RangeColumnSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -359,143 +383,3 @@ class RangeColumnSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Range column series -class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of RangeColumnSeriesRenderer class. - RangeColumnSeriesRenderer(); - - // Store the rect position // - late num _rectPosition; - - // Store the rect count // - late num _rectCount; - - /// To add range column segments in segments list - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - _isRectSeries = true; - final RangeColumnSegment segment = createSegment(); - final List? oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - final RangeColumnSeries _rangeColumnSeries = - _series as RangeColumnSeries; - final BorderRadius borderRadius = _rangeColumnSeries.borderRadius; - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.points.add( - Offset(currentPoint.markerPoint2!.x, currentPoint.markerPoint2!.y)); - segment._seriesRenderer = this; - segment._series = _rangeColumnSeries; - segment._chart = _chart; - segment._chartState = _chartState!; - segment.animationFactor = animateFactor; - segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - _chartState!._zoomPanBehaviorRenderer._isPinching != true && - !_renderingDetails!.isLegendToggled && - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && - segment._oldSeriesRenderer!._segments[0] is RangeColumnSegment && - segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer!._dataPoints[pointIndex] - : null; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - if ((_chartState!._selectedSegments.length - 1 >= pointIndex) && - _chartState?._selectedSegments[pointIndex]._oldSegmentIndex == null) { - final ChartSegment selectedSegment = - _chartState?._selectedSegments[pointIndex] as ChartSegment; - selectedSegment._oldSeriesRenderer = - oldSeriesRenderers[selectedSegment._seriesIndex]; - selectedSegment._seriesRenderer = this; - selectedSegment._oldSegmentIndex = _getOldSegmentIndex(selectedSegment); - } - } else if (_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - _chartState!._segments != null && - _chartState!._segments.isNotEmpty) { - segment._oldSeriesVisible = - _chartState!._oldSeriesVisible[segment._seriesIndex]; - RangeColumnSegment oldSegment; - for (int i = 0; i < _chartState!._segments.length; i++) { - oldSegment = _chartState!._segments[i] as RangeColumnSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - oldSegment._seriesIndex == segment._seriesIndex) { - segment._oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segment._path = _findingRectSeriesDashedBorder( - currentPoint, _rangeColumnSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (borderRadius != null) { - segment.segmentRect = - _getRRectFromRect(currentPoint.region!, borderRadius); - - //Tracker rect - if (_rangeColumnSeries.isTrackVisible) { - segment._trackRect = - _getRRectFromRect(currentPoint.trackerRectRegion!, borderRadius); - } - } - segment._segmentRect = segment.segmentRect; - customizeSegment(segment); - _segments.add(segment); - return segment; - } - - /// To render range column series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - RangeColumnSegment createSegment() => RangeColumnSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final RangeColumnSegment rangeColumnSegment = segment as RangeColumnSegment; - rangeColumnSegment._color = segment._seriesRenderer._seriesColor; - rangeColumnSegment._strokeColor = segment._series.borderColor; - rangeColumnSegment._strokeWidth = segment._series.borderWidth; - rangeColumnSegment.strokePaint = rangeColumnSegment.getStrokePaint(); - rangeColumnSegment.fillPaint = rangeColumnSegment.getFillPaint(); - rangeColumnSegment._trackerFillPaint = - rangeColumnSegment._getTrackerFillPaint(); - rangeColumnSegment._trackerStrokePaint = - rangeColumnSegment._getTrackerStrokePaint(); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart index 7897c6243..157035174 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/scatter_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/scatter_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the scatter series. /// @@ -39,10 +56,12 @@ class ScatterSeries extends XyDataSeries { SortingOrder? sortingOrder, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -74,6 +93,8 @@ class ScatterSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -132,10 +153,12 @@ class ScatterSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -171,6 +194,7 @@ class ScatterSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, initialSelectedDataIndexes, onPointTap, @@ -180,138 +204,3 @@ class ScatterSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Scatter series -class ScatterSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of ScatterSeriesRenderer class. - ScatterSeriesRenderer(); - - // ignore:unused_field - CartesianChartPoint? _point; - - final bool _isLineType = false; - - ScatterSegment? _segment; - - ///Adds the points to the segments . - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final ScatterSegment segment = createSegment(); - final List oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - segment.animationFactor = animateFactor; - segment._point = currentPoint; - segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - !_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && - segment._oldSeriesRenderer!._segments[0] is ScatterSegment && - segment._oldSeriesRenderer!._dataPoints.length - 1 >= - pointIndex) - ? segment._oldSeriesRenderer!._dataPoints[pointIndex] - : null; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - if ((_chartState!._selectedSegments.length - 1 >= pointIndex) && - _chartState?._selectedSegments[pointIndex]._oldSegmentIndex == - null) { - final ChartSegment selectedSegment = - _chartState?._selectedSegments[pointIndex] as ChartSegment; - selectedSegment._oldSeriesRenderer = - oldSeriesRenderers[selectedSegment._seriesIndex]; - selectedSegment._seriesRenderer = this; - selectedSegment._oldSegmentIndex = - _getOldSegmentIndex(selectedSegment); - } - } - final _ChartLocation location = _calculatePoint( - currentPoint.xValue, - currentPoint.yValue, - _xAxisRenderer!, - _yAxisRenderer!, - _chartState!._requireInvertedAxis, - _series, - _chartState!._chartAxis._axisClipRect); - segment.points.add(Offset(location.x, location.y)); - segment._segmentRect = - RRect.fromRectAndRadius(currentPoint.region!, Radius.zero); - customizeSegment(segment); - _segments.add(segment); - _segment = segment; - } - return segment; - } - - /// To render scatter series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - ScatterSegment createSegment() => ScatterSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final ScatterSegment scatterSegment = segment as ScatterSegment; - scatterSegment._color = scatterSegment._seriesRenderer._seriesColor; - scatterSegment._strokeColor = scatterSegment._series.borderColor; - scatterSegment._strokeWidth = - ((scatterSegment._series.markerSettings.shape == - DataMarkerType.verticalLine || - scatterSegment._series.markerSettings.shape == - DataMarkerType.horizontalLine) && - scatterSegment._series.borderWidth == 0) - ? scatterSegment._series.markerSettings.borderWidth - : scatterSegment._series.borderWidth; - scatterSegment.strokePaint = scatterSegment.getStrokePaint(); - scatterSegment.fillPaint = scatterSegment.getFillPaint(); - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - final Size size = - Size(_series.markerSettings.width, _series.markerSettings.height); - final Path markerPath = _getMarkerShapesPath( - _series.markerSettings.shape, - Offset(pointX, pointY), - size, - seriesRenderer!, - index, - null, - seriesRenderer._seriesElementAnimation, - _segment); - canvas.drawPath(markerPath, fillPaint); - canvas.drawPath(markerPath, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart index e1026d8a0..67a547da8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series.dart @@ -1,9 +1,44 @@ -part of charts; - -/// This class has the properties of the cartesian series. +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/rendering_details.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/template/rendering.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.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 '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/data_label.dart'; +import '../common/interactive_tooltip.dart'; +import '../common/marker.dart'; +import '../trendlines/trendlines.dart'; +import '../user_interaction/trackball.dart'; +import '../user_interaction/trackball_painter.dart'; +import '../user_interaction/trackball_template.dart'; +import '../utils/helper.dart'; +import 'series_renderer_properties.dart'; +import 'xy_data_series.dart'; + +/// This class has the properties of the Cartesian series. /// /// CartesianSeries Provides a variety of options, such as animation, dynamic animation, Transpose, color palette, -/// color mapping to customize the cartesian chart. The chart’s data source can be sorted using the sorting order and +/// color mapping to customize the Cartesian chart. The chart’s data source can be sorted using the sorting order and /// sortFieldValueMapper properties of series. /// /// Provides the options for animation, color palette, sorting, and empty point mode to customize the charts. @@ -37,6 +72,7 @@ abstract class CartesianSeries extends ChartSeries { this.onPointTap, this.onPointDoubleTap, this.onPointLongPress, + this.onCreateShader, MarkerSettings? markerSettings, bool? isVisible, bool? enableTooltip, @@ -51,6 +87,7 @@ abstract class CartesianSeries extends ChartSeries { bool? isVisibleInLegend, LegendIconType? legendIconType, double? opacity, + double? animationDelay, SortingOrder? sortingOrder}) : isVisible = isVisible ?? true, markerSettings = markerSettings ?? const MarkerSettings(), @@ -66,6 +103,7 @@ abstract class CartesianSeries extends ChartSeries { isVisibleInLegend = isVisibleInLegend ?? true, borderWidth = borderWidth ?? 0, opacity = opacity ?? 1, + animationDelay = animationDelay ?? 0, sortingOrder = sortingOrder ?? SortingOrder.none, super( name: name, @@ -82,6 +120,7 @@ abstract class CartesianSeries extends ChartSeries { selectionBehavior: selectionBehavior, legendIconType: legendIconType, sortingOrder: sortingOrder, + animationDelay: animationDelay, opacity: opacity); ///Key to identify a series in a collection. @@ -177,7 +216,7 @@ abstract class CartesianSeries extends ChartSeries { ///Called when tapped on the chart data point. /// - ///The user can fetch the series index, point index, viewport point index and + ///The user can fetch the series index, point index, view port point index and /// data of the tapped data point. ///```dart ///Widget build(BuildContext context) { @@ -198,7 +237,7 @@ abstract class CartesianSeries extends ChartSeries { ///Called when double tapped on the chart data point. /// - ///The user can fetch the series index, point index, viewport point index and + ///The user can fetch the series index, point index, view port point index and /// data of the double-tapped data point. ///```dart ///Widget build(BuildContext context) { @@ -219,7 +258,7 @@ abstract class CartesianSeries extends ChartSeries { ///Called when long pressed on the chart data point. /// - ///The user can fetch the series index, point index, viewport point index and + ///The user can fetch the series index, point index, view port point index and /// data of the long-pressed data point. ///```dart ///Widget build(BuildContext context) { @@ -1128,6 +1167,29 @@ abstract class CartesianSeries extends ChartSeries { @override final bool isVisible; + /// Delay duration of the series animation.It takes a millisecond value as input. + /// By default, the series will get animated for the specified duration. + /// If animationDelay is specified, then the series will begin to animate + /// after the specified duration. + /// + /// Defaults to 0 for all the series except ErrorBarSeries. + /// The default value for the ErrorBarSeries is 1500. + /// + ///```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// BarSeries( + /// animationDelay: 300, + /// ), + /// ], + /// )); + /// } + ///``` + @override + final double? animationDelay; + /// List of data indexes to initially be selected /// /// Defaults to `null`. @@ -1144,20 +1206,41 @@ abstract class CartesianSeries extends ChartSeries { /// ); /// } final List? initialSelectedDataIndexes; + + ///Fills the data points with the gradient and image shaders. + /// + ///The data points of pie, doughnut and radial bar charts can be filled with [gradient](https://api.flutter.dev/flutter/dart-ui/Gradient-class.html) + /// (linear, radial and sweep gradient) and [image shaders](https://api.flutter.dev/flutter/dart-ui/ImageShader-class.html). + /// + ///All the data points are together considered as a single segment and the shader is applied commonly. + /// + ///Defaults to `null`. + /// + ///```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// child: SfCartesianChart( + /// series: >[ + /// BarSeries( + /// onCreateShader: (ShaderDetails details) + /// { + /// return ui.Gradient.linear(details.rect.topRight, + /// details.rect.bottomLeft, [Colors.yellow, Colors.lightBlue, Colors.brown], [0.2,0.6,1]); + /// }, + /// ), + /// ] + /// ) + /// ) + /// ) + /// ); + /// } + final CartesianShaderCallback? onCreateShader; } /// Creates a series renderer for Chart series -abstract class ChartSeriesRenderer { - String? _seriesName; - -//ignore: prefer_final_fields - bool? _visible; - - //ignore: prefer_final_fields - bool _needsRepaint = true; - - late SfCartesianChart _chart; -} +abstract class ChartSeriesRenderer {} ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods ///in this before we must get the ChartSeriesController onRendererCreated event. @@ -1179,7 +1262,7 @@ class ChartSeriesController { /// onRendererCreated: (ChartSeriesController controller) { /// _chartSeriesController = controller; /// // prints series yAxisName - /// print(_chartSeriesController.seriesRenderer._series.yAxisName); + /// print(_chartSeriesController.seriesRenderer.seriesRendererDetails.series.yAxisName); /// }, /// ), /// ], @@ -1279,27 +1362,30 @@ class ChartSeriesController { /// add or update a data point in the given index void _addOrUpdateDataPoint(int index, bool needUpdate) { - final CartesianSeries series = seriesRenderer._series; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final CartesianSeries series = + seriesRendererDetails.series; if (index >= 0 && series.dataSource.length > index && series.dataSource[index] != null) { - final _VisibleRange xRange = - seriesRenderer._xAxisRenderer!._visibleRange!; - final _VisibleRange yRange = - seriesRenderer._yAxisRenderer!._visibleRange!; + final VisibleRange xRange = + seriesRendererDetails.xAxisDetails!.visibleRange!; + final VisibleRange yRange = + seriesRendererDetails.yAxisDetails!.visibleRange!; final CartesianChartPoint currentPoint = - _getChartPoint(seriesRenderer, series.dataSource[index], index)!; - final String seriesType = seriesRenderer._seriesType; + getChartPoint(seriesRenderer, series.dataSource[index], index)!; + final String seriesType = seriesRendererDetails.seriesType; dynamic x = currentPoint.x; - if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer || - seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) { + if (seriesRendererDetails.xAxisDetails is DateTimeAxisDetails || + seriesRendererDetails.xAxisDetails is DateTimeCategoryAxisDetails) { x = x.millisecondsSinceEpoch; - } else if (seriesRenderer._xAxisRenderer is LogarithmicAxisRenderer) { + } else if (seriesRendererDetails.xAxisDetails is LogarithmicAxisDetails) { final LogarithmicAxis axis = - seriesRenderer._xAxisRenderer!._axis as LogarithmicAxis; + seriesRendererDetails.xAxisDetails!.axis as LogarithmicAxis; currentPoint.xValue = currentPoint.x; - x = _calculateLogBaseValue((x > 1) == true ? x : 1, axis.logBase); - } else if (seriesRenderer._xAxisRenderer is CategoryAxisRenderer) { + x = calculateLogBaseValue((x > 1) == true ? x : 1, axis.logBase); + } else if (seriesRendererDetails.xAxisDetails is CategoryAxisDetails) { x = index; } currentPoint.xValue ??= x; @@ -1308,31 +1394,36 @@ class ChartSeriesController { ((xRange.minimum >= x) == true || (xRange.maximum <= x) == true)) { _needXRecalculation = true; } - num minYVal = currentPoint.y ?? currentPoint.low; - num maxYVal = currentPoint.y ?? currentPoint.high; - if (seriesRenderer._yAxisRenderer is LogarithmicAxisRenderer) { + num? minYVal = currentPoint.y ?? currentPoint.low; + num? maxYVal = currentPoint.y ?? currentPoint.high; + if (seriesRendererDetails.yAxisDetails is LogarithmicAxisRenderer && + minYVal != null && + maxYVal != null) { final LogarithmicAxis axis = - seriesRenderer._yAxisRenderer!._axis as LogarithmicAxis; + seriesRendererDetails.yAxisDetails!.axis as LogarithmicAxis; minYVal = - _calculateLogBaseValue(minYVal > 1 ? minYVal : 1, axis.logBase); + calculateLogBaseValue(minYVal > 1 ? minYVal : 1, axis.logBase); maxYVal = - _calculateLogBaseValue(maxYVal > 1 ? maxYVal : 1, axis.logBase); + calculateLogBaseValue(maxYVal > 1 ? maxYVal : 1, axis.logBase); } if (!_needYRecalculation && + minYVal != null && + maxYVal != null && ((yRange.minimum >= minYVal) == true || (yRange.maximum <= maxYVal) == true)) { _needYRecalculation = true; } if (needUpdate) { - if (seriesRenderer._dataPoints.length > index) { - seriesRenderer._dataPoints[index] = currentPoint; + if (seriesRendererDetails.dataPoints.length > index == true) { + seriesRendererDetails.dataPoints[index] = currentPoint; } } else { - if (seriesRenderer._dataPoints.length == index) { - seriesRenderer._dataPoints.add(currentPoint); - } else if (seriesRenderer._dataPoints.length > index && index >= 0) { - seriesRenderer._dataPoints.insert(index, currentPoint); + if (seriesRendererDetails.dataPoints.length == index) { + seriesRendererDetails.dataPoints.add(currentPoint); + } else if (seriesRendererDetails.dataPoints.length > index == true && + index >= 0) { + seriesRendererDetails.dataPoints.insert(index, currentPoint); } } @@ -1372,9 +1463,6 @@ class ChartSeriesController { /// Since this method is in the series controller, x and y-axis associated with this particular series will be /// considering for conversion value. /// - /// _Note_: This method is only applicable for cartesian chart, not for the circular, pyramid, - /// and funnel charts. - /// ///```dart /// Widget build(BuildContext context) { /// ChartSeriesController seriesController; @@ -1400,56 +1488,7 @@ class ChartSeriesController { ///``` CartesianChartPoint pixelToPoint(Offset position) { - ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - - final ChartAxis xAxis = xAxisRenderer._axis; - final ChartAxis yAxis = yAxisRenderer._axis; - - final CartesianSeries series = seriesRenderer._series; - - final Rect rect = seriesRenderer._chartState!._chartAxis._axisClipRect; - - if (series.xAxisName != null || series.yAxisName != null) { - for (final ChartAxisRenderer axisRenderer - in seriesRenderer._chartState!._chartAxis._axisRenderersCollection) { - if (xAxis.name == series.xAxisName) { - xAxisRenderer = axisRenderer; - } else if (yAxis.name == series.yAxisName) { - yAxisRenderer = axisRenderer; - } - } - } else { - xAxisRenderer = xAxisRenderer; - yAxisRenderer = yAxisRenderer; - } - - num xValue = _pointToXValue( - seriesRenderer._chartState!._requireInvertedAxis, - xAxisRenderer, - rect, - position.dx - (rect.left + xAxis.plotOffset), - position.dy - (rect.top + yAxis.plotOffset)); - num yValue = _pointToYValue( - seriesRenderer._chartState!._requireInvertedAxis, - yAxisRenderer, - rect, - position.dx - (rect.left + xAxis.plotOffset), - position.dy - (rect.top + yAxis.plotOffset)); - - if (xAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis axis = xAxis as LogarithmicAxis; - xValue = math.pow(xValue, _calculateLogBaseValue(xValue, axis.logBase)); - } else { - xValue = xValue; - } - if (yAxisRenderer is LogarithmicAxisRenderer) { - final LogarithmicAxis axis = yAxis as LogarithmicAxis; - yValue = math.pow(yValue, _calculateLogBaseValue(yValue, axis.logBase)); - } else { - yValue = yValue; - } - return CartesianChartPoint(xValue, yValue); + return calculatePixelToPoint(position, seriesRenderer); } /// Converts chart data point value to logical pixel value. @@ -1459,9 +1498,6 @@ class ChartSeriesController { /// Since this method is in the series controller, x and y-axis associated with this particular series will be /// considering for conversion value. /// - /// _Note_: This method is only applicable for cartesian chart, not for the circular, pyramid, - /// and funnel charts. - /// ///```dart /// Widget build(BuildContext context) { /// ChartSeriesController seriesController; @@ -1487,25 +1523,7 @@ class ChartSeriesController { /// } ///``` Offset pointToPixel(CartesianChartPoint point) { - final num x = point.x; - final num y = point.y; - - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - - final bool isInverted = seriesRenderer._chartState!._requireInvertedAxis; - - final CartesianSeries series = seriesRenderer._series; - final _ChartLocation location = _calculatePoint( - x, - y, - xAxisRenderer, - yAxisRenderer, - isInverted, - series, - seriesRenderer._chartState!._chartAxis._axisClipRect); - - return Offset(location.x, location.y); + return calculatePointToPixel(point, seriesRenderer); } ///If you wish to perform initial animation again in the existing series, this method can be called. @@ -1537,76 +1555,88 @@ class ChartSeriesController { /// } ///``` void animate() { - if (seriesRenderer._visible! && - seriesRenderer._series.animationDuration > 0) { - final SfCartesianChartState chartState = seriesRenderer._chartState!; - final SfCartesianChart chart = chartState._chart; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.visible! == true && + seriesRendererDetails.series.animationDuration > 0 == true) { + final CartesianStateProperties stateProperties = + seriesRendererDetails.stateProperties; + final SfCartesianChart chart = + seriesRendererDetails.stateProperties.chart; final TooltipBehavior tooltip = chart.tooltipBehavior; final TrackballBehavior trackball = chart.trackballBehavior; - final _TrackballPainter? trackballPainter = - chartState._trackballBehaviorRenderer._trackballPainter; - final TrackballBehaviorRenderer trackballBehaviorRenderer = - chartState._trackballBehaviorRenderer; - final _RenderingDetails renderingDetails = chartState._renderingDetails; - + final TrackballRenderingDetails trackballRenderingDetails = + TrackballHelper.getRenderingDetails( + stateProperties.trackballBehaviorRenderer); + final TrackballPainter? trackballPainter = + trackballRenderingDetails.trackballPainter; + final RenderingDetails renderingDetails = + stateProperties.renderingDetails; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + renderingDetails.tooltipBehaviorRenderer); //This hides the tooltip if rendered for this current series renderer if (tooltip.enable && (tooltip.builder != null - ? renderingDetails.tooltipBehaviorRenderer._seriesIndex == - seriesRenderer._segments[0]._seriesIndex - : renderingDetails.tooltipBehaviorRenderer._currentSeries == + ? tooltipRenderingDetails.seriesIndex == + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]) + .seriesIndex + : tooltipRenderingDetails.currentSeriesDetails?.renderer == seriesRenderer)) { tooltip.hide(); } //This hides the trackball if rendered for this current series renderer if (trackball.enable) { - for (final _ChartPointInfo point - in trackballBehaviorRenderer._chartPointInfo) { - if (point.seriesRenderer == seriesRenderer) { + for (final ChartPointInfo point + in trackballRenderingDetails.chartPointInfo) { + if (point.seriesRendererDetails?.renderer == seriesRenderer) { if (trackballPainter != null) { - chartState._repaintNotifiers['trackball']!.value++; + stateProperties.repaintNotifiers['trackball']!.value++; trackballPainter.canResetPath = true; break; } else { - final GlobalKey key = trackballBehaviorRenderer - ._trackballTemplate!.key as GlobalKey; - final _TrackballTemplateState trackballTemplateState = - key.currentState! as _TrackballTemplateState; + final GlobalKey key = + trackballRenderingDetails.trackballTemplate!.key as GlobalKey; + final TrackballTemplateState trackballTemplateState = + key.currentState! as TrackballTemplateState; trackballTemplateState.hideTrackballTemplate(); break; } } } } - seriesRenderer._reAnimate = seriesRenderer._needsAnimation = - seriesRenderer._needAnimateSeriesElements = true; + seriesRendererDetails.reAnimate = seriesRendererDetails.needsAnimation = + seriesRendererDetails.needAnimateSeriesElements = true; renderingDetails.initialRender = false; //This repaints the datalabels for the series if renderered. - chartState._renderDataLabel?.state?.repaintDataLabelElements(); + stateProperties.renderDataLabel?.state?.repaintDataLabelElements(); //This animates the datalabel templates of the animating series. - if (seriesRenderer._series.dataLabelSettings.builder != null) { - for (final _ChartTemplateInfo template in renderingDetails.templates) { + if (seriesRendererDetails.series.dataLabelSettings.builder != null) { + for (final ChartTemplateInfo template in renderingDetails.templates) { if (template.templateType == 'DataLabel' && template.animationDuration > 0 && template.seriesIndex == - seriesRenderer._segments[0]._seriesIndex) { + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]) + .seriesIndex) { template.animationController.forward(from: 0.0); } } } - chartState._totalAnimatingSeries = 1; - chartState._animationCompleteCount = 0; - chartState._forwardAnimation(seriesRenderer); + stateProperties.totalAnimatingSeries = 1; + stateProperties.animationCompleteCount = 0; + stateProperties.forwardAnimation(seriesRendererDetails); //This animates the trendlines of the animating series. - if (seriesRenderer._trendlineRenderer.isNotEmpty) { + if (seriesRendererDetails.trendlineRenderer.isNotEmpty == true) { for (final TrendlineRenderer trendlineRenderer - in seriesRenderer._trendlineRenderer) { - if (trendlineRenderer._visible) { - final Trendline trendline = trendlineRenderer._trendline; - trendlineRenderer._animationController.duration = + in seriesRendererDetails.trendlineRenderer) { + if (trendlineRenderer.visible) { + final Trendline trendline = trendlineRenderer.trendline; + trendlineRenderer.animationController.duration = Duration(milliseconds: trendline.animationDuration.toInt()); - trendlineRenderer._animationController.forward(from: 0.0); + trendlineRenderer.animationController.forward(from: 0.0); } } } @@ -1626,24 +1656,28 @@ class ChartSeriesController { /// remove a data point in the given index void _removeDataPoint(int index) { - if (seriesRenderer._dataPoints.isNotEmpty && + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.dataPoints.isNotEmpty == true && index >= 0 && - index < seriesRenderer._dataPoints.length) { + index < seriesRendererDetails.dataPoints.length) { final CartesianChartPoint currentPoint = - seriesRenderer._dataPoints[index]; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + seriesRendererDetails.dataPoints[index]; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + if (xAxisDetails is DateTimeCategoryAxisRenderer || + xAxisDetails is CategoryAxisRenderer) { _needXRecalculation = true; } - seriesRenderer._dataPoints.removeAt(index); + seriesRendererDetails.dataPoints.removeAt(index); // ignore: unnecessary_null_comparison if (currentPoint != null) { if (!_needXRecalculation && - (seriesRenderer._minimumX == currentPoint.x || - seriesRenderer._maximumX == currentPoint.x)) { + (seriesRendererDetails.minimumX == currentPoint.x || + seriesRendererDetails.maximumX == currentPoint.x)) { _needXRecalculation = true; } - final String seriesType = seriesRenderer._seriesType; + final String seriesType = seriesRendererDetails.seriesType; /// Below lines for changing high, low values based on input if ((seriesType.contains('range') || @@ -1655,229 +1689,90 @@ class ChartSeriesController { currentPoint.high = math.max(high, low); currentPoint.low = math.min(high, low); } - final num minYVal = currentPoint.y ?? currentPoint.low; - final num maxYVal = currentPoint.y ?? currentPoint.high; + final num? minYVal = currentPoint.y ?? currentPoint.low; + final num? maxYVal = currentPoint.y ?? currentPoint.high; if (!_needYRecalculation && - (seriesRenderer._minimumY == minYVal || - seriesRenderer._maximumY == maxYVal)) { + minYVal != null && + maxYVal != null && + (seriesRendererDetails.minimumY == minYVal || + seriesRendererDetails.maximumY == maxYVal)) { _needYRecalculation = true; } } } } - /// After add/remove/update datapoints, recalculate the x, y range and inveral + /// After add/remove/update data points, recalculate the x, y range and interval void _updateCartesianSeries( bool needXRecalculation, bool needYRecalculation, bool needUpdate) { - final SfCartesianChartState chartState = - seriesRenderer._xAxisRenderer!._chartState; - chartState._isRedrawByZoomPan = false; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final CartesianStateProperties stateProperties = + seriesRendererDetails.stateProperties; + stateProperties.isRedrawByZoomPan = false; if (needXRecalculation || needYRecalculation || needUpdate) { if (needXRecalculation) { - seriesRenderer._minimumX = seriesRenderer._maximumX = null; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - if (xAxisRenderer is DateTimeCategoryAxisRenderer) { - xAxisRenderer._labels.clear(); + seriesRendererDetails.minimumX = seriesRendererDetails.maximumX = null; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + if (xAxisDetails is DateTimeCategoryAxisDetails) { + xAxisDetails.labels.clear(); } } if (needYRecalculation) { - seriesRenderer._minimumY = seriesRenderer._maximumY = null; + seriesRendererDetails.minimumY = seriesRendererDetails.maximumY = null; } - chartState._chartSeries._findSeriesMinMax(seriesRenderer); - if (seriesRenderer._seriesType.contains('stacked')) { - chartState._chartSeries - ._calculateStackedValues(_findSeriesCollection(chartState)); + stateProperties.chartSeries.findSeriesMinMax(seriesRendererDetails); + if (seriesRendererDetails.seriesType.contains('stacked') == true) { + stateProperties.chartSeries + .calculateStackedValues(findSeriesCollection(stateProperties)); } } if (needXRecalculation) { - final dynamic axisRenderer = seriesRenderer._xAxisRenderer!; - axisRenderer._calculateRangeAndInterval(chartState); + final dynamic axisRenderer = seriesRendererDetails.xAxisDetails!; + axisRenderer.calculateRangeAndInterval(stateProperties); } if (needYRecalculation) { - final dynamic axisRenderer = seriesRenderer._yAxisRenderer!; - axisRenderer._calculateRangeAndInterval(chartState); + final dynamic axisRenderer = seriesRendererDetails.yAxisDetails!; + axisRenderer.calculateRangeAndInterval(stateProperties); } if (needXRecalculation || needYRecalculation) { - chartState._renderOutsideAxis.state.axisRepaintNotifier.value++; - chartState._renderInsideAxis.state.axisRepaintNotifier.value++; + stateProperties.renderOutsideAxis.state.axisRepaintNotifier.value++; + stateProperties.renderInsideAxis.state.axisRepaintNotifier.value++; for (final CartesianSeriesRenderer seriesRenderer - in chartState._chartSeries.visibleSeriesRenderers) { - _repaintSeries(chartState, seriesRenderer); + in stateProperties.chartSeries.visibleSeriesRenderers) { + _repaintSeries(stateProperties, + SeriesHelper.getSeriesRendererDetails(seriesRenderer)); } } else { - _repaintSeries(chartState, seriesRenderer); + _repaintSeries(stateProperties, seriesRendererDetails); } - chartState._isLoadMoreIndicator = false; + stateProperties.isLoadMoreIndicator = false; //This makes the update data source method work with dynamic animation(scheduled for release) - // seriesRenderer._needsAnimation = seriesRenderer._needAnimateSeriesElements = + // seriesRenderer.seriesRendererDetails.needsAnimation = seriesRenderer.seriesRendererDetails.needAnimateSeriesElements = // chartState.widgetNeedUpdate = true; // chartState.initialRender = false; - // seriesRenderer._chartState!._totalAnimatingSeries = 1; - // seriesRenderer._chartState!._animationCompleteCount = 0; - // seriesRenderer._chartState!._forwardAnimation( - // seriesRenderer, seriesRenderer._series.animationDuration); + // seriesRenderer.seriesRendererDetails.stateProperties.totalAnimatingSeries = 1; + // seriesRenderer.seriesRendererDetails.stateProperties.animationCompleteCount = 0; + // seriesRenderer.seriesRendererDetails.stateProperties.forwardAnimation( + // seriesRenderer, seriesRenderer.seriesRendererDetails.series.animationDuration); } //This method repaints the series and its elements for the given series renderer - void _repaintSeries(SfCartesianChartState chartState, - CartesianSeriesRenderer seriesRenderer) { - seriesRenderer._calculateRegion = true; - seriesRenderer._repaintNotifier.value++; - if (seriesRenderer._series.dataLabelSettings.isVisible) { - chartState._renderDataLabel?.state!.dataLabelRepaintNotifier.value++; + void _repaintSeries(CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails) { + seriesRendererDetails.calculateRegion = true; + seriesRendererDetails.repaintNotifier.value++; + if (seriesRendererDetails.series.dataLabelSettings.isVisible == true) { + stateProperties.renderDataLabel?.state!.dataLabelRepaintNotifier.value++; } } } -/// Creates a series renderer for cartesian series +/// Creates a series renderer for Cartesian series abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { - /// Stores the series type - late String _seriesType; - - /// Whether to check the series is rect series or not - // ignore: prefer_final_fields - bool _isRectSeries = false; - - final List> _drawControlPoints = >[]; - - final List> _drawLowControlPoints = >[]; - - final List> _drawHighControlPoints = >[]; - - Path? _segmentPath; - - /// Gets the Segments collection variable declarations. - // ignore: prefer_final_fields - List _segments = []; - - //Maintain the old series state. - //ignore: unused_field - CartesianSeries? _oldSeries; - - //Store the current series state - late CartesianSeries _series; - - /// Holds the collection of cartesian data points - // ignore: prefer_final_fields - List> _dataPoints = - >[]; - - /// Holds the collection of cartesian visible data points - List>? _visibleDataPoints; - - /// Holds the collection of old data points - List>? _oldDataPoints; - - /// Holds the old series initial selected data indexes - List? _oldSelectedIndexes; - - /// Holds the information for x Axis - ChartAxisRenderer? _xAxisRenderer; - - /// Holds the information for y Axis - ChartAxisRenderer? _yAxisRenderer; - - /// Minimum x value for Series - num? _minimumX; - - /// Maximum x value for Series - num? _maximumX; - - /// Minimum y value for Series - num? _minimumY; - - /// Maximum y value for Series - num? _maximumY; - - /// Hold the data about point regions - Map? _regionalData; - - /// Color for the series based on color palette - Color? _seriesColor; - List? _xValues; - - /// Hold the information about chart class - @override - late SfCartesianChart _chart; - - /// Holds the information about chart state class - SfCartesianChartState? _chartState; - - _RenderingDetails? get _renderingDetails => _chartState?._renderingDetails; - - /// Contains the collection of path for markers - late List _markerShapes; - - late List _markerShapes2; - - // ignore: prefer_final_fields - bool _isOuterRegion = false; - - /// used to differentiate indicator from series - // ignore: prefer_final_fields - bool _isIndicator = false; - - ///storing mindelta for rect series - num? _minDelta; - - /// Repaint notifier for series - late ValueNotifier _repaintNotifier; - - // ignore: prefer_final_fields - bool _needAnimateSeriesElements = false; - - //ignore: prefer_final_fields - bool _needsAnimation = false; - - //ignore: prefer_final_fields - bool _reAnimate = false; - - bool _calculateRegion = false; - - //ignore: prefer_final_fields - Animation? _seriesAnimation; - - //ignore: prefer_final_fields - Animation? _seriesElementAnimation; - - //controls the animation of the corresponding series - late AnimationController _animationController; - - ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods - ///in this before we must get the ChartSeriesController onRendererCreated event. - ChartSeriesController? _controller; - - //ignore: prefer_final_fields - List _trendlineRenderer = []; - - late DataLabelSettingsRenderer _dataLabelSettingsRenderer; - - MarkerSettingsRenderer? _markerSettingsRenderer; - - // ignore: prefer_final_fields - bool _isSelectionEnable = false; - - SelectionBehaviorRenderer? _selectionBehaviorRenderer; - - dynamic _selectionBehavior; - - // ignore: prefer_final_fields - bool _isMarkerRenderEvent = false; - - // bool for animation status - late bool _animationCompleted; - - // ignore: prefer_final_fields - bool _hasDataLabelTemplate = false; - - // ignore: prefer_final_fields - /// It specifies the side by side information of the visible range. - _VisibleRange? _sideBySideInfo; - - /// Holds the collection of cartesian overall data points - // ignore: prefer_final_fields - List?> _overAllDataPoints = - ?>[]; + /// Holds the properties required to render the series + late SeriesRendererDetails _seriesRendererDetails; /// To create segment for series ChartSegment createSegment(); @@ -1899,4 +1794,23 @@ abstract class CartesianSeriesRenderer extends ChartSeriesRenderer { void calculateEmptyPointValue( int pointIndex, CartesianChartPoint currentPoint, [CartesianSeriesRenderer seriesRenderer]); + + /// To dispose the CartesianSeriesRenderer class objects. + void dispose() { + _seriesRendererDetails.dispose(); + } +} + +// ignore: avoid_classes_with_only_static_members +/// Helper class to get the private fields of chart series renderer +class SeriesHelper { + /// Method to get the series renderer details of corresponding series renderer + static SeriesRendererDetails getSeriesRendererDetails( + CartesianSeriesRenderer renderer) => + renderer._seriesRendererDetails; + + /// Method to set the series renderer details of corresponding series renderer + static void setSeriesRendererDetails(CartesianSeriesRenderer renderer, + SeriesRendererDetails rendererDetails) => + renderer._seriesRendererDetails = rendererDetails; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series_renderer_properties.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series_renderer_properties.dart new file mode 100644 index 000000000..7a3fbfec5 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/series_renderer_properties.dart @@ -0,0 +1,661 @@ +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/helper.dart'; +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../series_painter/box_and_whisker_painter.dart'; +import '../series_painter/scatter_painter.dart'; +import '../trendlines/trendlines.dart'; +import '../utils/helper.dart'; +import 'candle_series.dart'; +import 'financial_series_base.dart'; +import 'hiloopenclose_series.dart'; +import 'histogram_series.dart'; +import 'xy_data_series.dart'; + +/// Represents the series renderer details +class SeriesRendererDetails { + /// argument Constructor for SeriesRendererDetails class + SeriesRendererDetails(this.renderer); + + /// Specifies the value of Cartesian series renderer + final CartesianSeriesRenderer renderer; + + /// Specifies the value of series name + String? seriesName; + + /// Holds whether the series is visible + bool? visible; + + /// Specifies whether to repaint the series + bool needsRepaint = true; + + ///Holds the value of Cartesian chart + late SfCartesianChart chart; + + /// Stores the series type + late String seriesType; + + /// Whether to check the series is rect series or not + // ignore: prefer_final_fields + bool isRectSeries = false; + + /// Specifies the histogram values + late HistogramValues histogramValues; + + /// Specifies the list of draw control points + final List> drawControlPoints = >[]; + + /// Specifies the list of low control points + final List> drawLowControlPoints = >[]; + + /// Specifies the list of high control points + final List> drawHighControlPoints = >[]; + + /// Specifies the segment path value + Path? segmentPath; + + /// Gets the Segments collection variable declarations. + // ignore: prefer_final_fields + List segments = []; + + /// Maintain the old series state. + CartesianSeries? oldSeries; + + /// Store the current series state + late CartesianSeries series; + + /// Holds the collection of Cartesian data points + // ignore: prefer_final_fields + List> dataPoints = + >[]; + + /// Holds the collection of Cartesian visible data points + List>? visibleDataPoints; + + /// Holds the collection of old data points + List>? oldDataPoints; + + /// Holds the old series initial selected data indexes + List? oldSelectedIndexes; + + /// Holds the information for x Axis + ChartAxisRendererDetails? xAxisDetails; + + /// Holds the information for y Axis + ChartAxisRendererDetails? yAxisDetails; + + /// Minimum x value for Series + num? minimumX; + + /// Maximum x value for Series + num? maximumX; + + /// Minimum y value for Series + num? minimumY; + + /// Maximum y value for Series + num? maximumY; + + /// Hold the data about point regions + Map? regionalData; + + /// Color for the series based on color palette + Color? seriesColor; + + /// Specifies the list of x-values + List? xValues; + + /// Holds the Cartesian state properties + late CartesianStateProperties stateProperties; + + /// Contains the collection of path for markers + late List markerShapes; + + /// Specifies the value of marker shapes + late List markerShapes2; + + /// Specifies whether the region is outer + // ignore: prefer_final_fields + bool isOuterRegion = false; + + /// used to differentiate indicator from series + // ignore: prefer_final_fields + bool isIndicator = false; + + ///storing mindelta for rect series + num? minDelta; + + /// Repaint notifier for series + late ValueNotifier repaintNotifier; + + /// Specifies whether the series elements need to animate + // ignore: prefer_final_fields + bool needAnimateSeriesElements = false; + + /// Specifies whether needs to animate + bool needsAnimation = false; + + /// Specifies whether to reanimate the elements + bool reAnimate = false; + + /// Specifies whether to calculate the region + bool calculateRegion = false; + + /// Specifies the series animation + Animation? seriesAnimation; + + /// Specifies the series element animation + Animation? seriesElementAnimation; + + /// controls the animation of the corresponding series + late AnimationController animationController; + + ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods + ///in this before we must get the ChartSeriesController onRendererCreated event. + ChartSeriesController? controller; + + /// Specifies the trendline renderer + List trendlineRenderer = []; + + /// Specifies the value of data label setting renderer + late DataLabelSettingsRenderer dataLabelSettingsRenderer; + + /// Specifies the marker setting renderer + MarkerSettingsRenderer? markerSettingsRenderer; + + /// Specifies whether the selection is enabled + // ignore: prefer_final_fields + bool isSelectionEnable = false; + + /// Specifies the value of selection behavior renderer + SelectionBehaviorRenderer? selectionBehaviorRenderer; + + /// Specifies the selection behavior + dynamic selectionBehavior; + + /// Specifies whether the marker is rendered + // ignore: prefer_final_fields + bool isMarkerRenderEvent = false; + + /// bool for animation status + late bool animationCompleted; + + /// Specifies whether the series has data label templates + // ignore: prefer_final_fields + bool hasDataLabelTemplate = false; + + // ignore: prefer_final_fields + /// It specifies the side by side information of the visible range. + VisibleRange? sideBySideInfo; + + /// Store the rect position + late num rectPosition; + + /// Store the rect count + late num rectCount; + + /// Represents the old series renderer + List? oldSeriesRenderers; + + /// Represents the maximum size + double? maxSize; + + /// Represents the minimum size + double? minSize; + + /// Represents the index value of the series corresponding to this renderer + int? seriesIndex; + + /// Represents the candle series renderer + late CandleSeries candleSeries; + + /// Specifies the over all data points + List> overallDataPoints = + >[]; + + /// Specifies the hilo open close series + late HiloOpenCloseSeries hiloOpenCloseSeries; + + /// Store the stacking values + List stackingValues = []; + + /// Store the percentage values + List percentageValues = []; + + /// Specifies the list of xValue + final List xValueList = []; + + /// Specifies the list of yValue + final List yValueList = []; + + /// Specifies whether the series is line type + final bool isLineType = false; + + /// Holds the collection of Cartesian overall data points + // ignore: prefer_final_fields + List?> overAllDataPoints = + ?>[]; + + /// To draw area segments + //ignore: unused_element + void drawSegment(Canvas canvas, ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + if (SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .isSelectionEnable == + true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + segments[segment.currentSegmentIndex!], stateProperties.chart); + } + segment.onPaint(canvas); + } + + /// To render a series of elements for all series + void renderSeriesElements(SfCartesianChart chart, Canvas canvas, + Animation? animationController) { + markerShapes = []; + markerShapes2 = []; + assert( + // ignore: unnecessary_null_comparison + !(series.markerSettings.height != null) || + series.markerSettings.height >= 0, + 'The height of the marker should be greater than or equal to 0.'); + assert( + // ignore: unnecessary_null_comparison + !(series.markerSettings.width != null) || + series.markerSettings.width >= 0, + 'The width of the marker must be greater than or equal to 0.'); + for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { + final CartesianChartPoint point = dataPoints[pointIndex]; + if ((series.markerSettings.isVisible && + renderer is! BoxAndWhiskerSeriesRenderer) || + renderer is ScatterSeriesRenderer) { + markerSettingsRenderer?.renderMarker( + this, point, animationController, canvas, pointIndex); + } + } + } + + /// Method to repaint the series element + void repaintSeriesElement() { + repaintNotifier.value++; + } + + /// Method to listen the animation status + void animationStatusListener(AnimationStatus status) { + if (stateProperties != null && status == AnimationStatus.completed) { + reAnimate = false; + animationCompleted = true; + stateProperties.animationCompleteCount++; + setAnimationStatus(stateProperties); + } else if (stateProperties != null && status == AnimationStatus.forward) { + stateProperties.renderingDetails.animateCompleted = false; + animationCompleted = false; + } + } + + /// To store the series properties + void storeSeriesProperties( + CartesianStateProperties stateProperties, int index) { + this.stateProperties = stateProperties; + chart = stateProperties.chart; + isRectSeries = seriesType.contains('column') || + (seriesType.contains('bar') && !seriesType.contains('errorbar')) || + seriesType == 'histogram'; + regionalData = {}; + segmentPath = Path(); + segments = []; + seriesColor = series.color ?? chart.palette[index % chart.palette.length]; + + // calculates the tooltip region for trenlines in this series + final List? trendlines = series.trendlines; + if (trendlines != null && + // ignore: unnecessary_null_comparison + chart.tooltipBehavior != null && + chart.tooltipBehavior.enable) { + for (int j = 0; j < trendlines.length; j++) { + if (trendlineRenderer[j].isNeedRender) { + if (trendlineRenderer[j].pointsData != null) { + for (int k = 0; k < trendlineRenderer[j].pointsData!.length; k++) { + final CartesianChartPoint trendlinePoint = + trendlineRenderer[j].pointsData![k]; + calculateTooltipRegion(trendlinePoint, index, this, + stateProperties, trendlines[j], trendlineRenderer[j], j); + } + } + } + } + } + } + + /// To get the proper data for histogram series + void processData(HistogramSeries series, List yValues, + num yValuesCount) { + histogramValues = HistogramValues(); + histogramValues.yValues = yValues; + final num mean = yValuesCount / histogramValues.yValues!.length; + histogramValues.mean = mean; + num sumValue = 0; + num sDValue; + for (int value = 0; value < histogramValues.yValues!.length; value++) { + sumValue += (histogramValues.yValues![value] - histogramValues.mean!) * + (histogramValues.yValues![value] - histogramValues.mean!); + } + sDValue = math.sqrt(sumValue / histogramValues.yValues!.length - 1).isNaN + ? 0 + : (math.sqrt(sumValue / histogramValues.yValues!.length - 1)).round(); + histogramValues.sDValue = sDValue; + } + + /// To find the region data of a series + /// To find the region data of a series + void calculateRegionData( + CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails, + int seriesIndex, + CartesianChartPoint point, + int pointIndex, + [VisibleRange? sideBySideInfo, + CartesianChartPoint? _nextPoint, + num? midX, + num? midY]) { + if (withInRange(seriesRendererDetails.dataPoints[pointIndex].xValue, + seriesRendererDetails.xAxisDetails!.visibleRange!)) { + seriesRendererDetails.visibleDataPoints! + .add(seriesRendererDetails.dataPoints[pointIndex]); + if (seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex = + seriesRendererDetails.visibleDataPoints!.length - 1; + } + } + chart = stateProperties.chart; + final ChartAxis xAxis = xAxisDetails!.axis; + final ChartAxis yAxis = yAxisDetails!.axis; + final Rect rect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxis.plotOffset, yAxis.plotOffset)); + isRectSeries = seriesType == 'column' || + seriesType == 'bar' || + seriesType.contains('stackedcolumn') || + seriesType.contains('stackedbar') || + seriesType == 'rangecolumn' || + seriesType == 'histogram' || + seriesType == 'waterfall'; + CartesianChartPoint point; + + final double markerHeight = series.markerSettings.height, + markerWidth = series.markerSettings.width; + final bool isPointSeries = + seriesType == 'scatter' || seriesType == 'bubble'; + final bool isFastLine = seriesType == 'fastline'; + if ((!isFastLine || + (isFastLine && + (series.markerSettings.isVisible || + series.dataLabelSettings.isVisible || + series.enableTooltip))) && + visible!) { + point = dataPoints[pointIndex]; + if (point.region == null || + seriesRendererDetails.calculateRegion == true || + seriesType.contains('waterfall') || + seriesType.contains('stackedcolumn') || + seriesType.contains('stackedbar')) { + if (seriesRendererDetails.calculateRegion == true && + dataPoints.length == pointIndex - 1) { + seriesRendererDetails.calculateRegion = false; + } + + /// side by side range calculated + seriesRendererDetails.sideBySideInfo = (isRectSeries || + (seriesType.contains('candle') || + seriesType.contains('hilo') || + seriesType.contains('histogram') || + seriesType.contains('box'))) + ? calculateSideBySideInfo( + seriesRendererDetails.renderer, stateProperties) + : seriesRendererDetails.sideBySideInfo; + if (isRectSeries) { + calculateRectSeriesRegion( + point, pointIndex, seriesRendererDetails, stateProperties); + } else if (isPointSeries) { + calculatePointSeriesRegion( + point, pointIndex, seriesRendererDetails, stateProperties, rect); + } else if (seriesType == 'errorbar') { + calculateErrorBarSeriesRegion( + point, pointIndex, seriesRendererDetails, stateProperties, rect); + } else { + calculatePathSeriesRegion( + point, + pointIndex, + this, + stateProperties, + rect, + markerHeight, + markerWidth, + sideBySideInfo, + _nextPoint, + midX, + midY); + } + } + // ignore: unnecessary_null_comparison + if (chart.tooltipBehavior != null && + seriesType != 'errorbar' && + (chart.tooltipBehavior.enable || + chart.onPointTapped != null || + seriesRendererDetails.series.onPointTap != null || + seriesRendererDetails.series.onPointDoubleTap != null || + seriesRendererDetails.series.onPointLongPress != null) && + seriesType != 'boxandwhisker') { + calculateTooltipRegion( + point, seriesIndex, seriesRendererDetails, stateProperties); + } + } + } + + /// To find the region data of chart tooltip + void calculateTooltipRegionUsingIndex(SfCartesianChart chart, int seriesIndex, + CartesianChartPoint point, int pointIndex) { + /// For tooltip implementation + // ignore: unnecessary_null_comparison + if (series.enableTooltip != null && + series.enableTooltip && + // ignore: unnecessary_null_comparison + point != null && + !point.isGap && + !point.isDrop) { + final List regionData = []; + String? date; + final List regionRect = []; + final dynamic primaryAxisDetails = xAxisDetails; + if (primaryAxisDetails is DateTimeAxisDetails) { + final DateTimeAxis _axis = primaryAxisDetails.axis as DateTimeAxis; + final DateFormat dateFormat = _axis.dateFormat ?? + getDateTimeLabelFormat(xAxisDetails!.axisRenderer); + date = dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(point.xValue)); + } else if (primaryAxisDetails is DateTimeCategoryAxisDetails) { + date = primaryAxisDetails.dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); + } + xAxisDetails is CategoryAxisDetails + ? regionData.add(point.x.toString()) + : xAxisDetails is DateTimeAxisDetails || + xAxisDetails is DateTimeCategoryAxisDetails + ? regionData.add(date.toString()) + : regionData.add(getLabelValue(point.xValue, xAxisDetails!.axis, + chart.tooltipBehavior.decimalPlaces) + .toString()); + if (seriesType.contains('range')) { + regionData.add(getLabelValue(point.high, yAxisDetails!.axis, + chart.tooltipBehavior.decimalPlaces) + .toString()); + regionData.add(getLabelValue(point.low, yAxisDetails!.axis, + chart.tooltipBehavior.decimalPlaces) + .toString()); + } else { + regionData.add(getLabelValue(point.yValue, yAxisDetails!.axis, + chart.tooltipBehavior.decimalPlaces) + .toString()); + } + regionData.add(series.name ?? 'series $seriesIndex'); + regionRect.add(point.region); + regionRect.add(isRectSeries + ? seriesType == 'column' || seriesType.contains('stackedcolumn') + ? (point.yValue > 0) == true + ? point.region!.topCenter + : point.region!.bottomCenter + : point.region!.topCenter + : (seriesType == 'rangearea' + ? Offset(point.markerPoint!.x, + (point.markerPoint!.y + point.markerPoint2!.y) / 2) + : point.region!.center)); + regionRect.add(point.pointColorMapper); + regionRect.add(point.bubbleSize); + if (seriesType.contains('stacked')) { + regionData.add((point.cumulativeValue).toString()); + } + regionalData![regionRect] = regionData; + } + } + + /// To calculate the empty point average mode value + void calculateAverageModeValue( + int pointIndex, + int pointLength, + CartesianChartPoint currentPoint, + CartesianChartPoint prevPoint) { + final List dataSource = series.dataSource; + final CartesianChartPoint _nextPoint = getPointFromData( + this, + pointLength < dataSource.length - 1 + ? dataSource.indexOf(dataSource[pointLength + 1]) + : dataSource.indexOf(dataSource[pointLength])); + if (seriesType.contains('range') || + seriesType.contains('hilo') || + seriesType.contains('candle')) { + final CartesianSeries _cartesianSeries = series; + if (_cartesianSeries is FinancialSeriesBase && + _cartesianSeries.showIndicationForSameValues) { + if (currentPoint.low != null || currentPoint.high != null) { + currentPoint.low = currentPoint.low ?? currentPoint.high; + currentPoint.high = currentPoint.high ?? currentPoint.low; + } else { + currentPoint.low = 0; + currentPoint.high = 0; + currentPoint.open = 0; + currentPoint.close = 0; + currentPoint.isGap = true; + } + if (seriesType == 'hiloopenclose' || seriesType == 'candle') { + if (currentPoint.open != null || currentPoint.close != null) { + currentPoint.open = currentPoint.open ?? currentPoint.close; + currentPoint.close = currentPoint.close ?? currentPoint.open; + } else { + currentPoint.low = 0; + currentPoint.high = 0; + currentPoint.open = 0; + currentPoint.close = 0; + currentPoint.isGap = true; + } + } + } else { + if (pointIndex == 0) { + if (currentPoint.low == null) { + pointIndex == dataSource.length - 1 + ? currentPoint.low = 0 + : currentPoint.low = ((_nextPoint.low) ?? 0) / 2; + } + if (currentPoint.high == null) { + pointIndex == dataSource.length - 1 + ? currentPoint.high = 0 + : currentPoint.high = ((_nextPoint.high) ?? 0) / 2; + } + if (seriesType == 'hiloopenclose' || seriesType == 'candle') { + if (currentPoint.open == null) { + pointIndex == dataSource.length - 1 + ? currentPoint.open = 0 + : currentPoint.open = ((_nextPoint.open) ?? 0) / 2; + } + if (currentPoint.close == null) { + pointIndex == dataSource.length - 1 + ? currentPoint.close = 0 + : currentPoint.close = ((_nextPoint.close) ?? 0) / 2; + } + } + } else if (pointIndex == dataSource.length - 1) { + currentPoint.low = currentPoint.low ?? ((prevPoint.low) ?? 0) / 2; + currentPoint.high = currentPoint.high ?? ((prevPoint.high) ?? 0) / 2; + + if (seriesType == 'hiloopenclose' || seriesType == 'candle') { + currentPoint.open = + currentPoint.open ?? ((prevPoint.open) ?? 0) / 2; + currentPoint.close = + currentPoint.close ?? ((prevPoint.close) ?? 0) / 2; + } + } else { + currentPoint.low = currentPoint.low ?? + (((prevPoint.low) ?? 0) + ((_nextPoint.low) ?? 0)) / 2; + currentPoint.high = currentPoint.high ?? + (((prevPoint.high) ?? 0) + ((_nextPoint.high) ?? 0)) / 2; + + if (seriesType == 'hiloopenclose' || seriesType == 'candle') { + currentPoint.open = currentPoint.open ?? + (((prevPoint.open) ?? 0) + ((_nextPoint.open) ?? 0)) / 2; + currentPoint.close = currentPoint.close ?? + (((prevPoint.close) ?? 0) + ((_nextPoint.close) ?? 0)) / 2; + } + } + } + } else { + if (pointIndex == 0) { + ///Check the first point is null + pointIndex == dataSource.length - 1 + ? + + ///Check the series contains single point with null value + currentPoint.y = 0 + : currentPoint.y = ((_nextPoint.y) ?? 0) / 2; + } else if (pointIndex == dataSource.length - 1) { + ///Check the last point is null + currentPoint.y = ((prevPoint.y) ?? 0) / 2; + } else { + currentPoint.y = (((prevPoint.y) ?? 0) + ((_nextPoint.y) ?? 0)) / 2; + } + } + } + + /// To dispose the objects. + void dispose() { + for (final ChartSegment segment in segments) { + segment.dispose(); + } + + segments.clear(); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart index 5731ac2ff..0b253adb6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_area_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/spline_area_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the spline series. /// @@ -39,10 +57,12 @@ class SplineAreaSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, this.borderDrawMode = BorderDrawMode.top}) : super( key: key, @@ -77,7 +97,9 @@ class SplineAreaSeries extends XyDataSeries { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Border type of area series. /// @@ -194,11 +216,13 @@ class SplineAreaSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.borderDrawMode == borderDrawMode && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.cardinalSplineTension == cardinalSplineTension && other.splineType == splineType; } @@ -236,6 +260,7 @@ class SplineAreaSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, borderDrawMode, onRendererCreated, cardinalSplineTension, @@ -247,75 +272,3 @@ class SplineAreaSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Spline area series -class SplineAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of SplineAreaSeriesRenderer class. - SplineAreaSeriesRenderer(); - - /// SplineArea segment is created here - ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, - double animateFactor, Path path, Path strokePath, - [List? _points]) { - final SplineAreaSegment segment = createSegment(); - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.animationFactor = animateFactor; - segment._series = _series as XyDataSeries; - segment._seriesRenderer = this; - if (_points != null) { - segment.points = _points; - } - segment._path = path; - segment._strokePath = strokePath; - segment._chart = chart; - customizeSegment(segment); - segment._oldSegmentIndex = 0; - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render spline area series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer - ?._checkWithSelectionState(_segments[0], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - SplineAreaSegment createSegment() => SplineAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._seriesRenderer._seriesColor; - segment._strokeWidth = segment._series.width; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart index d541c7400..2b4ea0538 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_range_area_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/spline_range_area_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the spline range area series. /// @@ -42,10 +60,12 @@ class SplineRangeAreaSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, this.borderDrawMode = RangeAreaBorderMode.all}) : super( key: key, @@ -81,7 +101,9 @@ class SplineRangeAreaSeries extends XyDataSeries { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Border type of the spline range area series. /// @@ -169,11 +191,13 @@ class SplineRangeAreaSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.borderDrawMode == borderDrawMode && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.cardinalSplineTension == cardinalSplineTension && other.splineType == splineType; } @@ -212,6 +236,7 @@ class SplineRangeAreaSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, borderDrawMode, onRendererCreated, cardinalSplineTension, @@ -223,76 +248,3 @@ class SplineRangeAreaSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Spline range area series -class SplineRangeAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of SplineRangeAreaSeriesRenderer class. - SplineRangeAreaSeriesRenderer(); - - /// SplineRangeArea segment is created here - ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, - double animateFactor, Path path, Path strokePath, - [List? _points]) { - final SplineRangeAreaSegment segment = createSegment(); - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.animationFactor = animateFactor; - segment._series = _series as XyDataSeries; - segment._seriesRenderer = this; - if (_points != null) { - segment.points = _points; - } - segment._chart = chart; - segment._path = path; - segment._strokePath = strokePath; - segment._oldSegmentIndex = 0; - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render spline range area series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer - ?._checkWithSelectionState(_segments[0], _chart); - } - segment.onPaint(canvas); - } - - @override - SplineRangeAreaSegment createSegment() => SplineRangeAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._seriesRenderer._seriesColor; - segment._strokeWidth = segment._series.width; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - canvas.drawPath(seriesRenderer._markerShapes2[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart index 85396696d..7b9b19c1f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/spline_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/spline_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the spline series. /// @@ -39,10 +57,12 @@ class SplineSeries extends XyDataSeries { SortingOrder? sortingOrder, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -72,6 +92,8 @@ class SplineSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -171,10 +193,12 @@ class SplineSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes && other.cardinalSplineTension == cardinalSplineTension && other.splineType == splineType; @@ -211,6 +235,7 @@ class SplineSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, initialSelectedDataIndexes, cardinalSplineTension, @@ -222,97 +247,3 @@ class SplineSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Spline series -class SplineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of SplineSeriesRenderer class. - SplineSeriesRenderer(); - - final List _xValueList = []; - final List _yValueList = []; - - /// Spline segment is created here - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint nextPoint, - int pointIndex, - int seriesIndex, - double animateFactor) { - final SplineSegment segment = createSegment() as SplineSegment; - final List _oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._chart = _chart; - segment._chartState = _chartState!; - segment.animationFactor = animateFactor; - segment._currentPoint = currentPoint; - segment._nextPoint = nextPoint; - segment._pointColorMapper = currentPoint.pointColorMapper; - segment.currentSegmentIndex = pointIndex; - segment._seriesIndex = seriesIndex; - segment._series = _series as XyDataSeries; - segment._seriesRenderer = this; - if (_renderingDetails!.widgetNeedUpdate && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segment._seriesIndex && - _oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSeries = segment._oldSeriesRenderer!._series - as XyDataSeries; - } - segment.calculateSegmentPoints(); - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment.points.add(Offset(segment._x2, segment._y2)); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render spline series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - ChartSegment createSegment() => SplineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._seriesRenderer._seriesColor; - segment._strokeWidth = segment._series.width; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart index 5c5781992..906be7741 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_area_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_area_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the stacked area series. /// @@ -13,7 +31,7 @@ part of charts; /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedAreaSeries extends _StackedSeriesBase { +class StackedAreaSeries extends StackedSeriesBase { /// Creating an argument constructor of StackedAreaSeries class. StackedAreaSeries( {ValueKey? key, @@ -47,10 +65,12 @@ class StackedAreaSeries extends _StackedSeriesBase { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, this.borderDrawMode = BorderDrawMode.top}) : super( key: key, @@ -87,7 +107,9 @@ class StackedAreaSeries extends _StackedSeriesBase { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Border type of stacked area series. /// @@ -147,10 +169,12 @@ class StackedAreaSeries extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.borderColor == borderColor && other.borderWidth == borderWidth && other.borderGradient == borderGradient && @@ -188,6 +212,7 @@ class StackedAreaSeries extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, borderColor, borderWidth, @@ -214,87 +239,3 @@ class StackedAreaSeries extends _StackedSeriesBase { return StackedAreaSeriesRenderer(); } } - -/// Creates series renderer for Stacked area series -class StackedAreaSeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedAreaSeriesRenderer class. - StackedAreaSeriesRenderer(); - - /// Stacked area segment is created here. - // ignore: unused_element - ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, double animateFactor, - [List? _points]) { - final StackedAreaSegment segment = createSegment(); - final List _oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - segment._seriesIndex = seriesIndex; - if (_points != null) { - segment.points = _points; - } - segment.animationFactor = animateFactor; - if (_renderingDetails!.widgetNeedUpdate && - _xAxisRenderer!._zoomFactor == 1 && - _yAxisRenderer!._zoomFactor == 1 && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segment._seriesIndex && - _oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSegmentIndex = 0; - } - customizeSegment(segment); - segment._chart = chart; - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked area series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer - ?._checkWithSelectionState(_segments[0], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedAreaSegment createSegment() => StackedAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._series.borderColor; - segment._strokeWidth = segment._series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart index 40c45dc3a..ac5f46cf1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_bar_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_bar_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the stacked bar series. /// @@ -9,12 +27,12 @@ part of charts; /// the series collection property of [SfCartesianChart]. /// ///Provides options to customize properties such as [color], [opacity], -///[borderWidth], [borderColor], [borderRadius] of the Stackedbar segemnts. +///[borderWidth], [borderColor], [borderRadius] of the Stackedbar segments. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedBarSeries extends _StackedSeriesBase { +class StackedBarSeries extends StackedSeriesBase { /// Creating an argument constructor of StackedBarSeries class. StackedBarSeries( {ValueKey? key, @@ -56,10 +74,12 @@ class StackedBarSeries extends _StackedSeriesBase { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -101,6 +121,8 @@ class StackedBarSeries extends _StackedSeriesBase { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -148,6 +170,7 @@ class StackedBarSeries extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -159,6 +182,7 @@ class StackedBarSeries extends _StackedSeriesBase { other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -196,6 +220,7 @@ class StackedBarSeries extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -226,91 +251,3 @@ class StackedBarSeries extends _StackedSeriesBase { return StackedBarSeriesRenderer(); } } - -/// Creates series renderer for Stacked bar series -class StackedBarSeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedBarSeriesRenderer class. - StackedBarSeriesRenderer(); - @override - late num _rectPosition; - @override - late num _rectCount; - - /// Stacked Bar segment is created here. - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final StackedBarSegment segment = createSegment(); - final StackedBarSeries _stackedBarSeries = - _series as StackedBarSeries; - _isRectSeries = true; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment._seriesRenderer = this; - segment._series = _stackedBarSeries; - segment._currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segment._path = _findingRectSeriesDashedBorder( - currentPoint, _stackedBarSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (_stackedBarSeries.borderRadius != null) { - segment.segmentRect = _getRRectFromRect( - currentPoint.region!, _stackedBarSeries.borderRadius); - - //Tracker rect - if (_stackedBarSeries.isTrackVisible) { - segment._trackRect = _getRRectFromRect( - currentPoint.trackerRectRegion!, _stackedBarSeries.borderRadius); - segment._trackerFillPaint = segment._getTrackerFillPaint(); - segment._trackerStrokePaint = segment._getTrackerStrokePaint(); - } - } - segment._segmentRect = segment.segmentRect; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked bar series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - @override - StackedBarSegment createSegment() => StackedBarSegment(); - - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._series.borderColor; - segment._strokeWidth = segment._series.borderWidth; - } - - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart index 0060b07b6..6970add8a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_column_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_column_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the stacked column series. /// @@ -8,11 +26,11 @@ part of charts; /// and add it to the series collection property of [SfCartesianChart]. /// ///Provides options to customize properties such as [color], [opacity], -///[borderWidth], [borderColor], [borderRadius] of the Stackedcolumn segemnts. +///[borderWidth], [borderColor], [borderRadius] of the Stackedcolumn segments. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedColumnSeries extends _StackedSeriesBase { +class StackedColumnSeries extends StackedSeriesBase { /// Creating an argument constructor of StackedColumnSeries class. StackedColumnSeries( {ValueKey? key, @@ -54,10 +72,12 @@ class StackedColumnSeries extends _StackedSeriesBase { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -99,6 +119,8 @@ class StackedColumnSeries extends _StackedSeriesBase { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -146,6 +168,7 @@ class StackedColumnSeries extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -157,6 +180,7 @@ class StackedColumnSeries extends _StackedSeriesBase { other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -194,6 +218,7 @@ class StackedColumnSeries extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -224,88 +249,3 @@ class StackedColumnSeries extends _StackedSeriesBase { return StackedColumnSeriesRenderer(); } } - -/// Creates series renderer for Stacked column series -class StackedColumnSeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedColumnSeriesRenderer class. - StackedColumnSeriesRenderer(); - @override - late num _rectPosition; - @override - late num _rectCount; - - /// Stacked Bar segment is created here. - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final StackedColumnSegment segment = createSegment(); - final StackedColumnSeries _stackedColumnSeries = - _series as StackedColumnSeries; - _isRectSeries = true; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment._seriesRenderer = this; - segment._series = _stackedColumnSeries; - segment._currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segment._path = _findingRectSeriesDashedBorder( - currentPoint, _stackedColumnSeries.borderWidth); - segment.segmentRect = _getRRectFromRect( - currentPoint.region!, _stackedColumnSeries.borderRadius); - - //Tracker rect - if (_stackedColumnSeries.isTrackVisible) { - segment._trackRect = _getRRectFromRect( - currentPoint.trackerRectRegion!, _stackedColumnSeries.borderRadius); - segment._trackerFillPaint = segment._getTrackerFillPaint(); - segment._trackerStrokePaint = segment._getTrackerStrokePaint(); - } - segment._segmentRect = segment.segmentRect; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked column series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - @override - StackedColumnSegment createSegment() => StackedColumnSegment(); - - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._series.borderColor; - segment._strokeWidth = segment._series.borderWidth; - } - - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart index fdfc76f25..abcf8eeba 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_line_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_line_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the stacked line series. /// @@ -7,11 +25,11 @@ part of charts; /// A stacked line chart displays series as a set of points connected by a line. /// /// To render a stacked line chart, create an instance of StackedLineSeries, and add it to the series collection property of [SfCartesianChart]. -/// Provides options to customise [color], [opacity], [width] of the Stacked Line segments. +/// Provides options to customize [color], [opacity], [width] of the Stacked Line segments. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedLineSeries extends _StackedSeriesBase { +class StackedLineSeries extends StackedSeriesBase { /// Creating an argument constructor of StackedLineSeries class. StackedLineSeries( {ValueKey? key, @@ -42,10 +60,12 @@ class StackedLineSeries extends _StackedSeriesBase { SortingOrder? sortingOrder, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -76,6 +96,8 @@ class StackedLineSeries extends _StackedSeriesBase { sortingOrder: sortingOrder, groupName: groupName, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -120,10 +142,12 @@ class StackedLineSeries extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -158,6 +182,7 @@ class StackedLineSeries extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, initialSelectedDataIndexes, onPointTap, @@ -181,98 +206,3 @@ class StackedLineSeries extends _StackedSeriesBase { return StackedLineSeriesRenderer(); } } - -/// Creates series renderer for Stacked line series -class StackedLineSeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedLineSeriesRenderer class. - StackedLineSeriesRenderer(); - - ///Stacked line segment is created here - // ignore: unused_element - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animationFactor, - double currentCummulativePos, - double nextCummulativePos) { - final StackedLineSegment segment = createSegment(); - final List? _oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - segment._seriesIndex = seriesIndex; - segment._currentPoint = currentPoint; - segment.currentSegmentIndex = pointIndex; - segment._nextPoint = _nextPoint; - segment._chart = _chart; - segment._chartState = _chartState!; - segment.animationFactor = animationFactor; - segment._pointColorMapper = currentPoint.pointColorMapper; - segment._currentCummulativePos = currentCummulativePos; - segment._nextCummulativePos = nextCummulativePos; - if (_renderingDetails!.widgetNeedUpdate && - _xAxisRenderer!._zoomFactor == 1 && - _yAxisRenderer!._zoomFactor == 1 && - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segment._seriesIndex && - _oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - } - segment.calculateSegmentPoints(); - segment.points.add(Offset(segment._x1, segment._y1)); - segment.points.add(Offset(segment._x2, segment._y2)); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked line series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedLineSegment createSegment() => StackedLineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._seriesRenderer._seriesColor; - segment._strokeWidth = segment._series.width; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart index 03ca5521c..576bb26b0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stacked_series_base.dart @@ -1,7 +1,23 @@ -part of charts; +import 'dart:ui'; -abstract class _StackedSeriesBase extends XyDataSeries { - _StackedSeriesBase( +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; + +/// Represents the stacked series base class +abstract class StackedSeriesBase extends XyDataSeries { + /// Creates an instance of stacked series renderer + StackedSeriesBase( {ValueKey? key, ChartSeriesRendererFactory? onCreateRenderer, required List dataSource, @@ -45,6 +61,8 @@ abstract class _StackedSeriesBase extends XyDataSeries { ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, + double? animationDelay, double? opacity}) : borderRadius = borderRadius ?? const BorderRadius.all(Radius.zero), trackColor = trackColor ?? Colors.grey, @@ -90,7 +108,9 @@ abstract class _StackedSeriesBase extends XyDataSeries { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Customizes the corners of the column. Each corner can be customized with a desired ///value or with a single value. @@ -232,44 +252,14 @@ abstract class _StackedSeriesBase extends XyDataSeries { ///} ///``` final double trackPadding; + + /// Specifies the dash array @override final List dashArray; + + /// Specifies the group name final String groupName; } -abstract class _StackedSeriesRenderer extends XyDataSeriesRenderer { - _StackedSeriesRenderer(); - - // Store the stacking values // - //ignore: prefer_final_fields - List<_StackedValues> _stackingValues = <_StackedValues>[]; - - // Store the percentage values // - //ignore: prefer_final_fields - List _percentageValues = []; - - // Store the rect position // - late num _rectPosition; - - // Store the rect count // - late num _rectCount; - - @override - ChartSegment createSegment(); - - /// Changes the series color, border color, and border width. - @override - // ignore: unused_element - void customizeSegment(ChartSegment segment) {} - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) {} - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) {} -} +/// Represents the Stacked series renderer class +abstract class StackedSeriesRenderer extends XyDataSeriesRenderer {} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart index eadd1a4d8..35ac28eb7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedarea100_series.dart @@ -1,4 +1,21 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_area_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the stacked area series. /// @@ -15,7 +32,7 @@ part of charts; /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedArea100Series extends _StackedSeriesBase { +class StackedArea100Series extends StackedSeriesBase { /// Creating an argument constructor of StackedArea100Series class. StackedArea100Series( {ValueKey? key, @@ -49,10 +66,12 @@ class StackedArea100Series extends _StackedSeriesBase { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, this.borderDrawMode = BorderDrawMode.top}) : super( key: key, @@ -89,7 +108,9 @@ class StackedArea100Series extends _StackedSeriesBase { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Border type of stacked area series. /// @@ -149,10 +170,12 @@ class StackedArea100Series extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.borderColor == borderColor && other.borderWidth == borderWidth && other.borderGradient == borderGradient && @@ -190,6 +213,7 @@ class StackedArea100Series extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, borderColor, borderWidth, @@ -216,89 +240,3 @@ class StackedArea100Series extends _StackedSeriesBase { return StackedArea100SeriesRenderer(); } } - -/// Creates series renderer for Stacked area 100 series -class StackedArea100SeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedArea100SeriesRenderer class. - StackedArea100SeriesRenderer(); - - /// Stacked Area segment is created here. - // ignore: unused_element - ChartSegment _createSegments( - int seriesIndex, SfCartesianChart chart, double animateFactor, - [List? _points]) { - final StackedArea100Segment segment = createSegment(); - final List _oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - segment._seriesIndex = seriesIndex; - if (_points != null) { - segment.points = _points; - } - segment.animationFactor = animateFactor; - if (_renderingDetails!.widgetNeedUpdate && - _xAxisRenderer!._zoomFactor == 1 && - _yAxisRenderer!._zoomFactor == 1 && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segment._seriesIndex && - _oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSeries = segment._oldSeriesRenderer!._series - as XyDataSeries; - segment._oldSegmentIndex = 0; - } - customizeSegment(segment); - segment._chart = chart; - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked area 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer - ?._checkWithSelectionState(_segments[0], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedArea100Segment createSegment() => StackedArea100Segment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._series.borderColor; - segment._strokeWidth = segment._series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart index 29d18d35f..f38168ef2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedbar100_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_bar_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the 100% stacked bar series. /// @@ -13,7 +31,7 @@ part of charts; /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedBar100Series extends _StackedSeriesBase { +class StackedBar100Series extends StackedSeriesBase { /// Creating an argument constructor of StackedBar100Series class. StackedBar100Series( {ValueKey? key, @@ -50,10 +68,12 @@ class StackedBar100Series extends _StackedSeriesBase { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -90,6 +110,8 @@ class StackedBar100Series extends _StackedSeriesBase { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -137,6 +159,7 @@ class StackedBar100Series extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -148,6 +171,7 @@ class StackedBar100Series extends _StackedSeriesBase { other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -185,6 +209,7 @@ class StackedBar100Series extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -215,83 +240,3 @@ class StackedBar100Series extends _StackedSeriesBase { return StackedBar100SeriesRenderer(); } } - -/// Creates series renderer for Stacked bar 100 series -class StackedBar100SeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedBar100SeriesRenderer class. - StackedBar100SeriesRenderer(); - - @override - late num _rectPosition; - @override - late num _rectCount; - - /// Stacked Bar segment is created here - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final StackedBar100Segment segment = createSegment(); - final StackedBar100Series _stackedBar100Series = - _series as StackedBar100Series; - _isRectSeries = true; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment._seriesRenderer = this; - segment._series = _stackedBar100Series; - segment._currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segment._path = _findingRectSeriesDashedBorder( - currentPoint, _stackedBar100Series.borderWidth); - segment.segmentRect = _getRRectFromRect( - currentPoint.region!, _stackedBar100Series.borderRadius); - segment._segmentRect = segment.segmentRect; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked bar 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - @override - StackedBar100Segment createSegment() => StackedBar100Segment(); - - @override - void customizeSegment(ChartSegment segment) { - final StackedBar100Segment bar100Segment = segment as StackedBar100Segment; - bar100Segment._color = bar100Segment._currentPoint!.pointColorMapper ?? - bar100Segment._seriesRenderer._seriesColor; - bar100Segment._strokeColor = bar100Segment._series.borderColor; - bar100Segment._strokeWidth = bar100Segment._series.borderWidth; - } - - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart index 6c1540215..0ae4f1b8f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedcolumn100_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_column_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the 100% stacked column series. /// @@ -9,11 +27,11 @@ part of charts; /// and add it to the series collection property of [SfCartesianChart]. /// ///Provides options to customize properties such as [color], [opacity], -///[borderWidth], [borderColor], [borderRadius] of the StackedColumn100 segemnts. +///[borderWidth], [borderColor], [borderRadius] of the StackedColumn100 segments. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedColumn100Series extends _StackedSeriesBase { +class StackedColumn100Series extends StackedSeriesBase { /// Creating an argument constructor of StackedColumn100Series class. StackedColumn100Series( {ValueKey? key, @@ -50,10 +68,12 @@ class StackedColumn100Series extends _StackedSeriesBase { String? legendItemText, List? dashArray, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -90,6 +110,8 @@ class StackedColumn100Series extends _StackedSeriesBase { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -137,6 +159,7 @@ class StackedColumn100Series extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.trackColor == trackColor && other.trackBorderColor == trackBorderColor && other.trackBorderWidth == trackBorderWidth && @@ -148,6 +171,7 @@ class StackedColumn100Series extends _StackedSeriesBase { other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -185,6 +209,7 @@ class StackedColumn100Series extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, trackColor, trackBorderColor, trackBorderWidth, @@ -215,89 +240,3 @@ class StackedColumn100Series extends _StackedSeriesBase { return StackedColumn100SeriesRenderer(); } } - -/// Creates series renderer for Stacked column 100 series -class StackedColumn100SeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedColumn100SeriesRenderer class. - StackedColumn100SeriesRenderer(); - - @override - late num _rectPosition; - @override - late num _rectCount; - - /// Stacked Column 100 segment is created here - // ignore: unused_element - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final StackedColumn100Segment segment = createSegment(); - final StackedColumn100Series _stackedColumn100Series = - _series as StackedColumn100Series; - _isRectSeries = true; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points.add( - Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment._seriesRenderer = this; - segment._series = _stackedColumn100Series; - segment._currentPoint = currentPoint; - segment.animationFactor = animateFactor; - segment._path = _findingRectSeriesDashedBorder( - currentPoint, _stackedColumn100Series.borderWidth); - segment.segmentRect = _getRRectFromRect( - currentPoint.region!, _stackedColumn100Series.borderRadius); - segment._segmentRect = segment.segmentRect; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked column 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedColumn100Segment createSegment() => StackedColumn100Segment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final StackedColumn100Segment column100Segment = - segment as StackedColumn100Segment; - column100Segment._color = - column100Segment._currentPoint!.pointColorMapper ?? - column100Segment._seriesRenderer._seriesColor; - column100Segment._strokeColor = column100Segment._series.borderColor; - column100Segment._strokeWidth = column100Segment._series.borderWidth; - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart index 5d503d177..6ac2b8cba 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stackedline100_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stacked_line_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'stacked_series_base.dart'; /// Renders the 100% stacked line series. /// @@ -6,11 +24,11 @@ part of charts; /// In the stacked 100 line chart, the lines reach a total of 100% of the axis range at each point /// /// To render a 100% stacked line chart, create an instance of StackedLine100Series, and add it to the series collection property of [SfCartesianChart]. -/// Provides options to customise color,opacity and width of the StackedLine100 segments. +/// Provides options to customize color,opacity and width of the StackedLine100 segments. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=NCUDBD_ClHo} @immutable -class StackedLine100Series extends _StackedSeriesBase { +class StackedLine100Series extends StackedSeriesBase { /// Creating an argument constructor of StackedLine100Series class. StackedLine100Series( {ValueKey? key, @@ -41,10 +59,12 @@ class StackedLine100Series extends _StackedSeriesBase { SortingOrder? sortingOrder, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -75,6 +95,8 @@ class StackedLine100Series extends _StackedSeriesBase { sortingOrder: sortingOrder, groupName: groupName, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, onRendererCreated: onRendererCreated, onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, @@ -119,10 +141,12 @@ class StackedLine100Series extends _StackedSeriesBase { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -157,6 +181,7 @@ class StackedLine100Series extends _StackedSeriesBase { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, initialSelectedDataIndexes, onPointTap, @@ -180,99 +205,3 @@ class StackedLine100Series extends _StackedSeriesBase { return StackedLine100SeriesRenderer(); } } - -/// Creates series renderer for Stacked line 100 series -class StackedLine100SeriesRenderer extends _StackedSeriesRenderer { - /// Calling the default constructor of StackedLine100SeriesRenderer class. - StackedLine100SeriesRenderer(); - - ///Stacked line segment is created here - // ignore: unused_element - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animationFactor, - double currentCummulativePos, - double nextCummulativePos) { - final StackedLine100Segment segment = createSegment(); - final List _oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - // ignore: unnecessary_null_comparison - if (segment != null) { - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - segment._seriesIndex = seriesIndex; - segment._currentPoint = currentPoint; - segment.currentSegmentIndex = pointIndex; - segment._nextPoint = _nextPoint; - segment._chart = _chart; - segment._chartState = _chartState!; - segment.animationFactor = animationFactor; - segment._pointColorMapper = currentPoint.pointColorMapper; - segment._currentCummulativePos = currentCummulativePos; - segment._nextCummulativePos = nextCummulativePos; - if (_renderingDetails!.widgetNeedUpdate && - _xAxisRenderer!._zoomFactor == 1 && - _yAxisRenderer!._zoomFactor == 1 && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segment._seriesIndex && - _oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - } - segment.calculateSegmentPoints(); - segment.points.add(Offset(segment._x1, segment._y1)); - segment.points.add(Offset(segment._x2, segment._y2)); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - } - return segment; - } - - /// To render stacked line 100 series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StackedLine100Segment createSegment() => StackedLine100Segment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._seriesRenderer._seriesColor; - segment._strokeWidth = segment._series.width; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart index c1ad5cb48..341d60369 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/step_area_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/step_area_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the step area series. /// @@ -47,10 +65,12 @@ class StepAreaSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, this.borderDrawMode = BorderDrawMode.top}) : super( key: key, @@ -86,7 +106,9 @@ class StepAreaSeries extends XyDataSeries { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); ///Border type of step area series. /// @@ -161,11 +183,13 @@ class StepAreaSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.borderDrawMode == borderDrawMode && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -201,6 +225,7 @@ class StepAreaSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, borderDrawMode, onRendererCreated, onPointTap, @@ -210,74 +235,3 @@ class StepAreaSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Step area series -class StepAreaSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of StepAreaSeriesRenderer class. - StepAreaSeriesRenderer(); - - /// StepArea segment is created here - ChartSegment _createSegments( - Path path, Path strokePath, int seriesIndex, double animateFactor, - [List? _points]) { - final StepAreaSegment segment = createSegment(); - _isRectSeries = false; - segment._path = path; - segment._strokePath = strokePath; - segment._seriesIndex = seriesIndex; - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - if (_points != null) { - segment.points = _points; - } - segment._chart = _chart; - segment._chartState = _chartState!; - segment.animationFactor = animateFactor; - segment.calculateSegmentPoints(); - segment._oldSegmentIndex = 0; - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - return segment; - } - - /// To render step area series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer - ?._checkWithSelectionState(_segments[0], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StepAreaSegment createSegment() => StepAreaSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._series.borderColor; - segment._strokeWidth = segment._series.borderWidth; - } - - /// Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart index b8a3c06b2..3590c60a0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/stepline_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/stepline_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the step line series. /// @@ -6,7 +24,7 @@ part of charts; /// by horizontal and vertical line segments, looking like steps of a staircase. /// /// To render a step line chart, create an instance of StepLineSeries, and add it to the series collection property of [SfCartesianChart]. -/// Provides option to customise the [color], [opacity], [width] of the stepline segments +/// Provides option to customize the [color], [opacity], [width] of the stepline segments @immutable class StepLineSeries extends XyDataSeries { /// Creating an argument constructor of StepLineSeries class. @@ -41,6 +59,8 @@ class StepLineSeries extends XyDataSeries { ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, + double? animationDelay, double? opacity}) : super( key: key, @@ -73,7 +93,9 @@ class StepLineSeries extends XyDataSeries { onPointTap: onPointTap, onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, - opacity: opacity); + opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader); /// Create the stacked area series renderer. StepLineSeriesRenderer createRenderer(ChartSeries series) { @@ -127,10 +149,12 @@ class StepLineSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && - other.onPointLongPress == onPointLongPress; + other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader; } @override @@ -164,6 +188,7 @@ class StepLineSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, onPointTap, onPointDoubleTap, @@ -172,93 +197,3 @@ class StepLineSeries extends XyDataSeries { return hashList(values); } } - -/// Creates series renderer for Step line series -class StepLineSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of StepLineSeriesRenderer class. - StepLineSeriesRenderer(); - - /// StepLine segment is created here - ChartSegment _createSegments( - CartesianChartPoint currentPoint, - num midX, - num midY, - CartesianChartPoint _nextPoint, - int pointIndex, - int seriesIndex, - double animateFactor) { - final StepLineSegment segment = createSegment(); - final List _oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _isRectSeries = false; - segment.currentSegmentIndex = pointIndex; - segment._seriesIndex = seriesIndex; - segment._seriesRenderer = this; - segment._series = _series as XyDataSeries; - segment._currentPoint = currentPoint; - segment._midX = midX; - segment._midY = midY; - segment._nextPoint = _nextPoint; - segment._chart = _chart; - segment._chartState = _chartState!; - segment.animationFactor = animateFactor; - segment._pointColorMapper = currentPoint.pointColorMapper; - if (_renderingDetails!.widgetNeedUpdate && - // ignore: unnecessary_null_comparison - _oldSeriesRenderers != null && - _oldSeriesRenderers.isNotEmpty && - _oldSeriesRenderers.length - 1 >= segment._seriesIndex && - _oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = _oldSeriesRenderers[segment._seriesIndex]; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - } - segment.calculateSegmentPoints(); - segment.points.add(Offset(segment._x1, segment._y1)); - segment.points.add(Offset(segment._x2, segment._y2)); - customizeSegment(segment); - segment.strokePaint = segment.getStrokePaint(); - segment.fillPaint = segment.getFillPaint(); - _segments.add(segment); - return segment; - } - - /// To render step line series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - StepLineSegment createSegment() => StepLineSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - segment._color = segment._seriesRenderer._seriesColor; - segment._strokeColor = segment._seriesRenderer._seriesColor; - segment._strokeWidth = segment._series.width; - } - - ///Draws marker with different shape and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart index 12c583c39..d621d7ef6 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/waterfall_series.dart @@ -1,4 +1,22 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../chart/utils/enum.dart'; +import '../../circular_chart/renderer/circular_chart_annotation.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/waterfall_painter.dart'; +import '../trendlines/trendlines.dart'; +import 'xy_data_series.dart'; /// Renders the waterfall series. /// @@ -48,11 +66,13 @@ class WaterfallSeries extends XyDataSeries { LegendIconType? legendIconType, String? legendItemText, double? opacity, + double? animationDelay, List? dashArray, SeriesRendererCreatedCallback? onRendererCreated, ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, List? initialSelectedDataIndexes}) : super( key: key, @@ -86,6 +106,8 @@ class WaterfallSeries extends XyDataSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, dashArray: dashArray, onRendererCreated: onRendererCreated, onPointTap: onPointTap, @@ -278,10 +300,12 @@ class WaterfallSeries extends XyDataSeries { other.legendIconType == legendIconType && other.legendItemText == legendItemText && other.opacity == opacity && + other.animationDelay == animationDelay && other.onRendererCreated == onRendererCreated && other.onPointTap == onPointTap && other.onPointDoubleTap == onPointDoubleTap && other.onPointLongPress == onPointLongPress && + other.onCreateShader == onCreateShader && other.initialSelectedDataIndexes == initialSelectedDataIndexes; } @@ -327,6 +351,7 @@ class WaterfallSeries extends XyDataSeries { legendIconType, legendItemText, opacity, + animationDelay, onRendererCreated, initialSelectedDataIndexes, onPointTap, @@ -350,126 +375,6 @@ class WaterfallSeries extends XyDataSeries { } } -/// Creates series renderer for waterfall series -class WaterfallSeriesRenderer extends XyDataSeriesRenderer { - /// Calling the default constructor of WaterfallSeriesRenderer class. - WaterfallSeriesRenderer(); - late num _rectPosition; - late num _rectCount; - - late WaterfallSeries _waterfallSeries; - - /// To add waterfall segments in segments list - ChartSegment _createSegments(CartesianChartPoint currentPoint, - int pointIndex, int seriesIndex, double animateFactor) { - final WaterfallSegment segment = createSegment(); - final List? oldSeriesRenderers = - _chartState!._oldSeriesRenderers; - _waterfallSeries = _series as WaterfallSeries; - final BorderRadius borderRadius = _waterfallSeries.borderRadius; - segment._seriesIndex = seriesIndex; - segment.currentSegmentIndex = pointIndex; - segment.points - .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); - segment._seriesRenderer = this; - segment._series = _waterfallSeries; - segment._chart = _chart; - segment._chartState = _chartState!; - segment.animationFactor = animateFactor; - segment._currentPoint = currentPoint; - if (_renderingDetails!.widgetNeedUpdate && - _chartState!._zoomPanBehaviorRenderer._isPinching != true && - !_renderingDetails!.isLegendToggled && - oldSeriesRenderers != null && - oldSeriesRenderers.isNotEmpty && - oldSeriesRenderers.length - 1 >= segment._seriesIndex && - oldSeriesRenderers[segment._seriesIndex]._seriesName == - segment._seriesRenderer._seriesName) { - segment._oldSeriesRenderer = oldSeriesRenderers[segment._seriesIndex]; - segment._oldPoint = (segment._oldSeriesRenderer!._segments.isNotEmpty && - segment._oldSeriesRenderer!._segments[0] is WaterfallSegment && - segment._oldSeriesRenderer!._dataPoints.length - 1 >= pointIndex) - ? segment._oldSeriesRenderer!._dataPoints[pointIndex] - : null; - segment._oldSegmentIndex = _getOldSegmentIndex(segment); - } else if (_renderingDetails!.isLegendToggled && - // ignore: unnecessary_null_comparison - _chartState!._segments != null && - _chartState!._segments.isNotEmpty) { - segment._oldSeriesVisible = - _chartState!._oldSeriesVisible[segment._seriesIndex]; - WaterfallSegment oldSegment; - for (int i = 0; i < _chartState!._segments.length; i++) { - oldSegment = _chartState!._segments[i] as WaterfallSegment; - if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && - oldSegment._seriesIndex == segment._seriesIndex) { - segment._oldRegion = oldSegment.segmentRect.outerRect; - } - } - } - segment._path = _findingRectSeriesDashedBorder( - currentPoint, _waterfallSeries.borderWidth); - // ignore: unnecessary_null_comparison - if (borderRadius != null) { - segment.segmentRect = - _getRRectFromRect(currentPoint.region!, borderRadius); - } - segment._segmentRect = segment.segmentRect; - customizeSegment(segment); - _segments.add(segment); - return segment; - } - - /// To render waterfall series segments - //ignore: unused_element - void _drawSegment(Canvas canvas, ChartSegment segment) { - if (segment._seriesRenderer._isSelectionEnable) { - final SelectionBehaviorRenderer? selectionBehaviorRenderer = - segment._seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - _segments[segment.currentSegmentIndex!], _chart); - } - segment.onPaint(canvas); - } - - /// Creates a segment for a data point in the series. - @override - WaterfallSegment createSegment() => WaterfallSegment(); - - /// Changes the series color, border color, and border width. - @override - void customizeSegment(ChartSegment segment) { - final WaterfallSegment waterfallSegment = segment as WaterfallSegment; - waterfallSegment._color = segment._seriesRenderer._seriesColor; - waterfallSegment._negativePointsColor = - _waterfallSeries.negativePointsColor; - waterfallSegment._intermediateSumColor = - _waterfallSeries.intermediateSumColor; - waterfallSegment._totalSumColor = _waterfallSeries.totalSumColor; - waterfallSegment._strokeColor = segment._series.borderColor; - waterfallSegment._strokeWidth = segment._series.borderWidth; - waterfallSegment.strokePaint = waterfallSegment.getStrokePaint(); - waterfallSegment.fillPaint = waterfallSegment.getFillPaint(); - waterfallSegment.connectorLineStrokePaint = - waterfallSegment._getConnectorLineStrokePaint(); - } - - ///Draws the marker with different shapes and color of the appropriate data point in the series. - @override - void drawDataMarker(int index, Canvas canvas, Paint fillPaint, - Paint strokePaint, double pointX, double pointY, - [CartesianSeriesRenderer? seriesRenderer]) { - canvas.drawPath(seriesRenderer!._markerShapes[index]!, fillPaint); - canvas.drawPath(seriesRenderer._markerShapes[index]!, strokePaint); - } - - /// Draws data label text of the appropriate data point in a series. - @override - void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, - double pointY, int angle, TextStyle style) => - _drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); -} - ///Options to customize the waterfall chart connector line. /// ///Data points in waterfall chart are connected using the connector line and this class hold the diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart index a136ed608..bc70c905b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/chart_series/xy_data_series.dart @@ -1,4 +1,20 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/event_args.dart' show ErrorBarValues; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../trendlines/trendlines.dart'; /// Renders the xy series. /// @@ -12,6 +28,7 @@ abstract class XyDataSeries extends CartesianSeries { ChartPointInteractionCallback? onPointTap, ChartPointInteractionCallback? onPointDoubleTap, ChartPointInteractionCallback? onPointLongPress, + CartesianShaderCallback? onCreateShader, ChartValueMapper? xValueMapper, ChartValueMapper? yValueMapper, ChartValueMapper? dataLabelMapper, @@ -44,6 +61,7 @@ abstract class XyDataSeries extends CartesianSeries { bool? isVisibleInLegend, LegendIconType? legendIconType, double? opacity, + double? animationDelay, Color? color, List? initialSelectedDataIndexes, SortingOrder? sortingOrder}) @@ -106,6 +124,8 @@ abstract class XyDataSeries extends CartesianSeries { legendIconType: legendIconType, sortingOrder: sortingOrder, opacity: opacity, + animationDelay: animationDelay, + onCreateShader: onCreateShader, gradient: gradient, borderGradient: borderGradient, markerSettings: markerSettings, @@ -204,10 +224,10 @@ class CartesianChartPoint { num? volume; /// Marker point location. - _ChartLocation? markerPoint; + ChartLocation? markerPoint; /// Second marker point location. - _ChartLocation? markerPoint2; + ChartLocation? markerPoint2; /// Size of the bubble. num? bubbleSize; @@ -227,7 +247,7 @@ class CartesianChartPoint { /// Used to map the color value from data points. Color? pointColorMapper; - /// Maps the datalabel value from data points. + /// Maps the data label value from data points. String? dataLabelMapper; /// Stores the region rect. @@ -286,20 +306,8 @@ class CartesianChartPoint { // ignore: prefer_final_fields bool isTooltipRenderEvent = false; - //specifies the style of data label in the onDataLabelEvent - TextStyle? _dataLabelStyle; - - //specifies the color of the data label in onDataLabelEvent - Color? _dataLabelColor; - - /// Stores the tooltip label text. - String? _tooltipLabelText; - - /// Stores the tooltip header text. - String? _tooltipHeaderText; - /// Stores the chart location. - _ChartLocation? openPoint, + ChartLocation? openPoint, closePoint, centerOpenPoint, centerClosePoint, @@ -308,10 +316,6 @@ class CartesianChartPoint { centerLowPoint, centerHighPoint, currentPoint, - // ignore:unused_field - _nextPoint, - // ignore:unused_field - _midPoint, startControl, endControl, highStartControl, @@ -330,10 +334,17 @@ class CartesianChartPoint { originValueLeftPoint, originValueRightPoint, endValueLeftPoint, - endValueRightPoint; + endValueRightPoint, + horizontalPositiveErrorPoint, + horizontalNegativeErrorPoint, + verticalPositiveErrorPoint, + verticalNegativeErrorPoint; + + /// Stores the error values in all directions + ErrorBarValues? errorBarValues; /// Stores the outliers location. - List<_ChartLocation> outliersPoint = <_ChartLocation>[]; + List outliersPoint = []; /// Control points for spline series. List? controlPoint; @@ -390,22 +401,22 @@ class CartesianChartPoint { List outliersFillRect = []; /// Stores the data label location. - _ChartLocation? labelLocation; + ChartLocation? labelLocation; /// Stores the second data label location. - _ChartLocation? labelLocation2; + ChartLocation? labelLocation2; /// Stores the third data label location. - _ChartLocation? labelLocation3; + ChartLocation? labelLocation3; /// Stores the fourth data label location. - _ChartLocation? labelLocation4; + ChartLocation? labelLocation4; /// Stores the median data label location. - _ChartLocation? labelLocation5; + ChartLocation? labelLocation5; /// Stores the outliers data label location. - List<_ChartLocation> outliersLocation = <_ChartLocation>[]; + List outliersLocation = []; /// Data label region saturation. bool dataLabelSaturationRegionInside = false; @@ -439,64 +450,66 @@ class CartesianChartPoint { /// Stores the visible point index. int? visiblePointIndex; -} -class _ChartLocation { - _ChartLocation(this.x, this.y); - double x; - double y; + TextStyle? _dataLabelTextStyle; + + Color? _dataLabelColor; } -/// To calculate dash array path for series -Path? _dashPath( - Path? source, { - required _CircularIntervalList dashArray, -}) { - if (source == null) { - return null; +// ignore: avoid_classes_with_only_static_members +/// Helper class for Cartesian chart point +class CartesianPointHelper { + /// Returns the datalabel text style for a given point + static TextStyle? getDataLabelTextStyle(CartesianChartPoint point) { + return point._dataLabelTextStyle; } - const double intialValue = 0.0; - final Path path = Path(); - for (final PathMetric measurePath in source.computeMetrics()) { - double distance = intialValue; - bool draw = true; - while (distance < measurePath.length) { - final double length = dashArray.next; - if (draw) { - path.addPath( - measurePath.extractPath(distance, distance + length), Offset.zero); - } - distance += length; - draw = !draw; - } + + /// Sets the datalabel text style in a given point + static void setDataLabelTextStyle( + CartesianChartPoint point, TextStyle? style) { + point._dataLabelTextStyle = style; } - return path; -} -/// A circular array for dash offsets and lengths. -class _CircularIntervalList { - _CircularIntervalList(this._values); - final List _values; - int _index = 0; - T get next { - if (_index >= _values.length) { - _index = 0; - } - return _values[_index++]; + /// Returns the datalabel color for a given point + static Color? getDataLabelColor(CartesianChartPoint point) { + return point._dataLabelColor; + } + + /// Sets the datalabel color in a given point + static void setDataLabelColor( + CartesianChartPoint point, Color? color) { + point._dataLabelColor = color; } } -/// Creates series renderer for Xy data series +/// 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; +} + +/// Creates series renderer for xy data series. abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { /// To calculate empty point value for the specific mode @override void calculateEmptyPointValue( int pointIndex, CartesianChartPoint currentPoint, [CartesianSeriesRenderer? seriesRenderer]) { - final int pointLength = seriesRenderer!._dataPoints.length - 1; - final String _seriesType = seriesRenderer._seriesType; - final CartesianChartPoint prevPoint = seriesRenderer._dataPoints[ - seriesRenderer._dataPoints.length >= 2 ? pointLength - 1 : pointLength]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + final int pointLength = seriesRendererDetails.dataPoints.length - 1; + final String _seriesType = seriesRendererDetails.seriesType; + final CartesianChartPoint prevPoint = + seriesRendererDetails.dataPoints[ + seriesRendererDetails.dataPoints.length >= 2 == true + ? pointLength - 1 + : pointLength]; if (_seriesType.contains('range') || _seriesType.contains('hilo') || _seriesType == 'candle' @@ -507,7 +520,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { currentPoint.close == null) : (currentPoint.low == null || currentPoint.high == null) : currentPoint.y == null) { - switch (seriesRenderer._series.emptyPointSettings.mode) { + switch (seriesRendererDetails.series.emptyPointSettings.mode) { case EmptyPointMode.zero: currentPoint.isEmpty = true; if (_seriesType.contains('range') || @@ -526,7 +539,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { case EmptyPointMode.average: if (seriesRenderer is XyDataSeriesRenderer) { - _calculateAverageModeValue( + seriesRendererDetails.calculateAverageModeValue( pointIndex, pointLength, currentPoint, prevPoint); } currentPoint.isEmpty = true; @@ -571,6 +584,7 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { currentPoint.y = pointIndex != 0 ? prevPoint.y : 0; } } + currentPoint.isVisible = false; currentPoint.isGap = true; break; case EmptyPointMode.drop: @@ -606,355 +620,4 @@ abstract class XyDataSeriesRenderer extends CartesianSeriesRenderer { } } } - - /// To render a series of elements for all series - void _renderSeriesElements(SfCartesianChart chart, Canvas canvas, - Animation? animationController) { - _markerShapes = []; - _markerShapes2 = []; - assert( - // ignore: unnecessary_null_comparison - !(_series.markerSettings.height != null) || - _series.markerSettings.height >= 0, - 'The height of the marker should be greater than or equal to 0.'); - assert( - // ignore: unnecessary_null_comparison - !(_series.markerSettings.width != null) || - _series.markerSettings.width >= 0, - 'The width of the marker must be greater than or equal to 0.'); - for (int pointIndex = 0; pointIndex < _dataPoints.length; pointIndex++) { - final CartesianChartPoint point = _dataPoints[pointIndex]; - if ((_series.markerSettings.isVisible && - this is! BoxAndWhiskerSeriesRenderer) || - this is ScatterSeriesRenderer) { - final MarkerSettingsRenderer? markerSettingsRenderer = - _markerSettingsRenderer!; - markerSettingsRenderer?.renderMarker( - this, point, animationController, canvas, pointIndex); - } - } - } - - void _repaintSeriesElement() { - _repaintNotifier.value++; - } - - void _animationStatusListener(AnimationStatus status) { - if (_chartState != null && status == AnimationStatus.completed) { - _reAnimate = false; - _animationCompleted = true; - _chartState!._animationCompleteCount++; - _setAnimationStatus(_chartState); - } else if (_chartState != null && status == AnimationStatus.forward) { - _renderingDetails!.animateCompleted = false; - _animationCompleted = false; - } - } - - /// To store the series properties - void _storeSeriesProperties(SfCartesianChartState chartState, int index) { - _chartState = chartState; - _chart = chartState._chart; - _isRectSeries = _seriesType.contains('column') || - _seriesType.contains('bar') || - _seriesType == 'histogram'; - _regionalData = {}; - _segmentPath = Path(); - _segments = []; - _seriesColor = - _series.color ?? _chart.palette[index % _chart.palette.length]; - - // calculates the tooltip region for trenlines in this series - final List? trendlines = _series.trendlines; - if (trendlines != null && - // ignore: unnecessary_null_comparison - _chart.tooltipBehavior != null && - _chart.tooltipBehavior.enable) { - for (int j = 0; j < trendlines.length; j++) { - if (_trendlineRenderer[j]._isNeedRender) { - if (_trendlineRenderer[j]._pointsData != null) { - for (int k = 0; - k < _trendlineRenderer[j]._pointsData!.length; - k++) { - final CartesianChartPoint trendlinePoint = - _trendlineRenderer[j]._pointsData![k]; - _calculateTooltipRegion(trendlinePoint, index, this, _chartState!, - trendlines[j], _trendlineRenderer[j], j); - } - } - } - } - } - } - - /// To find the region data of a series - void _calculateRegionData( - SfCartesianChartState _chartState, - CartesianSeriesRenderer seriesRenderer, - int seriesIndex, - CartesianChartPoint point, - int pointIndex, - [_VisibleRange? sideBySideInfo, - CartesianChartPoint? _nextPoint, - num? midX, - num? midY]) { - if (_withInRange(seriesRenderer._dataPoints[pointIndex].xValue, - seriesRenderer._xAxisRenderer!._visibleRange!)) { - seriesRenderer._visibleDataPoints! - .add(seriesRenderer._dataPoints[pointIndex]); - if (seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._dataPoints[pointIndex].visiblePointIndex = - seriesRenderer._visibleDataPoints!.length - 1; - } - } - _chart = _chartState._chart; - final ChartAxis xAxis = _xAxisRenderer!._axis; - final ChartAxis yAxis = _yAxisRenderer!._axis; - final Rect rect = _calculatePlotOffset(_chartState._chartAxis._axisClipRect, - Offset(xAxis.plotOffset, yAxis.plotOffset)); - _isRectSeries = _seriesType == 'column' || - _seriesType == 'bar' || - _seriesType.contains('stackedcolumn') || - _seriesType.contains('stackedbar') || - _seriesType == 'rangecolumn' || - _seriesType == 'histogram' || - _seriesType == 'waterfall'; - CartesianChartPoint point; - - final double markerHeight = _series.markerSettings.height, - markerWidth = _series.markerSettings.width; - final bool isPointSeries = - _seriesType == 'scatter' || _seriesType == 'bubble'; - final bool isFastLine = _seriesType == 'fastline'; - if ((!isFastLine || - (isFastLine && - (_series.markerSettings.isVisible || - _series.dataLabelSettings.isVisible || - _series.enableTooltip))) && - _visible!) { - point = _dataPoints[pointIndex]; - if (point.region == null || - seriesRenderer._calculateRegion || - _seriesType.contains('waterfall') || - _seriesType.contains('stackedcolumn') || - _seriesType.contains('stackedbar')) { - if (seriesRenderer._calculateRegion && - _dataPoints.length == pointIndex - 1) { - seriesRenderer._calculateRegion = false; - } - - /// side by side range calculated - seriesRenderer._sideBySideInfo = (_isRectSeries || - (_seriesType.contains('candle') || - _seriesType.contains('hilo') || - _seriesType.contains('histogram') || - _seriesType.contains('box'))) - ? _calculateSideBySideInfo(seriesRenderer, _chartState) - : seriesRenderer._sideBySideInfo; - if (_isRectSeries) { - _calculateRectSeriesRegion(point, pointIndex, this, _chartState); - } else if (isPointSeries) { - _calculatePointSeriesRegion( - point, pointIndex, this, _chartState, rect); - } else { - _calculatePathSeriesRegion( - point, - pointIndex, - this, - _chartState, - rect, - markerHeight, - markerWidth, - sideBySideInfo, - _nextPoint, - midX, - midY); - } - } - // ignore: unnecessary_null_comparison - if (_chart.tooltipBehavior != null && - (_chart.tooltipBehavior.enable || - _chart.onPointTapped != null || - seriesRenderer._series.onPointTap != null || - seriesRenderer._series.onPointDoubleTap != null || - seriesRenderer._series.onPointLongPress != null) && - _seriesType != 'boxandwhisker') { - _calculateTooltipRegion(point, seriesIndex, this, _chartState); - } - } - } - - /// To find the region data of chart tooltip - void calculateTooltipRegion(SfCartesianChart chart, int seriesIndex, - CartesianChartPoint point, int pointIndex) { - /// For tooltip implementation - // ignore: unnecessary_null_comparison - if (_series.enableTooltip != null && - _series.enableTooltip && - // ignore: unnecessary_null_comparison - point != null && - !point.isGap && - !point.isDrop) { - final List regionData = []; - String? date; - final List regionRect = []; - final dynamic primaryAxisRenderer = _xAxisRenderer; - if (primaryAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _axis = primaryAxisRenderer._axis as DateTimeAxis; - final DateFormat dateFormat = - _axis.dateFormat ?? _getDateTimeLabelFormat(_xAxisRenderer!); - date = dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(point.xValue)); - } else if (primaryAxisRenderer is DateTimeCategoryAxisRenderer) { - date = primaryAxisRenderer._dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); - } - _xAxisRenderer is CategoryAxisRenderer - ? regionData.add(point.x.toString()) - : _xAxisRenderer is DateTimeAxisRenderer || - _xAxisRenderer is DateTimeCategoryAxisRenderer - ? regionData.add(date.toString()) - : regionData.add(_getLabelValue( - point.xValue, - _xAxisRenderer!._axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - if (_seriesType.contains('range')) { - regionData.add(_getLabelValue(point.high, _yAxisRenderer!._axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - regionData.add(_getLabelValue(point.low, _yAxisRenderer!._axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - } else { - regionData.add(_getLabelValue(point.yValue, _yAxisRenderer!._axis, - chart.tooltipBehavior.decimalPlaces) - .toString()); - } - regionData.add(_series.name ?? 'series $seriesIndex'); - regionRect.add(point.region); - regionRect.add(_isRectSeries - ? _seriesType == 'column' || _seriesType.contains('stackedcolumn') - ? (point.yValue > 0) == true - ? point.region!.topCenter - : point.region!.bottomCenter - : point.region!.topCenter - : (_seriesType == 'rangearea' - ? Offset(point.markerPoint!.x, - (point.markerPoint!.y + point.markerPoint2!.y) / 2) - : point.region!.center)); - regionRect.add(point.pointColorMapper); - regionRect.add(point.bubbleSize); - if (_seriesType.contains('stacked')) { - regionData.add((point.cumulativeValue).toString()); - } - _regionalData![regionRect] = regionData; - } - } - - /// To calculate the empty point average mode value - void _calculateAverageModeValue( - int pointIndex, - int pointLength, - CartesianChartPoint currentPoint, - CartesianChartPoint prevPoint) { - final List dataSource = _series.dataSource; - final CartesianChartPoint _nextPoint = _getPointFromData( - this, - pointLength < dataSource.length - 1 - ? dataSource.indexOf(dataSource[pointLength + 1]) - : dataSource.indexOf(dataSource[pointLength])); - if (_seriesType.contains('range') || - _seriesType.contains('hilo') || - _seriesType.contains('candle')) { - final CartesianSeries _cartesianSeries = _series; - if (_cartesianSeries is _FinancialSeriesBase && - _cartesianSeries.showIndicationForSameValues) { - if (currentPoint.low != null || currentPoint.high != null) { - currentPoint.low = currentPoint.low ?? currentPoint.high; - currentPoint.high = currentPoint.high ?? currentPoint.low; - } else { - currentPoint.low = 0; - currentPoint.high = 0; - currentPoint.open = 0; - currentPoint.close = 0; - currentPoint.isGap = true; - } - if (_seriesType == 'hiloopenclose' || _seriesType == 'candle') { - if (currentPoint.open != null || currentPoint.close != null) { - currentPoint.open = currentPoint.open ?? currentPoint.close; - currentPoint.close = currentPoint.close ?? currentPoint.open; - } else { - currentPoint.low = 0; - currentPoint.high = 0; - currentPoint.open = 0; - currentPoint.close = 0; - currentPoint.isGap = true; - } - } - } else { - if (pointIndex == 0) { - if (currentPoint.low == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.low = 0 - : currentPoint.low = ((_nextPoint.low) ?? 0) / 2; - } - if (currentPoint.high == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.high = 0 - : currentPoint.high = ((_nextPoint.high) ?? 0) / 2; - } - if (_seriesType == 'hiloopenclose' || _seriesType == 'candle') { - if (currentPoint.open == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.open = 0 - : currentPoint.open = ((_nextPoint.open) ?? 0) / 2; - } - if (currentPoint.close == null) { - pointIndex == dataSource.length - 1 - ? currentPoint.close = 0 - : currentPoint.close = ((_nextPoint.close) ?? 0) / 2; - } - } - } else if (pointIndex == dataSource.length - 1) { - currentPoint.low = currentPoint.low ?? ((prevPoint.low) ?? 0) / 2; - currentPoint.high = currentPoint.high ?? ((prevPoint.high) ?? 0) / 2; - - if (_seriesType == 'hiloopenclose' || _seriesType == 'candle') { - currentPoint.open = - currentPoint.open ?? ((prevPoint.open) ?? 0) / 2; - currentPoint.close = - currentPoint.close ?? ((prevPoint.close) ?? 0) / 2; - } - } else { - currentPoint.low = currentPoint.low ?? - (((prevPoint.low) ?? 0) + ((_nextPoint.low) ?? 0)) / 2; - currentPoint.high = currentPoint.high ?? - (((prevPoint.high) ?? 0) + ((_nextPoint.high) ?? 0)) / 2; - - if (_seriesType == 'hiloopenclose' || _seriesType == 'candle') { - currentPoint.open = currentPoint.open ?? - (((prevPoint.open) ?? 0) + ((_nextPoint.open) ?? 0)) / 2; - currentPoint.close = currentPoint.close ?? - (((prevPoint.close) ?? 0) + ((_nextPoint.close) ?? 0)) / 2; - } - } - } - } else { - if (pointIndex == 0) { - ///Check the first point is null - pointIndex == dataSource.length - 1 - ? - - ///Check the series contains single point with null value - currentPoint.y = 0 - : currentPoint.y = ((_nextPoint.y) ?? 0) / 2; - } else if (pointIndex == dataSource.length - 1) { - ///Check the last point is null - currentPoint.y = ((prevPoint.y) ?? 0) / 2; - } else { - currentPoint.y = (((prevPoint.y) ?? 0) + ((_nextPoint.y) ?? 0)) / 2; - } - } - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/cartesian_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/cartesian_state_properties.dart new file mode 100644 index 000000000..9597902f7 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/cartesian_state_properties.dart @@ -0,0 +1,392 @@ +import 'dart:async'; +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; + +import '../../common/event_args.dart'; +import '../../common/rendering_details.dart'; +import '../../common/state_properties.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../axis/axis.dart'; +import '../axis/axis_panel.dart'; +import '../axis/axis_renderer.dart'; +import '../base/chart_base.dart'; +import '../base/series_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/series.dart'; +import '../common/common.dart'; +import '../common/interactive_tooltip.dart'; +import '../common/renderer.dart'; + +import '../series_painter/bar_painter.dart'; +import '../series_painter/column_painter.dart'; +import '../technical_indicators/technical_indicator.dart'; +import '../user_interaction/crosshair.dart'; +import '../user_interaction/trackball.dart'; +import '../user_interaction/trackball_marker_setting_renderer.dart'; +import '../user_interaction/zooming_panning.dart'; +import '../utils/enum.dart'; + +/// Represents the Cartesian state properties class +class CartesianStateProperties extends StateProperties { + /// Creates an instance of Cartesian state properties + CartesianStateProperties( + {required this.renderingDetails, required this.chartState}) + : super(renderingDetails, chartState); + + /// Holds the Cartesian chart state + @override + final SfCartesianChartState chartState; + + /// Specifies the chart rendering details + @override + late RenderingDetails renderingDetails; + + /// Holds the animation controller along with their listener for all series and trendlines + late Map controllerList; + + /// Holds the value of repaint notifiers + late Map> repaintNotifiers; + + /// Holds the value of zoom axis renderer state + late List zoomedAxisRendererStates; + + /// Holds the value of old axis renderer + late List oldAxisRenderers; + + /// Specifies whether the zoom is in progress + late bool zoomProgress; + + /// Specifies the list of zoom axes + late List zoomAxes; + + /// Specifies the value of selected segments + late List selectedSegments; + + /// Specifies the list of unselected segments + late List unselectedSegments; + + /// Holds the list of data label region + late List renderDatalabelRegions; + + /// Holds the list of annotation region + late List annotationRegions; + + /// Specifies whether the legend is refreshed + bool legendRefresh = false; + + /// Holds the data label renderer + DataLabelRenderer? renderDataLabel; + + /// Holds the value axis renderer outside + late CartesianAxisWidget renderOutsideAxis; + + /// Holds the value axis renderer inside + late CartesianAxisWidget renderInsideAxis; + + ///Holds the list of old series renderers + late List oldSeriesRenderers; + + /// Holds the list of old series keys + late List?> oldSeriesKeys; + + /// Holds the list of segments + late List segments; + + /// Holds the list of old visible series + late List oldSeriesVisible; + + /// Specifies the zoom state + bool? zoomedState; + + /// Holds the touch state position + late List touchStartPositions; + + /// Holds the touch move position + late List touchMovePositions; + + ///Specifies whether the double tap or mouse hover is enabled + late bool enableDoubleTap, enableMouseHover; + + /// Specifies whether legend is toggled + bool legendToggling = false; + + /// Specifies the value of background image + dart_ui.Image? backgroundImage; + + /// Specifies the value of legend icon image + dart_ui.Image? legendIconImage; + + /// Specifies whether the trendline is toggled + bool isTrendlineToggled = false; + + /// Specifies the list of painter keys + late List painterKeys; + + /// Specifies whether the trigger is loaded + late bool triggerLoaded; + + /// Specifies the range change by slider value + //ignore: preferfinalfields + bool rangeChangeBySlider = false; + + /// Specifies the value of range changed by chart + //ignore: preferfinalfields + bool rangeChangedByChart = false; + + /// Specifies whether the range is selected by slider + //ignore: preferfinalfields + bool isRangeSelectionSlider = false; + + /// Specifies whether the series is loaded + bool? isSeriesLoaded; + + /// Specifies whether update is needed + late bool isNeedUpdate; + + /// Specifies the list of series renderer + late List seriesRenderers; + + /// Holds the information of AxisBase class + late ChartAxisPanel chartAxis; + + /// Holds the information of SeriesBase class + late ChartSeriesPanel chartSeries; + + /// Holds the information of ContainerArea class + /// ignore: unusedfield + late ContainerArea containerArea; + + /// Whether to check chart axis is inverted or not + late bool requireInvertedAxis; + + /// To check if axis trimmed text is tapped + //ignore: preferfinalfields + bool requireAxisTooltip = false; + + /// To check the trackball orientation changes. + bool isTrackballOrientationChanged = false; + + /// To check the crosshair orientation changes. + bool isCrosshairOrientationChanged = false; + + /// Trackball timer. + Timer? trackballTimer; + + /// Tooltip timer. + Timer? tooltipTimer; + + /// To check the tooltip orientation changes. + bool isTooltipOrientationChanged = false; + + /// To check if tooltip has been hidden or not. + bool isTooltipHidden = false; + + /// Specifies the list of chart point info + //ignore: preferfinalfields + List chartPointInfo = []; + + /// Holds the zoom pan behavior renderer + late ZoomPanBehaviorRenderer zoomPanBehaviorRenderer; + + /// Holds the trackball behavior renderer + late TrackballBehaviorRenderer trackballBehaviorRenderer; + + /// Holds the cross hair behavior renderer + late CrosshairBehaviorRenderer crosshairBehaviorRenderer; + + /// Holds the technical indicator renderer + late List technicalIndicatorRenderer; + + /// Holds the trackball marker setting renderer + late TrackballMarkerSettingsRenderer trackballMarkerSettingsRenderer; + + //Here, we are using get keyword in order to get the proper & updated instance of chart widget + //When we initialize chart widget as a property to other classes like ChartSeries, the chart widget is not updated properly and by using get we can rectify this. + @override + SfCartesianChart get chart => chartState.widget; + + /// Setting series animation duration factor + double seriesDurationFactor = 0.85; + + /// Setting trendline animation duration factor + double trendlineDurationFactor = 0.85; + + /// holds the count for total no of series that should be animated + late int totalAnimatingSeries; + + /// holds the no of animation completed series + late int animationCompleteCount; + + /// Specifies the value of selection args + SelectionArgs? selectionArgs; + + /// Specifies whether the touch is up + bool isTouchUp = false; + + /// Holds the value of load more state setter + late StateSetter loadMoreViewStateSetter; + + /// Represents the swipe direction + late ChartSwipeDirection swipeDirection; + + /// Specifies the value of offset + Offset? startOffset, currentPosition; + + /// Specifies whether redrawn is due to zoom and pan + late bool isRedrawByZoomPan; + + /// Holds the value of pointer device kind + late PointerDeviceKind pointerDeviceKind; + + ///To check the load more widget is in progress or not + late bool isLoadMoreIndicator; + + /// Specifies whether to set the range controller + bool canSetRangeController = false; + + /// Specifies the shader for series + Shader? shader; + + /// Method to set the painter key + void setPainterKey(int index, String name, bool renderComplete) { + int value = 0; + for (int i = 0; i < painterKeys.length; i++) { + final PainterKey painterKey = painterKeys[i]; + if (painterKey.isRenderCompleted) { + value++; + } else if (painterKey.index == index && + painterKey.name == name && + !painterKey.isRenderCompleted) { + painterKey.isRenderCompleted = renderComplete; + value++; + } + if (value >= painterKeys.length && !triggerLoaded) { + triggerLoaded = true; + } + } + } + + /// Method to check whether the animation is completed + // ignore: unused_element + bool get animationCompleted { + SeriesRendererDetails seriesRendererDetails; + for (final CartesianSeriesRenderer seriesRenderer in seriesRenderers) { + seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.animationController.status == + AnimationStatus.forward) { + return false; + } + } + return true; + } + + /// Method to repaint the trendlines + void repaintTrendlines() { + repaintNotifiers['trendline']!.value++; + } + + /// Method to forward the animation + void forwardAnimation(SeriesRendererDetails seriesRendererDetails) { + final int totalAnimationDuration = + seriesRendererDetails.series.animationDuration.toInt() + + seriesRendererDetails.series.animationDelay!.toInt(); + seriesRendererDetails.animationController.duration = + Duration(milliseconds: totalAnimationDuration); + const double maxSeriesInterval = 0.8; + double minSeriesInterval = 0.1; + minSeriesInterval = seriesRendererDetails.series.animationDelay!.toInt() / + totalAnimationDuration * + (maxSeriesInterval - minSeriesInterval) + + minSeriesInterval; + seriesRendererDetails.seriesAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: seriesRendererDetails.animationController, + curve: Interval(minSeriesInterval, maxSeriesInterval, + curve: Curves.decelerate), + )); + seriesRendererDetails.seriesElementAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: seriesRendererDetails.animationController, + curve: const Interval(0.85, 1.0, curve: Curves.decelerate), + )); + seriesRendererDetails.animationController.forward(from: 0.0); + } + + /// Method to redraw by change + void redrawByRangeChange() { + oldAxisRenderers = chartAxis.axisRenderersCollection; + if (chartState.mounted) { + // ignore: invalid_use_of_protected_member + chartState.setState(() { + /// check the "mounted" property of this object and to ensure the object is still in the tree. + /// When we do the range change by using the slider or other way, chart will be rebuilding again. + }); + } + } + + /// Redraw method for chart axis + void redraw() { + oldAxisRenderers = chartAxis.axisRenderersCollection; + TooltipHelper.getRenderingDetails(renderingDetails.tooltipBehaviorRenderer) + .timer + ?.cancel(); + if (TrackballHelper.getRenderingDetails(trackballBehaviorRenderer) + .trackballPainter + ?.timer != + null) { + TrackballHelper.getRenderingDetails(trackballBehaviorRenderer) + .trackballPainter + ?.timer! + .cancel(); + } + if (renderingDetails.isLegendToggled) { + segments = []; + oldSeriesVisible = + List.filled(chartSeries.visibleSeriesRenderers.length, null); + for (int i = 0; i < chartSeries.visibleSeriesRenderers.length; i++) { + final CartesianSeriesRenderer seriesRenderer = + chartSeries.visibleSeriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRenderer is ColumnSeriesRenderer || + seriesRenderer is BarSeriesRenderer) { + for (int j = 0; j < seriesRendererDetails.segments.length; j++) { + segments.add(seriesRendererDetails.segments[j]); + } + } + } + } + // ignore: unnecessary_null_comparison + if (zoomedAxisRendererStates != null && + zoomedAxisRendererStates.isNotEmpty) { + zoomedState = false; + for (final ChartAxisRenderer axisRenderer in zoomedAxisRendererStates) { + zoomedState = + AxisHelper.getAxisRendererDetails(axisRenderer).zoomFactor != 1; + if (zoomedState!) { + break; + } + } + } + + renderingDetails.widgetNeedUpdate = false; + + if (chartState.mounted) { + isRedrawByZoomPan = true; + // ignore: invalid_use_of_protected_member + chartState.setState(() { + /// check the "mounted" property of this object and to ensure the object is still in the tree. + /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. + }); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart index 2625d88dc..0c96cc577 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/common.dart @@ -1,614 +1,163 @@ -part of charts; - -class _CustomPaintStyle { - _CustomPaintStyle(this.strokeWidth, this.color, this.paintStyle); +import 'dart:async'; +import 'dart:math' as math; +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/axis/datetime_axis.dart' + show DateTimeAxisDetails; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../common/event_args.dart'; +import '../../common/rendering_details.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../../common/utils/typedef.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/scatter_segment.dart'; +import '../chart_series/error_bar_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/waterfall_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../series_painter/box_and_whisker_painter.dart'; +import '../series_painter/stacked_line_painter.dart'; +import '../user_interaction/trackball.dart'; +import '../utils/enum.dart' + show ErrorBarType, RenderingMode, Direction, DateTimeIntervalType; +import '../utils/helper.dart'; + +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; + +/// Represents the custom paint style class +class CustomPaintStyle { + /// Creates an instance of custom paint class + CustomPaintStyle(this.strokeWidth, this.color, this.paintStyle); + + /// Specifies the color value Color color; + + /// Specifies the value of stroke width double strokeWidth; + + /// Specifies the value of painting style PaintingStyle paintStyle; } -class _AxisSize { - _AxisSize(this.axisRenderer, this.size); +/// Represents the axis size class +class AxisSize { + /// Creates an instance of axis size class + AxisSize(this.axisRenderer, this.size); + + /// Holds the value of size double size; + + /// Holds the value of axis renderer ChartAxisRenderer axisRenderer; } -class _PainterKey { - _PainterKey( +/// Represents the painter key +class PainterKey { + /// Creates an instance for painter key + PainterKey( {required this.index, required this.name, required this.isRenderCompleted}); + + /// Represents the value of index final int index; + + /// Specifies the key name final String name; + + /// Specifies whether the rendering is completed bool isRenderCompleted; } -/// Customizes the interactive tooltip. -/// -///Shows the information about the segments.To enable the interactiveToolTip, set the property to true. -/// -/// By using this,to customize the [color], [borderWidth], [borderRadius], -/// [format] and so on. -/// -/// _Note:_ IntereactivetoolTip applicable for axis types and trackball. - -@immutable -class InteractiveTooltip { - /// Creating an argument constructor of InteractiveTooltip class. - const InteractiveTooltip( - {this.enable = true, - this.color, - this.borderColor, - this.borderWidth = 0, - this.borderRadius = 5, - this.arrowLength = 7, - this.arrowWidth = 5, - this.format, - this.connectorLineColor, - this.connectorLineWidth = 1.5, - this.connectorLineDashArray, - this.decimalPlaces = 3, - this.canShowMarker = true, - this.textStyle = const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12)}); - - ///Toggles the visibility of the interactive tooltip in an axis. - /// - /// This tooltip will be displayed at the axis for crosshair and - /// will be displayed near to the trackline for trackball. - /// - ///Defaults to `true`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip(enable:false)), - /// )); - ///} - ///``` - final bool enable; - - ///Color of the interactive tooltip. - /// - ///Used to change the [color] of the tooltip text. - /// - ///Defaults to `null`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// color:Colors.grey)), - /// )); - ///} - ///``` - final Color? color; - - ///Border color of the interactive tooltip. - /// - ///Used to change the stroke color of the axis tooltip. - /// - ///Defaults to `Colors.black`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// borderColor:Colors.white, - /// borderWidth:2)), - /// )); - ///} - ///``` - final Color? borderColor; - - ///Border width of the interactive tooltip. - /// - ///Used to change the stroke width of the axis tooltip. - /// - ///Defaults to `0`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// borderColor:Colors.white, - /// borderWidth:2)), - /// )); - ///} - ///``` - final double borderWidth; - - ///Customizes the text in the interactive tooltip. - /// - ///Used to change the text color, size, font family, fontStyle, and font weight. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// textStyle: TextStyle(color:Colors.red))), - /// )); - ///} - ///``` - final TextStyle textStyle; - - ///Customizes the corners of the interactive tooltip. - /// - ///Each corner can be customized with a desired value or a single value. - /// - ///Defaults to `Radius.zero`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// borderColor:Colors.white, - /// borderWidth:3, - /// borderRadius:2)), - /// )); - ///} - ///``` - final double borderRadius; - - ///It Specifies the length of the tooltip. - /// - ///Defaults to `7`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// arrowLength:4)), - /// )); - ///} - ///``` - final double arrowLength; - - ///It specifies the width of the tooltip arrow. - /// - ///Defaults to `5`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// arrowWidth:4)), - /// )); - ///} - ///``` - final double arrowWidth; - - ///Text format of the interactive tooltip. - /// - /// By default, axis value will be displayed in the tooltip, and it can be customized by - /// adding desired text as prefix or suffix. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior(enable: true, - /// tooltipSettings: InteractiveTooltip( - /// format:'point.x %')), - /// )); - ///} - ///``` - final String? format; - - ///Width of the selection zooming tooltip connector line. - /// - ///Defaults to `1.5`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(interactiveTooltip: - /// InteractiveTooltip(connectorLineWidth:2)), - /// )); - ///} - ///``` - final double connectorLineWidth; - - ///Color of the selection zooming tooltip connector line. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// interactiveTooltip: InteractiveTooltip(connectorLineColor:Colors.red)), - /// )); - ///} - ///``` - final Color? connectorLineColor; - - ///Giving dashArray to the selection zooming tooltip connector line. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis( - /// interactiveTooltip: InteractiveTooltip(connectorLineDashArray:[2,3])), - /// )); - ///} - ///``` - final List? connectorLineDashArray; - - ///Rounding decimal places of the selection zooming tooltip or trackball tooltip label. - /// - ///Defaults to `3`. - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(interactiveTooltip: InteractiveTooltip(decimalPlaces:4)), - /// )); - ///} - ///``` - final int decimalPlaces; - - ///Toggles the visibility of the marker in the trackball tooltip. - /// - ///Markers are rendered with the series color and placed near the value in trackball - ///tooltip to convey which value belongs to which series. - /// - ///Trackball tooltip marker uses the same shape specified for the series marker. But - ///trackball tooltip marker will render based on the value specified to this property - ///irrespective of considering the series marker's visibility. - /// - /// Defaults to `true` - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: NumericAxis(interactiveTooltip: InteractiveTooltip(canSHowMarker:true)), - /// )); - ///} - ///``` - final bool canShowMarker; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - - return other is InteractiveTooltip && - other.enable == enable && - other.color == color && - other.borderColor == borderColor && - other.borderWidth == borderWidth && - other.borderRadius == borderRadius && - other.arrowLength == arrowLength && - other.arrowWidth == arrowWidth && - other.format == format && - other.connectorLineColor == connectorLineColor && - other.connectorLineWidth == connectorLineWidth && - other.connectorLineDashArray == connectorLineDashArray && - other.decimalPlaces == decimalPlaces && - other.canShowMarker == canShowMarker && - other.textStyle == textStyle; - } - - @override - int get hashCode { - final List values = [ - enable, - color, - borderColor, - borderWidth, - borderRadius, - arrowLength, - arrowWidth, - format, - connectorLineColor, - connectorLineWidth, - connectorLineDashArray, - decimalPlaces, - canShowMarker, - textStyle - ]; - return hashList(values); - } -} +/// Represents the class of stacked values +class StackedValues { + /// Creates the instance of stacked values + StackedValues(this.startValues, this.endValues); -class _StackedValues { - _StackedValues(this.startValues, this.endValues); + /// Represents the start value List startValues; - List endValues; -} - -class _ClusterStackedItemInfo { - _ClusterStackedItemInfo(this.stackName, this.stackedItemInfo); - String stackName; - List<_StackedItemInfo> stackedItemInfo; -} - -class _StackedItemInfo { - _StackedItemInfo(this.seriesIndex, this.seriesRenderer); - int seriesIndex; - _StackedSeriesRenderer seriesRenderer; -} - -class _ChartPointInfo { - /// Marker x position - double? markerXPos; - - /// Marker y position - double? markerYPos; - - /// label for trackball and cross hair - String? label; - - /// Data point index - int? dataPointIndex; - - /// Instance of chart series - CartesianSeries? series; - - /// Instance of cartesian seriesRenderer - CartesianSeriesRenderer? seriesRenderer; - - /// Chart data point - CartesianChartPoint? chartDataPoint; - /// X position of the label - double? xPosition; - - /// Y position of the label - double? yPosition; - - /// Color of the segment - Color? color; - - /// header text - String? header; - - /// Low Y position of financial series - double? lowYPosition; - - /// High X position of financial series - double? highXPosition; - - /// High Y position of financial series - double? highYPosition; - - /// Open y position of financial series - double? openYPosition; - - /// close y position of financial series - double? closeYPosition; - - /// open x position of finanical series - double? openXPosition; - - /// close x position of finanical series - double? closeXPosition; - - /// Minimum Y position of box plot series - double? minYPosition; - - /// Maximum Y position of box plot series - double? maxYPosition; - - /// Lower y position of box plot series - double? lowerXPosition; - - /// Upper y position of box plot series - double? upperXPosition; - - /// Lower x position of box plot series - double? lowerYPosition; - - /// Upper x position of box plot series - double? upperYPosition; - - // Maximum x position forr vox plot series - double? maxXPosition; - - /// series index value - late int seriesIndex; + /// Represents the end values + List endValues; } -///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, - double? height, - double? width, - Color? color, - DataMarkerType? shape, - double? borderWidth, - Color? borderColor, - ImageProvider? image}) - : super( - height: height, - width: width, - color: color, - shape: shape, - borderWidth: borderWidth, - borderColor: borderColor, - image: image); - - ///Whether marker should be visible or not when trackball is enabled. - /// - ///The below values are applicable for this: - ///* 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. - ///* visible - Makes the trackball marker visible for all the series, - /// irrespective of considering the [isVisible] property's value in the `markerSettings`. - ///* hidden - Hides the trackball marker for all the series. - /// - ///Defaults to `TrackballVisibilityMode.auto`. - - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// trackballBehavior: TrackballBehavior( - /// enable: true, - /// markerSettings: TrackballMarkerSettings( - /// markerVisibility: TrackballVisibilityMode.auto, - /// width: 10 - /// ), - /// series: >[ - /// SplineSeries( - /// markerSettings: MarkerSettings(isVisible: true), - /// ), - /// ], - /// ); - ///} - ///``` - final TrackballVisibilityMode markerVisibility; - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } +/// Represents the cluster stacked item info class +class ClusterStackedItemInfo { + /// Creates an instance of cluster stacked info + ClusterStackedItemInfo(this.stackName, this.stackedItemInfo); - 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; - } + /// Holds the stack name value + String stackName; - @override - int get hashCode { - final List values = [ - markerVisibility, - height, - width, - color, - shape, - borderWidth, - borderColor, - image - ]; - return hashList(values); - } + /// Holds the list of stacked item info + List stackedItemInfo; } -///Options to show the details of the trackball template. -@immutable -class TrackballDetails { - ///Constructor of TrackballDetails class. - const TrackballDetails( - [this.point, - this.series, - this.pointIndex, - this.seriesIndex, - this.groupingModeInfo]); - - /// It specifies the cartesian chart point. - final CartesianChartPoint? point; - - /// It specifies the cartesian series. - final CartesianSeries? series; - - /// It specifies the point index. - final int? pointIndex; - - /// It specifies the series index. - final int? seriesIndex; - - /// It specifies the trackball grouping mode info. - final TrackballGroupingModeInfo? groupingModeInfo; - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } +/// Represents the stacked item info class +class StackedItemInfo { + /// Creates an instance of stacked item info + StackedItemInfo(this.seriesIndex, this.seriesRenderer); - return other is TrackballDetails && - other.point == point && - other.series == series && - other.pointIndex == pointIndex && - other.seriesIndex == seriesIndex && - other.groupingModeInfo == groupingModeInfo; - } + /// Specifies the value of series index + int seriesIndex; - @override - int get hashCode { - final List values = [ - point, - series, - pointIndex, - seriesIndex, - groupingModeInfo - ]; - return hashList(values); - } + /// Specifies the value of stacked series renderer + StackedSeriesRenderer seriesRenderer; } -/// To get cartesian type data label saturation color. -Color _getDataLabelSaturationColor( +/// To get Cartesian type data label saturation color. +Color getDataLabelSaturationColor( CartesianChartPoint currentPoint, - CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, DataLabelSettingsRenderer dataLabelSettingsRenderer) { - final SfCartesianChart chart = _chartState._chart; + final SfCartesianChart chart = stateProperties.chart; Color color; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; - final _RenderingDetails renderingDetails = _chartState._renderingDetails; + final DataLabelSettings dataLabel = + seriesRendererDetails.series.dataLabelSettings; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final ChartDataLabelAlignment labelPosition = - (seriesRenderer._seriesType == 'rangecolumn' && + (seriesRendererDetails.seriesType == 'rangecolumn' && (dataLabel.labelAlignment == ChartDataLabelAlignment.bottom || dataLabel.labelAlignment == ChartDataLabelAlignment.middle)) ? ChartDataLabelAlignment.auto : dataLabel.labelAlignment; final ChartAlignment alignment = dataLabel.alignment; - final String seriesType = seriesRenderer._seriesType == 'line' || - seriesRenderer._seriesType == 'stackedline' || - seriesRenderer._seriesType == 'stackedline100' || - seriesRenderer._seriesType == 'spline' || - seriesRenderer._seriesType == 'stepline' + final String seriesType = seriesRendererDetails.seriesType == 'line' || + seriesRendererDetails.seriesType == 'stackedline' || + seriesRendererDetails.seriesType == 'stackedline100' || + seriesRendererDetails.seriesType == 'spline' || + seriesRendererDetails.seriesType == 'stepline' ? 'Line' - : seriesRenderer._isRectSeries + : seriesRendererDetails.isRectSeries == true ? 'Column' - : seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType == 'scatter' + : seriesRendererDetails.seriesType == 'bubble' || + seriesRendererDetails.seriesType == 'scatter' ? 'Circle' - : seriesRenderer._seriesType.contains('area') + : seriesRendererDetails.seriesType.contains('area') == true ? 'area' : 'Default'; - if (dataLabel.useSeriesColor || dataLabelSettingsRenderer._color != null) { - color = dataLabelSettingsRenderer._color ?? - (currentPoint.pointColorMapper ?? seriesRenderer._seriesColor!); + if (dataLabel.useSeriesColor || dataLabelSettingsRenderer.color != null) { + color = dataLabelSettingsRenderer.color ?? + (currentPoint.pointColorMapper ?? seriesRendererDetails.seriesColor!); } else { switch (seriesType) { case 'Line': @@ -620,17 +169,19 @@ Color _getDataLabelSaturationColor( (labelPosition == ChartDataLabelAlignment.outer || (labelPosition == ChartDataLabelAlignment.top && alignment == ChartAlignment.far) || - seriesRenderer._seriesType == 'rangecolumn' && + seriesRendererDetails.seriesType == 'rangecolumn' && (labelPosition == ChartDataLabelAlignment.top && alignment == ChartAlignment.near) || (labelPosition == ChartDataLabelAlignment.auto && - (!seriesRenderer._seriesType.contains('100') && - seriesRenderer._seriesType != 'stackedbar' && - seriesRenderer._seriesType != 'stackedcolumn')))) + (seriesRendererDetails.seriesType.contains('100') == + false && + seriesRendererDetails.seriesType != 'stackedbar' && + seriesRendererDetails.seriesType != + 'stackedcolumn')))) ? _getOuterDataLabelColor(dataLabelSettingsRenderer, chart.plotAreaBackgroundColor, renderingDetails.chartTheme) - : _getInnerDataLabelColor( - currentPoint, seriesRenderer, renderingDetails.chartTheme); + : _getInnerDataLabelColor(currentPoint, seriesRendererDetails, + renderingDetails.chartTheme); break; case 'Circle': color = (labelPosition == ChartDataLabelAlignment.middle && @@ -641,8 +192,8 @@ Color _getDataLabelSaturationColor( alignment == ChartAlignment.near || labelPosition == ChartDataLabelAlignment.outer && alignment == ChartAlignment.near) - ? _getInnerDataLabelColor( - currentPoint, seriesRenderer, renderingDetails.chartTheme) + ? _getInnerDataLabelColor(currentPoint, seriesRendererDetails, + renderingDetails.chartTheme) : _getOuterDataLabelColor(dataLabelSettingsRenderer, chart.plotAreaBackgroundColor, renderingDetails.chartTheme); break; @@ -652,29 +203,30 @@ Color _getDataLabelSaturationColor( currentPoint.labelLocation!.y < currentPoint.markerPoint!.y) ? _getOuterDataLabelColor(dataLabelSettingsRenderer, chart.plotAreaBackgroundColor, renderingDetails.chartTheme) - : _getInnerDataLabelColor( - currentPoint, seriesRenderer, renderingDetails.chartTheme); + : _getInnerDataLabelColor(currentPoint, seriesRendererDetails, + renderingDetails.chartTheme); break; default: color = Colors.white; } } - return _getSaturationColor(color); + return getSaturationColor(color); } /// Get the data label color of open-close series -Color _getOpenCloseDataLabelColor(CartesianChartPoint currentPoint, - CartesianSeriesRenderer seriesRenderer, SfCartesianChart chart) { - final Color color = seriesRenderer - ._segments[seriesRenderer._dataPoints.indexOf(currentPoint)] +Color getOpenCloseDataLabelColor(CartesianChartPoint currentPoint, + SeriesRendererDetails seriesRendererDetails, SfCartesianChart chart) { + final Color color = seriesRendererDetails + .segments[seriesRendererDetails.dataPoints.indexOf(currentPoint)] .fillPaint! .style == PaintingStyle.fill - ? seriesRenderer - ._segments[seriesRenderer._dataPoints.indexOf(currentPoint)]._color! + ? SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[seriesRendererDetails.dataPoints.indexOf(currentPoint)]) + .color! : const Color.fromRGBO(255, 255, 255, 1); - return _getSaturationColor(color); + return getSaturationColor(color); } /// To get outer data label color @@ -682,7 +234,7 @@ Color _getOuterDataLabelColor( DataLabelSettingsRenderer dataLabelSettingsRenderer, Color? backgroundColor, SfChartThemeData theme) => - dataLabelSettingsRenderer._color ?? + dataLabelSettingsRenderer.color ?? backgroundColor ?? (theme.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) @@ -690,20 +242,20 @@ Color _getOuterDataLabelColor( ///To get inner data label Color _getInnerDataLabelColor(CartesianChartPoint currentPoint, - CartesianSeriesRenderer seriesRenderer, SfChartThemeData theme) { + SeriesRendererDetails seriesRendererDetails, SfChartThemeData theme) { final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; + seriesRendererDetails.dataLabelSettingsRenderer; Color innerColor; - Color? seriesColor = seriesRenderer._series.color; - if (seriesRenderer._seriesType == 'waterfall') { - seriesColor = _getWaterfallSeriesColor( - seriesRenderer._series as WaterfallSeries, + Color? seriesColor = seriesRendererDetails.series.color; + if (seriesRendererDetails.seriesType == 'waterfall') { + seriesColor = getWaterfallSeriesColor( + seriesRendererDetails.series as WaterfallSeries, currentPoint, seriesColor); } // ignore: prefer_if_null_operators - innerColor = dataLabelSettingsRenderer._color != null - ? dataLabelSettingsRenderer._color! + innerColor = dataLabelSettingsRenderer.color != null + ? dataLabelSettingsRenderer.color! // ignore: prefer_if_null_operators : currentPoint.pointColorMapper != null // ignore: prefer_if_null_operators @@ -712,8 +264,8 @@ Color _getInnerDataLabelColor(CartesianChartPoint currentPoint, : seriesColor != null ? seriesColor // ignore: prefer_if_null_operators - : seriesRenderer._seriesColor != null - ? seriesRenderer._seriesColor! + : seriesRendererDetails.seriesColor != null + ? seriesRendererDetails.seriesColor! : theme.brightness == Brightness.light ? const Color.fromRGBO(255, 255, 255, 1) : Colors.black; @@ -721,7 +273,7 @@ Color _getInnerDataLabelColor(CartesianChartPoint currentPoint, } /// To animate column and bar series -void _animateRectSeries( +void animateRectSeries( Canvas canvas, CartesianSeriesRenderer seriesRenderer, Paint fillPaint, @@ -731,23 +283,28 @@ void _animateRectSeries( Rect? oldSegmentRect, num? oldYValue, bool? oldSeriesVisible) { - final bool comparePrev = seriesRenderer._renderingDetails!.widgetNeedUpdate && - oldYValue != null && - !seriesRenderer._reAnimate && - oldSegmentRect != null; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final bool comparePrev = + seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == + true && + oldYValue != null && + seriesRendererDetails.reAnimate == false && + oldSegmentRect != null; final bool isLargePrev = oldYValue != null && oldYValue > yPoint; final bool isSingleSeries = - seriesRenderer._renderingDetails!.isLegendToggled && - _checkSingleSeries(seriesRenderer); - ((seriesRenderer._seriesType == 'column' && - seriesRenderer._chart.isTransposed) || - (seriesRenderer._seriesType == 'bar' && - !seriesRenderer._chart.isTransposed) || - (seriesRenderer._seriesType == 'histogram' && - seriesRenderer._chart.isTransposed)) + seriesRendererDetails.stateProperties.renderingDetails.isLegendToggled == + true && + _checkSingleSeries(seriesRendererDetails); + ((seriesRendererDetails.seriesType == 'column' && + seriesRendererDetails.chart.isTransposed == true) || + (seriesRendererDetails.seriesType == 'bar' && + seriesRendererDetails.chart.isTransposed == false) || + (seriesRendererDetails.seriesType == 'histogram' && + seriesRendererDetails.chart.isTransposed == true)) ? _animateTransposedRectSeries( canvas, - seriesRenderer, + seriesRendererDetails, fillPaint, segmentRect, yPoint, @@ -760,7 +317,7 @@ void _animateRectSeries( oldYValue) : _animateNormalRectSeries( canvas, - seriesRenderer, + seriesRendererDetails, fillPaint, segmentRect, yPoint, @@ -776,7 +333,7 @@ void _animateRectSeries( /// To animate transposed bar and column series void _animateTransposedRectSeries( Canvas canvas, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, Paint fillPaint, RRect segmentRect, num yPoint, @@ -793,25 +350,29 @@ void _animateTransposedRectSeries( double left = segmentRect.left, right = segmentRect.right; Rect? rect; const num defaultCrossesAtValue = 0; - final num crossesAt = - _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState!) ?? - defaultCrossesAtValue; - seriesRenderer._needAnimateSeriesElements = - seriesRenderer._needAnimateSeriesElements || + final num crossesAt = getCrossesAtValue(seriesRendererDetails.renderer, + seriesRendererDetails.stateProperties) ?? + defaultCrossesAtValue; + seriesRendererDetails.needAnimateSeriesElements = + seriesRendererDetails.needAnimateSeriesElements == true || segmentRect.outerRect != oldSegmentRect; - if (!seriesRenderer._renderingDetails!.isLegendToggled || - seriesRenderer._reAnimate) { + if (seriesRendererDetails.stateProperties.renderingDetails.isLegendToggled == + false || + seriesRendererDetails.reAnimate == true) { width = segmentRect.width * ((!comparePrev && - !seriesRenderer._reAnimate && - !seriesRenderer._renderingDetails!.initialRender! && + seriesRendererDetails.reAnimate == false && + seriesRendererDetails + .stateProperties.renderingDetails.initialRender! == + false && oldSegmentRect == null && - seriesRenderer._series.key != null && - seriesRenderer._chartState!._oldSeriesKeys - .contains(seriesRenderer._series.key)) + seriesRendererDetails.series.key != null && + seriesRendererDetails.stateProperties.oldSeriesKeys + .contains(seriesRendererDetails.series.key) == + true) ? 1 : animationFactor); - if (!seriesRenderer._yAxisRenderer!._axis.isInversed) { + if (seriesRendererDetails.yAxisDetails!.axis.isInversed == false) { if (comparePrev) { if (yPoint < crossesAt) { left = isLargePrev @@ -889,10 +450,17 @@ void _animateTransposedRectSeries( } } rect = Rect.fromLTWH(right - width, top, width, height); - } else if (seriesRenderer._renderingDetails!.isLegendToggled && + } else if (seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + true && oldSegmentRect != null) { - rect = _performTransposedLegendToggleAnimation(seriesRenderer, segmentRect, - oldSegmentRect, oldSeriesVisible, isSingleSeries, animationFactor); + rect = _performTransposedLegendToggleAnimation( + seriesRendererDetails, + segmentRect, + oldSegmentRect, + oldSeriesVisible, + isSingleSeries, + animationFactor); } canvas.drawRRect( @@ -906,13 +474,13 @@ void _animateTransposedRectSeries( /// To perform legend toggled animation on transposed chart Rect _performTransposedLegendToggleAnimation( - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, RRect segmentRect, Rect? oldSegmentRect, bool? oldSeriesVisible, bool isSingleSeries, double animationFactor) { - final CartesianSeries series = seriesRenderer._series; + final CartesianSeries series = seriesRendererDetails.series; num bottom; double height = segmentRect.height; double top = segmentRect.top; @@ -931,13 +499,13 @@ Rect _performTransposedLegendToggleAnimation( (animationFactor * (segmentRect.top - oldSegmentRect.top)); height = bottom - top; } else { - if (series == seriesRenderer._chart.series[0] && !isSingleSeries) { + if (series == seriesRendererDetails.chart.series[0] && !isSingleSeries) { bottom = segmentRect.bottom; top = bottom - (segmentRect.height * animationFactor); height = bottom - top; } else if (series == - seriesRenderer - ._chart.series[seriesRenderer._chart.series.length - 1] && + seriesRendererDetails + .chart.series[seriesRendererDetails.chart.series.length - 1] && !isSingleSeries) { top = segmentRect.top; height = segmentRect.height * animationFactor; @@ -953,7 +521,7 @@ Rect _performTransposedLegendToggleAnimation( /// Rect animation for bar and column series void _animateNormalRectSeries( Canvas canvas, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, Paint fillPaint, RRect segmentRect, num yPoint, @@ -970,25 +538,29 @@ void _animateNormalRectSeries( double top = segmentRect.top, bottom; Rect? rect; const num defaultCrossesAtValue = 0; - final num crossesAt = - _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState!) ?? - defaultCrossesAtValue; - seriesRenderer._needAnimateSeriesElements = - seriesRenderer._needAnimateSeriesElements || + final num crossesAt = getCrossesAtValue(seriesRendererDetails.renderer, + seriesRendererDetails.stateProperties) ?? + defaultCrossesAtValue; + seriesRendererDetails.needAnimateSeriesElements = + seriesRendererDetails.needAnimateSeriesElements == true || segmentRect.outerRect != oldSegmentRect; - if (!seriesRenderer._renderingDetails!.isLegendToggled || - seriesRenderer._reAnimate) { + if (seriesRendererDetails.stateProperties.renderingDetails.isLegendToggled == + false || + seriesRendererDetails.reAnimate == true) { height = segmentRect.height * ((!comparePrev && - !seriesRenderer._reAnimate && - !seriesRenderer._renderingDetails!.initialRender! && + seriesRendererDetails.reAnimate == false && + seriesRendererDetails + .stateProperties.renderingDetails.initialRender! == + false && oldSegmentRect == null && - seriesRenderer._series.key != null && - seriesRenderer._chartState!._oldSeriesKeys - .contains(seriesRenderer._series.key)) + seriesRendererDetails.series.key != null && + seriesRendererDetails.stateProperties.oldSeriesKeys + .contains(seriesRendererDetails.series.key) == + true) ? 1 : animationFactor); - if (!seriesRenderer._yAxisRenderer!._axis.isInversed) { + if (seriesRendererDetails.yAxisDetails!.axis.isInversed == false) { if (comparePrev) { if (yPoint < crossesAt) { bottom = isLargePrev @@ -1064,10 +636,12 @@ void _animateNormalRectSeries( } } rect = Rect.fromLTWH(left, top, width, height); - } else if (seriesRenderer._renderingDetails!.isLegendToggled && + } else if (seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + true && oldSegmentRect != null && oldSeriesVisible != null) { - rect = _performLegendToggleAnimation(seriesRenderer, segmentRect, + rect = _performLegendToggleAnimation(seriesRendererDetails, segmentRect, oldSegmentRect, oldSeriesVisible, isSingleSeries, animationFactor); } canvas.drawRRect( @@ -1081,14 +655,14 @@ void _animateNormalRectSeries( /// Perform legend toggle animation Rect _performLegendToggleAnimation( - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, RRect segmentRect, Rect oldSegmentRect, bool oldSeriesVisible, bool isSingleSeries, double animationFactor) { num right; - final CartesianSeries series = seriesRenderer._series; + final CartesianSeries series = seriesRendererDetails.series; final double height = segmentRect.height; double width = segmentRect.width; double left = segmentRect.left; @@ -1106,12 +680,12 @@ Rect _performLegendToggleAnimation( (animationFactor * (segmentRect.left - oldSegmentRect.left)); width = right - left; } else { - if (series == seriesRenderer._chart.series[0] && !isSingleSeries) { + if (series == seriesRendererDetails.chart.series[0] && !isSingleSeries) { left = segmentRect.left; width = segmentRect.width * animationFactor; } else if (series == - seriesRenderer - ._chart.series[seriesRenderer._chart.series.length - 1] && + seriesRendererDetails + .chart.series[seriesRendererDetails.chart.series.length - 1] && !isSingleSeries) { right = segmentRect.right; left = right - (segmentRect.width * animationFactor); @@ -1125,50 +699,53 @@ Rect _performLegendToggleAnimation( } /// To animation rect for stacked column series -void _animateStackedRectSeries( +void animateStackedRectSeries( Canvas canvas, RRect segmentRect, Paint fillPaint, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, double animationFactor, CartesianChartPoint currentPoint, - SfCartesianChartState _chartState) { + CartesianStateProperties stateProperties) { int index, seriesIndex; List seriesCollection; Rect? prevRegion; - final _StackedSeriesBase series = - seriesRenderer._series as _StackedSeriesBase; - index = seriesRenderer._dataPoints.indexOf(currentPoint); - seriesCollection = _findSeriesCollection(_chartState); - seriesIndex = seriesCollection.indexOf(seriesRenderer); + final StackedSeriesBase series = + seriesRendererDetails.series as StackedSeriesBase; + index = seriesRendererDetails.dataPoints.indexOf(currentPoint); + seriesCollection = findSeriesCollection(stateProperties); + seriesIndex = seriesCollection.indexOf(seriesRendererDetails.renderer); bool isStackLine = false; if (seriesIndex != 0) { - if (seriesRenderer._chartState!._chartSeries + if (seriesRendererDetails.stateProperties.chartSeries .visibleSeriesRenderers[seriesIndex - 1] is StackedLineSeriesRenderer || - seriesRenderer._chartState!._chartSeries + seriesRendererDetails.stateProperties.chartSeries .visibleSeriesRenderers[seriesIndex - 1] is StackedLine100SeriesRenderer) { isStackLine = true; } if (!isStackLine) { for (int j = 0; - j < _chartState._chartSeries.clusterStackedItemInfo.length; + j < stateProperties.chartSeries.clusterStackedItemInfo.length; j++) { - final _ClusterStackedItemInfo clusterStackedItemInfo = - _chartState._chartSeries.clusterStackedItemInfo[j]; + final ClusterStackedItemInfo clusterStackedItemInfo = + stateProperties.chartSeries.clusterStackedItemInfo[j]; if (clusterStackedItemInfo.stackName == series.groupName) { if (clusterStackedItemInfo.stackedItemInfo.length >= 2) { for (int k = 0; k < clusterStackedItemInfo.stackedItemInfo.length; k++) { - final _StackedItemInfo stackedItemInfo = + final StackedItemInfo stackedItemInfo = clusterStackedItemInfo.stackedItemInfo[k]; if (stackedItemInfo.seriesIndex == seriesIndex && k != 0 && index < - clusterStackedItemInfo.stackedItemInfo[k - 1] - .seriesRenderer._dataPoints.length) { + SeriesHelper.getSeriesRendererDetails( + clusterStackedItemInfo + .stackedItemInfo[k - 1].seriesRenderer) + .dataPoints + .length) { prevRegion = _getPrevRegion( currentPoint.yValue, clusterStackedItemInfo, index, k); } @@ -1178,8 +755,8 @@ void _animateStackedRectSeries( } } } - _drawAnimatedStackedRect(canvas, segmentRect, fillPaint, seriesRenderer, - animationFactor, currentPoint, index, prevRegion); + _drawAnimatedStackedRect(canvas, segmentRect, fillPaint, + seriesRendererDetails, animationFactor, currentPoint, index, prevRegion); } /// To draw the animated rect for stacked series @@ -1187,7 +764,7 @@ void _drawAnimatedStackedRect( Canvas canvas, RRect segmentRect, Paint fillPaint, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, double animationFactor, CartesianChartPoint currentPoint, int index, @@ -1196,24 +773,24 @@ void _drawAnimatedStackedRect( double right = segmentRect.right, width = segmentRect.width; final double height1 = segmentRect.height, top1 = segmentRect.top; const num defaultCrossesAtValue = 0; - final num crossesAt = - _getCrossesAtValue(seriesRenderer, seriesRenderer._chartState!) ?? - defaultCrossesAtValue; + final num crossesAt = getCrossesAtValue(seriesRendererDetails.renderer, + seriesRendererDetails.stateProperties) ?? + defaultCrossesAtValue; height = segmentRect.height * animationFactor; width = segmentRect.width * animationFactor; - if ((seriesRenderer._seriesType.contains('stackedcolumn')) && - seriesRenderer._chart.isTransposed != true || - (seriesRenderer._seriesType.contains('stackedbar')) && - seriesRenderer._chart.isTransposed == true) { - seriesRenderer._yAxisRenderer!._axis.isInversed != true - ? (seriesRenderer._dataPoints[index].y > crossesAt) == true + if ((seriesRendererDetails.seriesType.contains('stackedcolumn') == true) && + seriesRendererDetails.chart.isTransposed != true || + (seriesRendererDetails.seriesType.contains('stackedbar') == true) && + seriesRendererDetails.chart.isTransposed == true) { + seriesRendererDetails.yAxisDetails!.axis.isInversed != true + ? (seriesRendererDetails.dataPoints[index].y > crossesAt) == true ? prevRegion == null ? top = (segmentRect.top + segmentRect.height) - height : top = prevRegion.top - height : prevRegion == null ? top = segmentRect.top : top = prevRegion.bottom - : (seriesRenderer._dataPoints[index].y > crossesAt) == true + : (seriesRendererDetails.dataPoints[index].y > crossesAt) == true ? prevRegion == null ? top = segmentRect.top : top = prevRegion.bottom @@ -1230,19 +807,20 @@ void _drawAnimatedStackedRect( currentPoint.region = Rect.fromLTWH(segmentRect.left, top, segmentRect.width, height); canvas.drawRRect(segmentRect, fillPaint); - } else if ((seriesRenderer._seriesType.contains('stackedcolumn')) && - seriesRenderer._chart.isTransposed == true || - (seriesRenderer._seriesType.contains('stackedbar')) && - seriesRenderer._chart.isTransposed != true) { - seriesRenderer._yAxisRenderer!._axis.isInversed != true - ? (seriesRenderer._dataPoints[index].y > crossesAt) == true + } else if ((seriesRendererDetails.seriesType.contains('stackedcolumn') == + true) && + seriesRendererDetails.chart.isTransposed == true || + (seriesRendererDetails.seriesType.contains('stackedbar') == true) && + seriesRendererDetails.chart.isTransposed != true) { + seriesRendererDetails.yAxisDetails!.axis.isInversed != true + ? (seriesRendererDetails.dataPoints[index].y > crossesAt) == true ? prevRegion == null ? right = (segmentRect.right - segmentRect.width) + width : right = prevRegion.right + width : prevRegion == null ? right = segmentRect.right : right = prevRegion.left - : (seriesRenderer._dataPoints[index].y > crossesAt) == true + : (seriesRendererDetails.dataPoints[index].y > crossesAt) == true ? prevRegion == null ? right = segmentRect.right : right = prevRegion.left @@ -1257,27 +835,23 @@ void _drawAnimatedStackedRect( topLeft: segmentRect.tlRadius, topRight: segmentRect.trRadius); currentPoint.region = - Rect.fromLTWH(segmentRect.left, right, segmentRect.width, width); + Rect.fromLTWH(segmentRect.left, top, right - segmentRect.left, height); canvas.drawRRect(segmentRect, fillPaint); } } -// This recursive function returns the previous region of the cluster stakced info for animation purposes -Rect? _getPrevRegion(num yValue, _ClusterStackedItemInfo clusterStackedItemInfo, +/// This recursive function returns the previous region of the cluster stacked info for animation purposes +Rect? _getPrevRegion(num yValue, ClusterStackedItemInfo clusterStackedItemInfo, int index, int k) { Rect? prevRegion; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + clusterStackedItemInfo.stackedItemInfo[k - 1].seriesRenderer); if (((yValue > 0) == true && - (clusterStackedItemInfo.stackedItemInfo[k - 1].seriesRenderer - ._dataPoints[index].yValue > - 0) == - true) || - ((yValue < 0) == true && - (clusterStackedItemInfo.stackedItemInfo[k - 1].seriesRenderer - ._dataPoints[index].yValue < - 0) == - true)) { - prevRegion = clusterStackedItemInfo - .stackedItemInfo[k - 1].seriesRenderer._dataPoints[index].region; + (seriesRendererDetails.dataPoints[index].yValue > 0) == true) || + ((yValue < 0 == true) && + (seriesRendererDetails.dataPoints[index].yValue < 0 == true))) { + prevRegion = seriesRendererDetails.dataPoints[index].region; } else { if (k > 1) { prevRegion = _getPrevRegion(yValue, clusterStackedItemInfo, index, k - 1); @@ -1287,13 +861,15 @@ Rect? _getPrevRegion(num yValue, _ClusterStackedItemInfo clusterStackedItemInfo, } /// To check the series count -bool _checkSingleSeries(CartesianSeriesRenderer seriesRenderer) { +bool _checkSingleSeries(SeriesRendererDetails seriesRendererDetails) { int count = 0; - for (final CartesianSeriesRenderer seriesRenderer - in seriesRenderer._chartState!._chartSeries.visibleSeriesRenderers) { - if (seriesRenderer._visible! && - (seriesRenderer._seriesType == 'column' || - seriesRenderer._seriesType == 'bar')) { + SeriesRendererDetails seriesDetails; + for (final CartesianSeriesRenderer seriesRenderer in seriesRendererDetails + .stateProperties.chartSeries.visibleSeriesRenderers) { + seriesDetails = SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesDetails.visible! == true && + (seriesDetails.seriesType == 'column' || + seriesDetails.seriesType == 'bar')) { count++; } } @@ -1301,9 +877,9 @@ bool _checkSingleSeries(CartesianSeriesRenderer seriesRenderer) { } /// to animate dynamic update in line, spline, stepLine series -void _animateLineTypeSeries( +void animateLineTypeSeries( Canvas canvas, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, Paint strokePaint, double animationFactor, double newX1, @@ -1330,37 +906,47 @@ void _animateLineTypeSeries( double? y3 = newY3; double? x4 = newX4; double? y4 = newY4; - y1 = _getAnimateValue(animationFactor, y1, oldY1, newY1, seriesRenderer); - y2 = _getAnimateValue(animationFactor, y2, oldY2, newY2, seriesRenderer); + y1 = + getAnimateValue(animationFactor, y1, oldY1, newY1, seriesRendererDetails); + y2 = + getAnimateValue(animationFactor, y2, oldY2, newY2, seriesRendererDetails); y3 = y3 != null - ? _getAnimateValue(animationFactor, y3, oldY3, newY3, seriesRenderer) + ? getAnimateValue( + animationFactor, y3, oldY3, newY3, seriesRendererDetails) : y3; y4 = y4 != null - ? _getAnimateValue(animationFactor, y4, oldY4, newY4, seriesRenderer) + ? getAnimateValue( + animationFactor, y4, oldY4, newY4, seriesRendererDetails) : y4; - x1 = _getAnimateValue(animationFactor, x1, oldX1, newX1, seriesRenderer); - x2 = _getAnimateValue(animationFactor, x2, oldX2, newX2, seriesRenderer); + x1 = + getAnimateValue(animationFactor, x1, oldX1, newX1, seriesRendererDetails); + x2 = + getAnimateValue(animationFactor, x2, oldX2, newX2, seriesRendererDetails); x3 = x3 != null - ? _getAnimateValue(animationFactor, x3, oldX3, newX3, seriesRenderer) + ? getAnimateValue( + animationFactor, x3, oldX3, newX3, seriesRendererDetails) : x3; x4 = x4 != null - ? _getAnimateValue(animationFactor, x4, oldX4, newX4, seriesRenderer) + ? getAnimateValue( + animationFactor, x4, oldX4, newX4, seriesRendererDetails) : x4; final Path path = Path(); path.moveTo(x1, y1); - if (seriesRenderer._seriesType == 'stepline') { + if (seriesRendererDetails.seriesType == 'stepline') { path.lineTo(x3!, y3!); } - seriesRenderer._seriesType == 'spline' + seriesRendererDetails.seriesType == 'spline' ? path.cubicTo(x3!, y3!, x4!, y4!, x2, y2) : path.lineTo(x2, y2); - _drawDashedLine(canvas, seriesRenderer._series.dashArray, strokePaint, path); + drawDashedLine( + canvas, seriesRendererDetails.series.dashArray, strokePaint, path); } -void _animateToPoint( +/// Method to animate to point +void animateToPoint( Canvas canvas, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, Paint strokePaint, double animationFactor, double x1, @@ -1377,16 +963,18 @@ void _animateToPoint( path.moveTo(newX1, newY1); path.lineTo(newX1 + (x2 - newX1) * animationFactor, newY1 + (y2 - newY1) * animationFactor); - _drawDashedLine(canvas, seriesRenderer._series.dashArray, strokePaint, path); + drawDashedLine( + canvas, seriesRendererDetails.series.dashArray, strokePaint, path); } /// To get value of animation based on animation factor -double _getAnimateValue( +double getAnimateValue( double animationFactor, double? value, double? oldvalue, double? newValue, - [CartesianSeriesRenderer? seriesRenderer]) { - if (seriesRenderer != null) { - seriesRenderer._needAnimateSeriesElements = - seriesRenderer._needAnimateSeriesElements || oldvalue != newValue; + [SeriesRendererDetails? seriesRendererDetails]) { + if (seriesRendererDetails != null) { + seriesRendererDetails.needAnimateSeriesElements = + seriesRendererDetails.needAnimateSeriesElements == true || + oldvalue != newValue; } return ((oldvalue != null && newValue != null && !oldvalue.isNaN) ? ((oldvalue > newValue) @@ -1396,8 +984,8 @@ double _getAnimateValue( } /// To animate scatter series -void _animateScatterSeries( - CartesianSeriesRenderer seriesRenderer, +void animateScatterSeries( + SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, CartesianChartPoint? _oldPoint, double animationFactor, @@ -1406,29 +994,30 @@ void _animateScatterSeries( Paint strokePaint, int index, ScatterSegment segment) { - final CartesianSeries series = seriesRenderer._series; + final CartesianSeries series = seriesRendererDetails.series; double width = series.markerSettings.width, height = series.markerSettings.height; DataMarkerType markerType = series.markerSettings.shape; double x = point.markerPoint!.x; double y = point.markerPoint!.y; - if (seriesRenderer._renderingDetails!.widgetNeedUpdate && + if (seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == + true && _oldPoint != null && - !seriesRenderer._reAnimate && + seriesRendererDetails.reAnimate == false && _oldPoint.markerPoint != null) { - x = _getAnimateValue( - animationFactor, x, _oldPoint.markerPoint!.x, x, seriesRenderer); - y = _getAnimateValue( - animationFactor, y, _oldPoint.markerPoint!.y, y, seriesRenderer); + x = getAnimateValue( + animationFactor, x, _oldPoint.markerPoint!.x, x, seriesRendererDetails); + y = getAnimateValue( + animationFactor, y, _oldPoint.markerPoint!.y, y, seriesRendererDetails); animationFactor = 1; } - if (seriesRenderer._chart.onMarkerRender != null) { - final MarkerRenderArgs markerRenderArgs = _triggerMarkerRenderEvent( - seriesRenderer, + if (seriesRendererDetails.chart.onMarkerRender != null) { + final MarkerRenderArgs markerRenderArgs = triggerMarkerRenderEvent( + seriesRendererDetails, Size(width, height), markerType, index, - seriesRenderer._seriesAnimation, + seriesRendererDetails.seriesAnimation, segment)!; width = markerRenderArgs.markerWidth; @@ -1444,40 +1033,40 @@ void _animateScatterSeries( { switch (markerType) { case DataMarkerType.circle: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.circle); break; case DataMarkerType.rectangle: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.rectangle); break; case DataMarkerType.image: - _drawImageMarker(seriesRenderer, canvas, x, y); + drawImageMarker(seriesRendererDetails, canvas, x, y); break; case DataMarkerType.pentagon: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.pentagon); break; case DataMarkerType.verticalLine: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.verticalLine); break; case DataMarkerType.invertedTriangle: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.invertedTriangle); break; case DataMarkerType.horizontalLine: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.horizontalLine); break; case DataMarkerType.diamond: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.diamond); break; case DataMarkerType.triangle: - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.triangle); break; case DataMarkerType.none: @@ -1489,7 +1078,7 @@ void _animateScatterSeries( } /// To animate bubble series -void _animateBubbleSeries( +void animateBubbleSeries( Canvas canvas, double newX, double newY, @@ -1501,21 +1090,23 @@ void _animateBubbleSeries( Paint strokePaint, Paint fillPaint, CartesianSeriesRenderer seriesRenderer) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); double x = newX; double y = newY; double size = radius; - x = _getAnimateValue(animationFactor, x, oldX, newX, seriesRenderer); - y = _getAnimateValue(animationFactor, y, oldY, newY, seriesRenderer); - size = - _getAnimateValue(animationFactor, size, oldSize, radius, seriesRenderer); + x = getAnimateValue(animationFactor, x, oldX, newX, seriesRendererDetails); + y = getAnimateValue(animationFactor, y, oldY, newY, seriesRendererDetails); + size = getAnimateValue( + animationFactor, size, oldSize, radius, seriesRendererDetails); canvas.drawCircle(Offset(x, y), size, fillPaint); canvas.drawCircle(Offset(x, y), size, strokePaint); } /// To animates range column series -void _animateRangeColumn( +void animateRangeColumn( Canvas canvas, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, Paint fillPaint, RRect segmentRect, Rect? oldSegmentRect, @@ -1524,8 +1115,8 @@ void _animateRangeColumn( double width = segmentRect.width; double left = segmentRect.left; double top = segmentRect.top; - if (oldSegmentRect == null || seriesRenderer._reAnimate) { - if (!seriesRenderer._chart.isTransposed) { + if (oldSegmentRect == null || seriesRendererDetails.reAnimate == true) { + if (seriesRendererDetails.chart.isTransposed == false) { height = segmentRect.height * animationFactor; top = segmentRect.center.dy - height / 2; } else { @@ -1533,16 +1124,16 @@ void _animateRangeColumn( left = segmentRect.center.dx - width / 2; } } else { - if (!seriesRenderer._chart.isTransposed) { - height = _getAnimateValue(animationFactor, height, oldSegmentRect.height, - segmentRect.height, seriesRenderer); - top = _getAnimateValue(animationFactor, top, oldSegmentRect.top, - segmentRect.top, seriesRenderer); + if (seriesRendererDetails.chart.isTransposed == false) { + height = getAnimateValue(animationFactor, height, oldSegmentRect.height, + segmentRect.height, seriesRendererDetails); + top = getAnimateValue(animationFactor, top, oldSegmentRect.top, + segmentRect.top, seriesRendererDetails); } else { - width = _getAnimateValue(animationFactor, width, oldSegmentRect.width, - segmentRect.width, seriesRenderer); - left = _getAnimateValue(animationFactor, left, oldSegmentRect.left, - segmentRect.left, seriesRenderer); + width = getAnimateValue(animationFactor, width, oldSegmentRect.width, + segmentRect.width, seriesRendererDetails); + left = getAnimateValue(animationFactor, left, oldSegmentRect.left, + segmentRect.left, seriesRendererDetails); } } canvas.drawRRect( @@ -1554,78 +1145,80 @@ void _animateRangeColumn( fillPaint); } -// To animate linear type animation -void _performLinearAnimation(SfCartesianChartState chartState, ChartAxis axis, - Canvas canvas, double animationFactor) { - chartState._chart.isTransposed +/// To animate linear type animation +void performLinearAnimation(CartesianStateProperties stateProperties, + ChartAxis axis, Canvas canvas, double animationFactor) { + stateProperties.chart.isTransposed ? canvas.clipRect(Rect.fromLTRB( 0, axis.isInversed ? 0 : (1 - animationFactor) * - chartState._chartAxis._axisClipRect.bottom, - chartState._chartAxis._axisClipRect.left + - chartState._chartAxis._axisClipRect.width, + stateProperties.chartAxis.axisClipRect.bottom, + stateProperties.chartAxis.axisClipRect.left + + stateProperties.chartAxis.axisClipRect.width, axis.isInversed ? animationFactor * - (chartState._chartAxis._axisClipRect.top + - chartState._chartAxis._axisClipRect.height) - : chartState._chartAxis._axisClipRect.top + - chartState._chartAxis._axisClipRect.height)) + (stateProperties.chartAxis.axisClipRect.top + + stateProperties.chartAxis.axisClipRect.height) + : stateProperties.chartAxis.axisClipRect.top + + stateProperties.chartAxis.axisClipRect.height)) : canvas.clipRect(Rect.fromLTRB( axis.isInversed ? (1 - animationFactor) * - (chartState._chartAxis._axisClipRect.right) + (stateProperties.chartAxis.axisClipRect.right) : 0, 0, axis.isInversed - ? chartState._chartAxis._axisClipRect.left + - chartState._chartAxis._axisClipRect.width + ? stateProperties.chartAxis.axisClipRect.left + + stateProperties.chartAxis.axisClipRect.width : animationFactor * - (chartState._chartAxis._axisClipRect.left + - chartState._chartAxis._axisClipRect.width), - chartState._chartAxis._axisClipRect.top + - chartState._chartAxis._axisClipRect.height)); + (stateProperties.chartAxis.axisClipRect.left + + stateProperties.chartAxis.axisClipRect.width), + stateProperties.chartAxis.axisClipRect.top + + stateProperties.chartAxis.axisClipRect.height)); } /// To animate Hilo series -void _animateHiloSeries( +void animateHiloSeres( bool transposed, - _ChartLocation newLow, - _ChartLocation newHigh, - _ChartLocation? oldLow, - _ChartLocation? oldHigh, + ChartLocation newLow, + ChartLocation newHigh, + ChartLocation? oldLow, + ChartLocation? oldHigh, double animationFactor, Paint paint, Canvas canvas, CartesianSeriesRenderer seriesRenderer) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); if (transposed) { double lowX = newLow.x; double highX = newHigh.x; double y = newLow.y; - y = _getAnimateValue( - animationFactor, y, oldLow?.y, newLow.y, seriesRenderer); - lowX = _getAnimateValue( - animationFactor, lowX, oldLow?.x, newLow.x, seriesRenderer); - highX = _getAnimateValue( - animationFactor, highX, oldHigh?.x, newHigh.x, seriesRenderer); + y = getAnimateValue( + animationFactor, y, oldLow?.y, newLow.y, seriesRendererDetails); + lowX = getAnimateValue( + animationFactor, lowX, oldLow?.x, newLow.x, seriesRendererDetails); + highX = getAnimateValue( + animationFactor, highX, oldHigh?.x, newHigh.x, seriesRendererDetails); canvas.drawLine(Offset(lowX, y), Offset(highX, y), paint); } else { double low = newLow.y; double high = newHigh.y; double x = newLow.x; - x = _getAnimateValue( - animationFactor, x, oldLow?.x, newLow.x, seriesRenderer); - low = _getAnimateValue( - animationFactor, low, oldLow?.y, newLow.y, seriesRenderer); - high = _getAnimateValue( - animationFactor, high, oldHigh?.y, newHigh.y, seriesRenderer); + x = getAnimateValue( + animationFactor, x, oldLow?.x, newLow.x, seriesRendererDetails); + low = getAnimateValue( + animationFactor, low, oldLow?.y, newLow.y, seriesRendererDetails); + high = getAnimateValue( + animationFactor, high, oldHigh?.y, newHigh.y, seriesRendererDetails); canvas.drawLine(Offset(x, low), Offset(x, high), paint); } } /// To animate the Hilo open-close series -void _animateHiloOpenCloseSeries( +void animateHiloOpenCloseSeries( bool transposed, double newLow, double newHigh, @@ -1646,7 +1239,7 @@ void _animateHiloOpenCloseSeries( double animationFactor, Paint paint, Canvas canvas, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { double low = newLow; double high = newHigh; double openX = newOpenX; @@ -1655,21 +1248,22 @@ void _animateHiloOpenCloseSeries( double closeY = newCloseY; double centerHigh = newCenterHigh; double centerLow = newCenterLow; - low = _getAnimateValue(animationFactor, low, oldLow, newLow, seriesRenderer); - high = - _getAnimateValue(animationFactor, high, oldHigh, newHigh, seriesRenderer); - openX = _getAnimateValue( - animationFactor, openX, oldOpenX, newOpenX, seriesRenderer); - openY = _getAnimateValue( - animationFactor, openY, oldOpenY, newOpenY, seriesRenderer); - closeX = _getAnimateValue( - animationFactor, closeX, oldCloseX, newCloseX, seriesRenderer); - closeY = _getAnimateValue( - animationFactor, closeY, oldCloseY, newCloseY, seriesRenderer); - centerHigh = _getAnimateValue(animationFactor, centerHigh, oldCenterHigh, - newCenterHigh, seriesRenderer); - centerLow = _getAnimateValue( - animationFactor, centerLow, oldCenterLow, newCenterLow, seriesRenderer); + low = getAnimateValue( + animationFactor, low, oldLow, newLow, seriesRendererDetails); + high = getAnimateValue( + animationFactor, high, oldHigh, newHigh, seriesRendererDetails); + openX = getAnimateValue( + animationFactor, openX, oldOpenX, newOpenX, seriesRendererDetails); + openY = getAnimateValue( + animationFactor, openY, oldOpenY, newOpenY, seriesRendererDetails); + closeX = getAnimateValue( + animationFactor, closeX, oldCloseX, newCloseX, seriesRendererDetails); + closeY = getAnimateValue( + animationFactor, closeY, oldCloseY, newCloseY, seriesRendererDetails); + centerHigh = getAnimateValue(animationFactor, centerHigh, oldCenterHigh, + newCenterHigh, seriesRendererDetails); + centerLow = getAnimateValue(animationFactor, centerLow, oldCenterLow, + newCenterLow, seriesRendererDetails); if (transposed) { canvas.drawLine(Offset(low, centerLow), Offset(high, centerHigh), paint); canvas.drawLine(Offset(openX, openY), Offset(openX, centerHigh), paint); @@ -1682,7 +1276,7 @@ void _animateHiloOpenCloseSeries( } /// To animate the box and whisker series -void _animateBoxSeries( +void animateBoxSeries( bool showMean, Offset meanPosition, Offset transposedMeanPosition, @@ -1713,7 +1307,7 @@ void _animateBoxSeries( Paint fillPaint, Paint strokePaint, Canvas canvas, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final double lowerQuartile = lowerQuartile1; final double upperQuartile = upperQuartile1; double minY = newMin; @@ -1724,22 +1318,22 @@ void _animateBoxSeries( double upperQuartileY = newUpperQuartileY; double centerMax = newCenterMax; double centerMin = newCenterMin; - minY = - _getAnimateValue(animationFactor, minY, oldMin, newMin, seriesRenderer); - maxY = - _getAnimateValue(animationFactor, maxY, oldMax, newMax, seriesRenderer); - lowerQuartileX = _getAnimateValue(animationFactor, lowerQuartileX, - oldLowerQuartileX, newLowerQuartileX, seriesRenderer); - lowerQuartileY = _getAnimateValue(animationFactor, lowerQuartileY, - oldLowerQuartileY, newLowerQuartileY, seriesRenderer); - upperQuartileX = _getAnimateValue(animationFactor, upperQuartileX, - oldUpperQuartileX, newUpperQuartileX, seriesRenderer); - upperQuartileY = _getAnimateValue(animationFactor, upperQuartileY, - oldUpperQuartileY, newUpperQuartileY, seriesRenderer); - centerMax = _getAnimateValue( - animationFactor, centerMax, oldCenterMax, newCenterMax, seriesRenderer); - centerMin = _getAnimateValue( - animationFactor, centerMin, oldCenterMin, newCenterMin, seriesRenderer); + minY = getAnimateValue( + animationFactor, minY, oldMin, newMin, seriesRendererDetails); + maxY = getAnimateValue( + animationFactor, maxY, oldMax, newMax, seriesRendererDetails); + lowerQuartileX = getAnimateValue(animationFactor, lowerQuartileX, + oldLowerQuartileX, newLowerQuartileX, seriesRendererDetails); + lowerQuartileY = getAnimateValue(animationFactor, lowerQuartileY, + oldLowerQuartileY, newLowerQuartileY, seriesRendererDetails); + upperQuartileX = getAnimateValue(animationFactor, upperQuartileX, + oldUpperQuartileX, newUpperQuartileX, seriesRendererDetails); + upperQuartileY = getAnimateValue(animationFactor, upperQuartileY, + oldUpperQuartileY, newUpperQuartileY, seriesRendererDetails); + centerMax = getAnimateValue(animationFactor, centerMax, oldCenterMax, + newCenterMax, seriesRendererDetails); + centerMin = getAnimateValue(animationFactor, centerMin, oldCenterMin, + newCenterMin, seriesRendererDetails); final Path path = Path(); if (!transposed && lowerQuartile > upperQuartile) { final double temp = upperQuartileY; @@ -1763,7 +1357,7 @@ void _animateBoxSeries( path.lineTo(centerMin, lowerQuartileY); if (showMean) { _drawMeanValuePath( - seriesRenderer, + seriesRendererDetails, animationFactor, transposedMeanPosition.dy, transposedMeanPosition.dx, @@ -1799,8 +1393,8 @@ void _animateBoxSeries( canvas.drawLine(Offset(lowerQuartileX, minY), Offset(upperQuartileX, minY), strokePaint); if (showMean) { - _drawMeanValuePath(seriesRenderer, animationFactor, meanPosition.dx, - meanPosition.dy, size, strokePaint, canvas); + _drawMeanValuePath(seriesRendererDetails, animationFactor, + meanPosition.dx, meanPosition.dy, size, strokePaint, canvas); } if (lowerQuartileY == upperQuartileY) { canvas.drawLine(Offset(lowerQuartileX, lowerQuartileY), @@ -1814,12 +1408,12 @@ void _animateBoxSeries( path.close(); } } - if (seriesRenderer._series.dashArray[0] != 0 && - seriesRenderer._series.dashArray[1] != 0 && - seriesRenderer._series.animationDuration <= 0) { + if (seriesRendererDetails.series.dashArray[0] != 0 && + seriesRendererDetails.series.dashArray[1] != 0 && + seriesRendererDetails.series.animationDuration <= 0) { canvas.drawPath(path, fillPaint); - _drawDashedLine( - canvas, seriesRenderer._series.dashArray, strokePaint, path); + drawDashedLine( + canvas, seriesRendererDetails.series.dashArray, strokePaint, path); } else { canvas.drawPath(path, fillPaint); canvas.drawPath(path, strokePaint); @@ -1838,7 +1432,7 @@ void _animateBoxSeries( Offset(centerMin, lowerQuartileY), strokePaint); if (showMean) { _drawMeanValuePath( - seriesRenderer, + seriesRendererDetails, animationFactor, transposedMeanPosition.dy, transposedMeanPosition.dx, @@ -1858,8 +1452,8 @@ void _animateBoxSeries( canvas.drawLine(Offset(lowerQuartileX, minY), Offset(upperQuartileX, minY), strokePaint); if (showMean) { - _drawMeanValuePath(seriesRenderer, animationFactor, meanPosition.dx, - meanPosition.dy, size, strokePaint, canvas); + _drawMeanValuePath(seriesRendererDetails, animationFactor, + meanPosition.dx, meanPosition.dy, size, strokePaint, canvas); } } } @@ -1867,15 +1461,16 @@ void _animateBoxSeries( /// To draw the path of mean value in box plot series. void _drawMeanValuePath( - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, double animationFactor, num x, num y, Size size, Paint strokePaint, Canvas canvas) { - if (seriesRenderer._series.animationDuration <= 0 || - animationFactor >= seriesRenderer._chartState!._seriesDurationFactor) { + if (seriesRendererDetails.series.animationDuration <= 0 || + animationFactor >= + seriesRendererDetails.stateProperties.seriesDurationFactor) { canvas.drawLine(Offset(x + size.width / 2, y - size.height / 2), Offset(x - size.width / 2, y + size.height / 2), strokePaint); canvas.drawLine(Offset(x + size.width / 2, y + size.height / 2), @@ -1884,7 +1479,7 @@ void _drawMeanValuePath( } /// To animate the candle series -void _animateCandleSeries( +void animateCandleSeries( bool showSameValue, double high, bool transposed, @@ -1909,7 +1504,7 @@ void _animateCandleSeries( double animationFactor, Paint paint, Canvas canvas, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final double open = open1; final double close = close1; double lowY = newLow; @@ -1920,22 +1515,22 @@ void _animateCandleSeries( double closeY = newCloseY; double centerHigh = newCenterHigh; double centerLow = newCenterLow; - lowY = - _getAnimateValue(animationFactor, lowY, oldLow, newLow, seriesRenderer); - highY = _getAnimateValue( - animationFactor, highY, oldHigh, newHigh, seriesRenderer); - openX = _getAnimateValue( - animationFactor, openX, oldOpenX, newOpenX, seriesRenderer); - openY = _getAnimateValue( - animationFactor, openY, oldOpenY, newOpenY, seriesRenderer); - closeX = _getAnimateValue( - animationFactor, closeX, oldCloseX, newCloseX, seriesRenderer); - closeY = _getAnimateValue( - animationFactor, closeY, oldCloseY, newCloseY, seriesRenderer); - centerHigh = _getAnimateValue(animationFactor, centerHigh, oldCenterHigh, - newCenterHigh, seriesRenderer); - centerLow = _getAnimateValue( - animationFactor, centerLow, oldCenterLow, newCenterLow, seriesRenderer); + lowY = getAnimateValue( + animationFactor, lowY, oldLow, newLow, seriesRendererDetails); + highY = getAnimateValue( + animationFactor, highY, oldHigh, newHigh, seriesRendererDetails); + openX = getAnimateValue( + animationFactor, openX, oldOpenX, newOpenX, seriesRendererDetails); + openY = getAnimateValue( + animationFactor, openY, oldOpenY, newOpenY, seriesRendererDetails); + closeX = getAnimateValue( + animationFactor, closeX, oldCloseX, newCloseX, seriesRendererDetails); + closeY = getAnimateValue( + animationFactor, closeY, oldCloseY, newCloseY, seriesRendererDetails); + centerHigh = getAnimateValue(animationFactor, centerHigh, oldCenterHigh, + newCenterHigh, seriesRendererDetails); + centerLow = getAnimateValue(animationFactor, centerLow, oldCenterLow, + newCenterLow, seriesRendererDetails); final Path path = Path(); if (!transposed && open > close) { final double temp = closeY; @@ -1993,11 +1588,12 @@ void _animateCandleSeries( } } - (seriesRenderer._series.dashArray[0] != 0 && - seriesRenderer._series.dashArray[1] != 0 && + (seriesRendererDetails.series.dashArray[0] != 0 && + seriesRendererDetails.series.dashArray[1] != 0 && paint.style != PaintingStyle.fill && - seriesRenderer._series.animationDuration <= 0) - ? _drawDashedLine(canvas, seriesRenderer._series.dashArray, paint, path) + seriesRendererDetails.series.animationDuration <= 0) + ? drawDashedLine( + canvas, seriesRendererDetails.series.dashArray, paint, path) : canvas.drawPath(path, paint); if (paint.style == PaintingStyle.fill) { if (transposed) { @@ -2023,14 +1619,18 @@ void _animateCandleSeries( } } -// To get nearest chart points from the touch point -List>? _getNearestChartPoints( +/// To get nearest chart points from the touch point +List>? getNearestChartPoints( double pointX, double pointY, ChartAxisRenderer actualXAxisRenderer, ChartAxisRenderer actualYAxisRenderer, - CartesianSeriesRenderer cartesianSeriesRenderer, + SeriesRendererDetails seriesRendererDetails, [List>? firstNearestDataPoints]) { + final ChartAxisRendererDetails actualXAxisDetails = + AxisHelper.getAxisRendererDetails(actualXAxisRenderer); + final ChartAxisRendererDetails actualYAxisDetails = + AxisHelper.getAxisRendererDetails(actualYAxisRenderer); final List> dataPointList = >[]; List> dataList = @@ -2040,37 +1640,37 @@ List>? _getNearestChartPoints( firstNearestDataPoints != null ? dataList = firstNearestDataPoints - : dataList = cartesianSeriesRenderer._visibleDataPoints!; + : dataList = seriesRendererDetails.visibleDataPoints!; for (int i = 0; i < dataList.length; i++) { xValues.add(dataList[i].xValue); - (cartesianSeriesRenderer is BoxAndWhiskerSeriesRenderer) + (seriesRendererDetails.renderer is BoxAndWhiskerSeriesRenderer) ? yValues.add((dataList[i].maximum! + dataList[i].minimum!) / 2) : yValues.add( dataList[i].yValue ?? (dataList[i].high + dataList[i].low) / 2); } num nearPointX = dataList[0].xValue; - num nearPointY = actualYAxisRenderer._visibleRange!.minimum; + num nearPointY = actualYAxisDetails.visibleRange!.minimum; - final Rect rect = _calculatePlotOffset( - cartesianSeriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(cartesianSeriesRenderer._xAxisRenderer!._axis.plotOffset, - cartesianSeriesRenderer._yAxisRenderer!._axis.plotOffset)); + final Rect rect = calculatePlotOffset( + seriesRendererDetails.stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); - final num touchXValue = _pointToXValue( - cartesianSeriesRenderer._chartState!._requireInvertedAxis, + final num touchXValue = pointToXValue( + seriesRendererDetails.stateProperties.requireInvertedAxis, actualXAxisRenderer, - actualXAxisRenderer._axis.isVisible - ? actualXAxisRenderer._bounds - : cartesianSeriesRenderer._chartState!._chartAxis._axisClipRect, + actualXAxisDetails.axis.isVisible + ? actualXAxisDetails.bounds + : seriesRendererDetails.stateProperties.chartAxis.axisClipRect, pointX - rect.left, pointY - rect.top); - final num touchYValue = _pointToYValue( - cartesianSeriesRenderer._chartState!._requireInvertedAxis, + final num touchYValue = pointToYValue( + seriesRendererDetails.stateProperties.requireInvertedAxis, actualYAxisRenderer, - actualYAxisRenderer._axis.isVisible - ? actualYAxisRenderer._bounds - : cartesianSeriesRenderer._chartState!._chartAxis._axisClipRect, + actualYAxisDetails.axis.isVisible + ? actualYAxisDetails.bounds + : seriesRendererDetails.stateProperties.chartAxis.axisClipRect, pointX - rect.left, pointY - rect.top); double delta = 0; @@ -2101,12 +1701,12 @@ List>? _getNearestChartPoints( } /// Return the arguments for zoom event -ZoomPanArgs _bindZoomEvent(SfCartesianChart chart, - ChartAxisRenderer axisRenderer, ChartZoomingCallback zoomEventType) { - final ZoomPanArgs zoomPanArgs = ZoomPanArgs(axisRenderer._axis, - axisRenderer._previousZoomPosition, axisRenderer._previousZoomFactor); - zoomPanArgs.currentZoomFactor = axisRenderer._zoomFactor; - zoomPanArgs.currentZoomPosition = axisRenderer._zoomPosition; +ZoomPanArgs bindZoomEvent(SfCartesianChart chart, + ChartAxisRendererDetails axisDetails, ChartZoomingCallback zoomEventType) { + final ZoomPanArgs zoomPanArgs = ZoomPanArgs(axisDetails.axis, + axisDetails.previousZoomPosition, axisDetails.previousZoomFactor); + zoomPanArgs.currentZoomFactor = axisDetails.zoomFactor; + zoomPanArgs.currentZoomPosition = axisDetails.zoomPosition; zoomEventType == chart.onZoomStart ? chart.onZoomStart!(zoomPanArgs) : zoomEventType == chart.onZoomEnd @@ -2118,8 +1718,8 @@ ZoomPanArgs _bindZoomEvent(SfCartesianChart chart, return zoomPanArgs; } -//Method to returning path for dashed border in rect type series -Path _findingRectSeriesDashedBorder( +/// Method to returning path for dashed border in rect type series +Path findingRectSeriesDashedBorder( CartesianChartPoint currentPoint, double borderWidth) { Path path; Offset topLeft, topRight, bottomRight, bottomLeft; @@ -2139,7 +1739,7 @@ Path _findingRectSeriesDashedBorder( return path; } -/// below method is for getting image from the imageprovider +/// below method is for getting image from the image provider Future _getImageInfo(ImageProvider imageProvider) async { final Completer completer = Completer(); imageProvider @@ -2152,40 +1752,339 @@ Future _getImageInfo(ImageProvider imageProvider) async { return imageInfo.image; } +/// Method to calculate the image value //ignore: avoid_void_async -void _calculateImage(SfCartesianChartState chartState, - [CartesianSeriesRenderer? seriesRenderer, +void calculateImage(CartesianStateProperties stateProperties, + [SeriesRendererDetails? seriesRendererDetails, TrackballBehavior? trackballBehavior]) async { - final SfCartesianChart chart = chartState._chart; + final SfCartesianChart chart = stateProperties.chart; // ignore: unnecessary_null_comparison - if (chart != null && seriesRenderer == null) { + if (chart != null && seriesRendererDetails == null) { if (chart.plotAreaBackgroundImage != null) { - chartState._backgroundImage = + stateProperties.backgroundImage = await _getImageInfo(chart.plotAreaBackgroundImage!); - chartState._renderOutsideAxis.state.axisRepaintNotifier.value++; - } - if (chart.legend.image != null) { - chartState._legendIconImage = await _getImageInfo(chart.legend.image!); - chartState._renderingDetails.chartLegend.legendRepaintNotifier.value++; + stateProperties.renderOutsideAxis.state.axisRepaintNotifier.value++; } } else if (trackballBehavior != null && trackballBehavior.markerSettings!.image != null) { - chartState._trackballMarkerSettingsRenderer._image = + stateProperties.trackballMarkerSettingsRenderer.image = await _getImageInfo(trackballBehavior.markerSettings!.image!); - chartState._repaintNotifiers['trackball']!.value++; + stateProperties.repaintNotifiers['trackball']!.value++; } else { - final CartesianSeries series = seriesRenderer!._series; + final CartesianSeries series = + seriesRendererDetails!.series; if (series.markerSettings.image != null) { - seriesRenderer._markerSettingsRenderer!._image = + seriesRendererDetails.markerSettingsRenderer!.image = await _getImageInfo(series.markerSettings.image!); - if (!chartState._renderingDetails.isImageDrawn) - seriesRenderer._repaintNotifier.value++; - chartState._renderingDetails.isImageDrawn = true; - if (seriesRenderer._seriesType == 'scatter' && - seriesRenderer._chart.legend.isVisible!) { - seriesRenderer - ._renderingDetails!.chartLegend.legendRepaintNotifier.value++; + if (!seriesRendererDetails.markerSettingsRenderer!.isImageDrawn) { + seriesRendererDetails.repaintNotifier.value++; + seriesRendererDetails.markerSettingsRenderer!.isImageDrawn = true; + } + if (seriesRendererDetails.seriesType == 'scatter' && + seriesRendererDetails.chart.legend.isVisible! == true) { + seriesRendererDetails.stateProperties.renderingDetails.chartLegend + .legendRepaintNotifier.value++; + } + } + } +} + +/// Set the shader for series types. +void setShader(SegmentProperties segmentProperties, Paint paint) => + paint.shader = segmentProperties.stateProperties.shader ?? paint.shader; + +/// Used to calculate the error values of standard error type error bar. +ChartErrorValues _getStandardErrorValues( + SeriesRendererDetails seriesRendererDetails, + ErrorBarSeries series, + ChartErrorValues chartErrorValues, + ErrorBarMean mean, + int dataPointsLength) { + final num errorX = (chartErrorValues.errorX! * mean.horizontalSquareRoot!) / + math.sqrt(dataPointsLength); + final num errorY = (chartErrorValues.errorY! * mean.verticalSquareRoot!) / + math.sqrt(dataPointsLength); + return ChartErrorValues(errorX: errorX, errorY: errorY); +} + +/// Used to calculate the error values of standard deviation type error bar. +ChartErrorValues _getStandardDeviationValues( + SeriesRendererDetails seriesRendererDetails, + ErrorBarSeries series, + ChartErrorValues chartErrorValues, + ErrorBarMean mean, + int dataPointsLength) { + num errorY = chartErrorValues.errorY!, errorX = chartErrorValues.errorX!; + errorY = (series.mode == RenderingMode.horizontal) + ? errorY + : errorY * (mean.verticalSquareRoot! + mean.verticalMean!); + errorX = (series.mode == RenderingMode.vertical) + ? errorX + : errorX * (mean.horizontalSquareRoot! + mean.horizontalMean!); + return ChartErrorValues(errorX: errorX, errorY: errorY); +} + +/// Returns the error values of error bar. +ChartErrorValues _getErrorValues(SeriesRendererDetails seriesRendererDetails, + ErrorBarSeries series, num actualXValue, num actualYValue, + {ErrorBarMean? mean, int? dataPointsLength}) { + final RenderingMode? mode = series.mode; + num errorX = 0.0, + errorY = 0.0, + customNegativeErrorX = 0.0, + customNegativeErrorY = 0.0; + ChartErrorValues? chartErrorValues; + if (series.type != ErrorBarType.custom) { + errorY = (mode == RenderingMode.horizontal) + ? errorY + : series.verticalErrorValue!; + errorX = (mode == RenderingMode.vertical) + ? errorX + : series.horizontalErrorValue!; + chartErrorValues = ChartErrorValues(errorX: errorX, errorY: errorY); + if (series.type == ErrorBarType.standardError) { + chartErrorValues = _getStandardErrorValues(seriesRendererDetails, series, + chartErrorValues, mean!, dataPointsLength!); + } + // Updating the error values for standard deviation type + if (series.type == ErrorBarType.standardDeviation) { + chartErrorValues = _getStandardDeviationValues(seriesRendererDetails, + series, chartErrorValues, mean!, dataPointsLength!); + } + // Updating the error values for percentage type + if (series.type == ErrorBarType.percentage) { + errorY = (mode == RenderingMode.horizontal) + ? errorY + : (errorY / 100) * actualYValue; + errorX = (mode == RenderingMode.vertical) + ? errorX + : (errorX / 100) * actualXValue; + chartErrorValues = ChartErrorValues(errorX: errorX, errorY: errorY); + } + } + // Updating the error values for custom type + else if (series.type == ErrorBarType.custom) { + errorY = (mode == RenderingMode.horizontal) + ? errorY + : series.verticalPositiveErrorValue!; + customNegativeErrorY = (mode == RenderingMode.horizontal) + ? customNegativeErrorY + : series.verticalNegativeErrorValue!; + errorX = (mode == RenderingMode.vertical) + ? errorX + : series.horizontalPositiveErrorValue!; + customNegativeErrorX = (mode == RenderingMode.vertical) + ? customNegativeErrorX + : series.horizontalNegativeErrorValue!; + chartErrorValues = ChartErrorValues( + errorX: errorX, + errorY: errorY, + customNegativeX: customNegativeErrorX, + customNegativeY: customNegativeErrorY); + } + return chartErrorValues!; +} + +/// Returns the calculated error bar values. +ErrorBarValues _getCalculatedErrorValues( + SeriesRendererDetails seriesRendererDetails, + ErrorBarSeries series, + ChartErrorValues chartErrorValues, + num actualXValue, + num actualYValue) { + final bool isDirectionPlus = series.direction == Direction.plus; + final bool isBothDirection = series.direction == Direction.both; + final bool isDirectionMinus = series.direction == Direction.minus; + final bool isCustomFixedType = + series.type == ErrorBarType.fixed || series.type == ErrorBarType.custom; + double? verticalPositiveErrorValue, + horizontalPositiveErrorValue, + verticalNegativeErrorValue, + horizontalNegativeErrorValue; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + if (series.mode == RenderingMode.horizontal || + series.mode == RenderingMode.both) { + if (isDirectionPlus || isBothDirection) { + if (xAxisDetails is DateTimeAxisDetails && isCustomFixedType) { + horizontalPositiveErrorValue = _updateDateTimeHorizontalErrorValue( + seriesRendererDetails, actualXValue, chartErrorValues.errorX!); + } else { + horizontalPositiveErrorValue = + actualXValue + chartErrorValues.errorX! as double; } } + if (isDirectionMinus || isBothDirection) { + if (xAxisDetails is DateTimeAxisDetails && isCustomFixedType) { + horizontalNegativeErrorValue = _updateDateTimeHorizontalErrorValue( + seriesRendererDetails, + actualXValue, + (series.type == ErrorBarType.custom) + ? -chartErrorValues.customNegativeX! + : -chartErrorValues.errorX!); + } else { + horizontalNegativeErrorValue = actualXValue - + ((series.type == ErrorBarType.custom) + ? chartErrorValues.customNegativeX! + : chartErrorValues.errorX!) as double; + } + } + } + + if (series.mode == RenderingMode.vertical || + series.mode == RenderingMode.both) { + if (isDirectionPlus || isBothDirection) { + verticalPositiveErrorValue = + actualYValue + chartErrorValues.errorY! as double; + } + if (isDirectionMinus || isBothDirection) { + verticalNegativeErrorValue = actualYValue - + ((series.type == ErrorBarType.custom) + ? chartErrorValues.customNegativeY! + : chartErrorValues.errorY!) as double; + } + } + return ErrorBarValues( + horizontalPositiveErrorValue, + horizontalNegativeErrorValue, + verticalPositiveErrorValue, + verticalNegativeErrorValue); +} + +/// Set maximum and minimum values of axis in error bar series. +void updateErrorBarAxisRange(SeriesRendererDetails seriesRendererDetails, + CartesianChartPoint point) { + if (!point.isGap) { + final ErrorBarSeries series = + seriesRendererDetails.series as ErrorBarSeries; + // For standard error and standard deviation type error bars, all data + // points are needed to calculate the error values. So this iteration is + // executed. + if (series.type == ErrorBarType.standardDeviation || + series.type == ErrorBarType.standardError) { + final List yValuesList = []; + final List xValuesList = []; + num sumOfX = 0, sumOfY = 0; + final int dataPointsLength = series.dataSource.length; + num? verticalSquareRoot, + horizontalSquareRoot, + verticalMean, + horizontalMean; + for (int pointIndex = 0; + pointIndex < series.dataSource.length; + pointIndex++) { + yValuesList.add(series.yValueMapper!(pointIndex) ?? 0); + sumOfY += yValuesList[pointIndex]; + final dynamic xValue = series.xValueMapper!(pointIndex); + if (xValue is DateTime) { + xValuesList.add(xValue.millisecondsSinceEpoch); + } else if (xValue is String) { + xValuesList.add(pointIndex); + } else { + xValuesList.add(xValue); + } + sumOfX += xValuesList[pointIndex]; + } + verticalMean = (series.mode == RenderingMode.horizontal) + ? verticalMean + : sumOfY / dataPointsLength; + horizontalMean = (series.mode == RenderingMode.vertical) + ? horizontalMean + : sumOfX / dataPointsLength; + for (int pointIndex = 0; + pointIndex < series.dataSource.length; + pointIndex++) { + sumOfY = (series.mode == RenderingMode.horizontal) + ? sumOfY + : sumOfY + math.pow(yValuesList[pointIndex] - verticalMean!, 2); + sumOfX = (series.mode == RenderingMode.vertical) + ? sumOfX + : sumOfX + math.pow(xValuesList[pointIndex] - horizontalMean!, 2); + } + verticalSquareRoot = math.sqrt(sumOfY / (dataPointsLength - 1)); + horizontalSquareRoot = math.sqrt(sumOfX / (dataPointsLength - 1)); + final ErrorBarMean mean = ErrorBarMean( + verticalSquareRoot: verticalSquareRoot, + horizontalSquareRoot: horizontalSquareRoot, + verticalMean: verticalMean, + horizontalMean: horizontalMean); + final ChartErrorValues chartErrorValues = _getErrorValues( + seriesRendererDetails, series, point.xValue, point.yValue, + mean: mean, dataPointsLength: dataPointsLength); + point.errorBarValues = _getCalculatedErrorValues(seriesRendererDetails, + series, chartErrorValues, point.xValue, point.yValue); + } else { + final ChartErrorValues chartErrorValues = _getErrorValues( + seriesRendererDetails, series, point.xValue, point.yValue); + point.errorBarValues = _getCalculatedErrorValues(seriesRendererDetails, + series, chartErrorValues, point.xValue, point.yValue); + } + } + if (point.errorBarValues?.verticalNegativeErrorValue != null) { + seriesRendererDetails.minimumY = findMinValue( + seriesRendererDetails.minimumY ?? point.yValue, + point.errorBarValues!.verticalNegativeErrorValue!); + } + if (point.errorBarValues?.verticalPositiveErrorValue != null) { + seriesRendererDetails.maximumY = findMaxValue( + seriesRendererDetails.maximumY ?? point.yValue, + point.errorBarValues!.verticalPositiveErrorValue!); + } + if (point.errorBarValues?.horizontalPositiveErrorValue != null) { + seriesRendererDetails.maximumX = findMaxValue( + seriesRendererDetails.maximumX ?? point.xValue, + point.errorBarValues!.horizontalPositiveErrorValue!); + } + if (point.errorBarValues?.horizontalNegativeErrorValue != null) { + seriesRendererDetails.minimumX = findMinValue( + seriesRendererDetails.minimumX ?? point.xValue, + point.errorBarValues!.horizontalNegativeErrorValue!); + } +} + +/// To calculate the error values for DateTime axis +double _updateDateTimeHorizontalErrorValue( + SeriesRendererDetails seriesRendererDetails, + num actualXValue, + num errorValue) { + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + DateTime errorXValue = + DateTime.fromMillisecondsSinceEpoch(actualXValue.toInt()); + final int errorX = errorValue.toInt(); + final DateTimeAxisDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(xAxisDetails.axisRenderer) + as DateTimeAxisDetails; + final DateTimeIntervalType type = + axisRendererDetails.dateTimeAxis.intervalType; + switch (type) { + case DateTimeIntervalType.years: + errorXValue = DateTime( + errorXValue.year + errorX, errorXValue.month, errorXValue.day); + break; + case DateTimeIntervalType.months: + errorXValue = DateTime( + errorXValue.year, errorXValue.month + errorX, errorXValue.day); + break; + case DateTimeIntervalType.days: + errorXValue = DateTime( + errorXValue.year, errorXValue.month, errorXValue.day + errorX); + break; + case DateTimeIntervalType.hours: + errorXValue = errorXValue.add(Duration(hours: errorX)); + break; + case DateTimeIntervalType.minutes: + errorXValue = errorXValue.add(Duration(minutes: errorX)); + break; + case DateTimeIntervalType.seconds: + errorXValue = errorXValue.add(Duration(seconds: errorX)); + break; + case DateTimeIntervalType.milliseconds: + errorXValue = errorXValue.add(Duration(milliseconds: errorX)); + break; + case DateTimeIntervalType.auto: + errorXValue = errorXValue.add(Duration(milliseconds: errorX)); + break; } + return errorXValue.millisecondsSinceEpoch.toDouble(); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart index 1c8ec86c8..97e045cda 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label.dart @@ -1,4 +1,18 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../circular_chart/renderer/circular_chart_annotation.dart'; +import '../../circular_chart/utils/enum.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/data_label_renderer.dart'; +import '../utils/enum.dart'; /// Customizes the data label. /// @@ -31,7 +45,7 @@ class DataLabelSettings { this.showZeroValue = true, this.borderColor = Colors.transparent, this.borderWidth = 0, - this.labelIntersectAction = LabelIntersectAction.hide, + this.labelIntersectAction = LabelIntersectAction.shift, this.connectorLineSettings = const ConnectorLineSettings(), this.labelPosition = ChartDataLabelPosition.inside}); @@ -315,7 +329,9 @@ class DataLabelSettings { /// ///The intersecting data labels can be hidden. /// - ///Defaults to `LabelIntersectAction.hide`. + /// _Note:_ This is applicable for pie and doughnut series types alone. + /// + ///Defaults to `LabelIntersectAction.shift`. /// ///Also refer [LabelIntersectAction]. ///```dart @@ -357,7 +373,7 @@ class DataLabelSettings { ///``` final ChartWidgetBuilder? builder; - /// To show the cummulative values in stacked type series charts. + /// To show the cumulative values in stacked type series charts. /// /// Defaults to `false`. /// @@ -484,39 +500,52 @@ class DataLabelSettings { } } -///Datalabel renderer class for mutable fields and methods +///Data label renderer class for mutable fields and methods class DataLabelSettingsRenderer { /// Creates an argument constructor for DataLabelSettings renderer class - DataLabelSettingsRenderer(this._dataLabelSettings) { - _angle = _dataLabelSettings.angle; - _offset = _dataLabelSettings.offset; - _color = _dataLabelSettings.color; + DataLabelSettingsRenderer(this.dataLabelSettings) { + angle = dataLabelSettings.angle; + offset = dataLabelSettings.offset; + color = dataLabelSettings.color; } - final DataLabelSettings _dataLabelSettings; + /// Represents the data label settings + final DataLabelSettings dataLabelSettings; - Color? _color; + /// Holds the color value + Color? color; - TextStyle? _textStyle; + /// Holds the text style value + TextStyle? textStyle; - TextStyle? _originalStyle; + /// Holds the value of original style + TextStyle? originalStyle; - late int _angle; + /// Holds the value of angle + late int angle; - Offset? _offset; + /// Specifies the value of offset + Offset? offset; /// To render charts with data labels - void _renderDataLabel( - SfCartesianChartState chartState, - CartesianSeriesRenderer seriesRenderer, + void renderDataLabel( + CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, Animation animationController, Canvas canvas, int labelIndex, DataLabelSettingsRenderer dataLabelSettingsRenderer) { - _calculateDataLabelPosition(seriesRenderer, point, labelIndex, chartState, - dataLabelSettingsRenderer, animationController); - _drawDataLabel(canvas, seriesRenderer, chartState, _dataLabelSettings, - point, labelIndex, animationController, dataLabelSettingsRenderer); + calculateDataLabelPosition(seriesRendererDetails, point, labelIndex, + stateProperties, dataLabelSettingsRenderer, animationController); + drawDataLabel( + canvas, + seriesRendererDetails, + stateProperties, + dataLabelSettings, + point, + labelIndex, + animationController, + dataLabelSettingsRenderer); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart index 900f615b6..aa0c848fc 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/data_label_renderer.dart @@ -1,681 +1,242 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; -/// Calculating data label position and updating the label region for current data point -void _calculateDataLabelPosition( - CartesianSeriesRenderer seriesRenderer, +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../common/event_args.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../axis/logarithmic_axis.dart'; +import '../axis/numeric_axis.dart'; +import '../base/chart_base.dart'; +import '../chart_series/series.dart'; +import '../chart_series/waterfall_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/data_label.dart'; +import '../utils/helper.dart'; + +///Calculating the label location based on alignment value +List _getAlignedLabelLocations( + CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, - int index, - SfCartesianChartState _chartState, - DataLabelSettingsRenderer dataLabelSettingsRenderer, - Animation dataLabelAnimation, - [Size? templateSize, - Offset? templateLocation]) { - final SfCartesianChart chart = _chartState._chart; - final CartesianSeries series = seriesRenderer._series; - if (dataLabelSettingsRenderer._angle.isNegative) { - final int angle = dataLabelSettingsRenderer._angle + 360; - dataLabelSettingsRenderer._angle = angle; - } - final DataLabelSettings dataLabel = series.dataLabelSettings; - Size? textSize, textSize2, textSize3, textSize4, textSize5; - double? value1, value2; - const int boxPlotPadding = 8; - final Rect rect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); - if (seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle')) { - value1 = ((point.open != null && - point.close != null && - (point.close < point.open) == true) - ? point.close - : point.open) - ?.toDouble(); - value2 = ((point.open != null && - point.close != null && - (point.close > point.open) == true) - ? point.close - : point.open) - ?.toDouble(); - } - final bool transposed = _chartState._requireInvertedAxis; - final bool inversed = seriesRenderer._yAxisRenderer!._axis.isInversed; - final Rect clipRect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); - final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle'); - final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); - if (isBoxSeries) { - value1 = (point.upperQuartile != null && - point.lowerQuartile != null && - point.upperQuartile! < point.lowerQuartile!) - ? point.upperQuartile! - : point.lowerQuartile!; - value2 = (point.upperQuartile != null && - point.lowerQuartile != null && - point.upperQuartile! > point.lowerQuartile!) - ? point.upperQuartile! - : point.lowerQuartile!; - } - // ignore: prefer_final_locals - List labelList = []; - // ignore: prefer_final_locals - String label = point.dataLabelMapper ?? - point.label ?? - _getLabelText( - isRangeSeries - ? (!inversed ? point.high : point.low) - : isBoxSeries - ? (!inversed ? point.maximum : point.minimum) - : ((dataLabel.showCumulativeValues && - point.cumulativeValue != null) - ? point.cumulativeValue - : point.yValue), - seriesRenderer); - if (isRangeSeries) { - point.label2 = point.dataLabelMapper ?? - point.label2 ?? - _getLabelText(!inversed ? point.low : point.high, seriesRenderer); - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType.contains('candle')) { - point.label3 = point.dataLabelMapper ?? - point.label3 ?? - _getLabelText( - (point.open > point.close) == true - ? !inversed - ? point.close - : point.open - : !inversed - ? point.open - : point.close, - seriesRenderer); - point.label4 = point.dataLabelMapper ?? - point.label4 ?? - _getLabelText( - (point.open > point.close) == true - ? !inversed - ? point.open - : point.close - : !inversed - ? point.close - : point.open, - seriesRenderer); - } - } else if (isBoxSeries) { - point.label2 = point.dataLabelMapper ?? - point.label2 ?? - _getLabelText( - !inversed ? point.minimum : point.maximum, seriesRenderer); - point.label3 = point.dataLabelMapper ?? - point.label3 ?? - _getLabelText( - point.lowerQuartile! > point.upperQuartile! - ? !inversed - ? point.upperQuartile - : point.lowerQuartile - : !inversed - ? point.lowerQuartile - : point.upperQuartile, - seriesRenderer); - point.label4 = point.dataLabelMapper ?? - point.label4 ?? - _getLabelText( - point.lowerQuartile! > point.upperQuartile! - ? !inversed - ? point.lowerQuartile - : point.upperQuartile - : !inversed - ? point.upperQuartile - : point.lowerQuartile, - seriesRenderer); - point.label5 = point.dataLabelMapper ?? - point.label5 ?? - _getLabelText(point.median, seriesRenderer); - } - DataLabelRenderArgs dataLabelArgs; - TextStyle? dataLabelStyle = dataLabelSettingsRenderer._textStyle; - //ignore: prefer_conditional_assignment - if (dataLabelSettingsRenderer._originalStyle == null) { - dataLabelSettingsRenderer._originalStyle = dataLabel.textStyle; - } - dataLabelStyle = dataLabelSettingsRenderer._originalStyle; - if (chart.onDataLabelRender != null && - !seriesRenderer._visibleDataPoints![index].labelRenderEvent) { - labelList.add(label); - if (isRangeSeries) { - labelList.add(point.label2!); - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType.contains('candle')) { - labelList.add(point.label3!); - labelList.add(point.label4!); - } - } else if (isBoxSeries) { - labelList.add(point.label2!); - labelList.add(point.label3!); - labelList.add(point.label4!); - labelList.add(point.label5!); + DataLabelSettings dataLabel, + ChartLocation chartLocation, + ChartLocation? chartLocation2, + Size textSize) { + final SfCartesianChart chart = stateProperties.chart; + final XyDataSeries series = + seriesRendererDetails.series as XyDataSeries; + final bool transposed = stateProperties.requireInvertedAxis; + final bool isRangeSeries = + seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true; + final bool isBoxSeries = + seriesRendererDetails.seriesType.contains('boxandwhisker'); + final double alignmentValue = textSize.height + + (series.markerSettings.isVisible + ? ((series.markerSettings.borderWidth * 2) + + series.markerSettings.height) + : 0); + if ((seriesRendererDetails.seriesType.contains('bar') == true && + !chart.isTransposed) || + (seriesRendererDetails.seriesType.contains('column') == true && + chart.isTransposed) || + (seriesRendererDetails.seriesType.contains('waterfall') == true && + chart.isTransposed) || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + isBoxSeries) { + chartLocation.x = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) + ? chartLocation.x + : _calculateAlignment( + alignmentValue, + chartLocation.x, + dataLabel.alignment, + (isRangeSeries + ? point.high + : isBoxSeries + ? point.maximum + : point.yValue) < + 0, + transposed); + if (isRangeSeries || isBoxSeries) { + chartLocation2!.x = + (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) + ? chartLocation2.x + : _calculateAlignment( + alignmentValue, + chartLocation2.x, + dataLabel.alignment, + (isRangeSeries + ? point.low + : isBoxSeries + ? point.minimum + : point.yValue) < + 0, + transposed); } - seriesRenderer._visibleDataPoints![index].labelRenderEvent = true; - for (int i = 0; i < labelList.length; i++) { - dataLabelArgs = DataLabelRenderArgs( - seriesRenderer._series, - seriesRenderer._dataPoints, - index, - seriesRenderer._visibleDataPoints![index].overallDataPointIndex); - dataLabelArgs.text = labelList[i]; - dataLabelArgs.textStyle = dataLabelStyle!; - dataLabelArgs.color = seriesRenderer._series.dataLabelSettings.color; - chart.onDataLabelRender!(dataLabelArgs); - labelList[i] = dataLabelArgs.text; - index = dataLabelArgs.pointIndex!; - point._dataLabelStyle = dataLabelArgs.textStyle; - point._dataLabelColor = dataLabelArgs.color; - dataLabelSettingsRenderer._offset = dataLabelArgs.offset; + } else { + chartLocation.y = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) + ? chartLocation.y + : _calculateAlignment( + alignmentValue, + chartLocation.y, + dataLabel.alignment, + (isRangeSeries + ? point.high + : isBoxSeries + ? point.maximum + : point.yValue) < + 0, + transposed); + if (isRangeSeries || isBoxSeries) { + chartLocation2!.y = + (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) + ? chartLocation2.y + : _calculateAlignment( + alignmentValue, + chartLocation2.y, + dataLabel.alignment, + (isRangeSeries + ? point.low + : isBoxSeries + ? point.minimum + : point.yValue) < + 0, + transposed); } } - dataLabelSettingsRenderer._textStyle = dataLabelStyle; - if (chart.onDataLabelRender != null) { - dataLabelSettingsRenderer._color = point._dataLabelColor; - dataLabelSettingsRenderer._textStyle = point._dataLabelStyle; - dataLabelStyle = dataLabelSettingsRenderer._textStyle!; - } - // ignore: unnecessary_null_comparison - if (point != null && - point.isVisible && - point.isGap != true && - (point.y != 0 || dataLabel.showZeroValue)) { - final double markerPointX = dataLabel.builder == null - ? seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle' || - isBoxSeries - ? seriesRenderer._chartState!._requireInvertedAxis - ? point.region!.centerRight.dx - : point.region!.topCenter.dx - : point.markerPoint!.x - : templateLocation!.dx; - final double markerPointY = dataLabel.builder == null - ? seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle' || - isBoxSeries - ? seriesRenderer._chartState!._requireInvertedAxis - ? point.region!.centerRight.dy - : point.region!.topCenter.dy - : point.markerPoint!.y - : templateLocation!.dy; - final _ChartLocation markerPoint2 = _calculatePoint( - point.xValue, - seriesRenderer._yAxisRenderer!._axis.isInversed ? value2 : value1, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - series, - rect); - final _ChartLocation markerPoint3 = _calculatePoint( - point.xValue, - seriesRenderer._yAxisRenderer!._axis.isInversed ? value1 : value2, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - series, - rect); - final TextStyle font = (dataLabelSettingsRenderer._textStyle == null) - ? const TextStyle( - fontFamily: 'Roboto', - fontStyle: FontStyle.normal, - fontWeight: FontWeight.normal, - fontSize: 12) - : dataLabelStyle!; - point.label = labelList.isNotEmpty ? labelList[0] : label; - if (label.isNotEmpty) { - _ChartLocation? chartLocation, - chartLocation2, - chartLocation3, - chartLocation4, - chartLocation5; - textSize = - dataLabel.builder == null ? measureText(label, font) : templateSize!; - chartLocation = _ChartLocation(markerPointX, markerPointY); - if (isRangeSeries || isBoxSeries) { - point.label2 = labelList.isNotEmpty ? labelList[1] : point.label2; - textSize2 = dataLabel.builder == null - ? measureText(point.label2!, font) - : templateSize!; - chartLocation2 = _ChartLocation( - dataLabel.builder == null - ? seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle' || - isBoxSeries - ? seriesRenderer._chartState!._requireInvertedAxis - ? point.region!.centerLeft.dx - : point.region!.bottomCenter.dx - : point.markerPoint2!.x - : templateLocation!.dx, - dataLabel.builder == null - ? seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle' || - isBoxSeries - ? seriesRenderer._chartState!._requireInvertedAxis - ? point.region!.centerLeft.dy - : point.region!.bottomCenter.dy - : point.markerPoint2!.y - : templateLocation!.dy); - if (isBoxSeries) { - if (!seriesRenderer._chartState!._requireInvertedAxis) { - chartLocation.y = chartLocation.y - boxPlotPadding; - chartLocation2.y = chartLocation2.y + boxPlotPadding; - } else { - chartLocation.x = chartLocation.x + boxPlotPadding; - chartLocation2.x = chartLocation2.x - boxPlotPadding; - } - } - } - final List<_ChartLocation?> alignedLabelLocations = - _getAlignedLabelLocations(_chartState, seriesRenderer, point, - dataLabel, chartLocation, chartLocation2, textSize); - chartLocation = alignedLabelLocations[0]; - chartLocation2 = alignedLabelLocations[1]; - if (!seriesRenderer._seriesType.contains('column') && - !seriesRenderer._seriesType.contains('waterfall') && - !seriesRenderer._seriesType.contains('bar') && - !seriesRenderer._seriesType.contains('histogram') && - !seriesRenderer._seriesType.contains('rangearea') && - !seriesRenderer._seriesType.contains('hilo') && - !seriesRenderer._seriesType.contains('candle') && - !isBoxSeries) { - chartLocation!.y = _calculatePathPosition( - chartLocation.y, - dataLabel.labelAlignment, - textSize, - dataLabel.borderWidth, - seriesRenderer, - index, - transposed, - chartLocation, - _chartState, - point, - Size( - series.markerSettings.isVisible - ? series.markerSettings.width / 2 - : 0, - series.markerSettings.isVisible - ? series.markerSettings.height / 2 - : 0)); - } else { - final List<_ChartLocation?> _locations = _getLabelLocations( - index, - _chartState, - seriesRenderer, - point, - dataLabel, - chartLocation, - chartLocation2, - textSize, - textSize2); - chartLocation = _locations[0]; - chartLocation2 = _locations[1]; - } - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType.contains('candle') || - isBoxSeries) { - if (!isBoxSeries) { - point.label3 = labelList.isNotEmpty ? labelList[2] : point.label3; - point.label4 = labelList.isNotEmpty ? labelList[3] : point.label4; - // point.label3 = point.dataLabelMapper ?? - // _getLabelText( - // (point.open > point.close) == true - // ? !inversed - // ? point.close - // : point.open - // : !inversed - // ? point.open - // : point.close, - // seriesRenderer); - // point.label4 = point.dataLabelMapper ?? - // _getLabelText( - // (point.open > point.close) == true - // ? !inversed - // ? point.open - // : point.close - // : !inversed - // ? point.close - // : point.open, - // seriesRenderer); - } else { - point.label3 = labelList.isNotEmpty ? labelList[2] : point.label3; - point.label4 = labelList.isNotEmpty ? labelList[3] : point.label4; - point.label5 = labelList.isNotEmpty ? labelList[4] : point.label5; - // point.label3 = point.dataLabelMapper ?? - // _getLabelText( - // point.lowerQuartile! > point.upperQuartile! - // ? !inversed - // ? point.upperQuartile - // : point.lowerQuartile - // : !inversed - // ? point.lowerQuartile - // : point.upperQuartile, - // seriesRenderer); - // point.label4 = point.dataLabelMapper ?? - // _getLabelText( - // point.lowerQuartile! > point.upperQuartile! - // ? !inversed - // ? point.lowerQuartile - // : point.upperQuartile - // : !inversed - // ? point.upperQuartile - // : point.lowerQuartile, - // seriesRenderer); - // point.label5 = point.dataLabelMapper ?? - // _getLabelText(point.median, seriesRenderer); - } - textSize3 = dataLabel.builder == null - ? measureText(point.label3!, font) - : templateSize; - if (seriesRenderer._seriesType.contains('hilo')) { - chartLocation3 = (point.open > point.close) == true - ? _ChartLocation(point.centerClosePoint!.x + textSize3!.width, - point.closePoint!.y) - : _ChartLocation(point.centerOpenPoint!.x - textSize3!.width, - point.openPoint!.y); - } else if (seriesRenderer._seriesType == 'candle' && - seriesRenderer._chartState!._requireInvertedAxis) { - chartLocation3 = (point.open > point.close) == true - ? _ChartLocation(point.closePoint!.x, markerPoint2.y + 1) - : _ChartLocation(point.openPoint!.x, markerPoint2.y + 1); - } else if (isBoxSeries) { - chartLocation3 = (seriesRenderer._chartState!._requireInvertedAxis) - ? _ChartLocation(point.lowerQuartilePoint!.x + boxPlotPadding, - markerPoint2.y + 1) - : _ChartLocation( - point.region!.topCenter.dx, markerPoint2.y - boxPlotPadding); - } else { - chartLocation3 = - _ChartLocation(point.region!.topCenter.dx, markerPoint2.y); - } - textSize4 = dataLabel.builder == null - ? measureText(point.label4!, font) - : templateSize; - if (seriesRenderer._seriesType.contains('hilo')) { - chartLocation4 = (point.open > point.close) == true - ? _ChartLocation(point.centerOpenPoint!.x - textSize4!.width, - point.openPoint!.y) - : _ChartLocation(point.centerClosePoint!.x + textSize4!.width, - point.closePoint!.y); - } else if (seriesRenderer._seriesType == 'candle' && - seriesRenderer._chartState!._requireInvertedAxis) { - chartLocation4 = (point.open > point.close) == true - ? _ChartLocation(point.openPoint!.x, markerPoint3.y + 1) - : _ChartLocation(point.closePoint!.x, markerPoint3.y + 1); - } else if (isBoxSeries) { - chartLocation4 = (seriesRenderer._chartState!._requireInvertedAxis) - ? _ChartLocation(point.upperQuartilePoint!.x - boxPlotPadding, - markerPoint3.y + 1) - : _ChartLocation(point.region!.bottomCenter.dx, - markerPoint3.y + boxPlotPadding); - } else { - chartLocation4 = - _ChartLocation(point.region!.bottomCenter.dx, markerPoint3.y + 1); - } - if (isBoxSeries) { - textSize5 = measureText(point.label5!, font); - chartLocation5 = (!seriesRenderer._chartState!._requireInvertedAxis) - ? _ChartLocation( - point.centerMedianPoint!.x, point.centerMedianPoint!.y) - : _ChartLocation( - point.centerMedianPoint!.x, point.centerMedianPoint!.y); - } - final List<_ChartLocation?> alignedLabelLocations2 = - _getAlignedLabelLocations(_chartState, seriesRenderer, point, - dataLabel, chartLocation3, chartLocation4, textSize3!); - chartLocation3 = alignedLabelLocations2[0]; - chartLocation4 = alignedLabelLocations2[1]; - final List<_ChartLocation?> _locations = _getLabelLocations( - index, - _chartState, - seriesRenderer, - point, - dataLabel, - chartLocation3, - chartLocation4, - textSize3, - textSize4!); - chartLocation3 = _locations[0]; - chartLocation4 = _locations[1]; - } - _calculateDataLabelRegion( - point, - dataLabel, - _chartState, - chartLocation!, - chartLocation2, - isRangeSeries, - clipRect, - textSize, - textSize2, - chartLocation3, - chartLocation4, - chartLocation5, - textSize3, - textSize4, - textSize5, - seriesRenderer, - index); - } + return [chartLocation, chartLocation2]; +} + +///calculating the label location based on dataLabel position value +///(for range and rect series only) +List _getLabelLocations( + int index, + CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails, + CartesianChartPoint point, + DataLabelSettings dataLabel, + ChartLocation? chartLocation, + ChartLocation? chartLocation2, + Size textSize, + Size? textSize2) { + final bool transposed = stateProperties.requireInvertedAxis; + final EdgeInsets margin = dataLabel.margin; + final bool isRangeSeries = + seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true; + final bool isBoxSeries = + seriesRendererDetails.seriesType.contains('boxandwhisker'); + final bool inversed = seriesRendererDetails.yAxisDetails!.axis.isInversed; + final num value = isRangeSeries + ? point.high + : isBoxSeries + ? point.maximum + : point.yValue; + final bool minus = (value < 0 && !inversed) || (!(value < 0) && inversed); + if (!stateProperties.requireInvertedAxis) { + chartLocation!.y = !isBoxSeries + ? _calculateRectPosition( + chartLocation.y, + point.region!, + minus, + isRangeSeries + ? ((dataLabel.labelAlignment == ChartDataLabelAlignment.outer || + dataLabel.labelAlignment == ChartDataLabelAlignment.top) + ? dataLabel.labelAlignment + : ChartDataLabelAlignment.auto) + : dataLabel.labelAlignment, + seriesRendererDetails, + textSize, + dataLabel.borderWidth, + index, + chartLocation, + stateProperties, + transposed, + margin) + : chartLocation.y; + } else { + chartLocation!.x = !isBoxSeries + ? _calculateRectPosition( + chartLocation.x, + point.region!, + minus, + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == + true || + isBoxSeries + ? ChartDataLabelAlignment.auto + : isRangeSeries + ? ((dataLabel.labelAlignment == + ChartDataLabelAlignment.outer || + dataLabel.labelAlignment == + ChartDataLabelAlignment.top) + ? dataLabel.labelAlignment + : ChartDataLabelAlignment.auto) + : dataLabel.labelAlignment, + seriesRendererDetails, + textSize, + dataLabel.borderWidth, + index, + chartLocation, + stateProperties, + transposed, + margin) + : chartLocation.x; } + chartLocation2 = isRangeSeries + ? _getSecondLabelLocation(index, stateProperties, seriesRendererDetails, + point, dataLabel, chartLocation, chartLocation2!, textSize) + : chartLocation2; + return [chartLocation, chartLocation2]; } -///Calculating the label location based on alignment value -List<_ChartLocation?> _getAlignedLabelLocations( - SfCartesianChartState _chartState, - CartesianSeriesRenderer seriesRenderer, +///Finding range series second label location +ChartLocation _getSecondLabelLocation( + int index, + CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, DataLabelSettings dataLabel, - _ChartLocation chartLocation, - _ChartLocation? chartLocation2, + ChartLocation chartLocation, + ChartLocation chartLocation2, Size textSize) { - final SfCartesianChart chart = _chartState._chart; - final XyDataSeries series = - seriesRenderer._series as XyDataSeries; - final bool transposed = _chartState._requireInvertedAxis; - final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle'); - final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); - final double alignmentValue = textSize.height + - (series.markerSettings.isVisible - ? ((series.markerSettings.borderWidth * 2) + - series.markerSettings.height) - : 0); - if ((seriesRenderer._seriesType.contains('bar') && !chart.isTransposed) || - (seriesRenderer._seriesType.contains('column') && chart.isTransposed) || - (seriesRenderer._seriesType.contains('waterfall') && - chart.isTransposed) || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - isBoxSeries) { - chartLocation.x = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation.x - : _calculateAlignment( - alignmentValue, - chartLocation.x, - dataLabel.alignment, - (isRangeSeries - ? point.high - : isBoxSeries - ? point.maximum - : point.yValue) < - 0, - transposed); - if (isRangeSeries || isBoxSeries) { - chartLocation2!.x = - (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation2.x - : _calculateAlignment( - alignmentValue, - chartLocation2.x, - dataLabel.alignment, - (isRangeSeries - ? point.low - : isBoxSeries - ? point.minimum - : point.yValue) < - 0, - transposed); - } - } else { - chartLocation.y = (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation.y - : _calculateAlignment( - alignmentValue, - chartLocation.y, - dataLabel.alignment, - (isRangeSeries - ? point.high - : isBoxSeries - ? point.maximum - : point.yValue) < - 0, - transposed); - if (isRangeSeries || isBoxSeries) { - chartLocation2!.y = - (dataLabel.labelAlignment == ChartDataLabelAlignment.auto) - ? chartLocation2.y - : _calculateAlignment( - alignmentValue, - chartLocation2.y, - dataLabel.alignment, - (isRangeSeries - ? point.low - : isBoxSeries - ? point.minimum - : point.yValue) < - 0, - transposed); - } - } - return <_ChartLocation?>[chartLocation, chartLocation2]; -} - -///calculating the label loaction based on dataLabel position value -///(for range and rect series only) -List<_ChartLocation?> _getLabelLocations( - int index, - SfCartesianChartState _chartState, - CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint point, - DataLabelSettings dataLabel, - _ChartLocation? chartLocation, - _ChartLocation? chartLocation2, - Size textSize, - Size? textSize2) { - final bool transposed = _chartState._requireInvertedAxis; - final EdgeInsets margin = dataLabel.margin; - final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle'); - final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); - final bool inversed = seriesRenderer._yAxisRenderer!._axis.isInversed; - final num value = isRangeSeries - ? point.high - : isBoxSeries - ? point.maximum - : point.yValue; - final bool minus = (value < 0 && !inversed) || (!(value < 0) && inversed); - if (!_chartState._requireInvertedAxis) { - chartLocation!.y = !isBoxSeries - ? _calculateRectPosition( - chartLocation.y, - point.region!, - minus, - isRangeSeries - ? ((dataLabel.labelAlignment == ChartDataLabelAlignment.outer || - dataLabel.labelAlignment == ChartDataLabelAlignment.top) - ? dataLabel.labelAlignment - : ChartDataLabelAlignment.auto) - : dataLabel.labelAlignment, - seriesRenderer, - textSize, - dataLabel.borderWidth, - index, - chartLocation, - _chartState, - transposed, - margin) - : chartLocation.y; - } else { - chartLocation!.x = !isBoxSeries - ? _calculateRectPosition( - chartLocation.x, - point.region!, - minus, - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - isBoxSeries - ? ChartDataLabelAlignment.auto - : isRangeSeries - ? ((dataLabel.labelAlignment == - ChartDataLabelAlignment.outer || - dataLabel.labelAlignment == - ChartDataLabelAlignment.top) - ? dataLabel.labelAlignment - : ChartDataLabelAlignment.auto) - : dataLabel.labelAlignment, - seriesRenderer, - textSize, - dataLabel.borderWidth, - index, - chartLocation, - _chartState, - transposed, - margin) - : chartLocation.x; - } - chartLocation2 = isRangeSeries - ? _getSecondLabelLocation(index, _chartState, seriesRenderer, point, - dataLabel, chartLocation, chartLocation2!, textSize) - : chartLocation2; - return <_ChartLocation?>[chartLocation, chartLocation2]; -} - -///Finding range series second label location -_ChartLocation _getSecondLabelLocation( - int index, - SfCartesianChartState _chartState, - CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint point, - DataLabelSettings dataLabel, - _ChartLocation chartLocation, - _ChartLocation chartLocation2, - Size textSize) { - final bool inversed = seriesRenderer._yAxisRenderer!._axis.isInversed; - final bool transposed = _chartState._requireInvertedAxis; - final EdgeInsets margin = dataLabel.margin; - bool minus; - - minus = (seriesRenderer._seriesType == 'boxandwhisker') - ? (point.minimum! < 0 && !inversed) || (!(point.minimum! < 0) && inversed) - : ((point.low < 0) == true && !inversed) || - ((point.low < 0) == false && inversed); - - if (!_chartState._requireInvertedAxis) { - chartLocation2.y = _calculateRectPosition( - chartLocation2.y, - point.region!, - minus, - dataLabel.labelAlignment == ChartDataLabelAlignment.top - ? ChartDataLabelAlignment.auto - : ChartDataLabelAlignment.top, - seriesRenderer, - textSize, - dataLabel.borderWidth, - index, - chartLocation, - _chartState, - transposed, - margin); + final bool inversed = seriesRendererDetails.yAxisDetails!.axis.isInversed; + final bool transposed = stateProperties.requireInvertedAxis; + final EdgeInsets margin = dataLabel.margin; + bool minus; + + minus = (seriesRendererDetails.seriesType == 'boxandwhisker') + ? (point.minimum! < 0 && !inversed) || (!(point.minimum! < 0) && inversed) + : ((point.low < 0) == true && !inversed) || + ((point.low < 0) == false && inversed); + + if (!stateProperties.requireInvertedAxis) { + chartLocation2.y = _calculateRectPosition( + chartLocation2.y, + point.region!, + minus, + dataLabel.labelAlignment == ChartDataLabelAlignment.top + ? ChartDataLabelAlignment.auto + : ChartDataLabelAlignment.top, + seriesRendererDetails, + textSize, + dataLabel.borderWidth, + index, + chartLocation, + stateProperties, + transposed, + margin); } else { chartLocation2.x = _calculateRectPosition( chartLocation2.x, @@ -684,85 +245,86 @@ _ChartLocation _getSecondLabelLocation( dataLabel.labelAlignment == ChartDataLabelAlignment.top ? ChartDataLabelAlignment.auto : ChartDataLabelAlignment.top, - seriesRenderer, + seriesRendererDetails, textSize, dataLabel.borderWidth, index, chartLocation, - _chartState, + stateProperties, transposed, margin); } return chartLocation2; } -///Setting datalabel region +///Setting data label region void _calculateDataLabelRegion( CartesianChartPoint point, DataLabelSettings dataLabel, - SfCartesianChartState _chartState, - _ChartLocation chartLocation, - _ChartLocation? chartLocation2, + CartesianStateProperties stateProperties, + ChartLocation chartLocation, + ChartLocation? chartLocation2, bool isRangeSeries, Rect clipRect, Size textSize, Size? textSize2, - _ChartLocation? chartLocation3, - _ChartLocation? chartLocation4, - _ChartLocation? chartLocation5, + ChartLocation? chartLocation3, + ChartLocation? chartLocation4, + ChartLocation? chartLocation5, Size? textSize3, Size? textSize4, Size? textSize5, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, int index) { final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; + seriesRendererDetails.dataLabelSettingsRenderer; Rect? rect, rect2, rect3, rect4, rect5; final EdgeInsets margin = dataLabel.margin; - final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); + final bool isBoxSeries = + seriesRendererDetails.seriesType.contains('boxandwhisker'); rect = _calculateLabelRect(chartLocation, textSize, margin, - dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); + dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); // if angle is given label will - rect = ((index == 0 || index == seriesRenderer._dataPoints.length - 1) && - (dataLabelSettingsRenderer._angle / 90) % 2 == 1 && - !_chartState._requireInvertedAxis) - ? rect - : (dataLabelSettingsRenderer._angle / 90) % 2 == 1 + rect = + ((index == 0 || index == seriesRendererDetails.dataPoints.length - 1) && + (dataLabelSettingsRenderer.angle / 90) % 2 == 1 && + !stateProperties.requireInvertedAxis) ? rect - : _validateRect(rect, clipRect); + : (dataLabelSettingsRenderer.angle / 90) % 2 == 1 + ? rect + : _validateRect(rect, clipRect); if (isRangeSeries || isBoxSeries) { rect2 = _calculateLabelRect(chartLocation2!, textSize2!, margin, - dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); + dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); rect2 = _validateRect(rect2, clipRect); } - if ((seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('hilo') || + if ((seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || isBoxSeries) && (chartLocation3 != null || chartLocation4 != null || chartLocation5 != null)) { rect3 = _calculateLabelRect(chartLocation3!, textSize3!, margin, - dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); + dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); rect3 = _validateRect(rect3, clipRect); rect4 = _calculateLabelRect(chartLocation4!, textSize4!, margin, - dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); + dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); rect4 = _validateRect(rect4, clipRect); if (isBoxSeries) { rect5 = _calculateLabelRect(chartLocation5!, textSize5!, margin, - dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor); + dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor); rect5 = _validateRect(rect5, clipRect); } } - if (dataLabelSettingsRenderer._color != null || + if (dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor || // ignore: unnecessary_null_comparison (dataLabel.borderColor != null && dataLabel.borderWidth > 0)) { final RRect fillRect = _calculatePaddedFillRect(rect, dataLabel.borderRadius, margin); - point.labelLocation = _ChartLocation( - fillRect.center.dx - textSize.width / 2, + point.labelLocation = ChartLocation(fillRect.center.dx - textSize.width / 2, fillRect.center.dy - textSize.height / 2); point.dataLabelRegion = Rect.fromLTWH(point.labelLocation!.x, point.labelLocation!.y, textSize.width, textSize.height); @@ -770,32 +332,32 @@ void _calculateDataLabelRegion( point.labelFillRect = fillRect; } else { final Rect rect = fillRect.middleRect; - if (seriesRenderer._seriesType == 'candle' && - _chartState._requireInvertedAxis && + if (seriesRendererDetails.seriesType == 'candle' && + stateProperties.requireInvertedAxis && (point.close > point.high) == true) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left - rect.width - textSize.width, rect.top + rect.height / 2 - textSize.height / 2); } else if (isBoxSeries && - _chartState._requireInvertedAxis && + stateProperties.requireInvertedAxis && point.upperQuartile! > point.maximum!) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left - rect.width - textSize.width, rect.top + rect.height / 2 - textSize.height / 2); - } else if (seriesRenderer._seriesType == 'candle' && - !_chartState._requireInvertedAxis && + } else if (seriesRendererDetails.seriesType == 'candle' && + !stateProperties.requireInvertedAxis && (point.close > point.high) == true) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height + textSize.height); } else if (isBoxSeries && - !_chartState._requireInvertedAxis && + !stateProperties.requireInvertedAxis && point.upperQuartile! > point.maximum!) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height + textSize.height); } else { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height / 2 - textSize.height / 2); } @@ -806,7 +368,7 @@ void _calculateDataLabelRegion( if (isRangeSeries || isBoxSeries) { final RRect fillRect2 = _calculatePaddedFillRect(rect2!, dataLabel.borderRadius, margin); - point.labelLocation2 = _ChartLocation( + point.labelLocation2 = ChartLocation( fillRect2.center.dx - textSize2!.width / 2, fillRect2.center.dy - textSize2.height / 2); point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2!.x, @@ -815,7 +377,7 @@ void _calculateDataLabelRegion( point.labelFillRect2 = fillRect2; } else { final Rect rect2 = fillRect2.middleRect; - point.labelLocation2 = _ChartLocation( + point.labelLocation2 = ChartLocation( rect2.left + rect2.width / 2 - textSize2.width / 2, rect2.top + rect2.height / 2 - textSize2.height / 2); point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2!.x, @@ -823,12 +385,12 @@ void _calculateDataLabelRegion( point.labelFillRect2 = _rectToRrect(rect2, dataLabel.borderRadius); } } - if (seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('hilo') || + if (seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || isBoxSeries && (rect3 != null || rect4 != null || rect5 != null)) { final RRect fillRect3 = _calculatePaddedFillRect(rect3!, dataLabel.borderRadius, margin); - point.labelLocation3 = _ChartLocation( + point.labelLocation3 = ChartLocation( fillRect3.center.dx - textSize3!.width / 2, fillRect3.center.dy - textSize3.height / 2); point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3!.x, @@ -837,7 +399,7 @@ void _calculateDataLabelRegion( point.labelFillRect3 = fillRect3; } else { final Rect rect3 = fillRect3.middleRect; - point.labelLocation3 = _ChartLocation( + point.labelLocation3 = ChartLocation( rect3.left + rect3.width / 2 - textSize3.width / 2, rect3.top + rect3.height / 2 - textSize3.height / 2); point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3!.x, @@ -846,7 +408,7 @@ void _calculateDataLabelRegion( } final RRect fillRect4 = _calculatePaddedFillRect(rect4!, dataLabel.borderRadius, margin); - point.labelLocation4 = _ChartLocation( + point.labelLocation4 = ChartLocation( fillRect4.center.dx - textSize4!.width / 2, fillRect4.center.dy - textSize4.height / 2); point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4!.x, @@ -855,7 +417,7 @@ void _calculateDataLabelRegion( point.labelFillRect4 = fillRect4; } else { final Rect rect4 = fillRect4.middleRect; - point.labelLocation4 = _ChartLocation( + point.labelLocation4 = ChartLocation( rect4.left + rect4.width / 2 - textSize4.width / 2, rect4.top + rect4.height / 2 - textSize4.height / 2); point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4!.x, @@ -865,7 +427,7 @@ void _calculateDataLabelRegion( if (isBoxSeries) { final RRect fillRect5 = _calculatePaddedFillRect(rect5!, dataLabel.borderRadius, margin); - point.labelLocation5 = _ChartLocation( + point.labelLocation5 = ChartLocation( fillRect5.center.dx - textSize5!.width / 2, fillRect5.center.dy - textSize5.height / 2); point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5!.x, @@ -874,7 +436,7 @@ void _calculateDataLabelRegion( point.labelFillRect5 = fillRect5; } else { final Rect rect5 = fillRect5.middleRect; - point.labelLocation5 = _ChartLocation( + point.labelLocation5 = ChartLocation( rect5.left + rect5.width / 2 - textSize5.width / 2, rect5.top + rect5.height / 2 - textSize5.height / 2); point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5!.x, @@ -884,86 +446,86 @@ void _calculateDataLabelRegion( } } } else { - if (seriesRenderer._seriesType == 'candle' && - _chartState._requireInvertedAxis && + if (seriesRendererDetails.seriesType == 'candle' && + stateProperties.requireInvertedAxis && (point.close > point.high) == true) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left - rect.width - textSize.width - 2, rect.top + rect.height / 2 - textSize.height / 2); } else if (isBoxSeries && - _chartState._requireInvertedAxis && + stateProperties.requireInvertedAxis && point.upperQuartile! > point.maximum!) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left - rect.width - textSize.width - 2, rect.top + rect.height / 2 - textSize.height / 2); - } else if (seriesRenderer._seriesType == 'candle' && - !_chartState._requireInvertedAxis && + } else if (seriesRendererDetails.seriesType == 'candle' && + !stateProperties.requireInvertedAxis && (point.close > point.high) == true) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height + textSize.height / 2); } else if (isBoxSeries && - !_chartState._requireInvertedAxis && + !stateProperties.requireInvertedAxis && point.upperQuartile! > point.maximum!) { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height + textSize.height / 2); } else { - point.labelLocation = _ChartLocation( + point.labelLocation = ChartLocation( rect.left + rect.width / 2 - textSize.width / 2, rect.top + rect.height / 2 - textSize.height / 2); } point.dataLabelRegion = Rect.fromLTWH(point.labelLocation!.x, point.labelLocation!.y, textSize.width, textSize.height); if (isRangeSeries || isBoxSeries) { - if (seriesRenderer._seriesType == 'candle' && - _chartState._requireInvertedAxis && + if (seriesRendererDetails.seriesType == 'candle' && + stateProperties.requireInvertedAxis && (point.close > point.high) == true) { - point.labelLocation2 = _ChartLocation( + point.labelLocation2 = ChartLocation( rect2!.left + rect2.width + textSize2!.width + 2, rect2.top + rect2.height / 2 - textSize2.height / 2); } else if (isBoxSeries && - _chartState._requireInvertedAxis && + stateProperties.requireInvertedAxis && point.upperQuartile! > point.maximum!) { - point.labelLocation2 = _ChartLocation( + point.labelLocation2 = ChartLocation( rect2!.left + rect2.width + textSize2!.width + 2, rect2.top + rect2.height / 2 - textSize2.height / 2); - } else if (seriesRenderer._seriesType == 'candle' && - !_chartState._requireInvertedAxis && + } else if (seriesRendererDetails.seriesType == 'candle' && + !stateProperties.requireInvertedAxis && (point.close > point.high) == true) { - point.labelLocation2 = _ChartLocation( + point.labelLocation2 = ChartLocation( rect2!.left + rect2.width / 2 - textSize2!.width / 2, rect2.top - rect2.height - textSize2.height); } else if (isBoxSeries && - !_chartState._requireInvertedAxis && + !stateProperties.requireInvertedAxis && point.upperQuartile! > point.maximum!) { - point.labelLocation2 = _ChartLocation( + point.labelLocation2 = ChartLocation( rect2!.left + rect2.width / 2 - textSize2!.width / 2, rect2.top - rect2.height - textSize2.height); } else { - point.labelLocation2 = _ChartLocation( + point.labelLocation2 = ChartLocation( rect2!.left + rect2.width / 2 - textSize2!.width / 2, rect2.top + rect2.height / 2 - textSize2.height / 2); } point.dataLabelRegion2 = Rect.fromLTWH(point.labelLocation2!.x, point.labelLocation2!.y, textSize2.width, textSize2.height); } - if ((seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('hilo') || + if ((seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || isBoxSeries) && (rect3 != null || rect4 != null)) { - point.labelLocation3 = _ChartLocation( + point.labelLocation3 = ChartLocation( rect3!.left + rect3.width / 2 - textSize3!.width / 2, rect3.top + rect3.height / 2 - textSize3.height / 2); point.dataLabelRegion3 = Rect.fromLTWH(point.labelLocation3!.x, point.labelLocation3!.y, textSize3.width, textSize3.height); - point.labelLocation4 = _ChartLocation( + point.labelLocation4 = ChartLocation( rect4!.left + rect4.width / 2 - textSize4!.width / 2, rect4.top + rect4.height / 2 - textSize4.height / 2); point.dataLabelRegion4 = Rect.fromLTWH(point.labelLocation4!.x, point.labelLocation4!.y, textSize4.width, textSize4.height); if (rect5 != null) { - point.labelLocation5 = _ChartLocation( + point.labelLocation5 = ChartLocation( rect5.left + rect5.width / 2 - textSize5!.width / 2, rect5.top + rect5.height / 2 - textSize5.height / 2); point.dataLabelRegion5 = Rect.fromLTWH(point.labelLocation5!.x, @@ -979,32 +541,32 @@ double _calculatePathPosition( ChartDataLabelAlignment position, Size size, double borderWidth, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, int index, bool inverted, - _ChartLocation point, - SfCartesianChartState _chartState, + ChartLocation point, + CartesianStateProperties stateProperties, CartesianChartPoint currentPoint, Size markerSize) { final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; const double padding = 5; final bool needFill = series.dataLabelSettings.color != null || series.dataLabelSettings.color != Colors.transparent || series.dataLabelSettings.useSeriesColor; final num fillSpace = needFill ? padding : 0; - if (seriesRenderer._seriesType.contains('area') && - !seriesRenderer._seriesType.contains('rangearea') && - seriesRenderer._yAxisRenderer!._axis.isInversed) { + if (seriesRendererDetails.seriesType.contains('area') == true && + seriesRendererDetails.seriesType.contains('rangearea') == false && + seriesRendererDetails.yAxisDetails!.axis.isInversed == true) { position = position == ChartDataLabelAlignment.top ? ChartDataLabelAlignment.bottom : (position == ChartDataLabelAlignment.bottom ? ChartDataLabelAlignment.top : position); } - position = (_chartState._chartSeries.visibleSeriesRenderers.length == 1 && - (seriesRenderer._seriesType == 'stackedarea100' || - seriesRenderer._seriesType == 'stackedline100') && + position = (stateProperties.chartSeries.visibleSeriesRenderers.length == 1 && + (seriesRendererDetails.seriesType == 'stackedarea100' || + seriesRendererDetails.seriesType == 'stackedline100') && position == ChartDataLabelAlignment.auto) ? ChartDataLabelAlignment.bottom : position; @@ -1028,15 +590,15 @@ double _calculatePathPosition( break; case ChartDataLabelAlignment.auto: labelLocation = _calculatePathActualPosition( - seriesRenderer, + seriesRendererDetails, size, index, inverted, borderWidth, point, - _chartState, + stateProperties, currentPoint, - seriesRenderer._yAxisRenderer!._axis.isInversed); + seriesRendererDetails.yAxisDetails!.axis.isInversed); break; case ChartDataLabelAlignment.middle: break; @@ -1067,23 +629,23 @@ double _calculateAlignment(double value, double labelLocation, ///Calculate label position for non rect series double _calculatePathActualPosition( - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, Size size, int index, bool inverted, double borderWidth, - _ChartLocation point, - SfCartesianChartState _chartState, + ChartLocation point, + CartesianStateProperties stateProperties, CartesianChartPoint currentPoint, bool inversed) { final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; late double yLocation; bool isBottom, isOverLap = true; Rect labelRect; int positionIndex; final ChartDataLabelAlignment position = - _getActualPathDataLabelAlignment(seriesRenderer, index, inversed); + _getActualPathDataLabelAlignment(seriesRendererDetails, index, inversed); isBottom = position == ChartDataLabelAlignment.bottom; final List dataLabelPosition = List.filled(5, null); dataLabelPosition[0] = 'DataLabelPosition.Outer'; @@ -1098,24 +660,24 @@ double _calculatePathActualPosition( position, size, borderWidth, - seriesRenderer, + seriesRendererDetails, index, inverted, point, - _chartState, + stateProperties, currentPoint, Size( series.markerSettings.width / 2, series.markerSettings.height / 2)); labelRect = _calculateLabelRect( - _ChartLocation(point.x, yLocation), + ChartLocation(point.x, yLocation), size, series.dataLabelSettings.margin, series.dataLabelSettings.color != null || series.dataLabelSettings.useSeriesColor); isOverLap = labelRect.top < 0 || ((labelRect.top + labelRect.height) > - _chartState._chartAxis._axisClipRect.height) || - _findingCollision(labelRect, _chartState._renderDatalabelRegions); + stateProperties.chartAxis.axisClipRect.height) || + findingCollision(labelRect, stateProperties.renderDatalabelRegions); positionIndex = isBottom ? positionIndex - 1 : positionIndex + 1; isBottom = false; } @@ -1124,15 +686,17 @@ double _calculatePathActualPosition( /// Finding the label position for non rect series ChartDataLabelAlignment _getActualPathDataLabelAlignment( - CartesianSeriesRenderer seriesRenderer, int index, bool inversed) { - final List> points = seriesRenderer._dataPoints; + SeriesRendererDetails seriesRendererDetails, int index, bool inversed) { + final List> points = + seriesRendererDetails.dataPoints; final num yValue = points[index].yValue; final CartesianChartPoint? _nextPoint = points.length - 1 > index ? points[index + 1] : null; final CartesianChartPoint? previousPoint = index > 0 ? points[index - 1] : null; ChartDataLabelAlignment position; - if (seriesRenderer._seriesType == 'bubble' || index == points.length - 1) { + if (seriesRendererDetails.seriesType == 'bubble' || + index == points.length - 1) { position = ChartDataLabelAlignment.top; } else { if (index == 0) { @@ -1197,7 +761,7 @@ ChartDataLabelAlignment _getPosition(int position) { /// getting label rect Rect _calculateLabelRect( - _ChartLocation location, Size textSize, EdgeInsets margin, bool needRect) { + ChartLocation location, Size textSize, EdgeInsets margin, bool needRect) { return needRect ? Rect.fromLTWH( location.x - (textSize.width / 2) - margin.left, @@ -1209,10 +773,10 @@ Rect _calculateLabelRect( } /// Below method is for Rendering data label -void _drawDataLabel( +void drawDataLabel( Canvas canvas, - CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, DataLabelSettings dataLabel, CartesianChartPoint point, int index, @@ -1220,24 +784,25 @@ void _drawDataLabel( DataLabelSettingsRenderer dataLabelSettingsRenderer) { double x = 0; double y = 0; - if (dataLabelSettingsRenderer._offset != null) { - x = dataLabelSettingsRenderer._offset!.dx; - y = dataLabelSettingsRenderer._offset!.dy; + if (dataLabelSettingsRenderer.offset != null) { + x = dataLabelSettingsRenderer.offset!.dx; + y = dataLabelSettingsRenderer.offset!.dy; } final double opacity = // ignore: unnecessary_null_comparison - seriesRenderer._needAnimateSeriesElements && dataLabelAnimation != null + seriesRendererDetails.needAnimateSeriesElements == true && + dataLabelAnimation != null ? dataLabelAnimation.value : 1; TextStyle? dataLabelStyle; final String? label = point.label; - dataLabelStyle = dataLabelSettingsRenderer._textStyle; + dataLabelStyle = dataLabelSettingsRenderer.textStyle; if (label != null && // ignore: unnecessary_null_comparison point != null && point.isVisible && point.isGap != true && - _isLabelWithinRange(seriesRenderer, point)) { + isLabelWithinRange(seriesRendererDetails, point)) { final TextStyle font = (dataLabelStyle == null) ? const TextStyle( fontFamily: 'Roboto', @@ -1246,16 +811,16 @@ void _drawDataLabel( fontSize: 12) : dataLabelStyle; final Color fontColor = font.color ?? - _getDataLabelSaturationColor( - point, seriesRenderer, _chartState, dataLabelSettingsRenderer); + getDataLabelSaturationColor(point, seriesRendererDetails, + stateProperties, dataLabelSettingsRenderer); final Rect labelRect = (point.labelFillRect != null) ? Rect.fromLTWH(point.labelFillRect!.left, point.labelFillRect!.top, point.labelFillRect!.width, point.labelFillRect!.height) : Rect.fromLTWH(point.labelLocation!.x, point.labelLocation!.y, point.dataLabelRegion!.width, point.dataLabelRegion!.height); - final bool isDatalabelCollide = (_chartState._requireInvertedAxis || - (dataLabelSettingsRenderer._angle / 90) % 2 != 1) && - _findingCollision(labelRect, _chartState._renderDatalabelRegions); + final bool isDatalabelCollide = (stateProperties.requireInvertedAxis || + (dataLabelSettingsRenderer.angle / 90) % 2 != 1) && + findingCollision(labelRect, stateProperties.renderDatalabelRegions); if (!(label.isNotEmpty && isDatalabelCollide) // ignore: unnecessary_null_comparison || @@ -1283,30 +848,45 @@ void _drawDataLabel( decorationThickness: font.decorationThickness, debugLabel: font.debugLabel, fontFamilyFallback: font.fontFamilyFallback); - _drawDataLabelRectAndText(canvas, seriesRenderer, index, dataLabel, point, - _textStyle, opacity, label, x, y, _chartState, _chartState._chart); - _chartState._renderDatalabelRegions.add(labelRect); + _drawDataLabelRectAndText( + canvas, + seriesRendererDetails, + index, + dataLabel, + point, + _textStyle, + opacity, + label, + x, + y, + stateProperties, + stateProperties.chart); + stateProperties.renderDatalabelRegions.add(labelRect); } } } -void _triggerDataLabelEvent(SfCartesianChart chart, +/// Method to trigger the data label event +void triggerDataLabelEvent(SfCartesianChart chart, List visibleSeriesRenderer, Offset position) { + SeriesRendererDetails seriesRendererDetails; for (int seriesIndex = 0; seriesIndex < visibleSeriesRenderer.length; seriesIndex++) { final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[seriesIndex]; + seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final List>? dataPoints = - seriesRenderer._visibleDataPoints; + seriesRendererDetails.visibleDataPoints; for (int pointIndex = 0; pointIndex < dataPoints!.length; pointIndex++) { - if (seriesRenderer._series.dataLabelSettings.isVisible && + if (seriesRendererDetails.series.dataLabelSettings.isVisible == true && dataPoints[pointIndex].dataLabelRegion != null && dataPoints[pointIndex].dataLabelRegion!.contains(position)) { final CartesianChartPoint point = dataPoints[pointIndex]; final Offset position = Offset(point.labelLocation!.x, point.labelLocation!.y); - _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, + dataLabelTapEvent(chart, seriesRendererDetails.series.dataLabelSettings, pointIndex, point, position, seriesIndex); break; } @@ -1314,10 +894,10 @@ void _triggerDataLabelEvent(SfCartesianChart chart, } } -///Draw the datalabel text and datalabel rect +///Draw the data label text and data label rect void _drawDataLabelRectAndText( Canvas canvas, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, int index, DataLabelSettings dataLabel, CartesianChartPoint point, @@ -1326,36 +906,38 @@ void _drawDataLabelRectAndText( String label, double x, double y, - SfCartesianChartState _chartState, + CartesianStateProperties stateProperties, [SfCartesianChart? chart]) { final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; + seriesRendererDetails.dataLabelSettingsRenderer; final String? label2 = point.dataLabelMapper ?? point.label2; final String? label3 = point.dataLabelMapper ?? point.label3; final String? label4 = point.dataLabelMapper ?? point.label4; final String? label5 = point.dataLabelMapper ?? point.label5; - final bool isRangeSeries = seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle'); - final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); + final bool isRangeSeries = + seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true; + final bool isBoxSeries = + seriesRendererDetails.seriesType.contains('boxandwhisker'); double padding = 0.0; // ignore: unnecessary_null_comparison - if (dataLabelSettingsRenderer._angle != null && - dataLabelSettingsRenderer._angle > 0) { + if (dataLabelSettingsRenderer.angle != null && + dataLabelSettingsRenderer.angle > 0) { final Rect rect = rotatedTextSize( Size(point.dataLabelRegion!.width, point.dataLabelRegion!.height), - dataLabelSettingsRenderer._angle); - if (_chartState._chartAxis._axisClipRect.top > + dataLabelSettingsRenderer.angle); + if (stateProperties.chartAxis.axisClipRect.top > point.dataLabelRegion!.center.dy + rect.top) { padding = (point.dataLabelRegion!.center.dy + rect.top) - - _chartState._chartAxis._axisClipRect.top; - } else if (_chartState._chartAxis._axisClipRect.bottom < + stateProperties.chartAxis.axisClipRect.top; + } else if (stateProperties.chartAxis.axisClipRect.bottom < point.dataLabelRegion!.center.dy + rect.bottom) { padding = (point.dataLabelRegion!.center.dy + rect.bottom) - - _chartState._chartAxis._axisClipRect.bottom; + stateProperties.chartAxis.axisClipRect.bottom; } } - if (dataLabelSettingsRenderer._color != null || + if (dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor || // ignore: unnecessary_null_comparison (dataLabel.borderColor != null && dataLabel.borderWidth > 0)) { @@ -1373,8 +955,8 @@ void _drawDataLabelRectAndText( final Path path4 = Path(); final RRect? fillRect5 = point.labelFillRect5; final Path path5 = Path(); - if (seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || + if (seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || isBoxSeries) { path3.addRRect(fillRect3!); path4.addRRect(fillRect4!); @@ -1398,9 +980,9 @@ void _drawDataLabelRectAndText( canvas.translate(point.dataLabelRegion!.center.dx + x, point.dataLabelRegion!.center.dy - padding); // ignore: unnecessary_null_comparison - if (dataLabelSettingsRenderer._angle != null && - dataLabelSettingsRenderer._angle > 0) { - canvas.rotate((dataLabelSettingsRenderer._angle * math.pi) / 180); + if (dataLabelSettingsRenderer.angle != null && + dataLabelSettingsRenderer.angle > 0) { + canvas.rotate((dataLabelSettingsRenderer.angle * math.pi) / 180); } canvas.translate(-point.dataLabelRegion!.center.dx, -point.dataLabelRegion!.center.dy - y); @@ -1412,8 +994,8 @@ void _drawDataLabelRectAndText( if (point.label2!.isNotEmpty) { canvas.drawPath(path2, strokePaint); } - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType.contains('candle') || + if (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType.contains('candle') == true || isBoxSeries) { if (point.label3!.isNotEmpty) { canvas.drawPath(path3, strokePaint); @@ -1429,17 +1011,17 @@ void _drawDataLabelRectAndText( } } } - if (dataLabelSettingsRenderer._color != null || dataLabel.useSeriesColor) { - Color? seriesColor = seriesRenderer._seriesColor!; - if (seriesRenderer._seriesType == 'waterfall') { - seriesColor = _getWaterfallSeriesColor( - seriesRenderer._series as WaterfallSeries, + if (dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor) { + Color? seriesColor = seriesRendererDetails.seriesColor!; + if (seriesRendererDetails.seriesType == 'waterfall') { + seriesColor = getWaterfallSeriesColor( + seriesRendererDetails.series as WaterfallSeries, point, seriesColor); } final Paint paint = Paint() - ..color = dataLabelSettingsRenderer._color != Colors.transparent - ? ((dataLabelSettingsRenderer._color ?? + ..color = dataLabelSettingsRenderer.color != Colors.transparent + ? ((dataLabelSettingsRenderer.color ?? (point.pointColorMapper ?? seriesColor!)) .withOpacity((opacity - (1 - dataLabel.opacity)) < 0 ? 0 @@ -1450,9 +1032,9 @@ void _drawDataLabelRectAndText( canvas.translate(point.dataLabelRegion!.center.dx + x, point.dataLabelRegion!.center.dy - padding); // ignore: unnecessary_null_comparison - if (dataLabelSettingsRenderer._angle != null && - dataLabelSettingsRenderer._angle > 0) { - canvas.rotate((dataLabelSettingsRenderer._angle * math.pi) / 180); + if (dataLabelSettingsRenderer.angle != null && + dataLabelSettingsRenderer.angle > 0) { + canvas.rotate((dataLabelSettingsRenderer.angle * math.pi) / 180); } canvas.translate(-point.dataLabelRegion!.center.dx, -point.dataLabelRegion!.center.dy - y); @@ -1464,8 +1046,8 @@ void _drawDataLabelRectAndText( if (point.label2!.isNotEmpty) { canvas.drawPath(path2, paint); } - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType.contains('candle') || + if (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType.contains('candle') == true || isBoxSeries) { if (point.label3!.isNotEmpty) { canvas.drawPath(path3, paint); @@ -1483,53 +1065,53 @@ void _drawDataLabelRectAndText( } } - seriesRenderer.drawDataLabel( + seriesRendererDetails.renderer.drawDataLabel( index, canvas, label, - dataLabelSettingsRenderer._angle != 0 + dataLabelSettingsRenderer.angle != 0 ? point.dataLabelRegion!.center.dx + x : point.labelLocation!.x + x, - dataLabelSettingsRenderer._angle != 0 + dataLabelSettingsRenderer.angle != 0 ? point.dataLabelRegion!.center.dy - y - padding : point.labelLocation!.y - y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyle); if (isRangeSeries || isBoxSeries) { - if (_withInRange(isBoxSeries ? point.minimum : point.low, - seriesRenderer._yAxisRenderer!._visibleRange!)) { - seriesRenderer.drawDataLabel( + if (withInRange(isBoxSeries ? point.minimum : point.low, + seriesRendererDetails.yAxisDetails!.visibleRange!)) { + seriesRendererDetails.renderer.drawDataLabel( index, canvas, label2!, point.labelLocation2!.x + x, point.labelLocation2!.y - y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyle); } - if (seriesRenderer._seriesType == 'hiloopenclose' && + if (seriesRendererDetails.seriesType == 'hiloopenclose' && (label3 != null && label4 != null && (point.labelLocation3!.y - point.labelLocation4!.y).round() >= 8 || (point.labelLocation4!.x - point.labelLocation3!.x).round() >= 15)) { - seriesRenderer.drawDataLabel( + seriesRendererDetails.renderer.drawDataLabel( index, canvas, label3!, point.labelLocation3!.x + x, point.labelLocation3!.y + y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyle); - seriesRenderer.drawDataLabel( + seriesRendererDetails.renderer.drawDataLabel( index, canvas, label4!, point.labelLocation4!.x + x, point.labelLocation3!.y + y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyle); } else if (label3 != null && label4 != null && @@ -1537,7 +1119,7 @@ void _drawDataLabelRectAndText( (point.labelLocation4!.x - point.labelLocation3!.x).round() >= 15)) { final Color fontColor = - _getOpenCloseDataLabelColor(point, seriesRenderer, chart!); + getOpenCloseDataLabelColor(point, seriesRendererDetails, chart!); final TextStyle _textStyleOpenClose = TextStyle( color: fontColor.withOpacity(opacity), fontSize: _textStyle.fontSize, @@ -1563,40 +1145,40 @@ void _drawDataLabelRectAndText( fontFamilyFallback: _textStyle.fontFamilyFallback); if ((point.labelLocation2!.y - point.labelLocation3!.y).abs() >= 8 || (point.labelLocation2!.x - point.labelLocation3!.x).abs() >= 8) { - seriesRenderer.drawDataLabel( + seriesRendererDetails.renderer.drawDataLabel( index, canvas, label3, point.labelLocation3!.x + x, point.labelLocation3!.y + y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyleOpenClose); } if ((point.labelLocation!.y - point.labelLocation4!.y).abs() >= 8 || (point.labelLocation!.x - point.labelLocation4!.x).abs() >= 8) { - seriesRenderer.drawDataLabel( + seriesRendererDetails.renderer.drawDataLabel( index, canvas, label4, point.labelLocation4!.x + x, point.labelLocation4!.y + y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyleOpenClose); } if (label5 != null && point.labelLocation5 != null) { - seriesRenderer.drawDataLabel( + seriesRendererDetails.renderer.drawDataLabel( index, canvas, label5, point.labelLocation5!.x + x, point.labelLocation5!.y + y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyleOpenClose); } if (isBoxSeries) { if (point.outliers!.isNotEmpty) { - final List<_ChartLocation> outliersLocation = <_ChartLocation>[]; + final List outliersLocation = []; final List outliersTextSize = []; final List outliersRect = []; const int outlierPadding = 12; @@ -1604,17 +1186,18 @@ void _drawDataLabelRectAndText( outlierIndex < point.outliers!.length; outlierIndex++) { point.outliersLabel.add(point.dataLabelMapper ?? - _getLabelText(point.outliers![outlierIndex], seriesRenderer)); + _getLabelText( + point.outliers![outlierIndex], seriesRendererDetails)); outliersTextSize.add(measureText( point.outliersLabel[outlierIndex], - dataLabelSettingsRenderer._textStyle == null + dataLabelSettingsRenderer.textStyle == null ? const TextStyle( fontFamily: 'Roboto', fontStyle: FontStyle.normal, fontWeight: FontWeight.normal, fontSize: 12) - : dataLabelSettingsRenderer._originalStyle!)); - outliersLocation.add(_ChartLocation( + : dataLabelSettingsRenderer.originalStyle!)); + outliersLocation.add(ChartLocation( point.outliersPoint[outlierIndex].x, point.outliersPoint[outlierIndex].y + outlierPadding)); // ignore: unnecessary_null_comparison @@ -1623,16 +1206,18 @@ void _drawDataLabelRectAndText( outliersLocation[outlierIndex], outliersTextSize[outlierIndex], dataLabel.margin, - dataLabelSettingsRenderer._color != null || + dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor)); outliersRect[outlierIndex] = _validateRect( outliersRect[outlierIndex], - _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset))); + calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset( + seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails + .yAxisDetails!.axis.plotOffset))); } - if (dataLabelSettingsRenderer._color != null || + if (dataLabelSettingsRenderer.color != null || dataLabel.useSeriesColor || // ignore: unnecessary_null_comparison (dataLabel.borderColor != null && dataLabel.borderWidth > 0)) { @@ -1646,7 +1231,7 @@ void _drawDataLabelRectAndText( point.outliersFillRect.add(outliersFillRect); } else { final Rect outliersRect = outliersFillRect.middleRect; - point.outliersLocation.add(_ChartLocation( + point.outliersLocation.add(ChartLocation( outliersRect.left + outliersRect.width / 2 - outliersTextSize[outlierIndex].width / 2, @@ -1666,9 +1251,9 @@ void _drawDataLabelRectAndText( final Path outlierPath = Path(); outlierPath.addRRect(fillOutlierRect); final Paint paint = Paint() - ..color = (dataLabelSettingsRenderer._color ?? + ..color = (dataLabelSettingsRenderer.color ?? (point.pointColorMapper ?? - seriesRenderer._seriesColor!)) + seriesRendererDetails.seriesColor!)) .withOpacity((opacity - (1 - dataLabel.opacity)) < 0 ? 0 : opacity - (1 - dataLabel.opacity)) @@ -1689,7 +1274,7 @@ void _drawDataLabelRectAndText( } else { // ignore: unnecessary_null_comparison if (outliersRect[outlierIndex] != null) { - point.outliersLocation.add(_ChartLocation( + point.outliersLocation.add(ChartLocation( outliersRect[outlierIndex].left + outliersRect[outlierIndex].width / 2 - outliersTextSize[outlierIndex].width / 2, @@ -1705,309 +1290,807 @@ void _drawDataLabelRectAndText( } final String outlierLabel = point.dataLabelMapper ?? point.outliersLabel[outlierIndex]; - seriesRenderer.drawDataLabel( + seriesRendererDetails.renderer.drawDataLabel( index, canvas, outlierLabel, point.outliersLocation[outlierIndex].x + x, point.outliersLocation[outlierIndex].y + y, - dataLabelSettingsRenderer._angle, + dataLabelSettingsRenderer.angle, _textStyle); } } } - } - } -} - -/// Following method returns the data label text -String _getLabelText( - dynamic labelValue, CartesianSeriesRenderer seriesRenderer) { - if (labelValue.toString().split('.').length > 1) { - final String str = labelValue.toString(); - final List list = str.split('.'); - labelValue = double.parse(labelValue.toStringAsFixed(6)); - if (list[1] == '0' || - list[1] == '00' || - list[1] == '000' || - list[1] == '0000' || - list[1] == '00000' || - list[1] == '000000') { - labelValue = labelValue.round(); - } - } - final dynamic yAxis = seriesRenderer._yAxisRenderer!._axis; - if (yAxis is NumericAxis || yAxis is LogarithmicAxis) { - final dynamic value = yAxis?.numberFormat != null - ? yAxis.numberFormat.format(labelValue) - : labelValue; - return ((yAxis.labelFormat != null && yAxis.labelFormat != '') - ? yAxis.labelFormat.replaceAll(RegExp('{value}'), value.toString()) - : value.toString()) as String; - } else { - return labelValue.toString(); - } -} - -/// Calculating rect position for dataLabel -double _calculateRectPosition( - double labelLocation, - Rect rect, - bool isMinus, - ChartDataLabelAlignment position, - CartesianSeriesRenderer seriesRenderer, - Size textSize, - double borderWidth, - int index, - _ChartLocation point, - SfCartesianChartState _chartState, - bool inverted, - EdgeInsets margin) { - final XyDataSeries series = - seriesRenderer._series as XyDataSeries; - double padding; - padding = seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('rangecolumn') || - seriesRenderer._seriesType.contains('boxandwhisker') - ? 2 - : 5; - final bool needFill = series.dataLabelSettings.color != null || - series.dataLabelSettings.color != Colors.transparent || - series.dataLabelSettings.useSeriesColor; - final double textLength = !inverted ? textSize.height : textSize.width; - final double extraSpace = - borderWidth + textLength / 2 + padding + (needFill ? padding : 0); - if (seriesRenderer._seriesType.contains('stack')) { - position = position == ChartDataLabelAlignment.outer - ? ChartDataLabelAlignment.top - : position; - } - - /// Locating the data label based on position - switch (position) { - case ChartDataLabelAlignment.bottom: - labelLocation = !inverted - ? (isMinus - ? (labelLocation - rect.height + extraSpace) - : (labelLocation + rect.height - extraSpace)) - : (isMinus - ? (labelLocation + rect.width - extraSpace) - : (labelLocation - rect.width + extraSpace)); - break; - case ChartDataLabelAlignment.middle: - labelLocation = !inverted - ? (isMinus - ? labelLocation - (rect.height / 2) - : labelLocation + (rect.height / 2)) - : (isMinus - ? labelLocation + (rect.width / 2) - : labelLocation - (rect.width / 2)); - break; - case ChartDataLabelAlignment.auto: - labelLocation = _calculateRectActualPosition( - labelLocation, - rect, - isMinus, - seriesRenderer, - textSize, - index, - point, - inverted, - borderWidth, - _chartState, - margin); - break; - case ChartDataLabelAlignment.top: - case ChartDataLabelAlignment.outer: - labelLocation = _calculateTopAndOuterPosition( - textSize, - labelLocation, - rect, - position, - seriesRenderer, - index, - extraSpace, - isMinus, + } + } +} + +/// Following method returns the data label text +String _getLabelText( + dynamic labelValue, SeriesRendererDetails seriesRendererDetails) { + if (labelValue.toString().split('.').length > 1) { + final String str = labelValue.toString(); + final List list = str.split('.'); + labelValue = double.parse(labelValue.toStringAsFixed(6)); + if (list[1] == '0' || + list[1] == '00' || + list[1] == '000' || + list[1] == '0000' || + list[1] == '00000' || + list[1] == '000000') { + labelValue = labelValue.round(); + } + } + final dynamic yAxis = seriesRendererDetails.yAxisDetails!.axis; + if (yAxis is NumericAxis || yAxis is LogarithmicAxis) { + final dynamic value = yAxis?.numberFormat != null + ? yAxis.numberFormat.format(labelValue) + : labelValue; + return ((yAxis.labelFormat != null && yAxis.labelFormat != '') + ? yAxis.labelFormat.replaceAll(RegExp('{value}'), value.toString()) + : value.toString()) as String; + } else { + return labelValue.toString(); + } +} + +/// Calculating rect position for dataLabel +double _calculateRectPosition( + double labelLocation, + Rect rect, + bool isMinus, + ChartDataLabelAlignment position, + SeriesRendererDetails seriesRendererDetails, + Size textSize, + double borderWidth, + int index, + ChartLocation point, + CartesianStateProperties stateProperties, + bool inverted, + EdgeInsets margin) { + final XyDataSeries series = + seriesRendererDetails.series as XyDataSeries; + double padding; + padding = seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('rangecolumn') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true + ? 2 + : 5; + final bool needFill = series.dataLabelSettings.color != null || + series.dataLabelSettings.color != Colors.transparent || + series.dataLabelSettings.useSeriesColor; + final double textLength = !inverted ? textSize.height : textSize.width; + final double extraSpace = + borderWidth + textLength / 2 + padding + (needFill ? padding : 0); + if (seriesRendererDetails.seriesType.contains('stack') == true) { + position = position == ChartDataLabelAlignment.outer + ? ChartDataLabelAlignment.top + : position; + } + + /// Locating the data label based on position + switch (position) { + case ChartDataLabelAlignment.bottom: + labelLocation = !inverted + ? (isMinus + ? (labelLocation - rect.height + extraSpace) + : (labelLocation + rect.height - extraSpace)) + : (isMinus + ? (labelLocation + rect.width - extraSpace) + : (labelLocation - rect.width + extraSpace)); + break; + case ChartDataLabelAlignment.middle: + labelLocation = !inverted + ? (isMinus + ? labelLocation - (rect.height / 2) + : labelLocation + (rect.height / 2)) + : (isMinus + ? labelLocation + (rect.width / 2) + : labelLocation - (rect.width / 2)); + break; + case ChartDataLabelAlignment.auto: + labelLocation = _calculateRectActualPosition( + labelLocation, + rect, + isMinus, + seriesRendererDetails, + textSize, + index, + point, + inverted, + borderWidth, + stateProperties, + margin); + break; + case ChartDataLabelAlignment.top: + case ChartDataLabelAlignment.outer: + labelLocation = _calculateTopAndOuterPosition( + textSize, + labelLocation, + rect, + position, + seriesRendererDetails, + index, + extraSpace, + isMinus, + point, + inverted, + borderWidth); + break; + } + return labelLocation; +} + +/// Calculating the label location if position is given as auto +double _calculateRectActualPosition( + double labelLocation, + Rect rect, + bool minus, + SeriesRendererDetails seriesRendererDetails, + Size textSize, + int index, + ChartLocation point, + bool inverted, + double borderWidth, + CartesianStateProperties stateProperties, + EdgeInsets margin) { + late double location; + Rect labelRect; + bool isOverLap = true; + int position = 0; + final XyDataSeries series = + seriesRendererDetails.series as XyDataSeries; + final int finalPosition = + seriesRendererDetails.seriesType.contains('range') == true ? 2 : 4; + while (isOverLap && position < finalPosition) { + location = _calculateRectPosition( + labelLocation, + rect, + minus, + _getPosition(position), + seriesRendererDetails, + textSize, + borderWidth, + index, + point, + stateProperties, + inverted, + margin); + if (!inverted) { + labelRect = _calculateLabelRect( + ChartLocation(point.x, location), + textSize, + margin, + series.dataLabelSettings.color != null || + series.dataLabelSettings.useSeriesColor); + isOverLap = labelRect.top < 0 || + labelRect.top > stateProperties.chartAxis.axisClipRect.height || + ((series.dataLabelSettings.angle / 90) % 2 != 1 && + findingCollision( + labelRect, stateProperties.renderDatalabelRegions)); + } else { + labelRect = _calculateLabelRect( + ChartLocation(location, point.y), + textSize, + margin, + series.dataLabelSettings.color != null || + series.dataLabelSettings.useSeriesColor); + isOverLap = labelRect.left < 0 || + labelRect.left + labelRect.width > + stateProperties.chartAxis.axisClipRect.right || + (series.dataLabelSettings.angle % 180 != 0 && + findingCollision( + labelRect, stateProperties.renderDatalabelRegions)); + } + seriesRendererDetails.dataPoints[index].dataLabelSaturationRegionInside = + isOverLap || + seriesRendererDetails + .dataPoints[index].dataLabelSaturationRegionInside == + true; + position++; + } + return location; +} + +///calculation for top and outer position of data label for rect series +double _calculateTopAndOuterPosition( + Size textSize, + double location, + Rect rect, + ChartDataLabelAlignment position, + SeriesRendererDetails seriesRendererDetails, + int index, + double extraSpace, + bool isMinus, + ChartLocation point, + bool inverted, + double borderWidth) { + final XyDataSeries series = + seriesRendererDetails.series as XyDataSeries; + final num markerHeight = + series.markerSettings.isVisible ? series.markerSettings.height / 2 : 0; + if (((isMinus && + seriesRendererDetails.seriesType.contains('range') == false) && + position == ChartDataLabelAlignment.top) || + ((!isMinus || + seriesRendererDetails.seriesType.contains('range') == true) && + position == ChartDataLabelAlignment.outer)) { + location = !inverted + ? location - extraSpace - markerHeight + : location + extraSpace + markerHeight; + } else { + location = !inverted + ? location + extraSpace + markerHeight + : location - extraSpace - markerHeight; + } + return location; +} + +/// Add padding for fill rect (if data label fill color is given) +RRect _calculatePaddedFillRect(Rect rect, double radius, EdgeInsets margin) { + rect = Rect.fromLTRB(rect.left - margin.left, rect.top - margin.top, + rect.right + margin.right, rect.bottom + margin.bottom); + + return _rectToRrect(rect, radius); +} + +/// Converting rect into rounded rect +RRect _rectToRrect(Rect rect, double radius) => RRect.fromRectAndCorners(rect, + topLeft: Radius.elliptical(radius, radius), + topRight: Radius.elliptical(radius, radius), + bottomLeft: Radius.elliptical(radius, radius), + bottomRight: Radius.elliptical(radius, radius)); + +/// Checking the condition whether data Label has been exist in the clip rect +Rect _validateRect(Rect rect, Rect clipRect) { + /// please don't add padding here + double left, top; + left = rect.left < clipRect.left ? clipRect.left : rect.left; + top = double.parse(rect.top.toStringAsFixed(2)) < clipRect.top + ? clipRect.top + : rect.top; + left -= ((double.parse(left.toStringAsFixed(2)) + rect.width) > + clipRect.right) + ? (double.parse(left.toStringAsFixed(2)) + rect.width) - clipRect.right + : 0; + top -= (double.parse(top.toStringAsFixed(2)) + rect.height) > clipRect.bottom + ? (double.parse(top.toStringAsFixed(2)) + rect.height) - clipRect.bottom + : 0; + left = left < clipRect.left ? clipRect.left : left; + rect = Rect.fromLTWH(left, top, rect.width, rect.height); + return rect; +} + +/// It returns a boolean value that labels within range or not +bool isLabelWithinRange(SeriesRendererDetails seriesRendererDetails, + CartesianChartPoint point) { + bool isWithInRange = true; + final bool isBoxSeries = + seriesRendererDetails.seriesType.contains('boxandwhisker'); + if (seriesRendererDetails.yAxisDetails is! LogarithmicAxisDetails) { + isWithInRange = withInRange( + point.xValue, seriesRendererDetails.xAxisDetails!.visibleRange!) && + (seriesRendererDetails.seriesType.contains('range') || + seriesRendererDetails.seriesType == 'hilo' + ? (isBoxSeries && point.minimum != null && point.maximum != null) || + (!isBoxSeries && point.low != null && point.high != null) && + (withInRange( + isBoxSeries ? point.minimum : point.low, + seriesRendererDetails + .yAxisDetails!.visibleRange!) || + withInRange(isBoxSeries ? point.maximum : point.high, + seriesRendererDetails.yAxisDetails!.visibleRange!)) + : seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType.contains('candle') || + isBoxSeries + ? (withInRange(isBoxSeries ? point.minimum : point.low, + seriesRendererDetails.yAxisDetails!.visibleRange!) && + withInRange(isBoxSeries ? point.maximum : point.high, + seriesRendererDetails.yAxisDetails!.visibleRange!) && + withInRange(isBoxSeries ? point.lowerQuartile : point.open, + seriesRendererDetails.yAxisDetails!.visibleRange!) && + withInRange(isBoxSeries ? point.upperQuartile : point.close, + seriesRendererDetails.yAxisDetails!.visibleRange!)) + : withInRange( + seriesRendererDetails.seriesType.contains('100') + ? point.cumulativeValue + : seriesRendererDetails.seriesType == 'waterfall' + ? point.endValue ?? 0 + : point.yValue, + seriesRendererDetails.yAxisDetails!.visibleRange!)); + } + return isWithInRange; +} + +/// Calculating data label position and updating the label region for current data point +void calculateDataLabelPosition( + SeriesRendererDetails seriesRendererDetails, + CartesianChartPoint point, + int index, + CartesianStateProperties stateProperties, + DataLabelSettingsRenderer dataLabelSettingsRenderer, + Animation dataLabelAnimation, + [Size? templateSize, + Offset? templateLocation]) { + final SfCartesianChart chart = stateProperties.chart; + final CartesianSeries series = seriesRendererDetails.series; + if (dataLabelSettingsRenderer.angle.isNegative) { + final int angle = dataLabelSettingsRenderer.angle + 360; + dataLabelSettingsRenderer.angle = angle; + } + final DataLabelSettings dataLabel = series.dataLabelSettings; + Size? textSize, textSize2, textSize3, textSize4, textSize5; + double? value1, value2; + const int boxPlotPadding = 8; + final Rect rect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + if (seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true) { + value1 = ((point.open != null && + point.close != null && + (point.close < point.open) == true) + ? point.close + : point.open) + ?.toDouble(); + value2 = ((point.open != null && + point.close != null && + (point.close > point.open) == true) + ? point.close + : point.open) + ?.toDouble(); + } + final bool transposed = stateProperties.requireInvertedAxis; + final bool inversed = seriesRendererDetails.yAxisDetails!.axis.isInversed; + final Rect clipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + final bool isRangeSeries = + seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true; + final bool isBoxSeries = + seriesRendererDetails.seriesType.contains('boxandwhisker'); + if (isBoxSeries) { + value1 = (point.upperQuartile != null && + point.lowerQuartile != null && + point.upperQuartile! < point.lowerQuartile!) + ? point.upperQuartile! + : point.lowerQuartile!; + value2 = (point.upperQuartile != null && + point.lowerQuartile != null && + point.upperQuartile! > point.lowerQuartile!) + ? point.upperQuartile! + : point.lowerQuartile!; + } + // ignore: prefer_final_locals + List labelList = []; + // ignore: prefer_final_locals + String label = point.dataLabelMapper ?? + point.label ?? + _getLabelText( + isRangeSeries + ? (!inversed ? point.high : point.low) + : isBoxSeries + ? (!inversed ? point.maximum : point.minimum) + : ((dataLabel.showCumulativeValues && + point.cumulativeValue != null) + ? point.cumulativeValue + : point.yValue), + seriesRendererDetails); + if (isRangeSeries) { + point.label2 = point.dataLabelMapper ?? + point.label2 ?? + _getLabelText( + !inversed ? point.low : point.high, seriesRendererDetails); + if (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType.contains('candle') == true) { + point.label3 = point.dataLabelMapper ?? + point.label3 ?? + _getLabelText( + (point.open > point.close) == true + ? !inversed + ? point.close + : point.open + : !inversed + ? point.open + : point.close, + seriesRendererDetails); + point.label4 = point.dataLabelMapper ?? + point.label4 ?? + _getLabelText( + (point.open > point.close) == true + ? !inversed + ? point.open + : point.close + : !inversed + ? point.close + : point.open, + seriesRendererDetails); + } + } else if (isBoxSeries) { + point.label2 = point.dataLabelMapper ?? + point.label2 ?? + _getLabelText( + !inversed ? point.minimum : point.maximum, seriesRendererDetails); + point.label3 = point.dataLabelMapper ?? + point.label3 ?? + _getLabelText( + point.lowerQuartile! > point.upperQuartile! + ? !inversed + ? point.upperQuartile + : point.lowerQuartile + : !inversed + ? point.lowerQuartile + : point.upperQuartile, + seriesRendererDetails); + point.label4 = point.dataLabelMapper ?? + point.label4 ?? + _getLabelText( + point.lowerQuartile! > point.upperQuartile! + ? !inversed + ? point.lowerQuartile + : point.upperQuartile + : !inversed + ? point.upperQuartile + : point.lowerQuartile, + seriesRendererDetails); + point.label5 = point.dataLabelMapper ?? + point.label5 ?? + _getLabelText(point.median, seriesRendererDetails); + } + DataLabelRenderArgs dataLabelArgs; + TextStyle? dataLabelStyle = dataLabelSettingsRenderer.textStyle; + //ignore: prefer_conditional_assignment + if (dataLabelSettingsRenderer.originalStyle == null) { + dataLabelSettingsRenderer.originalStyle = dataLabel.textStyle; + } + dataLabelStyle = dataLabelSettingsRenderer.originalStyle; + if (chart.onDataLabelRender != null && + seriesRendererDetails.visibleDataPoints![index].labelRenderEvent == + false) { + labelList.add(label); + if (isRangeSeries) { + labelList.add(point.label2!); + if (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType.contains('candle') == true) { + labelList.add(point.label3!); + labelList.add(point.label4!); + } + } else if (isBoxSeries) { + labelList.add(point.label2!); + labelList.add(point.label3!); + labelList.add(point.label4!); + labelList.add(point.label5!); + } + seriesRendererDetails.visibleDataPoints![index].labelRenderEvent = true; + for (int i = 0; i < labelList.length; i++) { + dataLabelArgs = DataLabelRenderArgs( + seriesRendererDetails.series, + seriesRendererDetails.dataPoints, + index, + seriesRendererDetails + .visibleDataPoints![index].overallDataPointIndex); + dataLabelArgs.text = labelList[i]; + dataLabelArgs.textStyle = dataLabelStyle!; + dataLabelArgs.color = + seriesRendererDetails.series.dataLabelSettings.color; + chart.onDataLabelRender!(dataLabelArgs); + labelList[i] = dataLabelArgs.text; + index = dataLabelArgs.pointIndex!; + CartesianPointHelper.setDataLabelTextStyle( + point, dataLabelArgs.textStyle); + CartesianPointHelper.setDataLabelColor(point, dataLabelArgs.color); + dataLabelSettingsRenderer.offset = dataLabelArgs.offset; + } + } + dataLabelSettingsRenderer.textStyle = dataLabelStyle; + if (chart.onDataLabelRender != null) { + dataLabelSettingsRenderer.color = + CartesianPointHelper.getDataLabelColor(point); + dataLabelSettingsRenderer.textStyle = + CartesianPointHelper.getDataLabelTextStyle(point); + dataLabelStyle = dataLabelSettingsRenderer.textStyle; + } + // ignore: unnecessary_null_comparison + if (point != null && + point.isVisible && + point.isGap != true && + (point.y != 0 || dataLabel.showZeroValue)) { + final double markerPointX = dataLabel.builder == null + ? seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + isBoxSeries + ? seriesRendererDetails.stateProperties.requireInvertedAxis == true + ? point.region!.centerRight.dx + : point.region!.topCenter.dx + : point.markerPoint!.x + : templateLocation!.dx; + final double markerPointY = dataLabel.builder == null + ? seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + isBoxSeries + ? seriesRendererDetails.stateProperties.requireInvertedAxis == true + ? point.region!.centerRight.dy + : point.region!.topCenter.dy + : point.markerPoint!.y + : templateLocation!.dy; + final ChartLocation markerPoint2 = calculatePoint( + point.xValue, + seriesRendererDetails.yAxisDetails!.axis.isInversed == true + ? value2 + : value1, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + series, + rect); + final ChartLocation markerPoint3 = calculatePoint( + point.xValue, + seriesRendererDetails.yAxisDetails!.axis.isInversed == true + ? value1 + : value2, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + series, + rect); + final TextStyle font = (dataLabelSettingsRenderer.textStyle == null) + ? const TextStyle( + fontFamily: 'Roboto', + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 12) + : dataLabelStyle!; + point.label = labelList.isNotEmpty ? labelList[0] : label; + if (point.label!.isNotEmpty) { + ChartLocation? chartLocation, + chartLocation2, + chartLocation3, + chartLocation4, + chartLocation5; + textSize = dataLabel.builder == null + ? measureText(point.label!, font) + : templateSize!; + chartLocation = ChartLocation(markerPointX, markerPointY); + if (isRangeSeries || isBoxSeries) { + point.label2 = labelList.isNotEmpty ? labelList[1] : point.label2; + textSize2 = dataLabel.builder == null + ? measureText(point.label2!, font) + : templateSize!; + chartLocation2 = ChartLocation( + dataLabel.builder == null + ? seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + isBoxSeries + ? seriesRendererDetails + .stateProperties.requireInvertedAxis == + true + ? point.region!.centerLeft.dx + : point.region!.bottomCenter.dx + : point.markerPoint2!.x + : templateLocation!.dx, + dataLabel.builder == null + ? seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + isBoxSeries + ? seriesRendererDetails + .stateProperties.requireInvertedAxis == + true + ? point.region!.centerLeft.dy + : point.region!.bottomCenter.dy + : point.markerPoint2!.y + : templateLocation!.dy); + if (isBoxSeries) { + if (seriesRendererDetails.stateProperties.requireInvertedAxis == + false) { + chartLocation.y = chartLocation.y - boxPlotPadding; + chartLocation2.y = chartLocation2.y + boxPlotPadding; + } else { + chartLocation.x = chartLocation.x + boxPlotPadding; + chartLocation2.x = chartLocation2.x - boxPlotPadding; + } + } + } + final List alignedLabelLocations = + _getAlignedLabelLocations(stateProperties, seriesRendererDetails, + point, dataLabel, chartLocation, chartLocation2, textSize); + chartLocation = alignedLabelLocations[0]; + chartLocation2 = alignedLabelLocations[1]; + if (seriesRendererDetails.seriesType.contains('column') == false && + seriesRendererDetails.seriesType.contains('waterfall') == false && + seriesRendererDetails.seriesType.contains('bar') == false && + seriesRendererDetails.seriesType.contains('histogram') == false && + seriesRendererDetails.seriesType.contains('rangearea') == false && + seriesRendererDetails.seriesType.contains('hilo') == false && + seriesRendererDetails.seriesType.contains('candle') == false && + !isBoxSeries) { + chartLocation!.y = _calculatePathPosition( + chartLocation.y, + dataLabel.labelAlignment, + textSize, + dataLabel.borderWidth, + seriesRendererDetails, + index, + transposed, + chartLocation, + stateProperties, + point, + Size( + series.markerSettings.isVisible + ? series.markerSettings.width / 2 + : 0, + series.markerSettings.isVisible + ? series.markerSettings.height / 2 + : 0)); + } else { + final List _locations = _getLabelLocations( + index, + stateProperties, + seriesRendererDetails, + point, + dataLabel, + chartLocation, + chartLocation2, + textSize, + textSize2); + chartLocation = _locations[0]; + chartLocation2 = _locations[1]; + } + if (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType.contains('candle') == true || + isBoxSeries) { + if (!isBoxSeries) { + point.label3 = labelList.isNotEmpty ? labelList[2] : point.label3; + point.label4 = labelList.isNotEmpty ? labelList[3] : point.label4; + // point.label3 = point.dataLabelMapper ?? + // _getLabelText( + // (point.open > point.close) == true + // ? !inversed + // ? point.close + // : point.open + // : !inversed + // ? point.open + // : point.close, + // seriesRenderer); + // point.label4 = point.dataLabelMapper ?? + // _getLabelText( + // (point.open > point.close) == true + // ? !inversed + // ? point.open + // : point.close + // : !inversed + // ? point.close + // : point.open, + // seriesRenderer); + } else { + point.label3 = labelList.isNotEmpty ? labelList[2] : point.label3; + point.label4 = labelList.isNotEmpty ? labelList[3] : point.label4; + point.label5 = labelList.isNotEmpty ? labelList[4] : point.label5; + // point.label3 = point.dataLabelMapper ?? + // _getLabelText( + // point.lowerQuartile! > point.upperQuartile! + // ? !inversed + // ? point.upperQuartile + // : point.lowerQuartile + // : !inversed + // ? point.lowerQuartile + // : point.upperQuartile, + // seriesRenderer); + // point.label4 = point.dataLabelMapper ?? + // _getLabelText( + // point.lowerQuartile! > point.upperQuartile! + // ? !inversed + // ? point.lowerQuartile + // : point.upperQuartile + // : !inversed + // ? point.upperQuartile + // : point.lowerQuartile, + // seriesRenderer); + // point.label5 = point.dataLabelMapper ?? + // _getLabelText(point.median, seriesRenderer); + } + textSize3 = dataLabel.builder == null + ? measureText(point.label3!, font) + : templateSize; + if (seriesRendererDetails.seriesType.contains('hilo') == true) { + chartLocation3 = (point.open > point.close) == true + ? ChartLocation(point.centerClosePoint!.x + textSize3!.width, + point.closePoint!.y) + : ChartLocation(point.centerOpenPoint!.x - textSize3!.width, + point.openPoint!.y); + } else if (seriesRendererDetails.seriesType == 'candle' && + seriesRendererDetails.stateProperties.requireInvertedAxis == true) { + chartLocation3 = (point.open > point.close) == true + ? ChartLocation(point.closePoint!.x, markerPoint2.y + 1) + : ChartLocation(point.openPoint!.x, markerPoint2.y + 1); + } else if (isBoxSeries) { + chartLocation3 = (seriesRendererDetails + .stateProperties.requireInvertedAxis == + true) + ? ChartLocation(point.lowerQuartilePoint!.x + boxPlotPadding, + markerPoint2.y + 1) + : ChartLocation( + point.region!.topCenter.dx, markerPoint2.y - boxPlotPadding); + } else { + chartLocation3 = + ChartLocation(point.region!.topCenter.dx, markerPoint2.y); + } + textSize4 = dataLabel.builder == null + ? measureText(point.label4!, font) + : templateSize; + if (seriesRendererDetails.seriesType.contains('hilo') == true) { + chartLocation4 = (point.open > point.close) == true + ? ChartLocation(point.centerOpenPoint!.x - textSize4!.width, + point.openPoint!.y) + : ChartLocation(point.centerClosePoint!.x + textSize4!.width, + point.closePoint!.y); + } else if (seriesRendererDetails.seriesType == 'candle' && + seriesRendererDetails.stateProperties.requireInvertedAxis == true) { + chartLocation4 = (point.open > point.close) == true + ? ChartLocation(point.openPoint!.x, markerPoint3.y + 1) + : ChartLocation(point.closePoint!.x, markerPoint3.y + 1); + } else if (isBoxSeries) { + chartLocation4 = + (seriesRendererDetails.stateProperties.requireInvertedAxis == + true) + ? ChartLocation(point.upperQuartilePoint!.x - boxPlotPadding, + markerPoint3.y + 1) + : ChartLocation(point.region!.bottomCenter.dx, + markerPoint3.y + boxPlotPadding); + } else { + chartLocation4 = + ChartLocation(point.region!.bottomCenter.dx, markerPoint3.y + 1); + } + if (isBoxSeries) { + textSize5 = measureText(point.label5!, font); + chartLocation5 = + (seriesRendererDetails.stateProperties.requireInvertedAxis == + false) + ? ChartLocation( + point.centerMedianPoint!.x, point.centerMedianPoint!.y) + : ChartLocation( + point.centerMedianPoint!.x, point.centerMedianPoint!.y); + } + final List alignedLabelLocations2 = + _getAlignedLabelLocations(stateProperties, seriesRendererDetails, + point, dataLabel, chartLocation3, chartLocation4, textSize3!); + chartLocation3 = alignedLabelLocations2[0]; + chartLocation4 = alignedLabelLocations2[1]; + final List _locations = _getLabelLocations( + index, + stateProperties, + seriesRendererDetails, + point, + dataLabel, + chartLocation3, + chartLocation4, + textSize3, + textSize4!); + chartLocation3 = _locations[0]; + chartLocation4 = _locations[1]; + } + _calculateDataLabelRegion( point, - inverted, - borderWidth); - break; - } - return labelLocation; -} - -/// Calculating the label location if position is given as auto -double _calculateRectActualPosition( - double labelLocation, - Rect rect, - bool minus, - CartesianSeriesRenderer seriesRenderer, - Size textSize, - int index, - _ChartLocation point, - bool inverted, - double borderWidth, - SfCartesianChartState _chartState, - EdgeInsets margin) { - late double location; - Rect labelRect; - bool isOverLap = true; - int position = 0; - final XyDataSeries series = - seriesRenderer._series as XyDataSeries; - final int finalPosition = - seriesRenderer._seriesType.contains('range') ? 2 : 4; - while (isOverLap && position < finalPosition) { - location = _calculateRectPosition( - labelLocation, - rect, - minus, - _getPosition(position), - seriesRenderer, - textSize, - borderWidth, - index, - point, - _chartState, - inverted, - margin); - if (!inverted) { - labelRect = _calculateLabelRect( - _ChartLocation(point.x, location), - textSize, - margin, - series.dataLabelSettings.color != null || - series.dataLabelSettings.useSeriesColor); - isOverLap = labelRect.top < 0 || - labelRect.top > _chartState._chartAxis._axisClipRect.height || - ((series.dataLabelSettings.angle / 90) % 2 != 1 && - _findingCollision( - labelRect, _chartState._renderDatalabelRegions)); - } else { - labelRect = _calculateLabelRect( - _ChartLocation(location, point.y), + dataLabel, + stateProperties, + chartLocation!, + chartLocation2, + isRangeSeries, + clipRect, textSize, - margin, - series.dataLabelSettings.color != null || - series.dataLabelSettings.useSeriesColor); - isOverLap = labelRect.left < 0 || - labelRect.left + labelRect.width > - _chartState._chartAxis._axisClipRect.right || - (series.dataLabelSettings.angle % 180 != 0 && - _findingCollision( - labelRect, _chartState._renderDatalabelRegions)); + textSize2, + chartLocation3, + chartLocation4, + chartLocation5, + textSize3, + textSize4, + textSize5, + seriesRendererDetails, + index); } - seriesRenderer._dataPoints[index].dataLabelSaturationRegionInside = - isOverLap || - seriesRenderer._dataPoints[index].dataLabelSaturationRegionInside; - position++; - } - return location; -} - -///calculation for top and outer position of datalabel for rect series -double _calculateTopAndOuterPosition( - Size textSize, - double location, - Rect rect, - ChartDataLabelAlignment position, - CartesianSeriesRenderer seriesRenderer, - int index, - double extraSpace, - bool isMinus, - _ChartLocation point, - bool inverted, - double borderWidth) { - final XyDataSeries series = - seriesRenderer._series as XyDataSeries; - final num markerHeight = - series.markerSettings.isVisible ? series.markerSettings.height / 2 : 0; - if (((isMinus && !seriesRenderer._seriesType.contains('range')) && - position == ChartDataLabelAlignment.top) || - ((!isMinus || seriesRenderer._seriesType.contains('range')) && - position == ChartDataLabelAlignment.outer)) { - location = !inverted - ? location - extraSpace - markerHeight - : location + extraSpace + markerHeight; - } else { - location = !inverted - ? location + extraSpace + markerHeight - : location - extraSpace - markerHeight; - } - return location; -} - -/// Add padding for fill rect (if datalabel fill color is given) -RRect _calculatePaddedFillRect(Rect rect, double radius, EdgeInsets margin) { - rect = Rect.fromLTRB(rect.left - margin.left, rect.top - margin.top, - rect.right + margin.right, rect.bottom + margin.bottom); - - return _rectToRrect(rect, radius); -} - -/// Converting rect into rounded rect -RRect _rectToRrect(Rect rect, double radius) => RRect.fromRectAndCorners(rect, - topLeft: Radius.elliptical(radius, radius), - topRight: Radius.elliptical(radius, radius), - bottomLeft: Radius.elliptical(radius, radius), - bottomRight: Radius.elliptical(radius, radius)); - -/// Checking the condition whether data Label has been exist in the clip rect -Rect _validateRect(Rect rect, Rect clipRect) { - /// please don't add padding here - double left, top; - left = rect.left < clipRect.left ? clipRect.left : rect.left; - top = double.parse(rect.top.toStringAsFixed(2)) < clipRect.top - ? clipRect.top - : rect.top; - left -= ((double.parse(left.toStringAsFixed(2)) + rect.width) > - clipRect.right) - ? (double.parse(left.toStringAsFixed(2)) + rect.width) - clipRect.right - : 0; - top -= (double.parse(top.toStringAsFixed(2)) + rect.height) > clipRect.bottom - ? (double.parse(top.toStringAsFixed(2)) + rect.height) - clipRect.bottom - : 0; - left = left < clipRect.left ? clipRect.left : left; - rect = Rect.fromLTWH(left, top, rect.width, rect.height); - return rect; -} - -/// It returns a boolean value that labels within range or not -bool _isLabelWithinRange(CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint point) { - bool withInRange = true; - final bool isBoxSeries = seriesRenderer._seriesType.contains('boxandwhisker'); - if (seriesRenderer._yAxisRenderer is! LogarithmicAxisRenderer) { - withInRange = _withInRange( - point.xValue, seriesRenderer._xAxisRenderer!._visibleRange!) && - (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo' - ? (isBoxSeries && point.minimum != null && point.maximum != null) || - (!isBoxSeries && point.low != null && point.high != null) && - (_withInRange(isBoxSeries ? point.minimum : point.low, - seriesRenderer._yAxisRenderer!._visibleRange!) || - _withInRange(isBoxSeries ? point.maximum : point.high, - seriesRenderer._yAxisRenderer!._visibleRange!)) - : seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType.contains('candle') || - isBoxSeries - ? (_withInRange(isBoxSeries ? point.minimum : point.low, - seriesRenderer._yAxisRenderer!._visibleRange!) && - _withInRange(isBoxSeries ? point.maximum : point.high, - seriesRenderer._yAxisRenderer!._visibleRange!) && - _withInRange(isBoxSeries ? point.lowerQuartile : point.open, - seriesRenderer._yAxisRenderer!._visibleRange!) && - _withInRange( - isBoxSeries ? point.upperQuartile : point.close, - seriesRenderer._yAxisRenderer!._visibleRange!)) - : _withInRange( - seriesRenderer._seriesType.contains('100') - ? point.cumulativeValue - : seriesRenderer._seriesType == 'waterfall' - ? point.endValue ?? 0 - : point.yValue, - seriesRenderer._yAxisRenderer!._visibleRange!)); } - return withInRange; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/interactive_tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/interactive_tooltip.dart new file mode 100644 index 000000000..6e07b39f0 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/interactive_tooltip.dart @@ -0,0 +1,392 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; + +/// Customizes the interactive tooltip. +/// +///Shows the information about the segments.To enable the interactiveToolTip, set the property to true. +/// +/// By using this,to customize the [color], [borderWidth], [borderRadius], +/// [format] and so on. +/// +/// _Note:_ IntereactivetoolTip applicable for axis types and trackball. + +@immutable +class InteractiveTooltip { + /// Creating an argument constructor of InteractiveTooltip class. + const InteractiveTooltip( + {this.enable = true, + this.color, + this.borderColor, + this.borderWidth = 0, + this.borderRadius = 5, + this.arrowLength = 7, + this.arrowWidth = 5, + this.format, + this.connectorLineColor, + this.connectorLineWidth = 1.5, + this.connectorLineDashArray, + this.decimalPlaces = 3, + this.canShowMarker = true, + this.textStyle = const TextStyle( + fontFamily: 'Roboto', + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: 12)}); + + ///Toggles the visibility of the interactive tooltip in an axis. + /// + /// This tooltip will be displayed at the axis for crosshair and + /// will be displayed near to the trackline for trackball. + /// + ///Defaults to `true`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip(enable:false)), + /// )); + ///} + ///``` + final bool enable; + + ///Color of the interactive tooltip. + /// + ///Used to change the [color] of the tooltip text. + /// + ///Defaults to `null`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// color:Colors.grey)), + /// )); + ///} + ///``` + final Color? color; + + ///Border color of the interactive tooltip. + /// + ///Used to change the stroke color of the axis tooltip. + /// + ///Defaults to `Colors.black`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// borderColor:Colors.white, + /// borderWidth:2)), + /// )); + ///} + ///``` + final Color? borderColor; + + ///Border width of the interactive tooltip. + /// + ///Used to change the stroke width of the axis tooltip. + /// + ///Defaults to `0`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// borderColor:Colors.white, + /// borderWidth:2)), + /// )); + ///} + ///``` + final double borderWidth; + + ///Customizes the text in the interactive tooltip. + /// + ///Used to change the text color, size, font family, fontStyle, and font weight. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// textStyle: TextStyle(color:Colors.red))), + /// )); + ///} + ///``` + final TextStyle textStyle; + + ///Customizes the corners of the interactive tooltip. + /// + ///Each corner can be customized with a desired value or a single value. + /// + ///Defaults to `Radius.zero`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// borderColor:Colors.white, + /// borderWidth:3, + /// borderRadius:2)), + /// )); + ///} + ///``` + final double borderRadius; + + ///It Specifies the length of the tooltip. + /// + ///Defaults to `7`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// arrowLength:4)), + /// )); + ///} + ///``` + final double arrowLength; + + ///It specifies the width of the tooltip arrow. + /// + ///Defaults to `5`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// arrowWidth:4)), + /// )); + ///} + ///``` + final double arrowWidth; + + ///Text format of the interactive tooltip. + /// + /// By default, axis value will be displayed in the tooltip, and it can be customized by + /// adding desired text as prefix or suffix. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior(enable: true, + /// tooltipSettings: InteractiveTooltip( + /// format:'point.x %')), + /// )); + ///} + ///``` + final String? format; + + ///Width of the selection zooming tooltip connector line. + /// + ///Defaults to `1.5`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(interactiveTooltip: + /// InteractiveTooltip(connectorLineWidth:2)), + /// )); + ///} + ///``` + final double connectorLineWidth; + + ///Color of the selection zooming tooltip connector line. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// interactiveTooltip: InteractiveTooltip(connectorLineColor:Colors.red)), + /// )); + ///} + ///``` + final Color? connectorLineColor; + + ///Giving dashArray to the selection zooming tooltip connector line. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis( + /// interactiveTooltip: InteractiveTooltip(connectorLineDashArray:[2,3])), + /// )); + ///} + ///``` + final List? connectorLineDashArray; + + ///Rounding decimal places of the selection zooming tooltip or trackball tooltip label. + /// + ///Defaults to `3`. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(interactiveTooltip: InteractiveTooltip(decimalPlaces:4)), + /// )); + ///} + ///``` + final int decimalPlaces; + + ///Toggles the visibility of the marker in the trackball tooltip. + /// + ///Markers are rendered with the series color and placed near the value in trackball + ///tooltip to convey which value belongs to which series. + /// + ///Trackball tooltip marker uses the same shape specified for the series marker. But + ///trackball tooltip marker will render based on the value specified to this property + ///irrespective of considering the series marker's visibility. + /// + /// Defaults to `true` + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// primaryXAxis: NumericAxis(interactiveTooltip: InteractiveTooltip(canSHowMarker:true)), + /// )); + ///} + ///``` + final bool canShowMarker; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is InteractiveTooltip && + other.enable == enable && + other.color == color && + other.borderColor == borderColor && + other.borderWidth == borderWidth && + other.borderRadius == borderRadius && + other.arrowLength == arrowLength && + other.arrowWidth == arrowWidth && + other.format == format && + other.connectorLineColor == connectorLineColor && + other.connectorLineWidth == connectorLineWidth && + other.connectorLineDashArray == connectorLineDashArray && + other.decimalPlaces == decimalPlaces && + other.canShowMarker == canShowMarker && + other.textStyle == textStyle; + } + + @override + int get hashCode { + final List values = [ + enable, + color, + borderColor, + borderWidth, + borderRadius, + arrowLength, + arrowWidth, + format, + connectorLineColor, + connectorLineWidth, + connectorLineDashArray, + decimalPlaces, + canShowMarker, + textStyle + ]; + return hashList(values); + } +} + +/// Represents the class of chart point info +class ChartPointInfo { + /// Marker x position + double? markerXPos; + + /// Marker y position + double? markerYPos; + + /// label for trackball and cross hair + String? label; + + /// Data point index + int? dataPointIndex; + + /// Instance of chart series + CartesianSeries? series; + + /// Instance of SeriesRendererDetails + SeriesRendererDetails? seriesRendererDetails; + + /// Chart data point + CartesianChartPoint? chartDataPoint; + + /// X position of the label + double? xPosition; + + /// Y position of the label + double? yPosition; + + /// Color of the segment + Color? color; + + /// header text + String? header; + + /// Low Y position of financial series + double? lowYPosition; + + /// High X position of financial series + double? highXPosition; + + /// High Y position of financial series + double? highYPosition; + + /// Open y position of financial series + double? openYPosition; + + /// close y position of financial series + double? closeYPosition; + + /// open x position of financial series + double? openXPosition; + + /// close x position of financial series + double? closeXPosition; + + /// Minimum Y position of box plot series + double? minYPosition; + + /// Maximum Y position of box plot series + double? maxYPosition; + + /// Lower y position of box plot series + double? lowerXPosition; + + /// Upper y position of box plot series + double? upperXPosition; + + /// Lower x position of box plot series + double? lowerYPosition; + + /// Upper x position of box plot series + double? upperYPosition; + + /// Maximum x position for box plot series + double? maxXPosition; + + /// series index value + late int seriesIndex; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart index 88f30fc0a..0ba4a116b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/marker.dart @@ -1,4 +1,19 @@ -part of charts; +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/waterfall_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/data_label_renderer.dart'; +import '../utils/helper.dart'; + +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; /// Customizes the markers. /// @@ -221,91 +236,101 @@ class MarkerSettings { /// Marker settings renderer class for mutable fields and methods class MarkerSettingsRenderer { /// Creates an argument constructor for MarkerSettings renderer class - MarkerSettingsRenderer(this._markerSettings) { - _color = _markerSettings.color; + MarkerSettingsRenderer(this.markerSettings) { + color = markerSettings.color; - _borderColor = _markerSettings.borderColor; + borderColor = markerSettings.borderColor; - _borderWidth = _markerSettings.borderWidth; + borderWidth = markerSettings.borderWidth; } - final MarkerSettings _markerSettings; + /// Holds the marker settings value + final MarkerSettings markerSettings; + /// Holds the color value // ignore: prefer_final_fields - Color? _color; + Color? color; + + /// Holds the value of border color + Color? borderColor; - Color? _borderColor; + /// Holds the value of border width + late double borderWidth; - late double _borderWidth; + /// Holds the value of image + dart_ui.Image? image; - dart_ui.Image? _image; + /// Specifies the image drawn in the marker or not. + bool isImageDrawn = false; /// To paint the marker here void renderMarker( - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, CartesianChartPoint point, Animation? animationController, Canvas canvas, int markerIndex, [int? outlierIndex]) { - final bool isDataPointVisible = _isLabelWithinRange( - seriesRenderer, seriesRenderer._dataPoints[markerIndex]); + final bool isDataPointVisible = isLabelWithinRange( + seriesRendererDetails, seriesRendererDetails.dataPoints[markerIndex]); Paint strokePaint, fillPaint; final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; final Size size = Size(series.markerSettings.width, series.markerSettings.height); final DataMarkerType markerType = series.markerSettings.shape; CartesianChartPoint point; final bool hasPointColor = series.pointColorMapper != null; final bool isBoxSeries = - seriesRenderer._seriesType.contains('boxandwhisker'); + seriesRendererDetails.seriesType.contains('boxandwhisker'); final double opacity = (animationController != null && - (seriesRenderer._renderingDetails!.initialRender! || - seriesRenderer._needAnimateSeriesElements)) + (seriesRendererDetails + .stateProperties.renderingDetails.initialRender! == + true || + seriesRendererDetails.needAnimateSeriesElements == true)) ? animationController.value : 1; - point = seriesRenderer._dataPoints[markerIndex]; - Color? seriesColor = seriesRenderer._seriesColor; - if (seriesRenderer._seriesType == 'waterfall') { - seriesColor = _getWaterfallSeriesColor( - seriesRenderer._series as WaterfallSeries, + point = seriesRendererDetails.dataPoints[markerIndex]; + Color? seriesColor = seriesRendererDetails.seriesColor; + if (seriesRendererDetails.seriesType == 'waterfall') { + seriesColor = getWaterfallSeriesColor( + seriesRendererDetails.series as WaterfallSeries, point, seriesColor); } - _borderColor = series.markerSettings.borderColor ?? seriesColor; - _color = series.markerSettings.color; - _borderWidth = series.markerSettings.borderWidth; + borderColor = series.markerSettings.borderColor ?? seriesColor; + color = series.markerSettings.color; + borderWidth = series.markerSettings.borderWidth; !isBoxSeries - ? seriesRenderer._markerShapes.add(isDataPointVisible - ? _getMarkerShapesPath( + ? seriesRendererDetails.markerShapes.add(isDataPointVisible + ? getMarkerShapesPath( markerType, Offset(point.markerPoint!.x, point.markerPoint!.y), size, - seriesRenderer, + seriesRendererDetails, markerIndex, null, animationController) : null) - : seriesRenderer._markerShapes.add(isDataPointVisible - ? _getMarkerShapesPath( + : seriesRendererDetails.markerShapes.add(isDataPointVisible + ? getMarkerShapesPath( markerType, Offset(point.outliersPoint[outlierIndex!].x, point.outliersPoint[outlierIndex].y), size, - seriesRenderer, + seriesRendererDetails, markerIndex, null, animationController) : null); - if (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo') { - seriesRenderer._markerShapes2.add(isDataPointVisible - ? _getMarkerShapesPath( + if (seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType == 'hilo') { + seriesRendererDetails.markerShapes2.add(isDataPointVisible + ? getMarkerShapesPath( markerType, Offset(point.markerPoint2!.x, point.markerPoint2!.y), size, - seriesRenderer, + seriesRendererDetails, markerIndex, null, animationController) @@ -320,16 +345,16 @@ class MarkerSettingsRenderer { ? Colors.transparent : ((hasPointColor && point.pointColorMapper != null) ? point.pointColorMapper!.withOpacity(opacity) - : _borderColor!.withOpacity(opacity))) + : borderColor!.withOpacity(opacity))) ..style = PaintingStyle.stroke ..strokeWidth = point.isEmpty == true ? series.emptyPointSettings.borderWidth - : _borderWidth; + : borderWidth; if (series.gradient != null && series.markerSettings.borderColor == null) { - strokePaint = _getLinearGradientPaint( + strokePaint = getLinearGradientPaint( series.gradient!, - _getMarkerShapesPath( + getMarkerShapesPath( markerType, Offset( isBoxSeries @@ -339,12 +364,12 @@ class MarkerSettingsRenderer { ? point.outliersPoint[outlierIndex!].y : point.markerPoint!.y), size, - seriesRenderer, + seriesRendererDetails, null, null, animationController) .getBounds(), - seriesRenderer._chartState!._requireInvertedAxis); + seriesRendererDetails.stateProperties.requireInvertedAxis); strokePaint.style = PaintingStyle.stroke; strokePaint.strokeWidth = point.isEmpty == true ? series.emptyPointSettings.borderWidth @@ -354,33 +379,33 @@ class MarkerSettingsRenderer { fillPaint = Paint() ..color = point.isEmpty == true ? series.emptyPointSettings.color - : _color != Colors.transparent - ? (_color ?? - (seriesRenderer - ._renderingDetails!.chartTheme.brightness == + : color != Colors.transparent + ? (color ?? + (seriesRendererDetails.stateProperties.renderingDetails + .chartTheme.brightness == Brightness.light ? Colors.white : Colors.black)) .withOpacity(opacity) - : _color! + : color! ..style = PaintingStyle.fill; - final bool isScatter = seriesRenderer._seriesType == 'scatter'; + final bool isScatter = seriesRendererDetails.seriesType == 'scatter'; final Rect axisClipRect = - seriesRenderer._chartState!._chartAxis._axisClipRect; + seriesRendererDetails.stateProperties.chartAxis.axisClipRect; - /// Render marker points + // Render marker points if ((series.markerSettings.isVisible || isScatter || isBoxSeries) && point.isVisible && - _withInRect(seriesRenderer, point.markerPoint, axisClipRect) && + _withInRect(seriesRendererDetails, point.markerPoint, axisClipRect) && (point.markerPoint != null || // ignore: unnecessary_null_comparison point.outliersPoint[outlierIndex!] != null) && point.isGap != true && (!isScatter || series.markerSettings.shape == DataMarkerType.image) && - seriesRenderer - ._markerShapes[isBoxSeries ? outlierIndex! : markerIndex] != + seriesRendererDetails + .markerShapes[isBoxSeries ? outlierIndex! : markerIndex] != null) { - seriesRenderer.drawDataMarker( + seriesRendererDetails.renderer.drawDataMarker( isBoxSeries ? outlierIndex! : markerIndex, canvas, fillPaint, @@ -391,10 +416,10 @@ class MarkerSettingsRenderer { isBoxSeries ? point.outliersPoint[outlierIndex!].y : point.markerPoint!.y, - seriesRenderer); + seriesRendererDetails.renderer); if (series.markerSettings.shape == DataMarkerType.image) { - _drawImageMarker( - seriesRenderer, + drawImageMarker( + seriesRendererDetails, canvas, isBoxSeries ? point.outliersPoint[outlierIndex!].x @@ -402,9 +427,9 @@ class MarkerSettingsRenderer { isBoxSeries ? point.outliersPoint[outlierIndex!].y : point.markerPoint!.y); - if (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo') { - _drawImageMarker(seriesRenderer, canvas, point.markerPoint2!.x, + if (seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType == 'hilo') { + drawImageMarker(seriesRendererDetails, canvas, point.markerPoint2!.x, point.markerPoint2!.y); } } @@ -412,8 +437,8 @@ class MarkerSettingsRenderer { } /// To determine if the marker is within axis clip rect - bool _withInRect(CartesianSeriesRenderer seriesRenderer, - _ChartLocation? markerPoint, Rect axisClipRect) { + bool _withInRect(SeriesRendererDetails seriesRendererDetails, + ChartLocation? markerPoint, Rect axisClipRect) { bool withInRect = false; withInRect = markerPoint != null && @@ -426,17 +451,18 @@ class MarkerSettingsRenderer { } /// Paint the image marker - void _drawImageMarker(CartesianSeriesRenderer seriesRenderer, Canvas canvas, - double pointX, double pointY) { - // final MarkerSettingsRenderer markerSettingsRenderer = seriesRenderer._markerSettingsRenderer; - if (seriesRenderer._markerSettingsRenderer!._image != null) { - final double imageWidth = 2 * seriesRenderer._series.markerSettings.width; + void drawImageMarker(SeriesRendererDetails seriesRendererDetails, + Canvas canvas, double pointX, double pointY) { + // final MarkerSettingsRenderer markerSettingsRenderer = seriesRenderer.seriesRendererDetails.markerSettingsRenderer; + if (seriesRendererDetails.markerSettingsRenderer!.image != null) { + final double imageWidth = + 2 * seriesRendererDetails.series.markerSettings.width; final double imageHeight = - 2 * seriesRenderer._series.markerSettings.height; + 2 * seriesRendererDetails.series.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); + canvas: canvas, rect: positionRect, image: image!, fit: BoxFit.fill); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart index aa8a4da24..c5c6654ef 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/renderer.dart @@ -1,14 +1,48 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart' show DateFormat; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/error_bar_series.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; + +import '../../common/event_args.dart' show ErrorBarValues; +import '../../common/utils/helper.dart'; +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../base/chart_base.dart'; +import '../chart_series/series.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/data_label.dart'; +import '../common/marker.dart'; +import '../series_painter/histogram_painter.dart'; +import '../trendlines/trendlines.dart'; +import '../utils/helper.dart'; +import 'cartesian_state_properties.dart'; +import 'trackball_marker_settings.dart'; + +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; + +/// Represents the data label renderer class // ignore: must_be_immutable -class _DataLabelRenderer extends StatefulWidget { +class DataLabelRenderer extends StatefulWidget { + /// Creates an instance of data label renderer // ignore: prefer_const_constructors_in_immutables - _DataLabelRenderer({required this.cartesianChartState, required this.show}); + DataLabelRenderer({required this.stateProperties, required this.show}); - final SfCartesianChartState cartesianChartState; + /// Holds the value of state properties + final CartesianStateProperties stateProperties; + /// Specifies whether to show the data labels bool show; + /// Specifies the data label renderer state _DataLabelRendererState? state; @override @@ -19,7 +53,7 @@ class _DataLabelRenderer extends StatefulWidget { } } -class _DataLabelRendererState extends State<_DataLabelRenderer> +class _DataLabelRendererState extends State with SingleTickerProviderStateMixin { // List animationControllersList; @@ -42,9 +76,7 @@ class _DataLabelRendererState extends State<_DataLabelRenderer> widget.state = this; animationController.duration = Duration( milliseconds: - widget.cartesianChartState._renderingDetails.initialRender! - ? 500 - : 0); + widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -55,7 +87,7 @@ class _DataLabelRendererState extends State<_DataLabelRenderer> return Container( child: CustomPaint( painter: _DataLabelPainter( - cartesianChartState: widget.cartesianChartState, + stateProperties: widget.stateProperties, animation: dataLabelAnimation, state: this, animationController: animationController, @@ -86,14 +118,14 @@ class _DataLabelRendererState extends State<_DataLabelRenderer> class _DataLabelPainter extends CustomPainter { _DataLabelPainter( - {required this.cartesianChartState, + {required this.stateProperties, required this.state, required this.animationController, required this.animation, required ValueNotifier notifier}) : super(repaint: notifier); - final SfCartesianChartState cartesianChartState; + final CartesianStateProperties stateProperties; final _DataLabelRendererState state; @@ -103,39 +135,45 @@ class _DataLabelPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - canvas.clipRect(cartesianChartState._chartAxis._axisClipRect); - cartesianChartState._renderDatalabelRegions = []; + canvas.clipRect(stateProperties.chartAxis.axisClipRect); + stateProperties.renderDatalabelRegions = []; final List visibleSeriesRenderers = - cartesianChartState._chartSeries.visibleSeriesRenderers; + stateProperties.chartSeries.visibleSeriesRenderers; + SeriesRendererDetails seriesRendererDetails; for (int i = 0; i < visibleSeriesRenderers.length; i++) { final CartesianSeriesRenderer seriesRenderer = - cartesianChartState._chartSeries.visibleSeriesRenderers[i]; + stateProperties.chartSeries.visibleSeriesRenderers[i]; DataLabelSettingsRenderer dataLabelSettingsRenderer; - if (seriesRenderer._series.dataLabelSettings.isVisible && - (seriesRenderer._animationCompleted || - seriesRenderer._series.animationDuration == 0 || - !cartesianChartState._renderingDetails.initialRender!) && - (!seriesRenderer._needAnimateSeriesElements || - (cartesianChartState._seriesDurationFactor < - seriesRenderer._animationController.value || - seriesRenderer._series.animationDuration == 0) || - (seriesRenderer._animationController.status == + seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRendererDetails.series.dataLabelSettings.isVisible == true && + (seriesRendererDetails.animationCompleted == true || + seriesRendererDetails.series.animationDuration == 0 || + !stateProperties.renderingDetails.initialRender!) && + (seriesRendererDetails.needAnimateSeriesElements == false || + (stateProperties.seriesDurationFactor < + seriesRendererDetails.animationController.value || + seriesRendererDetails.series.animationDuration == 0) || + (seriesRendererDetails.animationController.status == AnimationStatus.dismissed)) && - seriesRenderer._series.dataLabelSettings.builder == null) { - seriesRenderer._dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer._series.dataLabelSettings); - if (seriesRenderer._visibleDataPoints != null && - seriesRenderer._visibleDataPoints!.isNotEmpty) { - for (int j = 0; j < seriesRenderer._visibleDataPoints!.length; j++) { - if (seriesRenderer._visible! && + seriesRendererDetails.series.dataLabelSettings.builder == null) { + seriesRendererDetails.dataLabelSettingsRenderer = + DataLabelSettingsRenderer( + seriesRendererDetails.series.dataLabelSettings); + if (seriesRendererDetails.visibleDataPoints != null && + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + for (int j = 0; + j < seriesRendererDetails.visibleDataPoints!.length; + j++) { + if (seriesRendererDetails.visible! == true && // ignore: unnecessary_null_comparison - seriesRenderer._series.dataLabelSettings != null) { + seriesRendererDetails.series.dataLabelSettings != null) { dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; - dataLabelSettingsRenderer._renderDataLabel( - cartesianChartState, - seriesRenderer, - seriesRenderer._visibleDataPoints![j], + seriesRendererDetails.dataLabelSettingsRenderer; + dataLabelSettingsRenderer.renderDataLabel( + stateProperties, + seriesRendererDetails, + seriesRendererDetails.visibleDataPoints![j], animation, canvas, j, @@ -144,7 +182,7 @@ class _DataLabelPainter extends CustomPainter { } } if (animation.value >= 1) { - seriesRenderer._needAnimateSeriesElements = false; + seriesRendererDetails.needAnimateSeriesElements = false; } } } @@ -155,205 +193,188 @@ class _DataLabelPainter extends CustomPainter { } /// find rect type series region -void _calculateRectSeriesRegion( +void calculateRectSeriesRegion( CartesianChartPoint point, int pointIndex, - CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final num? crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); - final num sideBySideMinimumVal = seriesRenderer._sideBySideInfo!.minimum; + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties) { + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final num? crossesAt = + getCrossesAtValue(seriesRendererDetails.renderer, stateProperties); + final num sideBySideMinimumVal = + seriesRendererDetails.sideBySideInfo!.minimum; - final num sideBySideMaximumVal = seriesRenderer._sideBySideInfo!.maximum; + final num sideBySideMaximumVal = + seriesRendererDetails.sideBySideInfo!.maximum; final num origin = - crossesAt ?? math.max(yAxisRenderer._visibleRange!.minimum, 0); + crossesAt ?? math.max(yAxisDetails.visibleRange!.minimum, 0); /// Get the rectangle based on points - final Rect rect = (seriesRenderer._seriesType.contains('stackedcolumn') || - seriesRenderer._seriesType.contains('stackedbar')) && - seriesRenderer is _StackedSeriesRenderer - ? _calculateRectangle( - point.xValue + sideBySideMinimumVal, - seriesRenderer._stackingValues[0].endValues[pointIndex], - point.xValue + sideBySideMaximumVal, - crossesAt ?? - seriesRenderer._stackingValues[0].startValues[pointIndex], - seriesRenderer, - _chartState) - : _calculateRectangle( - point.xValue + sideBySideMinimumVal, - seriesRenderer._seriesType == 'rangecolumn' - ? point.high - : seriesRenderer._seriesType == 'boxandwhisker' - ? point.maximum - : seriesRenderer._seriesType == 'waterfall' - ? point.endValue - : point.yValue, - point.xValue + sideBySideMaximumVal, - seriesRenderer._seriesType == 'rangecolumn' - ? point.low - : seriesRenderer._seriesType == 'boxandwhisker' - ? point.minimum - : seriesRenderer._seriesType == 'waterfall' - ? point.originValue - : origin, - seriesRenderer, - _chartState); + final Rect rect = + (seriesRendererDetails.seriesType.contains('stackedcolumn') == true || + seriesRendererDetails.seriesType.contains('stackedbar') == + true) && + seriesRendererDetails.renderer is StackedSeriesRenderer + ? calculateRectangle( + point.xValue + sideBySideMinimumVal, + seriesRendererDetails.stackingValues[0].endValues[pointIndex], + point.xValue + sideBySideMaximumVal, + crossesAt ?? + seriesRendererDetails + .stackingValues[0].startValues[pointIndex], + seriesRendererDetails.renderer, + stateProperties) + : calculateRectangle( + point.xValue + sideBySideMinimumVal, + seriesRendererDetails.seriesType == 'rangecolumn' + ? point.high + : seriesRendererDetails.seriesType == 'boxandwhisker' + ? point.maximum + : seriesRendererDetails.seriesType == 'waterfall' + ? point.endValue + : point.yValue, + point.xValue + sideBySideMaximumVal, + seriesRendererDetails.seriesType == 'rangecolumn' + ? point.low + : seriesRendererDetails.seriesType == 'boxandwhisker' + ? point.minimum + : seriesRendererDetails.seriesType == 'waterfall' + ? point.originValue + : origin, + seriesRendererDetails.renderer, + stateProperties); point.region = rect; - final dynamic series = seriesRenderer._series; + final dynamic series = seriesRendererDetails.series; ///Get shadow rect region - if (seriesRenderer._seriesType != 'stackedcolumn100' && - seriesRenderer._seriesType != 'stackedbar100' && - seriesRenderer._seriesType != 'waterfall' && + if (seriesRendererDetails.seriesType != 'stackedcolumn100' && + seriesRendererDetails.seriesType != 'stackedbar100' && + seriesRendererDetails.seriesType != 'waterfall' && series.isTrackVisible == true) { - final Rect shadowPointRect = _calculateShadowRectangle( + final Rect shadowPointRect = calculateShadowRectangle( point.xValue + sideBySideMinimumVal, - seriesRenderer._seriesType == 'rangecolumn' ? point.high : point.yValue, + seriesRendererDetails.seriesType == 'rangecolumn' + ? point.high + : point.yValue, point.xValue + sideBySideMaximumVal, - seriesRenderer._seriesType == 'rangecolumn' ? point.high : origin, - seriesRenderer, - _chartState, - Offset(xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.seriesType == 'rangecolumn' ? point.high : origin, + seriesRendererDetails.renderer, + stateProperties, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); point.trackerRectRegion = shadowPointRect; } - if (seriesRenderer._seriesType == 'rangecolumn' || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker')) { - point.markerPoint = _chartState._requireInvertedAxis != true - ? _ChartLocation(rect.topCenter.dx, rect.topCenter.dy) - : _ChartLocation(rect.centerRight.dx, rect.centerRight.dy); - point.markerPoint2 = _chartState._requireInvertedAxis != true - ? _ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy) - : _ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy); + if (seriesRendererDetails.seriesType == 'rangecolumn' || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { + point.markerPoint = stateProperties.requireInvertedAxis != true + ? ChartLocation(rect.topCenter.dx, rect.topCenter.dy) + : ChartLocation(rect.centerRight.dx, rect.centerRight.dy); + point.markerPoint2 = stateProperties.requireInvertedAxis != true + ? ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy) + : ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy); } else { - point.markerPoint = _chartState._requireInvertedAxis != true - ? (yAxisRenderer._axis.isInversed + point.markerPoint = stateProperties.requireInvertedAxis != true + ? (yAxisDetails.axis.isInversed ? (point.yValue.isNegative == true - ? _ChartLocation(rect.topCenter.dx, rect.topCenter.dy) - : _ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy)) + ? ChartLocation(rect.topCenter.dx, rect.topCenter.dy) + : ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy)) : (point.yValue.isNegative == true - ? _ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy) - : _ChartLocation(rect.topCenter.dx, rect.topCenter.dy))) - : (yAxisRenderer._axis.isInversed + ? ChartLocation(rect.bottomCenter.dx, rect.bottomCenter.dy) + : ChartLocation(rect.topCenter.dx, rect.topCenter.dy))) + : (yAxisDetails.axis.isInversed ? (point.yValue.isNegative == true - ? _ChartLocation(rect.centerRight.dx, rect.centerRight.dy) - : _ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy)) + ? ChartLocation(rect.centerRight.dx, rect.centerRight.dy) + : ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy)) : (point.yValue.isNegative == true - ? _ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy) - : _ChartLocation(rect.centerRight.dx, rect.centerRight.dy))); + ? ChartLocation(rect.centerLeft.dx, rect.centerLeft.dy) + : ChartLocation(rect.centerRight.dx, rect.centerRight.dy))); } - if (seriesRenderer._seriesType == 'waterfall') { + if (seriesRendererDetails.seriesType == 'waterfall') { /// The below values are used to find the chart location of the connector lines of each data point. - point.originValueLeftPoint = _calculatePoint( + point.originValueLeftPoint = calculatePoint( point.xValue + sideBySideMinimumVal, point.originValue, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, - _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(xAxisRenderer._axis.plotOffset, - yAxisRenderer._axis.plotOffset))); - point.originValueRightPoint = _calculatePoint( + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset( + xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); + point.originValueRightPoint = calculatePoint( point.xValue + sideBySideMaximumVal, point.originValue, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, - _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(xAxisRenderer._axis.plotOffset, - yAxisRenderer._axis.plotOffset))); - point.endValueLeftPoint = _calculatePoint( + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset( + xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); + point.endValueLeftPoint = calculatePoint( point.xValue + sideBySideMinimumVal, point.endValue, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, - _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(xAxisRenderer._axis.plotOffset, - yAxisRenderer._axis.plotOffset))); - point.endValueRightPoint = _calculatePoint( + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset( + xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); + point.endValueRightPoint = calculatePoint( point.xValue + sideBySideMaximumVal, point.endValue, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, - _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(xAxisRenderer._axis.plotOffset, - yAxisRenderer._axis.plotOffset))); + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset( + xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset))); } } -/// calculate scatter, bubble series datapoints region -void _calculatePointSeriesRegion( +/// calculate scatter, bubble series data points region +void calculatePointSeriesRegion( CartesianChartPoint point, int pointIndex, - CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, Rect rect) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final CartesianSeries series = seriesRenderer._series; - final _ChartLocation currentPoint = _calculatePoint( + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final CartesianSeries series = seriesRendererDetails.series; + final ChartLocation currentPoint = calculatePoint( point.xValue, point.yValue, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); point.markerPoint = currentPoint; - if (seriesRenderer._seriesType == 'scatter') { + if (seriesRendererDetails.seriesType == 'scatter') { point.region = Rect.fromLTRB( currentPoint.x - series.markerSettings.width, currentPoint.y - series.markerSettings.width, currentPoint.x + series.markerSettings.width, currentPoint.y + series.markerSettings.width); } else { - final BubbleSeries bubbleSeries = - series as BubbleSeries; - num bubbleRadius = 0, sizeRange = 0, radiusRange, bubbleSize; - if (seriesRenderer is BubbleSeriesRenderer) { - sizeRange = seriesRenderer._maxSize! - seriesRenderer._minSize!; - } - bubbleSize = ((point.bubbleSize) ?? 4).toDouble(); - if (bubbleSeries.sizeValueMapper == null) { - // ignore: unnecessary_null_comparison - bubbleSeries.minimumRadius != null - ? bubbleRadius = bubbleSeries.minimumRadius - : bubbleRadius = bubbleSeries.maximumRadius; - } else { - // ignore: unnecessary_null_comparison - if ((bubbleSeries.maximumRadius != null) && - // ignore: unnecessary_null_comparison - (bubbleSeries.minimumRadius != null)) { - if (sizeRange == 0) { - bubbleRadius = bubbleSeries.maximumRadius; - } else { - radiusRange = - (bubbleSeries.maximumRadius - bubbleSeries.minimumRadius) * 2; - if (seriesRenderer is BubbleSeriesRenderer) { - bubbleRadius = - (((bubbleSize.abs() - seriesRenderer._minSize!) * radiusRange) / - sizeRange) + - bubbleSeries.minimumRadius; - } - } - } - } + final num bubbleRadius = + calculateBubbleRadius(seriesRendererDetails, series, point); point.region = Rect.fromLTRB( currentPoint.x - 2 * bubbleRadius, currentPoint.y - 2 * bubbleRadius, @@ -362,122 +383,176 @@ void _calculatePointSeriesRegion( } } +/// calculate errorBar series data points region +void calculateErrorBarSeriesRegion( + CartesianChartPoint point, + int pointIndex, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, + Rect rect) { + if (!point.isGap) { + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final ErrorBarSeries series = + seriesRendererDetails.series as ErrorBarSeries; + final num actualXValue = point.xValue; + final num actualYValue = point.yValue; + final ChartLocation currentPoint = calculatePoint( + actualXValue, + actualYValue, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + rect); + point.currentPoint = currentPoint; + final ErrorBarValues errorBarValues = point.errorBarValues!; + if (errorBarValues.horizontalPositiveErrorValue != null) { + point.horizontalPositiveErrorPoint = calculatePoint( + errorBarValues.horizontalPositiveErrorValue!, + actualYValue, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, + series, + rect); + } + if (errorBarValues.horizontalNegativeErrorValue != null) { + point.horizontalNegativeErrorPoint = calculatePoint( + errorBarValues.horizontalNegativeErrorValue!, + actualYValue, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, + series, + rect); + } + if (errorBarValues.verticalPositiveErrorValue != null) { + point.verticalPositiveErrorPoint = calculatePoint( + actualXValue, + errorBarValues.verticalPositiveErrorValue!, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, + series, + rect); + } + if (errorBarValues.verticalNegativeErrorValue != null) { + point.verticalNegativeErrorPoint = calculatePoint( + actualXValue, + errorBarValues.verticalNegativeErrorValue, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, + series, + rect); + } + } +} + ///calculate data point region for path series like line, area, etc., -void _calculatePathSeriesRegion( +void calculatePathSeriesRegion( CartesianChartPoint point, int pointIndex, - CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, Rect rect, double markerHeight, double markerWidth, - [_VisibleRange? sideBySideInfo, + [VisibleRange? sideBySideInfo, CartesianChartPoint? _nextPoint, num? midX, num? midY]) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final num? sideBySideMinimumVal = seriesRenderer._sideBySideInfo?.minimum; - - final num? sideBySideMaximumVal = seriesRenderer._sideBySideInfo?.maximum; - if (seriesRenderer._seriesType != 'rangearea' && - seriesRenderer._seriesType != 'splinerangearea' && - (!seriesRenderer._seriesType.contains('hilo')) && - (!seriesRenderer._seriesType.contains('candle')) && - !seriesRenderer._seriesType.contains('boxandwhisker')) { - if (seriesRenderer._seriesType == 'spline' && - pointIndex <= seriesRenderer._dataPoints.length - 2) { - point.controlPoint = seriesRenderer._drawControlPoints[pointIndex]; - point.startControl = _calculatePoint( + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final num? sideBySideMinimumVal = + seriesRendererDetails.sideBySideInfo?.minimum; + + final num? sideBySideMaximumVal = + seriesRendererDetails.sideBySideInfo?.maximum; + if (seriesRendererDetails.seriesType != 'rangearea' && + seriesRendererDetails.seriesType != 'splinerangearea' && + (seriesRendererDetails.seriesType.contains('hilo') == false) && + (seriesRendererDetails.seriesType.contains('candle') == false) && + seriesRendererDetails.seriesType.contains('boxandwhisker') == false) { + if (seriesRendererDetails.seriesType == 'spline' && + pointIndex <= seriesRendererDetails.dataPoints.length - 2) { + point.controlPoint = seriesRendererDetails.drawControlPoints[pointIndex]; + point.startControl = calculatePoint( point.controlPoint![0].dx, point.controlPoint![0].dy, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.endControl = _calculatePoint( + point.endControl = calculatePoint( point.controlPoint![1].dx, point.controlPoint![1].dy, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); } - if (seriesRenderer._seriesType == 'splinearea' && + if (seriesRendererDetails.seriesType == 'splinearea' && pointIndex != 0 && - pointIndex <= seriesRenderer._dataPoints.length - 1) { - point.controlPoint = seriesRenderer._drawControlPoints[pointIndex - 1]; - point.startControl = _calculatePoint( + pointIndex <= seriesRendererDetails.dataPoints.length - 1) { + point.controlPoint = + seriesRendererDetails.drawControlPoints[pointIndex - 1]; + point.startControl = calculatePoint( point.controlPoint![0].dx, point.controlPoint![0].dy, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.endControl = _calculatePoint( + point.endControl = calculatePoint( point.controlPoint![1].dx, point.controlPoint![1].dy, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); } - if (seriesRenderer._seriesType == 'stepline') { - point.currentPoint = _calculatePoint( + if (seriesRendererDetails.seriesType == 'stepline') { + point.currentPoint = calculatePoint( point.xValue, point.yValue, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - if (_nextPoint != null) { - point._nextPoint = _calculatePoint( - _nextPoint.xValue, - _nextPoint.yValue, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, - rect); - } - if (midX != null && midY != null) { - point._midPoint = _calculatePoint( - midX, - midY, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, - rect); - } } - final _ChartLocation currentPoint = - (seriesRenderer._seriesType == 'stackedarea' || - seriesRenderer._seriesType == 'stackedarea100' || - seriesRenderer._seriesType == 'stackedline' || - seriesRenderer._seriesType == 'stackedline100') && - seriesRenderer is _StackedSeriesRenderer - ? _calculatePoint( + final ChartLocation currentPoint = + (seriesRendererDetails.seriesType == 'stackedarea' || + seriesRendererDetails.seriesType == 'stackedarea100' || + seriesRendererDetails.seriesType == 'stackedline' || + seriesRendererDetails.seriesType == 'stackedline100') && + seriesRendererDetails.renderer is StackedSeriesRenderer + ? calculatePoint( point.xValue, - seriesRenderer._stackingValues[0].endValues[pointIndex], - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.stackingValues[0].endValues[pointIndex], + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect) - : _calculatePoint( + : calculatePoint( point.xValue, point.yValue, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); point.region = Rect.fromLTWH(currentPoint.x - markerWidth, @@ -495,7 +570,7 @@ void _calculatePathSeriesRegion( (point.low > point.high) == true) ? point.high : point.low; - if (seriesRenderer._seriesType == 'boxandwhisker') { + if (seriesRendererDetails.seriesType == 'boxandwhisker') { value1 = (point.minimum != null && point.maximum != null && point.minimum! < point.maximum!) @@ -507,92 +582,92 @@ void _calculatePathSeriesRegion( ? point.maximum : point.minimum; } - point.markerPoint = _calculatePoint( + point.markerPoint = calculatePoint( point.xValue, - yAxisRenderer._axis.isInversed ? value2 : value1, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + yAxisDetails.axis.isInversed ? value2 : value1, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.markerPoint2 = _calculatePoint( + point.markerPoint2 = calculatePoint( point.xValue, - yAxisRenderer._axis.isInversed ? value1 : value2, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + yAxisDetails.axis.isInversed ? value1 : value2, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - if (seriesRenderer._seriesType == 'splinerangearea' && + if (seriesRendererDetails.seriesType == 'splinerangearea' && pointIndex != 0 && - pointIndex <= seriesRenderer._dataPoints.length - 1) { - point.controlPointshigh = seriesRenderer._drawHighControlPoints[ - seriesRenderer._dataPoints.indexOf(point) - 1]; + pointIndex <= seriesRendererDetails.dataPoints.length - 1) { + point.controlPointshigh = seriesRendererDetails.drawHighControlPoints[ + seriesRendererDetails.dataPoints.indexOf(point) - 1]; - point.highStartControl = _calculatePoint( + point.highStartControl = calculatePoint( point.controlPointshigh![0].dx, point.controlPointshigh![0].dy, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.highEndControl = _calculatePoint( + point.highEndControl = calculatePoint( point.controlPointshigh![1].dx, point.controlPointshigh![1].dy, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); } - if (seriesRenderer._seriesType == 'splinerangearea' && + if (seriesRendererDetails.seriesType == 'splinerangearea' && pointIndex >= 0 && - pointIndex <= seriesRenderer._dataPoints.length - 2) { - point.controlPointslow = seriesRenderer - ._drawLowControlPoints[seriesRenderer._dataPoints.indexOf(point)]; + pointIndex <= seriesRendererDetails.dataPoints.length - 2) { + point.controlPointslow = seriesRendererDetails.drawLowControlPoints[ + seriesRendererDetails.dataPoints.indexOf(point)]; - point.lowStartControl = _calculatePoint( + point.lowStartControl = calculatePoint( point.controlPointslow![0].dx, point.controlPointslow![0].dy, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.lowEndControl = _calculatePoint( + point.lowEndControl = calculatePoint( point.controlPointslow![1].dx, point.controlPointslow![1].dy, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); } - if (seriesRenderer._seriesType == 'hilo' && + if (seriesRendererDetails.seriesType == 'hilo' && point.low != null && point.high != null) { - point.lowPoint = _calculatePoint( + point.lowPoint = calculatePoint( point.xValue, point.low, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.highPoint = _calculatePoint( + point.highPoint = calculatePoint( point.xValue, point.high, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - } else if ((seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle') && + } else if ((seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType == 'candle') && point.open != null && point.close != null && point.low != null && @@ -601,80 +676,80 @@ void _calculatePathSeriesRegion( (((point.xValue + sideBySideMaximumVal) - (point.xValue + sideBySideMinimumVal)) / 2); - point.openPoint = _calculatePoint( + point.openPoint = calculatePoint( point.xValue + sideBySideMinimumVal, point.open, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.closePoint = _calculatePoint( + point.closePoint = calculatePoint( point.xValue + sideBySideMaximumVal, point.close, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - if (seriesRenderer._series.dataLabelSettings.isVisible) { - point.centerOpenPoint = _calculatePoint( + if (seriesRendererDetails.series.dataLabelSettings.isVisible == true) { + point.centerOpenPoint = calculatePoint( point.xValue, point.open, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.centerClosePoint = _calculatePoint( + point.centerClosePoint = calculatePoint( point.xValue, point.close, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); } - point.centerHighPoint = _calculatePoint( + point.centerHighPoint = calculatePoint( center, point.high, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.centerLowPoint = _calculatePoint( + point.centerLowPoint = calculatePoint( center, point.low, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.lowPoint = _calculatePoint( + point.lowPoint = calculatePoint( point.xValue + sideBySideMinimumVal, point.low, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.highPoint = _calculatePoint( + point.highPoint = calculatePoint( point.xValue + sideBySideMaximumVal, point.high, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); } - if (seriesRenderer._seriesType.contains('boxandwhisker') && + if (seriesRendererDetails.seriesType.contains('boxandwhisker') == true && point.minimum != null && point.maximum != null && point.upperQuartile != null && @@ -684,121 +759,122 @@ void _calculatePathSeriesRegion( (((point.xValue + sideBySideMaximumVal) - (point.xValue + sideBySideMinimumVal)) / 2); - point.centerMeanPoint = _calculatePoint( + + point.centerMeanPoint = calculatePoint( center, point.mean, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.minimumPoint = _calculatePoint( + point.minimumPoint = calculatePoint( point.xValue + sideBySideMinimumVal, point.minimum, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.maximumPoint = _calculatePoint( + point.maximumPoint = calculatePoint( point.xValue + sideBySideMaximumVal, point.maximum, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.centerMinimumPoint = _calculatePoint( + point.centerMinimumPoint = calculatePoint( center, point.minimum, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.centerMaximumPoint = _calculatePoint( + point.centerMaximumPoint = calculatePoint( center, point.maximum, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.lowerQuartilePoint = _calculatePoint( + point.lowerQuartilePoint = calculatePoint( point.xValue + sideBySideMinimumVal, point.lowerQuartile, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.upperQuartilePoint = _calculatePoint( + point.upperQuartilePoint = calculatePoint( point.xValue + sideBySideMaximumVal, point.upperQuartile, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.medianPoint = _calculatePoint( + point.medianPoint = calculatePoint( point.xValue + sideBySideMinimumVal, point.median, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - point.centerMedianPoint = _calculatePoint( + point.centerMedianPoint = calculatePoint( center, point.median, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); } - point.region = seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker') - ? !_chartState._requireInvertedAxis + point.region = seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true + ? !stateProperties.requireInvertedAxis ? Rect.fromLTWH( point.markerPoint!.x, point.markerPoint!.y, - seriesRenderer._series.borderWidth, + seriesRendererDetails.series.borderWidth, point.markerPoint2!.y - point.markerPoint!.y) : Rect.fromLTWH( point.markerPoint2!.x, point.markerPoint2!.y, (point.markerPoint!.x - point.markerPoint2!.x).abs(), - seriesRenderer._series.borderWidth) + seriesRendererDetails.series.borderWidth) : Rect.fromLTRB( point.markerPoint!.x - markerWidth, point.markerPoint!.y - markerHeight, point.markerPoint!.x + markerWidth, point.markerPoint2!.y); - if (seriesRenderer._seriesType.contains('boxandwhisker')) { - point.boxRectRegion = _calculateRectangle( + if (seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { + point.boxRectRegion = calculateRectangle( point.xValue + sideBySideMinimumVal, point.upperQuartile!, point.xValue + sideBySideMaximumVal, point.lowerQuartile!, - seriesRenderer, - _chartState); + seriesRendererDetails.renderer, + stateProperties); } } } /// Finding outliers region -void _calculateOutlierRegion(CartesianChartPoint point, - _ChartLocation outlierPosition, num outlierWidth) { +void calculateOutlierRegion(CartesianChartPoint point, + ChartLocation outlierPosition, num outlierWidth) { point.outlierRegion!.add(Rect.fromLTRB( outlierPosition.x - outlierWidth, outlierPosition.y - outlierWidth, @@ -807,37 +883,39 @@ void _calculateOutlierRegion(CartesianChartPoint point, } ///Finding tooltip region -void _calculateTooltipRegion( +void calculateTooltipRegion( CartesianChartPoint point, int seriesIndex, - CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, [Trendline? trendline, TrendlineRenderer? trendlineRenderer, int? trendlineIndex]) { - final SfCartesianChart chart = _chartState._chart; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final CartesianSeries series = seriesRenderer._series; - final num? crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); + final SfCartesianChart chart = stateProperties.chart; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final CartesianSeries series = seriesRendererDetails.series; + final num? crossesAt = + getCrossesAtValue(seriesRendererDetails.renderer, stateProperties); // ignore: unnecessary_null_comparison if ((series.enableTooltip != null || // ignore: unnecessary_null_comparison - seriesRenderer._chart.trackballBehavior != null || + seriesRendererDetails.chart.trackballBehavior != null || chart.onPointTapped != null || - seriesRenderer._series.onPointTap != null || - seriesRenderer._series.onPointDoubleTap != null || - seriesRenderer._series.onPointLongPress != null) && + seriesRendererDetails.series.onPointTap != null || + seriesRendererDetails.series.onPointDoubleTap != null || + seriesRendererDetails.series.onPointLongPress != null) && (series.enableTooltip || - seriesRenderer._chart.trackballBehavior.enable || + seriesRendererDetails.chart.trackballBehavior.enable == true || chart.onPointTapped != null || - seriesRenderer._series.onPointTap != null || - seriesRenderer._series.onPointDoubleTap != null || - seriesRenderer._series.onPointLongPress != null) && + seriesRendererDetails.series.onPointTap != null || + seriesRendererDetails.series.onPointDoubleTap != null || + seriesRendererDetails.series.onPointLongPress != null) && // ignore: unnecessary_null_comparison point != null && !point.isGap && !point.isDrop && - seriesRenderer._regionalData != null) { + seriesRendererDetails.regionalData != null) { bool isTrendline = false; if (trendline != null) { isTrendline = true; @@ -846,53 +924,52 @@ void _calculateTooltipRegion( num binWidth = 0; String? date; final List regionRect = []; - if (seriesRenderer is HistogramSeriesRenderer) { - binWidth = seriesRenderer._histogramValues.binWidth!; + if (seriesRendererDetails.renderer is HistogramSeriesRenderer) { + binWidth = seriesRendererDetails.histogramValues.binWidth!; } - if (xAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis xAxis = xAxisRenderer._axis as DateTimeAxis; + if (xAxisDetails is DateTimeAxisDetails) { + final DateTimeAxis xAxis = xAxisDetails.axis as DateTimeAxis; final DateFormat dateFormat = - xAxis.dateFormat ?? _getDateTimeLabelFormat(xAxisRenderer); + xAxis.dateFormat ?? getDateTimeLabelFormat(xAxisDetails.axisRenderer); date = dateFormat .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); - } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + } else if (xAxisDetails is DateTimeCategoryAxisDetails) { date = point.x is DateTime - ? xAxisRenderer._dateFormat.format(point.x) + ? xAxisDetails.dateFormat.format(point.x) : point.x.toString(); } - xAxisRenderer is CategoryAxisRenderer + xAxisDetails is CategoryAxisDetails ? regionData.add(point.x.toString()) - : (xAxisRenderer is DateTimeAxisRenderer || - xAxisRenderer is DateTimeCategoryAxisRenderer) + : (xAxisDetails is DateTimeAxisDetails || + xAxisDetails is DateTimeCategoryAxisDetails) ? regionData.add(date!.toString()) - : seriesRenderer._seriesType != 'histogram' - ? regionData.add(_getLabelValue( - point.xValue, - xAxisRenderer._axis, + : seriesRendererDetails.seriesType != 'histogram' + ? regionData.add(getLabelValue(point.xValue, xAxisDetails.axis, chart.tooltipBehavior.decimalPlaces) .toString()) - : regionData.add((_getLabelValue( + : regionData.add((getLabelValue( point.xValue - binWidth / 2, - xAxisRenderer._axis, + xAxisDetails.axis, chart.tooltipBehavior.decimalPlaces) .toString()) + ' - ' + - (_getLabelValue( + (getLabelValue( point.xValue + binWidth / 2, - xAxisRenderer._axis, + xAxisDetails.axis, chart.tooltipBehavior.decimalPlaces) .toString())); - if (seriesRenderer._seriesType.contains('range') && !isTrendline || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker')) { - if (seriesRenderer._seriesType != 'hiloopenclose' && - seriesRenderer._seriesType != 'candle' && - seriesRenderer._seriesType != 'boxandwhisker') { + if (seriesRendererDetails.seriesType.contains('range') == true && + !isTrendline || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { + if (seriesRendererDetails.seriesType != 'hiloopenclose' && + seriesRendererDetails.seriesType != 'candle' && + seriesRendererDetails.seriesType != 'boxandwhisker') { regionData.add(point.high.toString()); regionData.add(point.low.toString()); - } else if (seriesRenderer._seriesType != 'boxandwhisker') { + } else if (seriesRendererDetails.seriesType != 'boxandwhisker') { regionData.add(point.high.toString()); regionData.add(point.low.toString()); regionData.add(point.open.toString()); @@ -909,70 +986,77 @@ void _calculateTooltipRegion( regionData.add(point.yValue.toString()); } regionData.add(isTrendline - ? trendlineRenderer!._name ?? '' + ? trendlineRenderer!.name ?? '' : series.name ?? 'series $seriesIndex'); - regionRect.add(seriesRenderer._seriesType.contains('boxandwhisker') - ? point.boxRectRegion - : point.region); - regionRect.add((seriesRenderer._isRectSeries) || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker') - ? seriesRenderer._seriesType == 'column' || - seriesRenderer._seriesType.contains('stackedcolumn') || - seriesRenderer._seriesType == 'histogram' + regionRect.add( + seriesRendererDetails.seriesType.contains('boxandwhisker') == true + ? point.boxRectRegion + : point.region); + regionRect.add((seriesRendererDetails.isRectSeries == true) || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true + ? seriesRendererDetails.seriesType == 'column' || + seriesRendererDetails.seriesType.contains('stackedcolumn') == + true || + seriesRendererDetails.seriesType == 'histogram' ? (point.yValue > (crossesAt ?? 0)) == true ? point.region!.topCenter : point.region!.bottomCenter - : seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker') + : seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == + true || + seriesRendererDetails.seriesType + .contains('boxandwhisker') == + true ? point.region!.topCenter : point.region!.topCenter - : (seriesRenderer._seriesType.contains('rangearea') + : (seriesRendererDetails.seriesType.contains('rangearea') == true ? (isTrendline ? Offset(point.markerPoint!.x, point.markerPoint!.y) : Offset(point.markerPoint!.x, point.markerPoint!.y)) : point.region!.center)); regionRect.add( - isTrendline ? trendlineRenderer!._fillColor : point.pointColorMapper); + isTrendline ? trendlineRenderer!.fillColor : point.pointColorMapper); regionRect.add(point.bubbleSize); regionRect.add(point); regionRect.add(point.outlierRegion); regionRect.add(point.outlierRegionPosition); - if (seriesRenderer._seriesType.contains('stacked')) { + if (seriesRendererDetails.seriesType.contains('stacked') == true) { regionData.add((point.cumulativeValue).toString()); } regionData.add('$isTrendline'); if (isTrendline) { regionRect.add(trendline); } - seriesRenderer._regionalData![regionRect] = regionData; + seriesRendererDetails.regionalData![regionRect] = regionData; point.regionData = regionData; } } /// Paint the image marker -void _drawImageMarker(CartesianSeriesRenderer? seriesRenderer, Canvas canvas, - double pointX, double pointY, +void drawImageMarker(SeriesRendererDetails? seriesRendererDetails, + Canvas canvas, double pointX, double pointY, [TrackballMarkerSettings? trackballMarkerSettings, - SfCartesianChartState? chartState]) { + CartesianStateProperties? stateProperties]) { final MarkerSettingsRenderer? markerSettingsRenderer = - seriesRenderer?._markerSettingsRenderer; + seriesRendererDetails?.markerSettingsRenderer; - if (seriesRenderer != null && markerSettingsRenderer!._image != null) { - final double imageWidth = 2 * seriesRenderer._series.markerSettings.width; - final double imageHeight = 2 * seriesRenderer._series.markerSettings.height; + if (seriesRendererDetails != null && markerSettingsRenderer!.image != null) { + final double imageWidth = + 2.0 * seriesRendererDetails.series.markerSettings.width; + final double imageHeight = + 2.0 * seriesRendererDetails.series.markerSettings.height; final Rect positionRect = Rect.fromLTWH(pointX - imageWidth / 2, pointY - imageHeight / 2, imageWidth, imageHeight); paintImage( canvas: canvas, rect: positionRect, - image: markerSettingsRenderer._image!, + image: markerSettingsRenderer.image!, fit: BoxFit.fill); } - if (chartState?._trackballMarkerSettingsRenderer._image != null) { + if (stateProperties?.trackballMarkerSettingsRenderer.image != null) { final double imageWidth = 2 * trackballMarkerSettings!.width; final double imageHeight = 2 * trackballMarkerSettings.height; final Rect positionRect = Rect.fromLTWH(pointX - imageWidth / 2, @@ -980,13 +1064,13 @@ void _drawImageMarker(CartesianSeriesRenderer? seriesRenderer, Canvas canvas, paintImage( canvas: canvas, rect: positionRect, - image: chartState!._trackballMarkerSettingsRenderer._image!, + image: stateProperties!.trackballMarkerSettingsRenderer.image!, fit: BoxFit.fill); } } /// This method is for to calculate and rendering the length and Offsets of the dashed lines -void _drawDashedLine( +void drawDashedLine( Canvas canvas, List dashArray, Paint paint, Path path) { bool even = false; for (int i = 1; i < dashArray.length; i = i + 2) { @@ -997,9 +1081,9 @@ void _drawDashedLine( if (even == false) { paint.isAntiAlias = false; canvas.drawPath( - _dashPath( + dashPath( path, - dashArray: _CircularIntervalList(dashArray), + dashArray: CircularIntervalList(dashArray), )!, paint); } else { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/segment_properties.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/segment_properties.dart new file mode 100644 index 000000000..002c80825 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/segment_properties.dart @@ -0,0 +1,166 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/waterfall_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/waterfall_series.dart'; +import '../chart_series/xy_data_series.dart'; +import 'cartesian_state_properties.dart'; + +/// Represents the segment properties class +class SegmentProperties { + ///argument constructor for SegmentProperties class + SegmentProperties(this.stateProperties, this.segment); + + ///Color of the segment + Color? color, strokeColor; + + ///Border width of the segment + double? strokeWidth; + + /// Represents the value of chart series + late XyDataSeries series; + + /// Represents the value of old series + XyDataSeries? oldSeries; + + ///Chart series renderer + late CartesianSeriesRenderer seriesRenderer; + + /// Represents the value of old series renderer + CartesianSeriesRenderer? oldSeriesRenderer; + + /// Rectangle of the segment + RRect? segmentRect; + + /// Default fill color & stroke color + Paint? defaultFillColor, defaultStrokeColor; + + /// Represents the old segment index + int? oldSegmentIndex; + + /// Represents the series index + late int seriesIndex; + + /// Represents the current, old and next point + CartesianChartPoint? currentPoint, point, oldPoint, nextPoint; + + /// Old series visibility property. + bool? oldSeriesVisible; + + /// Old rect region. + Rect? oldRegion; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the segment + final ChartSegment segment; + + /// Represents the value of path + late Path path; + + /// Represents the value of stroke path + Path? strokePath; + + /// Represents the value of path rect + Rect? pathRect; + + /// Represents the track bar rect + late RRect trackBarRect; + + /// Represents the tracker fill paint + Paint? trackerFillPaint; + + /// Represents the tracker stroke paint + Paint? trackerStrokePaint; + + /// Represents the value of x, high and low + late double x, low, high; + + /// Represents the value of openX and closeX value + late double openX, closeX; + + /// Represents the value of top rect y and bottom rect y value + late double topRectY, bottomRectY; + + /// Represents the low, high, min and max point value + late ChartLocation lowPoint, highPoint, minPoint, maxPoint; + + /// Represents the point color mapper value + Color? pointColorMapper; + + /// Specifies the value of isSolid and isBull + late bool isSolid = false, isBull = false; + + /// Specifies the value of track rect + late RRect trackRect; + + /// Colors of the negative point, intermediate point and total point. + Color? negativePointsColor, intermediateSumColor, totalSumColor; + + /// Represents the value a1, y1, x2, y2, x3 and y3 + late double x1, y1, x2, y2, x3, y3; + + /// Represents the value of midX, midY + late num midX, midY; + + /// Represents the value of cumulative position + late double currentCummulativePos, nextCummulativePos; + + /// Represents the cumulative value + late double currentCummulativeValue, nextCummulativeValue; + + /// Represents the value of border path + Path? borderPath; + + /// Represents the openY, closeY + late double openY, closeY; + + /// Represents the value of min and max + late double min, max; + + /// Represents the value of lowerX and upperX + late double lowerX, upperX; + + /// Method to get series tracker fill. + Paint getTrackerFillPaint() { + final dynamic seriesWithTrack = series; + trackerFillPaint = Paint() + ..color = seriesWithTrack.trackColor + ..style = PaintingStyle.fill; + return trackerFillPaint!; + } + + /// Method to get series tracker stroke color. + Paint getTrackerStrokePaint() { + final dynamic seriesWithTrack = series; + trackerStrokePaint = Paint() + ..color = seriesWithTrack.trackBorderColor + ..strokeWidth = seriesWithTrack.trackBorderWidth + ..style = PaintingStyle.stroke; + seriesWithTrack.trackBorderWidth == 0 + ? trackerStrokePaint!.color = Colors.transparent + : trackerStrokePaint!.color; + return trackerStrokePaint!; + } + + /// Get the color of connector lines. + Paint? getConnectorLineStrokePaint() { + if (series is WaterfallSeries) { + final WaterfallSeries waterfallSeries = + series as WaterfallSeries; + + (segment as WaterfallSegment).connectorLineStrokePaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = waterfallSeries.connectorLineSettings.width + ..color = waterfallSeries.connectorLineSettings.color ?? + stateProperties + .renderingDetails.chartTheme.waterfallConnectorLineColor; + return (segment as WaterfallSegment).connectorLineStrokePaint!; + } + + return null; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/common/trackball_marker_settings.dart b/packages/syncfusion_flutter_charts/lib/src/chart/common/trackball_marker_settings.dart new file mode 100644 index 000000000..4875e2697 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/common/trackball_marker_settings.dart @@ -0,0 +1,163 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../user_interaction/trackball_marker_setting_renderer.dart'; +import '../utils/enum.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, + double? height, + double? width, + Color? color, + DataMarkerType? shape, + double? borderWidth, + Color? borderColor, + ImageProvider? image}) + : super( + height: height, + width: width, + color: color, + shape: shape, + borderWidth: borderWidth, + borderColor: borderColor, + image: image); + + ///Whether marker should be visible or not when trackball is enabled. + /// + ///The below values are applicable for this: + ///* 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. + ///* visible - Makes the trackball marker visible for all the series, + /// irrespective of considering the [isVisible] property's value in the `markerSettings`. + ///* hidden - Hides the trackball marker for all the series. + /// + ///Defaults to `TrackballVisibilityMode.auto`. + + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// trackballBehavior: TrackballBehavior( + /// enable: true, + /// markerSettings: TrackballMarkerSettings( + /// markerVisibility: TrackballVisibilityMode.auto, + /// width: 10 + /// ), + /// series: >[ + /// SplineSeries( + /// markerSettings: MarkerSettings(isVisible: true), + /// ), + /// ], + /// ); + ///} + ///``` + 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 hashList(values); + } +} + +///Options to show the details of the trackball template. +@immutable +class TrackballDetails { + ///Constructor of TrackballDetails class. + const TrackballDetails( + [this.point, + this.series, + this.pointIndex, + this.seriesIndex, + this.groupingModeInfo]); + + /// It specifies the Cartesian chart point. + final CartesianChartPoint? point; + + /// It specifies the Cartesian series. + final CartesianSeries? series; + + /// It specifies the point index. + final int? pointIndex; + + /// It specifies the series index. + final int? seriesIndex; + + /// It specifies the trackball grouping mode info. + final TrackballGroupingModeInfo? groupingModeInfo; + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is TrackballDetails && + other.point == point && + other.series == series && + other.pointIndex == pointIndex && + other.seriesIndex == seriesIndex && + other.groupingModeInfo == groupingModeInfo; + } + + @override + int get hashCode { + final List values = [ + point, + series, + pointIndex, + seriesIndex, + groupingModeInfo + ]; + return hashList(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart index 3160f1b9e..4b784f7d9 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/area_painter.dart @@ -1,103 +1,262 @@ -part of charts; +import 'dart:math' as math_lib; +import 'dart:ui'; -class _AreaChartPainter extends CustomPainter { - _AreaChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/area_segment.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/area_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Area series +class AreaSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of AreaSeriesRenderer class. + AreaSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// Creates a segment for a data point in the series. + ChartSegment _createSegments( + Path path, Path strokePath, int seriesIndex, double animateFactor, + [List? _points]) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + final AreaSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final List oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + segment.currentSegmentIndex = 0; + if (_points != null) { + segment.points = _points; + } + segmentProperties.seriesRenderer = this; + segmentProperties.seriesIndex = seriesIndex; + segment.animationFactor = animateFactor; + segmentProperties.path = path; + segmentProperties.strokePath = strokePath; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_currentSeriesDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + // ignore: unnecessary_null_comparison + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = 0; + } + customizeSegment(segment); + _currentSeriesDetails.segments.add(segment); + return segment; + } + + /// To draw area segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[segment.currentSegmentIndex!], + _currentSeriesDetails.chart); + } + segment.onPaint(canvas); + } + + /// To create area series segments + @override + AreaSegment createSegment() => AreaSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final AreaSegment areaSegment = segment as AreaSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(areaSegment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + areaSegment.strokePaint = areaSegment.getStrokePaint(); + areaSegment.fillPaint = areaSegment.getFillPaint(); + } + + /// Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the area chart painter +class AreaChartPainter extends CustomPainter { + /// Creates an instance of area chart painter + AreaChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the value of chart final SfCartesianChart chart; + + /// Specifies whether to repaint the series final bool isRepaint; + + /// Specifies the value of animation controller final Animation animationController; + + /// Specifies the area series renderer final AreaSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for area series @override void paint(Canvas canvas, Size size) { final int seriesIndex = painterKey.index; Rect clipRect; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final AreaSeries series = - seriesRenderer._series as AreaSeries; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); + seriesRendererDetails.series as AreaSeries; + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); double animationFactor; CartesianChartPoint? prevPoint, point, _point; - _ChartLocation? currentPoint, originPoint, _oldPoint; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + ChartLocation? currentPoint, originPoint, _oldPoint; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; CartesianSeriesRenderer? oldSeriesRenderer; + SeriesRendererDetails? oldSeriesRendererDetails; final Path _path = Path(); final Path _strokePath = Path(); - final num? crossesAt = _getCrossesAtValue(seriesRenderer, chartState); + final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); final num origin = crossesAt ?? 0; final List _points = []; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the area series must be greater than or equal to 0.'); final List oldSeriesRenderers = - chartState._oldSeriesRenderers; + stateProperties.oldSeriesRenderers; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final bool widgetNeedUpdate = renderingDetails.widgetNeedUpdate; final bool isLegendToggled = renderingDetails.isLegendToggled; final bool isTransposed = - seriesRenderer._chartState!._requireInvertedAxis; + seriesRendererDetails.stateProperties.requireInvertedAxis; canvas.save(); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - oldSeriesRenderer = _getOldSeriesRenderer( - chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + oldSeriesRenderer = getOldSeriesRenderer(stateProperties, + seriesRendererDetails, seriesIndex, oldSeriesRenderers); + + if (oldSeriesRenderer != null) { + oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); + } - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || ((!(widgetNeedUpdate || isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { - _point = _getOldChartPoint(chartState, seriesRenderer, AreaSegment, - seriesIndex, pointIndex, oldSeriesRenderer, oldSeriesRenderers); + _point = getOldChartPoint( + stateProperties, + seriesRendererDetails, + AreaSegment, + seriesIndex, + pointIndex, + oldSeriesRenderer, + oldSeriesRenderers); _oldPoint = _point != null - ? _calculatePoint( + ? calculatePoint( _point.xValue, _point.yValue, - oldSeriesRenderer!._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + oldSeriesRendererDetails!.xAxisDetails!, + oldSeriesRendererDetails.yAxisDetails!, isTransposed, - oldSeriesRenderer._series, + oldSeriesRendererDetails.series, axisClipRect) : null; - currentPoint = _calculatePoint(point.xValue, point.yValue, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - originPoint = _calculatePoint( + currentPoint = calculatePoint(point.xValue, point.yValue, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); + originPoint = calculatePoint( point.xValue, - math_lib.max(yAxisRenderer._visibleRange!.minimum, origin), - xAxisRenderer, - yAxisRenderer, + math_lib.max(yAxisDetails.visibleRange!.minimum, origin), + xAxisDetails, + yAxisDetails, isTransposed, series, axisClipRect); @@ -109,10 +268,10 @@ class _AreaChartPainter extends CustomPainter { _getSeriesVisibility(dataPoints, pointIndex); if (_oldPoint != null) { isTransposed - ? x = _getAnimateValue(animationFactor, x, _oldPoint.x, - currentPoint.x, seriesRenderer) - : y = _getAnimateValue(animationFactor, y, _oldPoint.y, - currentPoint.y, seriesRenderer); + ? x = getAnimateValue(animationFactor, x, _oldPoint.x, + currentPoint.x, seriesRendererDetails) + : y = getAnimateValue(animationFactor, y, _oldPoint.y, + currentPoint.y, seriesRendererDetails); } if (prevPoint == null || dataPoints[pointIndex - 1].isGap == true || @@ -160,39 +319,38 @@ class _AreaChartPainter extends CustomPainter { } // ignore: unnecessary_null_comparison if (_path != null) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments(_path, _strokePath, painterKey.index, animationFactor, _points)); } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The area series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @@ -209,5 +367,5 @@ class _AreaChartPainter extends CustomPainter { } @override - bool shouldRepaint(_AreaChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(AreaChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart index 36febb6b0..2adc0a0a1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bar_painter.dart @@ -1,33 +1,234 @@ -part of charts; +import 'dart:ui'; -class _BarChartPainter extends CustomPainter { - _BarChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/bar_segment.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/bar_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Bar series +class BarSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of BarSeriesRenderer class. + BarSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// To add bar segments to chart segments + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + final BarSeries _barSeries = + _currentSeriesDetails.series as BarSeries; + final BarSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final List oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + segmentProperties.series = _barSeries; + segmentProperties.seriesRenderer = this; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment.animationFactor = animateFactor; + segmentProperties.currentPoint = currentPoint; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_currentSeriesDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + _currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + // ignore: unnecessary_null_comparison + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + final SeriesRendererDetails segmentOldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + segmentProperties + .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && + segmentOldSeriesDetails.segments[0] is BarSegment && + (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == + true) + ? segmentOldSeriesDetails.dataPoints[pointIndex] + : null; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + if ((_currentSeriesDetails.stateProperties.selectedSegments.length - 1 >= + pointIndex) == + true && + SegmentHelper.getSegmentProperties(_currentSeriesDetails + .stateProperties.selectedSegments[pointIndex]) + .oldSegmentIndex == + null) { + final ChartSegment selectedSegment = + _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegment); + selectedSegmentProperties.oldSeriesRenderer = + oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; + selectedSegmentProperties.seriesRenderer = this; + selectedSegmentProperties.oldSegmentIndex = + getOldSegmentIndex(selectedSegment); + } + } else if (_currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + true && + // ignore: unnecessary_null_comparison + _currentSeriesDetails.stateProperties.segments != null && + _currentSeriesDetails.stateProperties.segments.isNotEmpty == true) { + segmentProperties.oldSeriesVisible = _currentSeriesDetails + .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; + for (int i = 0; + i < _currentSeriesDetails.stateProperties.segments.length; + i++) { + final BarSegment oldSegment = + _currentSeriesDetails.stateProperties.segments[i] as BarSegment; + if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && + SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == + segmentProperties.seriesIndex) { + segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; + } + } + } + segmentProperties.path = + findingRectSeriesDashedBorder(currentPoint, _barSeries.borderWidth); + segment.segmentRect = + getRRectFromRect(currentPoint.region!, _barSeries.borderRadius); + //Tracker rect + if (_barSeries.isTrackVisible) { + segmentProperties.trackBarRect = getRRectFromRect( + currentPoint.trackerRectRegion!, _barSeries.borderRadius); + } + segmentProperties.segmentRect = segment.segmentRect; + customizeSegment(segment); + _currentSeriesDetails.segments.add(segment); + return segment; + } + + /// To draw bar segment + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[segment.currentSegmentIndex!], + _currentSeriesDetails.stateProperties.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + BarSegment createSegment() => BarSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final BarSegment barSegment = segment as BarSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(barSegment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + barSegment.strokePaint = barSegment.getStrokePaint(); + barSegment.fillPaint = barSegment.getFillPaint(); + segmentProperties.trackerFillPaint = + segmentProperties.getTrackerFillPaint(); + segmentProperties.trackerStrokePaint = + segmentProperties.getTrackerStrokePaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the bar chart painter +class BarChartPainter extends CustomPainter { + /// Creates an instance of bar chart painter + BarChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Specifies the Cartesian state chart properties + final CartesianStateProperties stateProperties; + + /// Specifies the Cartesian chart final SfCartesianChart chart; + + /// Specifies whether to repaint the segment final bool isRepaint; + + /// Specifies the value of animation controller final Animation animationController; + + /// Specifies the value of bar series renderer final BarSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Represents the value of painter key + final PainterKey painterKey; /// Painter method for bar series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final BarSeries series = - seriesRenderer._series as BarSeries; - if (seriesRenderer._visible!) { + seriesRendererDetails.series as BarSeries; + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, @@ -37,63 +238,66 @@ class _BarChartPainter extends CustomPainter { CartesianChartPoint point; canvas.save(); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The bar series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_BarChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(BarChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart index ccddbd60d..31abe86aa 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/box_and_whisker_painter.dart @@ -1,58 +1,334 @@ -part of charts; +import 'dart:ui'; -class _BoxAndWhiskerPainter extends CustomPainter { - _BoxAndWhiskerPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/box_and_whisker_segment.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/box_and_whisker_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Box and Whisker series +class BoxAndWhiskerSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of BoxAndWhiskerSeriesRenderer class. + BoxAndWhiskerSeriesRenderer(); + + late BoxAndWhiskerSegment _segment; + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + late SeriesRendererDetails _oldSeriesDetails; + + /// Range box plot _segment is created here + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + _segment = createSegment(); + SegmentHelper.setSegmentProperties(_segment, + SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_segment); + _currentSeriesDetails.oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + _currentSeriesDetails.isRectSeries = false; + segmentProperties.seriesIndex = seriesIndex; + _segment.currentSegmentIndex = pointIndex; + segmentProperties.seriesRenderer = this; + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + _segment.animationFactor = animateFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + segmentProperties.currentPoint = currentPoint; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == + true && + _currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + _currentSeriesDetails.oldSeriesRenderers != null && + _currentSeriesDetails.oldSeriesRenderers!.isNotEmpty == true && + (_currentSeriesDetails.oldSeriesRenderers!.length - 1 >= + segmentProperties.seriesIndex) == + true) { + _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( + _currentSeriesDetails + .oldSeriesRenderers![segmentProperties.seriesIndex]); + if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = _currentSeriesDetails + .oldSeriesRenderers![segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); + } + } + _segment.calculateSegmentPoints(); + //stores the points for rendering box and whisker - high, low and rect points + _segment.points + ..add(Offset(currentPoint.markerPoint!.x, segmentProperties.maxPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, segmentProperties.minPoint.y)) + ..add(Offset(segmentProperties.lowerX, segmentProperties.topRectY)) + ..add(Offset(segmentProperties.upperX, segmentProperties.topRectY)) + ..add(Offset(segmentProperties.upperX, segmentProperties.bottomRectY)) + ..add(Offset(segmentProperties.lowerX, segmentProperties.bottomRectY)); + customizeSegment(_segment); + _segment.strokePaint = _segment.getStrokePaint(); + _segment.fillPaint = _segment.getFillPaint(); + _currentSeriesDetails.segments.add(_segment); + return _segment; + } + + late BoxPlotQuartileValues _boxPlotQuartileValues; + + /// To find the minimum, maximum, quartile and median value + /// of a box plot series. + void _findBoxPlotValues(List yValues, + CartesianChartPoint point, BoxPlotMode mode) { + final int yCount = yValues.length; + _boxPlotQuartileValues = BoxPlotQuartileValues(); + _boxPlotQuartileValues.average = + (yValues.fold(0, (num x, num? y) => (x.toDouble()) + y!)) / yCount; + if (mode == BoxPlotMode.exclusive) { + _boxPlotQuartileValues.lowerQuartile = + _getExclusiveQuartileValue(yValues, yCount, 0.25); + _boxPlotQuartileValues.upperQuartile = + _getExclusiveQuartileValue(yValues, yCount, 0.75); + _boxPlotQuartileValues.median = + _getExclusiveQuartileValue(yValues, yCount, 0.5); + } else if (mode == BoxPlotMode.inclusive) { + _boxPlotQuartileValues.lowerQuartile = + _getInclusiveQuartileValue(yValues, yCount, 0.25); + _boxPlotQuartileValues.upperQuartile = + _getInclusiveQuartileValue(yValues, yCount, 0.75); + _boxPlotQuartileValues.median = + _getInclusiveQuartileValue(yValues, yCount, 0.5); + } else { + _boxPlotQuartileValues.median = _getMedian(yValues); + _getQuartileValues(yValues, yCount, _boxPlotQuartileValues); + } + _getMinMaxOutlier(yValues, yCount, _boxPlotQuartileValues); + point.minimum = _boxPlotQuartileValues.minimum; + point.maximum = _boxPlotQuartileValues.maximum; + point.lowerQuartile = _boxPlotQuartileValues.lowerQuartile; + point.upperQuartile = _boxPlotQuartileValues.upperQuartile; + point.median = _boxPlotQuartileValues.median; + point.outliers = _boxPlotQuartileValues.outliers; + point.mean = _boxPlotQuartileValues.average; + } + + /// To find exclusive quartile values. + double _getExclusiveQuartileValue( + List yValues, int count, num percentile) { + if (count == 0) { + return 0; + } else if (count == 1) { + return yValues[0]!.toDouble(); + } + num value = 0; + final num rank = percentile * (count + 1); + final int integerRank = (rank.abs()).floor(); + final num fractionRank = rank - integerRank; + if (integerRank == 0) { + value = yValues[0]!; + } else if (integerRank > count - 1) { + value = yValues[count - 1]!; + } else { + value = + fractionRank * (yValues[integerRank]! - yValues[integerRank - 1]!) + + yValues[integerRank - 1]!; + } + return value.toDouble(); + } + + /// To find inclusive quartile values. + double _getInclusiveQuartileValue( + List yValues, int count, num percentile) { + if (count == 0) { + return 0; + } else if (count == 1) { + return yValues[0]!.toDouble(); + } + num value = 0; + final num rank = percentile * (count - 1); + final int integerRank = (rank.abs()).floor(); + final num fractionRank = rank - integerRank; + value = fractionRank * (yValues[integerRank + 1]! - yValues[integerRank]!) + + yValues[integerRank]!; + return value.toDouble(); + } + + /// To find a median value of each box plot point. + double _getMedian(List values) { + final int half = (values.length / 2).floor(); + return (values.length % 2 != 0 + ? values[half]! + : ((values[half - 1]! + values[half]!) / 2.0)) + .toDouble(); + } + + /// To get the quartile values. + void _getQuartileValues(dynamic yValues, num count, + BoxPlotQuartileValues _boxPlotQuartileValues) { + if (count == 1) { + _boxPlotQuartileValues.lowerQuartile = yValues[0]; + _boxPlotQuartileValues.upperQuartile = yValues[0]; + } + final bool isEvenList = count % 2 == 0; + final num halfLength = count ~/ 2; + final List lowerQuartileArray = yValues.sublist(0, halfLength); + final List upperQuartileArray = + yValues.sublist(isEvenList ? halfLength : halfLength + 1, count); + _boxPlotQuartileValues.lowerQuartile = _getMedian(lowerQuartileArray); + _boxPlotQuartileValues.upperQuartile = _getMedian(upperQuartileArray); + } + + /// To get the outliers values of box plot series. + void _getMinMaxOutlier(List yValues, int count, + BoxPlotQuartileValues _boxPlotQuartileValues) { + final double interquartile = _boxPlotQuartileValues.upperQuartile! - + _boxPlotQuartileValues.lowerQuartile!; + final num rangeIQR = 1.5 * interquartile; + for (int i = 0; i < count; i++) { + if (yValues[i]! < _boxPlotQuartileValues.lowerQuartile! - rangeIQR) { + _boxPlotQuartileValues.outliers!.add(yValues[i]!); + } else { + _boxPlotQuartileValues.minimum = yValues[i]; + break; + } + } + for (int i = count - 1; i >= 0; i--) { + if (yValues[i]! > _boxPlotQuartileValues.upperQuartile! + rangeIQR) { + _boxPlotQuartileValues.outliers!.add(yValues[i]!); + } else { + _boxPlotQuartileValues.maximum = yValues[i]; + break; + } + } + } + + @override + BoxAndWhiskerSegment createSegment() => BoxAndWhiskerSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment _segment) { + final BoxAndWhiskerSegment boxSegment = _segment as BoxAndWhiskerSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(boxSegment); + segmentProperties.color = + segmentProperties.currentPoint!.pointColorMapper ?? + _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + boxSegment.strokePaint = boxSegment.getStrokePaint(); + boxSegment.fillPaint = boxSegment.getFillPaint(); + } + + ///Draws outlier with different shape and color of the appropriate + ///data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the box and whisker painter +class BoxAndWhiskerPainter extends CustomPainter { + /// Creates an instance of box and whisker painter + BoxAndWhiskerPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + /// Represents the Cartesian chart state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart value final SfCartesianChart chart; + + /// Specifies whether to repaint the segment final bool isRepaint; + + /// Represents the animation controller value final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the box and whisker series renderer BoxAndWhiskerSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the value of painter key + final PainterKey painterKey; /// Painter method for box and whisker series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; assert(dataPoints.isNotEmpty, 'The data points should be available to render the box and whisker series.'); Rect clipRect; double animationFactor; final BoxAndWhiskerSeries series = - seriesRenderer._series as BoxAndWhiskerSeries; + seriesRendererDetails.series as BoxAndWhiskerSeries; CartesianChartPoint? point; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the bar series must be greater than or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; @@ -61,15 +337,15 @@ class _BoxAndWhiskerPainter extends CustomPainter { (point.y).remove(null); (point.y).sort(); seriesRenderer._findBoxPlotValues(point.y, point, series.boxPlotMode); - seriesRenderer._calculateRegionData( - chartState, - seriesRenderer, + seriesRendererDetails.calculateRegionData( + seriesRendererDetails.stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex, - seriesRenderer._sideBySideInfo); + seriesRendererDetails.sideBySideInfo); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); @@ -77,29 +353,29 @@ class _BoxAndWhiskerPainter extends CustomPainter { if (point.outliers!.isNotEmpty) { final MarkerSettingsRenderer markerSettingsRenderer = MarkerSettingsRenderer(series.markerSettings); - seriesRenderer._markerShapes = []; + seriesRendererDetails.markerShapes = []; point.outlierRegion = []; point.outlierRegionPosition = []; for (int outlierIndex = 0; outlierIndex < point.outliers!.length; outlierIndex++) { - point.outliersPoint.add(_calculatePoint( + point.outliersPoint.add(calculatePoint( point.xValue, point.outliers![outlierIndex], - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, axisClipRect)); - _calculateOutlierRegion(point, point.outliersPoint[outlierIndex], + calculateOutlierRegion(point, point.outliersPoint[outlierIndex], series.markerSettings.width); point.outlierRegionPosition!.add(Offset( point.outliersPoint[outlierIndex].x, point.outliersPoint[outlierIndex].y)); markerSettingsRenderer.renderMarker( - seriesRenderer, + seriesRendererDetails, point, - seriesRenderer._seriesElementAnimation, + seriesRendererDetails.seriesElementAnimation, canvas, pointIndex, outlierIndex); @@ -107,40 +383,39 @@ class _BoxAndWhiskerPainter extends CustomPainter { } // ignore: unnecessary_null_comparison if (chart.tooltipBehavior != null && chart.tooltipBehavior.enable) { - _calculateTooltipRegion( - point, seriesIndex, seriesRenderer, chartState); + calculateTooltipRegion( + point, seriesIndex, seriesRendererDetails, stateProperties); } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTWH( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= stateProperties.seriesDurationFactor) && series.dataLabelSettings.isVisible) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The box and whisker series should be available to render a data label on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } - if (seriesRenderer._visible! && animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + if (seriesRendererDetails.visible! == true && animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_BoxAndWhiskerPainter oldDelegate) => isRepaint; + bool shouldRepaint(BoxAndWhiskerPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart index 2b2fc1be1..e20d0b799 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/bubble_painter.dart @@ -1,61 +1,233 @@ -part of charts; +import 'dart:ui'; -class _BubbleChartPainter extends CustomPainter { - _BubbleChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/bubble_segment.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/bubble_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Bubble series +class BubbleSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of BubbleSeriesRenderer class. + BubbleSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// To add bubble segments to segment list + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + final BubbleSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final List oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + _currentSeriesDetails.isRectSeries = false; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segmentProperties.seriesIndex = seriesIndex; + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + segment.animationFactor = animateFactor; + segmentProperties.currentPoint = currentPoint; + segmentProperties.seriesRenderer = this; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_currentSeriesDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + final SeriesRendererDetails segmentOldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + segmentProperties.oldPoint = + (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == true + ? segmentOldSeriesDetails.dataPoints[pointIndex] + : null; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + if ((_currentSeriesDetails.stateProperties.selectedSegments.length - 1 >= + pointIndex) == + true && + SegmentHelper.getSegmentProperties(_currentSeriesDetails + .stateProperties.selectedSegments[pointIndex]) + .oldSegmentIndex == + null) { + final ChartSegment selectedSegment = + _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegment); + selectedSegmentProperties.oldSeriesRenderer = + oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; + selectedSegmentProperties.seriesRenderer = this; + selectedSegmentProperties.oldSegmentIndex = + getOldSegmentIndex(selectedSegment); + } + } + segment.calculateSegmentPoints(); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + _currentSeriesDetails.segments.add(segment); + return segment; + } + + /// To draw bubble segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[segment.currentSegmentIndex!], + _currentSeriesDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + BubbleSegment createSegment() => BubbleSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final BubbleSegment bubbleSegment = segment as BubbleSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(bubbleSegment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; // ?? + // bubbleSegment.segmentProperties.seriesRenderer.seriesRendererDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + bubbleSegment.strokePaint = bubbleSegment.getStrokePaint(); + bubbleSegment.fillPaint = bubbleSegment.getFillPaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + if (seriesRenderer != null) { + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the bubble chart painter +class BubbleChartPainter extends CustomPainter { + /// Creates an instance of bubble chart painter + BubbleChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian chart state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart final SfCartesianChart chart; + + /// Specifies whether to repaint the segment final bool isRepaint; + + /// Specifies the value of animation controller final Animation animationController; + + /// Specifies the bubble series renderer final BubbleSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Represents the painter key + final PainterKey painterKey; /// Painter method for bubble series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; double animationFactor; final BubbleSeries series = - seriesRenderer._series as BubbleSeries; - if (seriesRenderer._visible!) { + seriesRendererDetails.series as BubbleSeries; + if (seriesRendererDetails.visible! == true) { canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the bar series must be greater than or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { final CartesianChartPoint currentPoint = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, currentPoint, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, currentPoint, pointIndex); if (currentPoint.isVisible && !currentPoint.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments(currentPoint, segmentIndex += 1, seriesIndex, animationFactor)); @@ -64,22 +236,22 @@ class _BubbleChartPainter extends CustomPainter { canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The bubble series should be available to render a marker on it.'); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_BubbleChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(BubbleChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart index c26b83462..f05207bdf 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/candle_painter.dart @@ -1,107 +1,293 @@ -part of charts; +import 'dart:ui'; -class _CandlePainter extends CustomPainter { - _CandlePainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/candle_segment.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/candle_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Candle series +class CandleSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of CandleSeriesRenderer class. + CandleSeriesRenderer(); + + late CandleSegment _candleSegment, _segment; + + late CandleSeriesRenderer _candelSeriesRenderer; + + List? _oldSeriesRenderers; + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + late SeriesRendererDetails _oldSeriesDetails; + + /// Range column _segment is created here + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + _segment = createSegment(); + SegmentHelper.setSegmentProperties(_segment, + SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_segment); + _oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + _currentSeriesDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (_segment != null) { + segmentProperties.seriesIndex = seriesIndex; + _segment.currentSegmentIndex = pointIndex; + segmentProperties.seriesRenderer = this; + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + _segment.animationFactor = animateFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + segmentProperties.currentPoint = currentPoint; + _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer); + if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == + true && + _currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + _oldSeriesRenderers != null && + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= segmentProperties.seriesIndex) { + _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers![segmentProperties.seriesIndex]); + if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers![segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); + } + } + _segment.calculateSegmentPoints(); + //stores the points for rendering candle - high, low and rect points + _segment.points + ..add( + Offset(currentPoint.markerPoint!.x, segmentProperties.highPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, segmentProperties.lowPoint.y)) + ..add(Offset(segmentProperties.openX, segmentProperties.topRectY)) + ..add(Offset(segmentProperties.closeX, segmentProperties.topRectY)) + ..add(Offset(segmentProperties.closeX, segmentProperties.bottomRectY)) + ..add(Offset(segmentProperties.openX, segmentProperties.bottomRectY)); + _candleSegment = _segment; + customizeSegment(_segment); + _segment.strokePaint = _segment.getStrokePaint(); + _segment.fillPaint = _segment.getFillPaint(); + _currentSeriesDetails.segments.add(_segment); + } + return _segment; + } + + @override + CandleSegment createSegment() => CandleSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment _segment) { + _currentSeriesDetails.candleSeries = + _currentSeriesDetails.series as CandleSeries; + _candelSeriesRenderer = SegmentHelper.getSegmentProperties(_segment) + .seriesRenderer as CandleSeriesRenderer; + _candleSegment = _candelSeriesRenderer._candleSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_candleSegment); + + if (_currentSeriesDetails.candleSeries.enableSolidCandles! == true) { + segmentProperties.isSolid = true; + segmentProperties.color = segmentProperties.isBull == true + ? _currentSeriesDetails.candleSeries.bullColor + : _currentSeriesDetails.candleSeries.bearColor; + } else { + segmentProperties.isSolid = segmentProperties.isBull == false; + final SeriesRendererDetails candleSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer); + _candleSegment.currentSegmentIndex! - 1 >= 0 && + (candleSeriesDetails + .dataPoints[_candleSegment.currentSegmentIndex! - 1] + .close > + candleSeriesDetails + .dataPoints[_candleSegment.currentSegmentIndex!] + .close) == + true + ? segmentProperties.color = + _currentSeriesDetails.candleSeries.bearColor + : segmentProperties.color = + _currentSeriesDetails.candleSeries.bullColor; + } + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + /// To draw candle series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment _segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[_segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + _segment.onPaint(canvas); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) {} + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the candle series painter +class CandlePainter extends CustomPainter { + /// Calling the default constructor of CandlePainter class. + CandlePainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties. + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the candle series renderer CandleSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for candle series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; Rect clipRect; double animationFactor; final CandleSeries series = - seriesRenderer._series as CandleSeries; + seriesRendererDetails.series as CandleSeries; CartesianChartPoint point; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the bar series must be greater than or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - if (_withInRange(seriesRenderer._dataPoints[pointIndex].xValue, - seriesRenderer._xAxisRenderer!._visibleRange!)) { - seriesRenderer._calculateRegionData( - chartState, - seriesRenderer, + if (withInRange(seriesRendererDetails.dataPoints[pointIndex].xValue, + seriesRendererDetails.xAxisDetails!.visibleRange!)) { + seriesRendererDetails.calculateRegionData( + stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex, - seriesRenderer._sideBySideInfo); + seriesRendererDetails.sideBySideInfo); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments(point, segmentIndex += 1, painterKey.index, animationFactor)); } } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTWH( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= stateProperties.seriesDurationFactor) && series.dataLabelSettings.isVisible) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The candle series should be available to render a data label on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } - if (seriesRenderer._visible! && animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + if (seriesRendererDetails.visible! == true && animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_CandlePainter oldDelegate) => isRepaint; + bool shouldRepaint(CandlePainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart index 06a23c227..4aff30be7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/column_painter.dart @@ -1,34 +1,240 @@ -part of charts; +import 'dart:ui'; -class _ColumnChartPainter extends CustomPainter { - _ColumnChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/column_segment.dart'; +import '../chart_series/column_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Column series +class ColumnSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of ColumnSeriesRenderer class. + ColumnSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// To add column segments in segments list + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + final ColumnSegment segment = createSegment() as ColumnSegment; + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final CartesianStateProperties _stateProperties = + _currentSeriesDetails.stateProperties; + final List oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + final ColumnSeries _columnSeries = + _currentSeriesDetails.series as ColumnSeries; + segmentProperties.seriesRenderer = this; + segmentProperties.series = _columnSeries; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment.animationFactor = animateFactor; + segmentProperties.currentPoint = currentPoint; + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + _stateProperties.zoomPanBehaviorRenderer); + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_stateProperties.renderingDetails.widgetNeedUpdate && + zoomingBehaviorDetails.isPinching != true && + !_stateProperties.renderingDetails.isLegendToggled && + // ignore: unnecessary_null_comparison + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + final SeriesRendererDetails segmentOldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + segmentProperties + .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && + segmentOldSeriesDetails.segments[0] is ColumnSegment && + (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == + true) + ? segmentOldSeriesDetails.dataPoints[pointIndex] + : null; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + if ((_stateProperties.selectedSegments.length - 1 >= pointIndex) && + SegmentHelper.getSegmentProperties(_currentSeriesDetails + .stateProperties.selectedSegments[pointIndex]) + .oldSegmentIndex == + null) { + final ChartSegment selectedSegment = + _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegment); + selectedSegmentProperties.oldSeriesRenderer = + oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; + selectedSegmentProperties.seriesRenderer = this; + selectedSegmentProperties.oldSegmentIndex = + getOldSegmentIndex(selectedSegment); + } + } else if (_stateProperties.renderingDetails.isLegendToggled && + // ignore: unnecessary_null_comparison + _stateProperties.segments != null && + _stateProperties.segments.isNotEmpty) { + segmentProperties.oldSeriesVisible = + _stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; + ColumnSegment oldSegment; + for (int i = 0; i < _stateProperties.segments.length; i++) { + oldSegment = _stateProperties.segments[i] as ColumnSegment; + if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && + SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == + segmentProperties.seriesIndex) { + segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; + } + } + } + segmentProperties.path = + findingRectSeriesDashedBorder(currentPoint, _columnSeries.borderWidth); + // ignore: unnecessary_null_comparison + if (_columnSeries.borderRadius != null) { + segment.segmentRect = + getRRectFromRect(currentPoint.region!, _columnSeries.borderRadius); + + //Tracker rect + if (_columnSeries.isTrackVisible) { + segmentProperties.trackRect = getRRectFromRect( + currentPoint.trackerRectRegion!, _columnSeries.borderRadius); + } + } + segmentProperties.segmentRect = segment.segmentRect; + customizeSegment(segment); + _currentSeriesDetails.segments.add(segment); + return segment; + } + + /// To draw column series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[segment.currentSegmentIndex!], + _currentSeriesDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + ChartSegment createSegment() => ColumnSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final ColumnSegment columnSegment = segment as ColumnSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(columnSegment); + segmentProperties.color = + segmentProperties.currentPoint!.pointColorMapper ?? + _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + columnSegment.strokePaint = columnSegment.getStrokePaint(); + columnSegment.fillPaint = columnSegment.getFillPaint(); + segmentProperties.trackerFillPaint = + segmentProperties.getTrackerFillPaint(); + segmentProperties.trackerStrokePaint = + segmentProperties.getTrackerStrokePaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the column Chart painter +class ColumnChartPainter extends CustomPainter { + /// Calling the default constructor of ColumnChartPainter class. + ColumnChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the column series renderer ColumnSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for column series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final ColumnSeries series = - seriesRenderer._series as ColumnSeries; - if (seriesRenderer._visible!) { + seriesRendererDetails.series as ColumnSeries; + if (seriesRendererDetails.visible! == true) { Rect axisClipRect, clipRect; double animationFactor; CartesianChartPoint point; @@ -38,63 +244,66 @@ class _ColumnChartPainter extends CustomPainter { !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the bar series must be greater than or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The column series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_ColumnChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(ColumnChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/error_bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/error_bar_painter.dart new file mode 100644 index 000000000..2190a3401 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/error_bar_painter.dart @@ -0,0 +1,192 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_segment/error_bar_segment.dart'; + +import './../axis/axis.dart'; +import '../../common/event_args.dart' show ErrorBarRenderDetails; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/error_bar_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for error Bar series +class ErrorBarSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of ErrorBarSeriesRenderer class. + ErrorBarSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// To add error bar segments in segments list + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + final ErrorBarSegment segment = createSegment() as ErrorBarSegment; + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final ErrorBarSeries _errorBarSeries = + _currentSeriesDetails.series as ErrorBarSeries; + segmentProperties.seriesRenderer = this; + segmentProperties.series = _errorBarSeries; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.animationFactor = animateFactor; + if (_errorBarSeries.onRenderDetailsUpdate != null && + _currentSeriesDetails.animationController.status == + AnimationStatus.completed) { + final ErrorBarRenderDetails errorBarRenderDetails = ErrorBarRenderDetails( + currentPoint.visiblePointIndex, + currentPoint.overallDataPointIndex, + currentPoint.errorBarValues); + _errorBarSeries.onRenderDetailsUpdate!(errorBarRenderDetails); + } + segmentProperties.currentPoint = currentPoint; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + customizeSegment(segment); + _currentSeriesDetails.segments.add(segment); + return segment; + } + + /// To draw error bar series segments. + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + ChartSegment createSegment() => ErrorBarSegment(); + + /// Changes the series color. + @override + void customizeSegment(ChartSegment segment) { + final ErrorBarSegment errorBarSegment = segment as ErrorBarSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(errorBarSegment); + segmentProperties.color = + segmentProperties.currentPoint!.pointColorMapper ?? + _segmentSeriesDetails.seriesColor; + errorBarSegment.strokePaint = errorBarSegment.getStrokePaint(); + } + + /// Data marker is not applicable for error bar series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) {} + + /// Data label is not applicable for error bar series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) {} +} + +/// Represents the error bar chart painter. +class ErrorBarChartPainter extends CustomPainter { + /// Calling the default constructor of ErrorBarChartPainter class. + ErrorBarChartPainter( + {required this.stateProperties, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) + : chart = stateProperties.chart, + super(repaint: notifier); + + /// Represents the cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the cartesian chart. + final SfCartesianChart chart; + + /// Specifies whether to repaint the series. + final bool isRepaint; + + /// Specifies the value of animation controller. + final AnimationController animationController; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the error bar series renderer + ErrorBarSeriesRenderer seriesRenderer; + + /// Specifies the painter key value + final PainterKey painterKey; + + /// Painter method for error bar series. + @override + void paint(Canvas canvas, Size size) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final List> dataPoints = + seriesRendererDetails.dataPoints; + final ErrorBarSeries series = + seriesRendererDetails.series as ErrorBarSeries; + if (seriesRendererDetails.visible! == true) { + Rect axisClipRect; + double animationFactor; + CartesianChartPoint point; + canvas.save(); + assert( + // ignore: unnecessary_null_comparison + !(series.animationDuration != null) || series.animationDuration >= 0, + 'The animation duration of the error bar series must be greater than or equal to 0.'); + final int seriesIndex = painterKey.index; + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); + canvas.clipRect(axisClipRect); + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value + : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + int segmentIndex = -1; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; + } + for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { + point = dataPoints[pointIndex]; + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); + if (point.isVisible && !point.isGap) { + seriesRendererDetails.drawSegment( + canvas, + seriesRenderer._createSegments( + point, segmentIndex += 1, painterKey.index, animationFactor)); + } + } + canvas.restore(); + if (animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); + } + } + } + + @override + bool shouldRepaint(ErrorBarChartPainter oldDelegate) => isRepaint; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart index cb3a11c93..52507bea7 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/fastline_painter.dart @@ -1,66 +1,194 @@ -part of charts; +import 'dart:ui'; -class _FastLineChartPainter extends CustomPainter { - _FastLineChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/fastline_segment.dart'; +import '../chart_series/fastline_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Fastline series +class FastLineSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of FastLineSeriesRenderer class. + FastLineSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + ///Adds the segment to the segments list + ChartSegment _createSegments(int seriesIndex, int pointIndex, + SfCartesianChart chart, double animateFactor, + [List? _points]) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + final FastLineSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segmentProperties.seriesRenderer = this; + segment.animationFactor = animateFactor; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_points != null) { + segment.points = _points; + } + segmentProperties.oldSegmentIndex = 0; + customizeSegment(segment); + _currentSeriesDetails.segments.add(segment); + return segment; + } + + ///Renders the segment. + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[0], _currentSeriesDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + FastLineSegment createSegment() => FastLineSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final FastLineSegment fastLineSegment = segment as FastLineSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(fastLineSegment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + fastLineSegment.strokePaint = fastLineSegment.getStrokePaint(); + fastLineSegment.fillPaint = fastLineSegment.getFillPaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the fastline Chart painter +class FastLineChartPainter extends CustomPainter { + /// Calling the default constructor of FastLineChartPainter class. + FastLineChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; + + /// Specifies the fastline series renderer final FastLineSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for fast line series @override void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final FastLineSeries series = - seriesRenderer._series as FastLineSeries; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + seriesRendererDetails.series as FastLineSeries; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; final List _points = []; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || (series.animationDuration > 0 && - !seriesRenderer._renderingDetails!.isLegendToggled)) { - seriesRenderer._needAnimateSeriesElements = - seriesRenderer._needsAnimation; - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false)) { + seriesRendererDetails.needAnimateSeriesElements = + seriesRendererDetails.needsAnimation; + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } + int segmentIndex = -1; CartesianChartPoint? prevPoint, point; - _ChartLocation currentLocation; - final _VisibleRange xVisibleRange = xAxisRenderer._visibleRange!; - final _VisibleRange yVisibleRange = yAxisRenderer._visibleRange!; + ChartLocation currentLocation; + final VisibleRange xVisibleRange = xAxisDetails.visibleRange!; + final VisibleRange yVisibleRange = yAxisDetails.visibleRange!; final List> seriesPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; assert(seriesPoints.isNotEmpty, 'The data points should be available to render fast line series.'); final Rect areaBounds = - seriesRenderer._chartState!._chartAxis._axisClipRect; + seriesRendererDetails.stateProperties.chartAxis.axisClipRect; final num xTolerance = (xVisibleRange.delta / areaBounds.width).abs(); final num yTolerance = (yVisibleRange.delta / areaBounds.height).abs(); num prevXValue = (seriesPoints.isNotEmpty && @@ -83,47 +211,51 @@ class _FastLineChartPainter extends CustomPainter { ///Eliminating nearest points CartesianChartPoint currentPoint; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; - pointIndex < seriesRenderer._dataPoints.length; + pointIndex < seriesRendererDetails.dataPoints.length; pointIndex++) { - currentPoint = seriesRenderer._dataPoints[pointIndex]; + currentPoint = seriesRendererDetails.dataPoints[pointIndex]; xVal = currentPoint.xValue ?? xVisibleRange.minimum; yVal = currentPoint.yValue ?? yVisibleRange.minimum; if ((prevXValue - xVal).abs() >= xTolerance || (prevYValue - yVal).abs() >= yTolerance) { point = currentPoint; dataPoints.add(currentPoint); - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible) { - currentLocation = _calculatePoint( + currentLocation = calculatePoint( xVal, yVal, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState!._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, series, areaBounds); _points.add(Offset(currentLocation.x, currentLocation.y)); if (prevPoint == null) { - seriesRenderer._segmentPath! + seriesRendererDetails.segmentPath! .moveTo(currentLocation.x, currentLocation.y); - } else if (seriesRenderer._dataPoints[pointIndex - 1].isVisible == + } else if (seriesRendererDetails + .dataPoints[pointIndex - 1].isVisible == false && series.emptyPointSettings.mode == EmptyPointMode.gap) { - seriesRenderer._segmentPath! + seriesRendererDetails.segmentPath! .moveTo(currentLocation.x, currentLocation.y); } else if (point.isGap != true && - seriesRenderer._dataPoints[pointIndex - 1].isGap != true && - seriesRenderer._dataPoints[pointIndex].isVisible == true) { - seriesRenderer._segmentPath! + seriesRendererDetails.dataPoints[pointIndex - 1].isGap != + true && + seriesRendererDetails.dataPoints[pointIndex].isVisible == + true) { + seriesRendererDetails.segmentPath! .lineTo(currentLocation.x, currentLocation.y); } else { - seriesRenderer._segmentPath! + seriesRendererDetails.segmentPath! .moveTo(currentLocation.x, currentLocation.y); } prevPoint = point; @@ -133,44 +265,43 @@ class _FastLineChartPainter extends CustomPainter { } } - if (seriesRenderer._segmentPath != null) { - seriesRenderer._dataPoints = dataPoints; - seriesRenderer._drawSegment( + if (seriesRendererDetails.segmentPath != null) { + seriesRendererDetails.dataPoints = dataPoints; + seriesRendererDetails.drawSegment( canvas, - seriesRenderer._createSegments( - painterKey.index, chart, animationFactor, _points)); + seriesRenderer._createSegments(painterKey.index, segmentIndex += 1, + chart, animationFactor, _points)); } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The fast line series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_FastLineChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(FastLineChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart index 7f08487cf..37910598b 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hilo_painter.dart @@ -1,66 +1,228 @@ -part of charts; +import 'dart:ui'; -class _HiloPainter extends CustomPainter { - _HiloPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/hilo_segment.dart'; +import '../chart_series/hilo_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Hilo series +class HiloSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of HiloSeriesRenderer class. + HiloSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + late SeriesRendererDetails _oldSeriesDetails; + + /// Hilo segment is created here + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + _currentSeriesDetails.isRectSeries = false; + final HiloSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final List oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment.points.add( + Offset(currentPoint.markerPoint2!.x, currentPoint.markerPoint2!.y)); + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + segmentProperties.seriesRenderer = this; + segment.animationFactor = animateFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + segmentProperties.currentPoint = currentPoint; + _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer); + if (_currentSeriesDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + _currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + // ignore: unnecessary_null_comparison + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex) { + _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]); + if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + } + } + segment.calculateSegmentPoints(); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + _currentSeriesDetails.segments.add(segment); + } + return segment; + } + + /// To render hilo series segments. + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[segment.currentSegmentIndex!], + _currentSeriesDetails.chart); + } + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + if (!((segmentProperties.currentPoint?.low == + segmentProperties.currentPoint?.high) && + //ignore: always_specify_types + !(_currentSeriesDetails.series as HiloSeries) + .showIndicationForSameValues)) { + segment.onPaint(canvas); + } + } + + @override + HiloSegment createSegment() => HiloSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the Hilo series painter +class HiloPainter extends CustomPainter { + /// Calling the default constructor of HiloPainter class. + HiloPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the hilo series renderer HiloSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for Hilo series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; Rect clipRect; double animationFactor; final HiloSeries series = - seriesRenderer._series as HiloSeries; + seriesRendererDetails.series as HiloSeries; CartesianChartPoint point; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, - seriesRenderer, + seriesRendererDetails.calculateRegionData( + stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex, - seriesRenderer._sideBySideInfo); + seriesRendererDetails.sideBySideInfo); if (point.isVisible && !point.isGap) { seriesRenderer._drawSegment( canvas, @@ -68,37 +230,36 @@ class _HiloPainter extends CustomPainter { point, segmentIndex += 1, painterKey.index, animationFactor)); } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTWH( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The Hilo series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } - if (seriesRenderer._visible! && animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + if (seriesRendererDetails.visible! == true && animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_HiloPainter oldDelegate) => isRepaint; + bool shouldRepaint(HiloPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart index 5ea9925a2..ffc495c5f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/hiloopenclose_painter.dart @@ -1,22 +1,172 @@ -part of charts; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; -class _HiloOpenClosePainter extends CustomPainter { - _HiloOpenClosePainter( - {required this.chartState, +import '../../../charts.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/hiloopenclose_segment.dart'; +import '../chart_series/hiloopenclose_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Hilo open close series +class HiloOpenCloseSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of HiloOpenCloseSeriesRenderer class. + HiloOpenCloseSeriesRenderer(); + + late HiloOpenCloseSegment _segment; + + List? _oldSeriesRenderers; + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + late SeriesRendererDetails _oldSeriesDetails; + + /// HiloOpenClose _segment is created here + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + _segment = createSegment(); + SegmentHelper.setSegmentProperties(_segment, + SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_segment); + _oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + _currentSeriesDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (_segment != null) { + segmentProperties.seriesIndex = seriesIndex; + _segment.currentSegmentIndex = pointIndex; + segmentProperties.seriesRenderer = this; + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + _segment.animationFactor = animateFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + segmentProperties.currentPoint = currentPoint; + if (_oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length > segmentProperties.seriesIndex) { + _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers![segmentProperties.seriesIndex]); + } + + _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer); + if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == + true && + _currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + _oldSeriesRenderers != null && + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= segmentProperties.seriesIndex && + _oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers![segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); + } + _segment.calculateSegmentPoints(); + //stores the points for rendering Hilo open close segment, High, low, open, close + _segment.points + ..add( + Offset(currentPoint.markerPoint!.x, segmentProperties.highPoint.y)) + ..add(Offset(currentPoint.markerPoint!.x, segmentProperties.lowPoint.y)) + ..add(Offset(segmentProperties.openX, segmentProperties.openY)) + ..add(Offset(segmentProperties.closeX, segmentProperties.closeY)); + customizeSegment(_segment); + _segment.strokePaint = _segment.getStrokePaint(); + _segment.fillPaint = _segment.getFillPaint(); + _currentSeriesDetails.segments.add(_segment); + } + return _segment; + } + + /// To render hilo open close series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment _segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[_segment.currentSegmentIndex!], + _currentSeriesDetails.chart); + } + _segment.onPaint(canvas); + } + + @override + HiloOpenCloseSegment createSegment() => HiloOpenCloseSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment _segment) { + _currentSeriesDetails.hiloOpenCloseSeries = + _currentSeriesDetails.series as HiloOpenCloseSeries; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_segment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = + _segment is HiloOpenCloseSegment && segmentProperties.isBull == true + ? _currentSeriesDetails.hiloOpenCloseSeries.bullColor + : _currentSeriesDetails.hiloOpenCloseSeries.bearColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) {} + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the HiloOpenClose series painter +class HiloOpenClosePainter extends CustomPainter { + /// Calling the default constructor of HiloOpenClosePainter class. + HiloOpenClosePainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the Hilo open close series renderer HiloOpenCloseSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for Hilo open-close series @override @@ -24,81 +174,89 @@ class _HiloOpenClosePainter extends CustomPainter { Rect clipRect; double animationFactor; CartesianChartPoint point; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final HiloOpenCloseSeries series = - seriesRenderer._series as HiloOpenCloseSeries; - if (seriesRenderer._visible!) { + seriesRendererDetails.series as HiloOpenCloseSeries; + if (seriesRendererDetails.visible! == true) { canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, - seriesRenderer, + seriesRendererDetails.calculateRegionData( + stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex, - seriesRenderer._sideBySideInfo); + seriesRendererDetails.sideBySideInfo); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTWH( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= + seriesRendererDetails.stateProperties.seriesDurationFactor) && series.dataLabelSettings.isVisible) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The Hilo open-close series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } - if (seriesRenderer._visible! && animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + if (seriesRendererDetails.visible! == true && animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_HiloOpenClosePainter oldDelegate) => isRepaint; + bool shouldRepaint(HiloOpenClosePainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart index 362c46572..be46b2320 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/histogram_painter.dart @@ -1,24 +1,266 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; -class _HistogramChartPainter extends CustomPainter { - _HistogramChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../base/series_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/histogram_segment.dart'; +import '../chart_series/histogram_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Histogram series +class HistogramSeriesRenderer extends XyDataSeriesRenderer { + late HistogramSegment _segment; + late HistogramSeries _histogramSeries; + BorderRadius? _borderRadius; + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + late SeriesRendererDetails _oldSeriesDetails; + + /// Find the path for distribution line in the histogram + Path _findNormalDistributionPath( + HistogramSeries series, SfCartesianChart chart) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final num min = seriesRendererDetails.xAxisDetails!.visibleRange!.minimum; + final num max = seriesRendererDetails.xAxisDetails!.visibleRange!.maximum; + num xValue, yValue; + final Path path = Path(); + ChartLocation pointLocation; + const num pointsCount = 500; + final num del = (max - min) / (pointsCount - 1); + for (int i = 0; i < pointsCount; i++) { + xValue = min + i * del; + yValue = math.exp( + -(xValue - seriesRendererDetails.histogramValues.mean!) * + (xValue - seriesRendererDetails.histogramValues.mean!) / + (2 * + seriesRendererDetails.histogramValues.sDValue! * + seriesRendererDetails.histogramValues.sDValue!)) / + (seriesRendererDetails.histogramValues.sDValue! * + math.sqrt(2 * math.pi)); + pointLocation = calculatePoint( + xValue, + yValue * + seriesRendererDetails.histogramValues.binWidth! * + seriesRendererDetails.histogramValues.yValues!.length, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + series, + seriesRendererDetails.stateProperties.chartAxis.axisClipRect); + i == 0 + ? path.moveTo(pointLocation.x, pointLocation.y) + : path.lineTo(pointLocation.x, pointLocation.y); + } + return path; + } + + /// To add histogram segments to segments list + ChartSegment _createSegments( + CartesianChartPoint currentPoint, + int pointIndex, + VisibleRange sideBySideInfo, + int seriesIndex, + double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + _segment = createSegment(); + SegmentHelper.setSegmentProperties(_segment, + SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_segment); + _currentSeriesDetails.oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + _histogramSeries = + _currentSeriesDetails.series as HistogramSeries; + _borderRadius = _histogramSeries.borderRadius; + segmentProperties.seriesRenderer = this; + segmentProperties.series = _histogramSeries; + segmentProperties.seriesIndex = seriesIndex; + _segment.currentSegmentIndex = pointIndex; + _segment.points + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + _segment.animationFactor = animateFactor; + final num origin = + math.max(_currentSeriesDetails.yAxisDetails!.visibleRange!.minimum, 0); + currentPoint.region = calculateRectangle( + currentPoint.xValue + sideBySideInfo.minimum, + currentPoint.yValue, + currentPoint.xValue + sideBySideInfo.maximum, + math.max(_currentSeriesDetails.yAxisDetails!.visibleRange!.minimum, 0), + this, + _currentSeriesDetails.stateProperties); + segmentProperties.currentPoint = currentPoint; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == + true && + _currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + _currentSeriesDetails.oldSeriesRenderers != null && + _currentSeriesDetails.oldSeriesRenderers!.isNotEmpty == true && + (_currentSeriesDetails.oldSeriesRenderers!.length - 1 >= + segmentProperties.seriesIndex) == + true) { + _oldSeriesDetails = SeriesHelper.getSeriesRendererDetails( + _currentSeriesDetails + .oldSeriesRenderers![segmentProperties.seriesIndex]); + if (_oldSeriesDetails.seriesName == _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = _currentSeriesDetails + .oldSeriesRenderers![segmentProperties.seriesIndex]; + final SeriesRendererDetails segmentOldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + segmentProperties + .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && + segmentOldSeriesDetails.segments[0] is HistogramSegment && + (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == + true) + ? segmentOldSeriesDetails.dataPoints[pointIndex] + : null; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); + } + } else if (_currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + true && + // ignore: unnecessary_null_comparison + _currentSeriesDetails.stateProperties.segments != null && + _currentSeriesDetails.stateProperties.segments.isNotEmpty == true) { + segmentProperties.oldSeriesVisible = _currentSeriesDetails + .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; + for (int i = 0; + i < _currentSeriesDetails.stateProperties.segments.length; + i++) { + final HistogramSegment oldSegment = _currentSeriesDetails + .stateProperties.segments[i] as HistogramSegment; + if (oldSegment.currentSegmentIndex == _segment.currentSegmentIndex && + SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == + segmentProperties.seriesIndex) { + segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; + } + } + } + segmentProperties.path = findingRectSeriesDashedBorder( + currentPoint, _histogramSeries.borderWidth); + if (_borderRadius != null) { + _segment.segmentRect = + getRRectFromRect(currentPoint.region!, _borderRadius!); + } + //Tracker rect + if (_histogramSeries.isTrackVisible) { + currentPoint.trackerRectRegion = calculateShadowRectangle( + currentPoint.xValue + sideBySideInfo.minimum, + currentPoint.yValue, + currentPoint.xValue + sideBySideInfo.maximum, + origin, + this, + _currentSeriesDetails.stateProperties, + Offset(_segmentSeriesDetails.xAxisDetails!.axis.plotOffset, + _segmentSeriesDetails.yAxisDetails!.axis.plotOffset)); + if (_borderRadius != null) { + segmentProperties.trackRect = + getRRectFromRect(currentPoint.trackerRectRegion!, _borderRadius!); + } + } + segmentProperties.segmentRect = _segment.segmentRect; + customizeSegment(_segment); + _currentSeriesDetails.segments.add(_segment); + return _segment; + } + + /// Creates a segment for a data point in the series. + @override + HistogramSegment createSegment() => HistogramSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final HistogramSegment histogramSegment = segment as HistogramSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(histogramSegment); + segmentProperties.color = + segmentProperties.currentPoint!.pointColorMapper ?? + _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + histogramSegment.strokePaint = histogramSegment.getStrokePaint(); + histogramSegment.fillPaint = histogramSegment.getFillPaint(); + segmentProperties.trackerFillPaint = + segmentProperties.getTrackerFillPaint(); + segmentProperties.trackerStrokePaint = + segmentProperties.getTrackerStrokePaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the Histogram Chart painter +class HistogramChartPainter extends CustomPainter { + /// Calling the default constructor of HistogramChartPainter class. + HistogramChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.chartSeries, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the histogram series renderer HistogramSeriesRenderer seriesRenderer; - _ChartSeries chartSeries; - final _PainterKey painterKey; + + /// Holds the information of seriesBase class + ChartSeriesPanel chartSeries; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for histogram series @override @@ -26,67 +268,75 @@ class _HistogramChartPainter extends CustomPainter { Rect axisClipRect, clipRect; double animationFactor; CartesianChartPoint point; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; /// Clip rect added - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { final HistogramSeries series = - seriesRenderer._series as HistogramSeries; + seriesRendererDetails.series as HistogramSeries; canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } /// side by side range calculated int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, - seriesRenderer, + seriesRendererDetails.calculateRegionData( + stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex, - seriesRenderer._sideBySideInfo); + seriesRendererDetails.sideBySideInfo); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, - seriesRenderer._sideBySideInfo!, + seriesRendererDetails.sideBySideInfo!, painterKey.index, animationFactor)); } } if (series.showNormalDistributionCurve) { - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } final Path _path = seriesRenderer._findNormalDistributionPath(series, chart); @@ -96,39 +346,38 @@ class _HistogramChartPainter extends CustomPainter { ..style = PaintingStyle.stroke; series.curveDashArray == null ? canvas.drawPath(_path, _paint) - : _drawDashedLine(canvas, series.curveDashArray!, _paint, _path); + : drawDashedLine(canvas, series.curveDashArray!, _paint, _path); } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || !renderingDetails.initialRender! || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The histogram series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_HistogramChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(HistogramChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart index cdbe46618..73097d641 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/line_painter.dart @@ -1,57 +1,223 @@ -part of charts; +import 'dart:ui'; -class _LineChartPainter extends CustomPainter { - _LineChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/line_segment.dart'; +import '../chart_series/line_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Line series +class LineSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of LineSeriesRenderer class. + LineSeriesRenderer(); + + late LineSegment _lineSegment, _segment; + + List? _oldSeriesRenderers; + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// To add line segments to segments list + ChartSegment _createSegments( + CartesianChartPoint currentPoint, + CartesianChartPoint _nextPoint, + int pointIndex, + int seriesIndex, + double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + _segment = createSegment(); + SegmentHelper.setSegmentProperties(_segment, + SegmentProperties(_currentSeriesDetails.stateProperties, _segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_segment); + SegmentHelper.setSegmentProperties(_segment, segmentProperties); + _oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + segmentProperties.seriesRenderer = this; + segmentProperties.seriesIndex = seriesIndex; + segmentProperties.currentPoint = currentPoint; + _segment.currentSegmentIndex = pointIndex; + segmentProperties.nextPoint = _nextPoint; + _segment.animationFactor = animateFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_currentSeriesDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + _oldSeriesRenderers != null && + _oldSeriesRenderers!.isNotEmpty && + _oldSeriesRenderers!.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers![segmentProperties.seriesIndex]) + .seriesName == + _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers![segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(_segment); + } + _segment.calculateSegmentPoints(); + _segment.points.add(Offset(segmentProperties.x1, segmentProperties.y1)); + _segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); + customizeSegment(_segment); + _currentSeriesDetails.segments.add(_segment); + return _segment; + } + + /// To render line series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment _segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[_segment.currentSegmentIndex!], + _currentSeriesDetails.chart); + } + _segment.onPaint(canvas); + } + + /// Creates a _segment for a data point in the series. + @override + LineSegment createSegment() => LineSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment _segment) { + _lineSegment = _segment as LineSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(_lineSegment); + segmentProperties.color = segmentProperties.pointColorMapper ?? + segmentProperties.series.color ?? + _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.pointColorMapper ?? + segmentProperties.series.color ?? + _segmentSeriesDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + _lineSegment.strokePaint = _lineSegment.getStrokePaint(); + _lineSegment.fillPaint = _lineSegment.getFillPaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the Line Chart painter +class LineChartPainter extends CustomPainter { + /// Calling the default constructor of LineChartPainter class. + LineChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; + + /// Specifies the line series renderer final LineSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for line series @override void paint(Canvas canvas, Size size) { double animationFactor; Rect clipRect; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (!chart.legend.isVisible! && + !seriesRendererDetails.isSelectionEnable && + !chart.zoomPanBehavior.enableDoubleTapZooming && + !chart.zoomPanBehavior.enableMouseWheelZooming && + !chart.zoomPanBehavior.enablePanning && + !chart.zoomPanBehavior.enablePinching && + !chart.zoomPanBehavior.enableSelectionZooming) { + seriesRendererDetails.dispose(); + } + + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final LineSeries series = - seriesRenderer._series as LineSeries; - if (seriesRenderer._visible!) { + seriesRendererDetails.series as LineSeries; + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); canvas.save(); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } int segmentIndex = -1; CartesianChartPoint? currentPoint, @@ -59,14 +225,15 @@ class _LineChartPainter extends CustomPainter { startPoint, endPoint; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { currentPoint = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, seriesIndex, currentPoint, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, seriesIndex, currentPoint, pointIndex); if ((currentPoint.isVisible && !currentPoint.isGap) && startPoint == null) { startPoint = currentPoint; @@ -81,46 +248,45 @@ class _LineChartPainter extends CustomPainter { } if (startPoint != null && endPoint != null) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments(startPoint, endPoint, segmentIndex += 1, seriesIndex, animationFactor)); endPoint = startPoint = null; } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The line series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(seriesIndex, painterKey.name, true); + stateProperties.setPainterKey(seriesIndex, painterKey.name, true); } } } @override - bool shouldRepaint(_LineChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(LineChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart index 8b84d71df..0ec65b9f3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_area_painter.dart @@ -1,117 +1,252 @@ -part of charts; +import 'dart:ui'; -class _RangeAreaChartPainter extends CustomPainter { - _RangeAreaChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/range_area_segment.dart'; +import '../chart_series/range_area_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Range area series +class RangeAreaSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of RangeAreaSeriesRenderer class. + RangeAreaSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// Range Area segment is created here + ChartSegment _createSegments( + int seriesIndex, SfCartesianChart chart, double animateFactor, + [List? _points]) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + final RangeAreaSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + _currentSeriesDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.animationFactor = animateFactor; + segmentProperties.seriesRenderer = this; + segmentProperties.series = + _currentSeriesDetails.series as XyDataSeries; + _segmentSeriesDetails = SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer); + if (_points != null) { + segment.points = _points; + } + segment.currentSegmentIndex = 0; + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + segmentProperties.oldSegmentIndex = 0; + _currentSeriesDetails.segments.add(segment); + } + return segment; + } + + /// To render range area series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[0], _currentSeriesDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + RangeAreaSegment createSegment() => RangeAreaSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + /// Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the RangeArea Chart painter +class RangeAreaChartPainter extends CustomPainter { + /// Calling the default constructor of RangeAreaChartPainter class. + RangeAreaChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required this.painterKey, required ValueNotifier notifier}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final Animation animationController; + + /// Specifies the range area series renderer final RangeAreaSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for range area series @override void paint(Canvas canvas, Size size) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final RangeAreaSeries series = - seriesRenderer._series as RangeAreaSeries; + seriesRendererDetails.series as RangeAreaSeries; Rect clipRect; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; CartesianSeriesRenderer? oldSeriesRenderer; + SeriesRendererDetails? oldSeriesRendererDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; CartesianChartPoint? point, prevPoint, oldPoint; final Path _path = Path(); - _ChartLocation? currentPointLow, - currentPointHigh, - oldPointLow, - oldPointHigh; + ChartLocation? currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; double currentLowX, currentLowY, currentHighX, currentHighY; double animationFactor; final Path _borderPath = Path(); RangeAreaSegment rangeAreaSegment; final List _points = []; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); final List oldSeriesRenderers = - chartState._oldSeriesRenderers; + stateProperties.oldSeriesRenderers; canvas.save(); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - final bool isTransposed = chartState._requireInvertedAxis; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + final bool isTransposed = stateProperties.requireInvertedAxis; + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - oldSeriesRenderer = _getOldSeriesRenderer( - chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + oldSeriesRenderer = getOldSeriesRenderer(stateProperties, + seriesRendererDetails, seriesIndex, oldSeriesRenderers); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + if (oldSeriesRenderer != null) { + oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); + } + + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - if (seriesRenderer._reAnimate || + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { - oldPoint = _getOldChartPoint( - chartState, - seriesRenderer, + oldPoint = getOldChartPoint( + stateProperties, + seriesRendererDetails, RangeAreaSegment, seriesIndex, pointIndex, oldSeriesRenderer, oldSeriesRenderers); if (oldPoint != null) { - oldPointLow = _calculatePoint( + oldPointLow = calculatePoint( oldPoint.xValue, oldPoint.low, - oldSeriesRenderer!._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + oldSeriesRendererDetails!.xAxisDetails!, + oldSeriesRendererDetails.yAxisDetails!, isTransposed, - oldSeriesRenderer._series, + oldSeriesRendererDetails.series, axisClipRect); - oldPointHigh = _calculatePoint( + oldPointHigh = calculatePoint( oldPoint.xValue, oldPoint.high, - oldSeriesRenderer._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + oldSeriesRendererDetails.xAxisDetails!, + oldSeriesRendererDetails.yAxisDetails!, isTransposed, - oldSeriesRenderer._series, + oldSeriesRendererDetails.series, axisClipRect); } else { oldPointLow = oldPointHigh = null; } - currentPointLow = _calculatePoint(point.xValue, point.low, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - currentPointHigh = _calculatePoint(point.xValue, point.high, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); + currentPointLow = calculatePoint(point.xValue, point.low, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); + currentPointHigh = calculatePoint(point.xValue, point.high, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); _points.add(Offset(currentPointLow.x, currentPointLow.y)); _points.add(Offset(currentPointHigh.x, currentPointHigh.y)); @@ -121,20 +256,20 @@ class _RangeAreaChartPainter extends CustomPainter { currentHighY = currentPointHigh.y; if (oldPointLow != null) { if (chart.isTransposed) { - currentLowX = _getAnimateValue(animationFactor, currentLowX, - oldPointLow.x, currentPointLow.x, seriesRenderer); + currentLowX = getAnimateValue(animationFactor, currentLowX, + oldPointLow.x, currentPointLow.x, seriesRendererDetails); } else { - currentLowY = _getAnimateValue(animationFactor, currentLowY, - oldPointLow.y, currentPointLow.y, seriesRenderer); + currentLowY = getAnimateValue(animationFactor, currentLowY, + oldPointLow.y, currentPointLow.y, seriesRendererDetails); } } if (oldPointHigh != null) { if (chart.isTransposed) { - currentHighX = _getAnimateValue(animationFactor, currentHighX, - oldPointHigh.x, currentPointHigh.x, seriesRenderer); + currentHighX = getAnimateValue(animationFactor, currentHighX, + oldPointHigh.x, currentPointHigh.x, seriesRendererDetails); } else { - currentHighY = _getAnimateValue(animationFactor, currentHighY, - oldPointHigh.y, currentPointHigh.y, seriesRenderer); + currentHighY = getAnimateValue(animationFactor, currentHighY, + oldPointHigh.y, currentPointHigh.y, seriesRendererDetails); } } if (prevPoint == null || @@ -167,38 +302,38 @@ class _RangeAreaChartPainter extends CustomPainter { pointIndex--) { point = dataPoints[pointIndex]; if (point.isVisible && !point.isDrop) { - oldPoint = _getOldChartPoint( - chartState, - seriesRenderer, + oldPoint = getOldChartPoint( + stateProperties, + seriesRendererDetails, RangeAreaSegment, seriesIndex, pointIndex, oldSeriesRenderer, oldSeriesRenderers); if (oldPoint != null) { - oldPointLow = _calculatePoint( + oldPointLow = calculatePoint( oldPoint.xValue, oldPoint.low, - oldSeriesRenderer!._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + oldSeriesRendererDetails!.xAxisDetails!, + oldSeriesRendererDetails.yAxisDetails!, isTransposed, - oldSeriesRenderer._series, + oldSeriesRendererDetails.series, axisClipRect); - oldPointHigh = _calculatePoint( + oldPointHigh = calculatePoint( oldPoint.xValue, oldPoint.high, - oldSeriesRenderer._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + oldSeriesRendererDetails.xAxisDetails!, + oldSeriesRendererDetails.yAxisDetails!, isTransposed, - oldSeriesRenderer._series, + oldSeriesRendererDetails.series, axisClipRect); } else { oldPointLow = oldPointHigh = null; } - currentPointLow = _calculatePoint(point.xValue, point.low, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - currentPointHigh = _calculatePoint(point.xValue, point.high, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); + currentPointLow = calculatePoint(point.xValue, point.low, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); + currentPointHigh = calculatePoint(point.xValue, point.high, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); currentLowX = currentPointLow.x; currentLowY = currentPointLow.y; @@ -207,20 +342,20 @@ class _RangeAreaChartPainter extends CustomPainter { if (oldPointLow != null) { if (chart.isTransposed) { - currentLowX = _getAnimateValue(animationFactor, currentLowX, - oldPointLow.x, currentPointLow.x, seriesRenderer); + currentLowX = getAnimateValue(animationFactor, currentLowX, + oldPointLow.x, currentPointLow.x, seriesRendererDetails); } else { - currentLowY = _getAnimateValue(animationFactor, currentLowY, - oldPointLow.y, currentPointLow.y, seriesRenderer); + currentLowY = getAnimateValue(animationFactor, currentLowY, + oldPointLow.y, currentPointLow.y, seriesRendererDetails); } } if (oldPointHigh != null) { if (chart.isTransposed) { - currentHighX = _getAnimateValue(animationFactor, currentHighX, - oldPointHigh.x, currentPointHigh.x, seriesRenderer); + currentHighX = getAnimateValue(animationFactor, currentHighX, + oldPointHigh.x, currentPointHigh.x, seriesRendererDetails); } else { - currentHighY = _getAnimateValue(animationFactor, currentHighY, - oldPointHigh.y, currentPointHigh.y, seriesRenderer); + currentHighY = getAnimateValue(animationFactor, currentHighY, + oldPointHigh.y, currentPointHigh.y, seriesRendererDetails); } } if (dataPoints[pointIndex + 1].isGap == true) { @@ -242,47 +377,46 @@ class _RangeAreaChartPainter extends CustomPainter { // ignore: unnecessary_null_comparison if (_path != null && // ignore: unnecessary_null_comparison - seriesRenderer._segments != null && - seriesRenderer._segments.isNotEmpty) { - rangeAreaSegment = seriesRenderer._segments[0] as RangeAreaSegment; - seriesRenderer._drawSegment( - canvas, - rangeAreaSegment - .._path = _path - .._borderPath = _borderPath); + seriesRendererDetails.segments != null && + seriesRendererDetails.segments.isNotEmpty == true) { + rangeAreaSegment = + seriesRendererDetails.segments[0] as RangeAreaSegment; + SegmentHelper.getSegmentProperties(rangeAreaSegment) + ..path = _path + ..borderPath = _borderPath; + seriesRendererDetails.drawSegment(canvas, rangeAreaSegment); } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTWH( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || !renderingDetails.initialRender! || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The range area series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_RangeAreaChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(RangeAreaChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart index b0fce7ee6..3e617064a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/range_column_painter.dart @@ -1,34 +1,250 @@ -part of charts; +import 'dart:ui'; -class _RangeColumnChartPainter extends CustomPainter { - _RangeColumnChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/range_column_segment.dart'; +import '../chart_series/range_column_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Range column series +class RangeColumnSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of RangeColumnSeriesRenderer class. + RangeColumnSeriesRenderer(); + + late SeriesRendererDetails _currentSeriesDetails; + late SeriesRendererDetails _segmentSeriesDetails; + + /// To add range column segments in segments list + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + _currentSeriesDetails = SeriesHelper.getSeriesRendererDetails(this); + _currentSeriesDetails.isRectSeries = true; + final RangeColumnSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(_currentSeriesDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final List? oldSeriesRenderers = + _currentSeriesDetails.stateProperties.oldSeriesRenderers; + final RangeColumnSeries _rangeColumnSeries = + _currentSeriesDetails.series as RangeColumnSeries; + final BorderRadius borderRadius = _rangeColumnSeries.borderRadius; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment.points.add( + Offset(currentPoint.markerPoint2!.x, currentPoint.markerPoint2!.y)); + segmentProperties.seriesRenderer = this; + segmentProperties.series = _rangeColumnSeries; + segment.animationFactor = animateFactor; + segmentProperties.currentPoint = currentPoint; + _segmentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (_currentSeriesDetails.stateProperties.renderingDetails.widgetNeedUpdate == true && + ZoomPanBehaviorHelper.getRenderingDetails(_currentSeriesDetails + .stateProperties.zoomPanBehaviorRenderer) + .isPinching != + true && + _currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + _segmentSeriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + final SeriesRendererDetails segmentOldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + segmentProperties + .oldPoint = (segmentOldSeriesDetails.segments.isNotEmpty == true && + segmentOldSeriesDetails.segments[0] is RangeColumnSegment && + (segmentOldSeriesDetails.dataPoints.length - 1 >= pointIndex) == + true) + ? segmentOldSeriesDetails.dataPoints[pointIndex] + : null; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + if ((_currentSeriesDetails.stateProperties.selectedSegments.length - 1 >= + pointIndex) == + true && + SegmentHelper.getSegmentProperties(_currentSeriesDetails + .stateProperties.selectedSegments[pointIndex]) + .oldSegmentIndex == + null) { + final ChartSegment selectedSegment = + _currentSeriesDetails.stateProperties.selectedSegments[pointIndex]; + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegment); + selectedSegmentProperties.oldSeriesRenderer = + oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; + selectedSegmentProperties.seriesRenderer = this; + selectedSegmentProperties.oldSegmentIndex = + getOldSegmentIndex(selectedSegment); + } + } else if (_currentSeriesDetails + .stateProperties.renderingDetails.isLegendToggled == + true && + // ignore: unnecessary_null_comparison + _currentSeriesDetails.stateProperties.segments != null && + _currentSeriesDetails.stateProperties.segments.isNotEmpty == true) { + segmentProperties.oldSeriesVisible = _currentSeriesDetails + .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; + RangeColumnSegment oldSegment; + for (int i = 0; + i < _currentSeriesDetails.stateProperties.segments.length; + i++) { + oldSegment = _currentSeriesDetails.stateProperties.segments[i] + as RangeColumnSegment; + if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && + SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == + segmentProperties.seriesIndex) { + segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; + } + } + } + segmentProperties.path = findingRectSeriesDashedBorder( + currentPoint, _rangeColumnSeries.borderWidth); + // ignore: unnecessary_null_comparison + if (borderRadius != null) { + segment.segmentRect = + getRRectFromRect(currentPoint.region!, borderRadius); + + //Tracker rect + if (_rangeColumnSeries.isTrackVisible) { + segmentProperties.trackRect = + getRRectFromRect(currentPoint.trackerRectRegion!, borderRadius); + } + } + segmentProperties.segmentRect = segment.segmentRect; + customizeSegment(segment); + _currentSeriesDetails.segments.add(segment); + return segment; + } + + /// To render range column series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + if (_segmentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + _segmentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + _currentSeriesDetails.segments[segment.currentSegmentIndex!], + _currentSeriesDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + RangeColumnSegment createSegment() => RangeColumnSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final RangeColumnSegment rangeColumnSegment = segment as RangeColumnSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(rangeColumnSegment); + segmentProperties.color = _segmentSeriesDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + rangeColumnSegment.strokePaint = rangeColumnSegment.getStrokePaint(); + rangeColumnSegment.fillPaint = rangeColumnSegment.getFillPaint(); + segmentProperties.trackerFillPaint = + segmentProperties.getTrackerFillPaint(); + segmentProperties.trackerStrokePaint = + segmentProperties.getTrackerStrokePaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the RangeColumn Chart painter +class RangeColumnChartPainter extends CustomPainter { + /// Calling the default constructor of RangeColumnChartPainter class. + RangeColumnChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the range column series renderer RangeColumnSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for range column series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final RangeColumnSeries series = - seriesRenderer._series as RangeColumnSeries; - if (seriesRenderer._visible!) { + seriesRendererDetails.series as RangeColumnSeries; + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, @@ -38,64 +254,66 @@ class _RangeColumnChartPainter extends CustomPainter { CartesianChartPoint point; canvas.save(); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The range column series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_RangeColumnChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(RangeColumnChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart index de7ec322c..ced21a20d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/scatter_painter.dart @@ -1,77 +1,299 @@ -part of charts; +import 'dart:ui'; -class _ScatterChartPainter extends CustomPainter { - _ScatterChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/scatter_segment.dart'; +import '../chart_series/scatter_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +export 'package:syncfusion_flutter_core/core.dart' show DataMarkerType; + +/// Creates series renderer for Scatter series +class ScatterSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of ScatterSeriesRenderer class. + ScatterSeriesRenderer(); + + // ignore:unused_field + CartesianChartPoint? _point; + + ScatterSegment? _segment; + + ///Adds the points to the segments . + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final ScatterSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(seriesRendererDetails.stateProperties, segment)); + + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + + final List oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + seriesRendererDetails.isRectSeries = false; + + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segmentProperties.seriesRenderer = this; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segment.animationFactor = animateFactor; + segmentProperties.point = currentPoint; + segmentProperties.currentPoint = currentPoint; + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + // ignore: unnecessary_null_comparison + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + final SeriesRendererDetails oldSeriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + segmentProperties.oldPoint = (oldSeriesDetails.segments.isNotEmpty == + true && + oldSeriesDetails.segments[0] is ScatterSegment && + (oldSeriesDetails.dataPoints.length - 1 >= pointIndex) == true) + ? oldSeriesDetails.dataPoints[pointIndex] + : null; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + if ((seriesRendererDetails.stateProperties.selectedSegments.length - + 1 >= + pointIndex) == + true && + SegmentHelper.getSegmentProperties(seriesRendererDetails + .stateProperties.selectedSegments[pointIndex]) + .oldSegmentIndex == + null) { + final ChartSegment selectedSegment = seriesRendererDetails + .stateProperties.selectedSegments[pointIndex]; + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegment); + selectedSegmentProperties.oldSeriesRenderer = + oldSeriesRenderers[selectedSegmentProperties.seriesIndex]; + selectedSegmentProperties.seriesRenderer = this; + selectedSegmentProperties.oldSegmentIndex = + getOldSegmentIndex(selectedSegment); + } + } + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final ChartLocation location = calculatePoint( + currentPoint.xValue, + currentPoint.yValue, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + seriesRendererDetails.stateProperties.chartAxis.axisClipRect); + segment.points.add(Offset(location.x, location.y)); + segmentProperties.segmentRect = + RRect.fromRectAndRadius(currentPoint.region!, Radius.zero); + + customizeSegment(segment); + seriesRendererDetails.segments.add(segment); + _segment = segment; + } + return segment; + } + + /// To render scatter series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final SeriesRendererDetails currentSeriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (currentSeriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + currentSeriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + ScatterSegment createSegment() => ScatterSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final ScatterSegment scatterSegment = segment as ScatterSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(scatterSegment); + segmentProperties.color = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = + ((segmentProperties.series.markerSettings.shape == + DataMarkerType.verticalLine || + segmentProperties.series.markerSettings.shape == + DataMarkerType.horizontalLine) && + segmentProperties.series.borderWidth == 0) + ? segmentProperties.series.markerSettings.borderWidth + : segmentProperties.series.borderWidth; + scatterSegment.strokePaint = scatterSegment.getStrokePaint(); + scatterSegment.fillPaint = scatterSegment.getFillPaint(); + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + final Size size = Size(seriesRendererDetails.series.markerSettings.width, + seriesRendererDetails.series.markerSettings.height); + final Path markerPath = getMarkerShapesPath( + seriesRendererDetails.series.markerSettings.shape, + Offset(pointX, pointY), + size, + seriesRendererDetails, + index, + null, + seriesRendererDetails.seriesElementAnimation, + _segment); + canvas.drawPath(markerPath, fillPaint); + canvas.drawPath(markerPath, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the scatter Chart painter +class ScatterChartPainter extends CustomPainter { + /// Calling the default constructor of ScatterChartPainter class. + ScatterChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final Animation animationController; + + /// Specifies the scatter series renderer final ScatterSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for scatter series @override void paint(Canvas canvas, Size size) { canvas.save(); double animationFactor; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final ScatterSeries series = - seriesRenderer._series as ScatterSeries; - if (seriesRenderer._visible!) { + seriesRendererDetails.series as ScatterSeries; + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; final List> dataPoints = - seriesRenderer._dataPoints; - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + seriesRendererDetails.dataPoints; + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { final CartesianChartPoint currentPoint = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData(chartState, seriesRenderer, - painterKey.index, currentPoint, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, currentPoint, pointIndex); if (currentPoint.isVisible && !currentPoint.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments(currentPoint, segmentIndex += 1, seriesIndex, animationFactor)); } } if (series.animationDuration <= 0 || - animationFactor >= chartState._seriesDurationFactor) { - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + animationFactor >= stateProperties.seriesDurationFactor) { + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } canvas.restore(); } @override - bool shouldRepaint(_ScatterChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(ScatterChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart index f451874fe..eb9fd5685 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_area_painter.dart @@ -1,21 +1,148 @@ -part of charts; +import 'dart:math' as math_lib; +import 'dart:ui'; -class _SplineAreaChartPainter extends CustomPainter { - _SplineAreaChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/spline_area_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/spline_area_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Spline area series +class SplineAreaSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of SplineAreaSeriesRenderer class. + SplineAreaSeriesRenderer(); + + /// SplineArea segment is created here + ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, + double animateFactor, Path path, Path strokePath, + [List? _points]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SplineAreaSegment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + seriesRendererDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = 0; + segment.animationFactor = animateFactor; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segmentProperties.seriesRenderer = this; + if (_points != null) { + segment.points = _points; + } + segmentProperties.path = path; + segmentProperties.strokePath = strokePath; + SegmentHelper.setSegmentProperties(segment, segmentProperties); + customizeSegment(segment); + segmentProperties.oldSegmentIndex = 0; + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render spline area series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final SeriesRendererDetails currentDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (currentDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + currentDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[0], seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + SplineAreaSegment createSegment() => SplineAreaSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = seriesRendererDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the SplineArea Chart painter +class SplineAreaChartPainter extends CustomPainter { + /// Calling the default constructor of SplineAreaChartPainter class. + SplineAreaChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; + + /// Specifies the spline area series renderer final SplineAreaSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for spline area series @override @@ -23,95 +150,108 @@ class _SplineAreaChartPainter extends CustomPainter { double animationFactor; CartesianChartPoint? prevPoint, point, oldChartPoint; CartesianSeriesRenderer? oldSeriesRenderer; - _ChartLocation? currentPoint, originPoint, oldPointLocation; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + SeriesRendererDetails? oldSeriesRendererDetails; + ChartLocation? currentPoint, originPoint, oldPointLocation; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; Rect clipRect; final SplineAreaSeries series = - seriesRenderer._series as SplineAreaSeries; - if (!seriesRenderer._hasDataLabelTemplate) { - seriesRenderer._drawControlPoints.clear(); + seriesRendererDetails.series as SplineAreaSeries; + if (seriesRendererDetails.hasDataLabelTemplate == false) { + seriesRendererDetails.drawControlPoints.clear(); } final Path _path = Path(); final Path _strokePath = Path(); final List _points = []; - final num? crossesAt = _getCrossesAtValue(seriesRenderer, chartState); + final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); final num origin = crossesAt ?? 0; double? startControlX, startControlY, endControlX, endControlY; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); final List oldSeriesRenderers = - chartState._oldSeriesRenderers; + stateProperties.oldSeriesRenderers; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; canvas.save(); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); final bool isTransposed = - seriesRenderer._chartState!._requireInvertedAxis; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.stateProperties.requireInvertedAxis; + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + oldSeriesRenderer = getOldSeriesRenderer(stateProperties, + seriesRendererDetails, seriesIndex, oldSeriesRenderers); - oldSeriesRenderer = _getOldSeriesRenderer( - chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + oldSeriesRendererDetails = oldSeriesRenderer == null + ? null + : SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } - if (!seriesRenderer._hasDataLabelTemplate) { - _calculateSplineAreaControlPoints(seriesRenderer); + if (seriesRendererDetails.hasDataLabelTemplate == false) { + calculateSplineAreaControlPoints(seriesRenderer); } - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { //Stores the old data point details of the corresponding point index - oldChartPoint = _getOldChartPoint( - chartState, - seriesRenderer, + oldChartPoint = getOldChartPoint( + stateProperties, + seriesRendererDetails, SplineAreaSegment, seriesIndex, pointIndex, oldSeriesRenderer, oldSeriesRenderers); oldPointLocation = oldChartPoint != null - ? _calculatePoint( + ? calculatePoint( oldChartPoint.xValue, oldChartPoint.yValue, - oldSeriesRenderer!._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + oldSeriesRendererDetails!.xAxisDetails!, + oldSeriesRendererDetails.yAxisDetails!, isTransposed, - oldSeriesRenderer._series, + oldSeriesRendererDetails.series, axisClipRect) : null; - currentPoint = _calculatePoint(point.xValue, point.yValue, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - originPoint = _calculatePoint( + currentPoint = calculatePoint(point.xValue, point.yValue, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); + originPoint = calculatePoint( point.xValue, - math_lib.max(yAxisRenderer._visibleRange!.minimum, origin), - xAxisRenderer, - yAxisRenderer, + math_lib.max(yAxisDetails.visibleRange!.minimum, origin), + xAxisDetails, + yAxisDetails, isTransposed, series, axisClipRect); @@ -126,37 +266,37 @@ class _SplineAreaChartPainter extends CustomPainter { //calculates animation values for control points and data points if (oldPointLocation != null) { isTransposed - ? x = _getAnimateValue(animationFactor, x, oldPointLocation.x, - currentPoint.x, seriesRenderer) - : y = _getAnimateValue(animationFactor, y, oldPointLocation.y, - currentPoint.y, seriesRenderer); + ? x = getAnimateValue(animationFactor, x, oldPointLocation.x, + currentPoint.x, seriesRendererDetails) + : y = getAnimateValue(animationFactor, y, oldPointLocation.y, + currentPoint.y, seriesRendererDetails); if (point.startControl != null) { - startControlY = _getAnimateValue( + startControlY = getAnimateValue( animationFactor, startControlY, oldChartPoint!.startControl!.y, point.startControl!.y, - seriesRenderer); - startControlX = _getAnimateValue( + seriesRendererDetails); + startControlX = getAnimateValue( animationFactor, startControlX, oldChartPoint.startControl!.x, point.startControl!.x, - seriesRenderer); + seriesRendererDetails); } if (point.endControl != null) { - endControlX = _getAnimateValue( + endControlX = getAnimateValue( animationFactor, endControlX, oldChartPoint!.endControl!.x, point.endControl!.x, - seriesRenderer); - endControlY = _getAnimateValue( + seriesRendererDetails); + endControlY = getAnimateValue( animationFactor, endControlY, oldChartPoint.endControl!.y, point.endControl!.y, - seriesRenderer); + seriesRendererDetails); } } else { if (point.startControl != null) { @@ -227,39 +367,38 @@ class _SplineAreaChartPainter extends CustomPainter { painterKey.index, chart, animationFactor, _path, _strokePath)); } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || !renderingDetails.initialRender! || - animationFactor >= chartState._seriesDurationFactor) && + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The spline area series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_SplineAreaChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(SplineAreaChartPainter oldDelegate) => isRepaint; /// It returns the visibility of area series bool _getSeriesVisibility( diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart index 4440af8be..f3755df2c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_painter.dart @@ -1,86 +1,260 @@ -part of charts; +import 'dart:ui'; -class _SplineChartPainter extends CustomPainter { - _SplineChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/spline_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/spline_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Spline series +class SplineSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of SplineSeriesRenderer class. + SplineSeriesRenderer(); + + /// Spline segment is created here + ChartSegment _createSegments( + CartesianChartPoint currentPoint, + CartesianChartPoint nextPoint, + int pointIndex, + int seriesIndex, + double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SplineSegment segment = createSegment() as SplineSegment; + final List _oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + seriesRendererDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + segment.animationFactor = animateFactor; + segmentProperties.currentPoint = currentPoint; + segmentProperties.nextPoint = nextPoint; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + segment.currentSegmentIndex = pointIndex; + segmentProperties.seriesIndex = seriesIndex; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segmentProperties.seriesRenderer = this; + final SeriesRendererDetails seriesDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer); + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + // ignore: unnecessary_null_comparison + _oldSeriesRenderers != null && + _oldSeriesRenderers.isNotEmpty && + _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + seriesDetails.seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers[segmentProperties.seriesIndex]; + segmentProperties.oldSeries = SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!) + .series as XyDataSeries; + } + SegmentHelper.setSegmentProperties(segment, segmentProperties); + segment.calculateSegmentPoints(); + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); + + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render spline series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final SeriesRendererDetails seriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (seriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + ChartSegment createSegment() => SplineSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final SeriesRendererDetails seriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + segmentProperties.color = seriesDetails.seriesColor; + segmentProperties.strokeColor = seriesDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the spline Chart painter +class SplineChartPainter extends CustomPainter { + /// Calling the default constructor of SplineChartPainter class. + SplineChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; + + /// Specifies the spline series renderer final SplineSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for spline series @override void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (!chart.legend.isVisible! && + !seriesRendererDetails.isSelectionEnable && + !chart.zoomPanBehavior.enableDoubleTapZooming && + !chart.zoomPanBehavior.enableMouseWheelZooming && + !chart.zoomPanBehavior.enablePanning && + !chart.zoomPanBehavior.enablePinching && + !chart.zoomPanBehavior.enableSelectionZooming) { + seriesRendererDetails.dispose(); + } + final SplineSeries series = - seriesRenderer._series as SplineSeries; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + seriesRendererDetails.series as SplineSeries; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; - if (!seriesRenderer._hasDataLabelTemplate) { - seriesRenderer._drawControlPoints.clear(); + seriesRendererDetails.dataPoints; + if (seriesRendererDetails.hasDataLabelTemplate == false) { + seriesRendererDetails.drawControlPoints.clear(); } /// Clip rect will be added for series. - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); canvas.save(); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } - if (!seriesRenderer._hasDataLabelTemplate) { - _calculateSplineAreaControlPoints(seriesRenderer); + if (seriesRendererDetails.hasDataLabelTemplate == false) { + calculateSplineAreaControlPoints(seriesRenderer); } int segmentIndex = -1; CartesianChartPoint? point, _nextPoint, startPoint, endPoint; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } - ///Draw spline for spline series + //Draw spline for spline series for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - if (_withInRange(seriesRenderer._dataPoints[pointIndex].xValue, - seriesRenderer._xAxisRenderer!._visibleRange!) || + if (withInRange(seriesRendererDetails.dataPoints[pointIndex].xValue, + seriesRendererDetails.xAxisDetails!.visibleRange!) || (pointIndex < dataPoints.length - 1 && - _withInRange(seriesRenderer._dataPoints[pointIndex + 1].xValue, - seriesRenderer._xAxisRenderer!._visibleRange!))) { - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + withInRange( + seriesRendererDetails.dataPoints[pointIndex + 1].xValue, + seriesRendererDetails.xAxisDetails!.visibleRange!))) { + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if ((point.isVisible && !point.isGap) && startPoint == null) { startPoint = point; } @@ -97,7 +271,7 @@ class _SplineChartPainter extends CustomPainter { if (startPoint != null && endPoint != null && startPoint != endPoint) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments(startPoint, endPoint, segmentIndex += 1, seriesIndex, animationFactor)); @@ -105,24 +279,24 @@ class _SplineChartPainter extends CustomPainter { } } } - clipRect = _calculatePlotOffset( + + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison @@ -131,15 +305,15 @@ class _SplineChartPainter extends CustomPainter { canvas.clipRect(clipRect); ///Draw marker and other elements for spline series - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_SplineChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(SplineChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart index bf851e60a..6c8a6834a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/spline_range_area_painter.dart @@ -1,44 +1,175 @@ -part of charts; +import 'dart:ui'; -class _SplineRangeAreaChartPainter extends CustomPainter { - _SplineRangeAreaChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/spline_range_area_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/spline_range_area_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Spline range area series +class SplineRangeAreaSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of SplineRangeAreaSeriesRenderer class. + SplineRangeAreaSeriesRenderer(); + + /// SplineRangeArea segment is created here + ChartSegment _createSegments(int seriesIndex, SfCartesianChart chart, + double animateFactor, Path path, Path strokePath, + [List? _points]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SplineRangeAreaSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(seriesRendererDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + seriesRendererDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = 0; + segment.animationFactor = animateFactor; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segmentProperties.seriesRenderer = this; + if (_points != null) { + segment.points = _points; + } + segmentProperties.path = path; + segmentProperties.strokePath = strokePath; + segmentProperties.oldSegmentIndex = 0; + + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render spline range area series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final SeriesRendererDetails seriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + if (seriesDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[0], seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + @override + SplineRangeAreaSegment createSegment() => SplineRangeAreaSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final SeriesRendererDetails seriesDetails = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer); + segmentProperties.color = seriesDetails.seriesColor; + segmentProperties.strokeColor = seriesDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + canvas.drawPath(seriesRendererDetails.markerShapes2[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the SplineRangeArea Chart painter +class SplineRangeAreaChartPainter extends CustomPainter { + /// Calling the default constructor of SplineRangeAreaChartPainter class. + SplineRangeAreaChartPainter( + {required this.stateProperties, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey, required this.seriesRenderer}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final Animation animationController; + + /// Specifies the Spline range area series renderer final SplineRangeAreaSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for spline range area series @override void paint(Canvas canvas, Size size) { Rect clipRect; double animationFactor; - _ChartLocation? currentPointLow, - currentPointHigh, - oldPointLow, - oldPointHigh; - final int pointsLength = seriesRenderer._dataPoints.length; + ChartLocation? currentPointLow, currentPointHigh, oldPointLow, oldPointHigh; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final int pointsLength = seriesRendererDetails.dataPoints.length; CartesianChartPoint? prevPoint, point, oldChartPoint; final Path _path = Path(); final Path _strokePath = Path(); final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; CartesianSeriesRenderer? oldSeriesRenderer; + final List _points = []; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List oldSeriesRenderers = - chartState._oldSeriesRenderers; + seriesRendererDetails.stateProperties.oldSeriesRenderers; double? currentPointLowX, currentPointLowY, currentPointHighX, @@ -47,58 +178,64 @@ class _SplineRangeAreaChartPainter extends CustomPainter { startControlY, endControlX, endControlY; - seriesRenderer._drawHighControlPoints.clear(); - seriesRenderer._drawLowControlPoints.clear(); + seriesRendererDetails.drawHighControlPoints.clear(); + seriesRendererDetails.drawLowControlPoints.clear(); - /// Clip rect will be added for series. - if (seriesRenderer._visible!) { + // Clip rect will be added for series. + if (seriesRendererDetails.visible! == true) { canvas.save(); final int seriesIndex = painterKey.index; - oldSeriesRenderer = _getOldSeriesRenderer( - chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + oldSeriesRenderer = getOldSeriesRenderer(stateProperties, + seriesRendererDetails, seriesIndex, oldSeriesRenderers); - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - final bool isTransposed = chartState._requireInvertedAxis; + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + final bool isTransposed = stateProperties.requireInvertedAxis; final SplineRangeAreaSeries series = - seriesRenderer._series as SplineRangeAreaSeries; + seriesRendererDetails.series + as SplineRangeAreaSeries; assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); SplineRangeAreaSegment splineRangeAreaSegment; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - if (seriesRenderer._reAnimate || + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } - if (!seriesRenderer._hasDataLabelTemplate) { - _calculateSplineAreaControlPoints(seriesRenderer); + if (seriesRendererDetails.hasDataLabelTemplate == false) { + calculateSplineAreaControlPoints(seriesRenderer); } - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < pointsLength; pointIndex++) { - point = seriesRenderer._dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + point = seriesRendererDetails.dataPoints[pointIndex]; + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible && !point.isDrop) { - oldChartPoint = _getOldChartPoint( - chartState, - seriesRenderer, + oldChartPoint = getOldChartPoint( + stateProperties, + seriesRendererDetails, SplineRangeAreaSegment, seriesIndex, pointIndex, @@ -106,19 +243,19 @@ class _SplineRangeAreaChartPainter extends CustomPainter { oldSeriesRenderers); oldPointHigh = (oldChartPoint != null) - ? _calculatePoint( + ? calculatePoint( oldChartPoint.xValue, oldChartPoint.high, - oldSeriesRenderer!._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, isTransposed, series, axisClipRect) : null; - currentPointLow = _calculatePoint(point.xValue, point.low, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - currentPointHigh = _calculatePoint(point.xValue, point.high, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); + currentPointLow = calculatePoint(point.xValue, point.low, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); + currentPointHigh = calculatePoint(point.xValue, point.high, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); _points.add(Offset(currentPointLow.x, currentPointLow.y)); _points.add(Offset(currentPointHigh.x, currentPointHigh.y)); @@ -127,51 +264,51 @@ class _SplineRangeAreaChartPainter extends CustomPainter { if (oldPointHigh != null) { if (isTransposed) { - currentPointHighX = _getAnimateValue( + currentPointHighX = getAnimateValue( animationFactor, currentPointHighX, oldPointHigh.x, currentPointHigh.x, - seriesRenderer); + seriesRendererDetails); currentPointHighY = currentPointHigh.y; } else { currentPointHighX = currentPointHigh.x; - currentPointHighY = _getAnimateValue( + currentPointHighY = getAnimateValue( animationFactor, currentPointHighY, oldPointHigh.y, currentPointHigh.y, - seriesRenderer); + seriesRendererDetails); } if (point.highStartControl != null) { - startControlX = _getAnimateValue( + startControlX = getAnimateValue( animationFactor, startControlX, oldChartPoint!.highStartControl!.x, point.highStartControl!.x, - seriesRenderer); - startControlY = _getAnimateValue( + seriesRendererDetails); + startControlY = getAnimateValue( animationFactor, startControlY, oldChartPoint.highStartControl!.y, point.highStartControl!.y, - seriesRenderer); + seriesRendererDetails); } else { startControlX = startControlY = null; } if (point.highEndControl != null) { - endControlX = _getAnimateValue( + endControlX = getAnimateValue( animationFactor, endControlX, oldChartPoint!.highEndControl!.x, point.highEndControl!.x, - seriesRenderer); - endControlY = _getAnimateValue( + seriesRendererDetails); + endControlY = getAnimateValue( animationFactor, endControlY, oldChartPoint.highEndControl!.y, point.highEndControl!.y, - seriesRenderer); + seriesRendererDetails); } else { endControlX = endControlY = null; } @@ -225,78 +362,78 @@ class _SplineRangeAreaChartPainter extends CustomPainter { pointIndex--) { point = dataPoints[pointIndex]; if (point.isVisible && !point.isDrop) { - oldChartPoint = _getOldChartPoint( - chartState, - seriesRenderer, + oldChartPoint = getOldChartPoint( + stateProperties, + seriesRendererDetails, SplineRangeAreaSegment, seriesIndex, pointIndex, oldSeriesRenderer, oldSeriesRenderers); if (oldChartPoint != null) { - oldPointLow = _calculatePoint( + oldPointLow = calculatePoint( oldChartPoint.xValue, oldChartPoint.low, - oldSeriesRenderer!._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, isTransposed, series, axisClipRect); } else { oldPointLow = null; } - currentPointLow = _calculatePoint(point.xValue, point.low, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); - currentPointHigh = _calculatePoint(point.xValue, point.high, - xAxisRenderer, yAxisRenderer, isTransposed, series, axisClipRect); + currentPointLow = calculatePoint(point.xValue, point.low, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); + currentPointHigh = calculatePoint(point.xValue, point.high, + xAxisDetails, yAxisDetails, isTransposed, series, axisClipRect); if (oldPointLow != null) { if (isTransposed) { - currentPointLowX = _getAnimateValue( + currentPointLowX = getAnimateValue( animationFactor, currentPointLowX, oldPointLow.x, currentPointLow.x, - seriesRenderer); + seriesRendererDetails); currentPointLowY = currentPointLow.y; } else { currentPointLowX = currentPointLow.x; - currentPointLowY = _getAnimateValue( + currentPointLowY = getAnimateValue( animationFactor, currentPointLowY, oldPointLow.y, currentPointLow.y, - seriesRenderer); + seriesRendererDetails); } if (point.lowStartControl != null) { - startControlX = _getAnimateValue( + startControlX = getAnimateValue( animationFactor, startControlX, oldChartPoint!.lowStartControl!.x, point.lowStartControl!.x, - seriesRenderer); - startControlY = _getAnimateValue( + seriesRendererDetails); + startControlY = getAnimateValue( animationFactor, startControlY, oldChartPoint.lowStartControl!.y, point.lowStartControl!.y, - seriesRenderer); + seriesRendererDetails); } else { startControlX = startControlY = null; } if (point.lowEndControl != null) { - endControlX = _getAnimateValue( + endControlX = getAnimateValue( animationFactor, endControlX, oldChartPoint!.lowEndControl!.x, point.lowEndControl!.x, - seriesRenderer); - endControlY = _getAnimateValue( + seriesRendererDetails); + endControlY = getAnimateValue( animationFactor, endControlY, oldChartPoint.lowEndControl!.y, point.lowEndControl!.y, - seriesRenderer); + seriesRendererDetails); } else { endControlX = endControlY = null; } @@ -334,49 +471,47 @@ class _SplineRangeAreaChartPainter extends CustomPainter { // ignore: unnecessary_null_comparison if (_path != null && // ignore: unnecessary_null_comparison - seriesRenderer._segments != null && - seriesRenderer._segments.isNotEmpty) { + seriesRendererDetails.segments.isNotEmpty) { splineRangeAreaSegment = - seriesRenderer._segments[0] as SplineRangeAreaSegment; - seriesRenderer._drawSegment( - canvas, - splineRangeAreaSegment - .._path = _path - .._strokePath = _strokePath); + seriesRendererDetails.segments[0] as SplineRangeAreaSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(splineRangeAreaSegment); + segmentProperties.path = _path; + segmentProperties.strokePath = _strokePath; + seriesRendererDetails.drawSegment(canvas, splineRangeAreaSegment); } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || (!renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The spline range area series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } @override - bool shouldRepaint(_SplineRangeAreaChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(SplineRangeAreaChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart index 9400b2d7e..f59c23c64 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_area_painter.dart @@ -1,34 +1,534 @@ -part of charts; +import 'dart:math' as math_lib; +import 'dart:ui'; -class _StackedAreaChartPainter extends CustomPainter { - _StackedAreaChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/stacked_area_segment.dart'; +import '../chart_segment/stackedarea100_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Stacked area series +class StackedAreaSeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedAreaSeriesRenderer class. + StackedAreaSeriesRenderer(); + + /// Stacked area segment is created here. + // ignore: unused_element + ChartSegment _createSegments( + int seriesIndex, SfCartesianChart chart, double animateFactor, + [List? _points]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedAreaSegment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final List _oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + seriesRendererDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesRenderer = this; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segment.currentSegmentIndex = 0; + segmentProperties.seriesIndex = seriesIndex; + if (_points != null) { + segment.points = _points; + } + segment.animationFactor = animateFactor; + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && + seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && + // ignore: unnecessary_null_comparison + _oldSeriesRenderers != null && + _oldSeriesRenderers.isNotEmpty && + _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers[segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = 0; + } + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked area series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[0], seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + StackedAreaSegment createSegment() => StackedAreaSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + /// Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Creates series renderer for Stacked area 100 series +class StackedArea100SeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedArea100SeriesRenderer class. + StackedArea100SeriesRenderer(); + + /// Stacked Area segment is created here. + // ignore: unused_element + ChartSegment _createSegments( + int seriesIndex, SfCartesianChart chart, double animateFactor, + [List? _points]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedArea100Segment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final List _oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + seriesRendererDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesRenderer = this; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segment.currentSegmentIndex = 0; + segmentProperties.seriesIndex = seriesIndex; + if (_points != null) { + segment.points = _points; + } + segment.animationFactor = animateFactor; + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + false && + seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && + seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && + // ignore: unnecessary_null_comparison + _oldSeriesRenderers != null && + _oldSeriesRenderers.isNotEmpty && + _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers[segmentProperties.seriesIndex]; + segmentProperties.oldSeries = SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!) + .series as XyDataSeries; + segmentProperties.oldSegmentIndex = 0; + } + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked area 100 series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[0], seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + StackedArea100Segment createSegment() => StackedArea100Segment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + /// Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the StackedArea Chart painter +class StackedAreaChartPainter extends CustomPainter { + /// Calling the default constructor of StackedAreaChartPainter class. + StackedAreaChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required this.painterKey, required ValueNotifier notifier}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final Animation animationController; + + /// Specifies the Stacked area series renderer final StackedAreaSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for stacked area series @override void paint(Canvas canvas, Size size) { - _stackedAreaPainter( + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + stackedAreaPainter( + canvas, + seriesRenderer, + seriesRendererDetails, + stateProperties, + seriesRendererDetails.seriesAnimation, + seriesRendererDetails.seriesElementAnimation!, + painterKey); + } + + @override + bool shouldRepaint(StackedAreaChartPainter oldDelegate) => isRepaint; +} + +/// Represents the StackedArea100 Chart painter +class StackedArea100ChartPainter extends CustomPainter { + /// Calling the default constructor of StackedArea100ChartPainter class. + StackedArea100ChartPainter( + {required this.stateProperties, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier}) + : chart = stateProperties.chart, + super(repaint: notifier); + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. + final SfCartesianChart chart; + + /// Specifies whether to repaint the series. + final bool isRepaint; + + /// Specifies the value of animation controller. + final Animation animationController; + + /// Specifies the StackedArea100 series renderer + final StackedArea100SeriesRenderer seriesRenderer; + + /// Specifies the painter key value + final PainterKey painterKey; + + /// Painter method for stacked area 100 series + @override + void paint(Canvas canvas, Size size) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + stackedAreaPainter( canvas, seriesRenderer, - chartState, - seriesRenderer._seriesAnimation, - seriesRenderer._seriesElementAnimation, + seriesRendererDetails, + stateProperties, + seriesRendererDetails.seriesAnimation, + seriesRendererDetails.seriesElementAnimation!, painterKey); } @override - bool shouldRepaint(_StackedAreaChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(StackedArea100ChartPainter oldDelegate) => isRepaint; +} + +/// Painter method for stacked area series +void stackedAreaPainter( + Canvas canvas, + dynamic seriesRenderer, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, + Animation? seriesAnimation, + Animation chartElementAnimation, + PainterKey painterKey) { + Rect clipRect, axisClipRect; + final int seriesIndex = painterKey.index; + final SfCartesianChart chart = stateProperties.chart; + final RenderingDetails _renderingDetails = stateProperties.renderingDetails; + + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + double animationFactor; + final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); + final List> dataPoints = + seriesRendererDetails.dataPoints; + + // Clip rect will be added for series. + if (seriesRendererDetails.visible! == true) { + final dynamic series = seriesRendererDetails.series; + canvas.save(); + axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.clipRect(axisClipRect); + animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + if (seriesRendererDetails.reAnimate == true || + ((!(_renderingDetails.widgetNeedUpdate || + _renderingDetails.isLegendToggled) || + !stateProperties.oldSeriesKeys.contains(series.key)) && + series.animationDuration > 0 == true)) { + performLinearAnimation(stateProperties, + seriesRendererDetails.xAxisDetails!.axis, canvas, animationFactor); + } + + final Path _path = Path(), _strokePath = Path(); + final Rect rect = stateProperties.chartAxis.axisClipRect; + ChartLocation point1, point2; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!, + yAxisDetails = seriesRendererDetails.yAxisDetails!; + CartesianChartPoint? point; + final dynamic _series = seriesRendererDetails.series; + final List _points = []; + if (dataPoints.isNotEmpty) { + int startPoint = 0; + final StackedValues stackedValues = + seriesRendererDetails.stackingValues[0]; + List seriesRendererCollection; + CartesianSeriesRenderer previousSeriesRenderer; + SeriesRendererDetails previousSeriesRendererDetails; + seriesRendererCollection = findSeriesCollection(stateProperties); + point1 = calculatePoint( + dataPoints[0].xValue, + math_lib.max(yAxisDetails.visibleRange!.minimum, + crossesAt ?? stackedValues.startValues[0]), + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + _series, + rect); + _path.moveTo(point1.x, point1.y); + _strokePath.moveTo(point1.x, point1.y); + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; + } + for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { + point = dataPoints[pointIndex]; + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, seriesIndex, point, pointIndex); + if (point.isVisible) { + point1 = calculatePoint( + dataPoints[pointIndex].xValue, + stackedValues.endValues[pointIndex], + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + _series, + rect); + _points.add(Offset(point1.x, point1.y)); + _path.lineTo(point1.x, point1.y); + _strokePath.lineTo(point1.x, point1.y); + } else { + if (_series.emptyPointSettings.mode != EmptyPointMode.drop) { + for (int j = pointIndex - 1; j >= startPoint; j--) { + point2 = calculatePoint( + dataPoints[j].xValue, + crossesAt ?? stackedValues.startValues[j], + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + _series, + rect); + _path.lineTo(point2.x, point2.y); + if (_series.borderDrawMode == BorderDrawMode.excludeBottom) { + _strokePath.lineTo(point1.x, point2.y); + } else if (_series.borderDrawMode == BorderDrawMode.all) { + _strokePath.lineTo(point2.x, point2.y); + } + } + if (dataPoints.length > pointIndex + 1 && + // ignore: unnecessary_null_comparison + dataPoints[pointIndex + 1] != null && + dataPoints[pointIndex + 1].isVisible) { + point1 = calculatePoint( + dataPoints[pointIndex + 1].xValue, + crossesAt ?? stackedValues.startValues[pointIndex + 1], + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + _series, + rect); + _path.moveTo(point1.x, point1.y); + _strokePath.moveTo(point1.x, point1.y); + } + startPoint = pointIndex + 1; + } + } + if (pointIndex >= dataPoints.length - 1) { + seriesRenderer._createSegments( + painterKey.index, chart, animationFactor, _points); + } + } + for (int j = dataPoints.length - 1; j >= startPoint; j--) { + previousSeriesRenderer = + getPreviousSeriesRenderer(seriesRendererCollection, seriesIndex); + previousSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(previousSeriesRenderer); + if (previousSeriesRendererDetails.series.emptyPointSettings.mode != + EmptyPointMode.drop || + previousSeriesRendererDetails.dataPoints[j].isVisible == true) { + point2 = calculatePoint( + dataPoints[j].xValue, + crossesAt ?? stackedValues.startValues[j], + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, + _series, + rect); + _path.lineTo(point2.x, point2.y); + if (_series.borderDrawMode == BorderDrawMode.excludeBottom) { + _strokePath.lineTo(point1.x, point2.y); + } else if (_series.borderDrawMode == BorderDrawMode.all) { + _strokePath.lineTo(point2.x, point2.y); + } + } + } + } + // ignore: unnecessary_null_comparison + if (_path != null && + seriesRendererDetails.segments != null && + seriesRendererDetails.segments.isNotEmpty == true) { + final ChartSegment areaSegment = seriesRendererDetails.segments[0]; + SegmentHelper.getSegmentProperties(areaSegment) + ..path = _path + ..strokePath = _strokePath; + seriesRenderer._drawSegment(canvas, areaSegment); + } + + clipRect = calculatePlotOffset( + Rect.fromLTRB( + rect.left - _series.markerSettings.width, + rect.top - _series.markerSettings.height, + rect.right + _series.markerSettings.width, + rect.bottom + _series.markerSettings.height), + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.restore(); + if ((_series.animationDuration <= 0 == true || + !_renderingDetails.initialRender! || + animationFactor >= stateProperties.seriesDurationFactor) && + (_series.markerSettings.isVisible == true || + _series.dataLabelSettings.isVisible == true)) { + canvas.clipRect(clipRect); + seriesRendererDetails.renderSeriesElements( + chart, canvas, chartElementAnimation); + } + if (animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart index 4845f34a5..b7cfbc88d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_bar_painter.dart @@ -1,33 +1,411 @@ -part of charts; +import 'dart:ui'; -class _StackedBarChartPainter extends CustomPainter { - _StackedBarChartPainter({ - required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/stacked_bar_segment.dart'; +import '../chart_segment/stackedbar100_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/stacked_bar_series.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/stackedbar100_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Stacked bar series +class StackedBarSeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedBarSeriesRenderer class. + StackedBarSeriesRenderer(); + + /// Stacked Bar segment is created here. + // ignore: unused_element + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedBarSegment segment = createSegment(); + SegmentHelper.setSegmentProperties(segment, + SegmentProperties(seriesRendererDetails.stateProperties, segment)); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final StackedBarSeries _stackedBarSeries = + seriesRendererDetails.series as StackedBarSeries; + seriesRendererDetails.isRectSeries = true; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segmentProperties.seriesRenderer = this; + segmentProperties.series = _stackedBarSeries; + segmentProperties.currentPoint = currentPoint; + segment.animationFactor = animateFactor; + segmentProperties.path = findingRectSeriesDashedBorder( + currentPoint, _stackedBarSeries.borderWidth); + // ignore: unnecessary_null_comparison + if (_stackedBarSeries.borderRadius != null) { + segment.segmentRect = getRRectFromRect( + currentPoint.region!, _stackedBarSeries.borderRadius); + + //Tracker rect + if (_stackedBarSeries.isTrackVisible) { + segmentProperties.trackRect = getRRectFromRect( + currentPoint.trackerRectRegion!, _stackedBarSeries.borderRadius); + segmentProperties.trackerFillPaint = + segmentProperties.getTrackerFillPaint(); + segmentProperties.trackerStrokePaint = + segmentProperties.getTrackerStrokePaint(); + } + } + segmentProperties.segmentRect = segment.segmentRect; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked bar series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + @override + StackedBarSegment createSegment() => StackedBarSegment(); + + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); + + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } +} + +/// Represents the StackedBar Chart painter +class StackedBarChartPainter extends CustomPainter { + /// Calling the default constructor of StackedBarChartPainter class. + StackedBarChartPainter({ + required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required this.painterKey, required ValueNotifier notifier, - }) : chart = chartState._chart, + }) : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies the Cartesian chart point. CartesianChartPoint? point; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the StackedBar series renderer final StackedBarSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; // ignore: unused_field // static double animation; /// Painter method for stacked bar series @override void paint(Canvas canvas, Size size) { - _stackedRectPainter(canvas, seriesRenderer, chartState, painterKey); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + _stackedBarPainter(canvas, seriesRenderer, seriesRendererDetails, + stateProperties, painterKey); + } + + @override + bool shouldRepaint(StackedBarChartPainter oldDelegate) => isRepaint; +} + +/// Creates series renderer for Stacked bar 100 series +class StackedBar100SeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedBar100SeriesRenderer class. + StackedBar100SeriesRenderer(); + + /// Stacked Bar segment is created here + // ignore: unused_element + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedBar100Segment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + + final StackedBar100Series _stackedBar100Series = + seriesRendererDetails.series as StackedBar100Series; + seriesRendererDetails.isRectSeries = true; + + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segmentProperties.seriesRenderer = this; + segmentProperties.series = _stackedBar100Series; + segmentProperties.currentPoint = currentPoint; + segment.animationFactor = animateFactor; + segmentProperties.path = findingRectSeriesDashedBorder( + currentPoint, _stackedBar100Series.borderWidth); + segment.segmentRect = getRRectFromRect( + currentPoint.region!, _stackedBar100Series.borderRadius); + segmentProperties.segmentRect = segment.segmentRect; + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(segment); + currentSegmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked bar 100 series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + @override + StackedBar100Segment createSegment() => StackedBar100Segment(); + + @override + void customizeSegment(ChartSegment segment) { + final SegmentProperties bar100SegmentProperties = + SegmentHelper.getSegmentProperties(segment); + bar100SegmentProperties.color = + bar100SegmentProperties.currentPoint!.pointColorMapper ?? + SeriesHelper.getSeriesRendererDetails( + bar100SegmentProperties.seriesRenderer) + .seriesColor; + bar100SegmentProperties.strokeColor = + bar100SegmentProperties.series.borderColor; + bar100SegmentProperties.strokeWidth = + bar100SegmentProperties.series.borderWidth; + } + + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); + + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } +} + +/// Represents the StackedBar100 Chart painter +class StackedBar100ChartPainter extends CustomPainter { + /// Calling the default constructor of StackedBar100ChartPainter class. + StackedBar100ChartPainter({ + required this.stateProperties, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier, + }) : chart = stateProperties.chart, + super(repaint: notifier); + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. + final SfCartesianChart chart; + + /// Specifies the Cartesian chart point. + CartesianChartPoint? point; + + /// Specifies whether to repaint the series. + final bool isRepaint; + + /// Specifies the value of animation controller. + final AnimationController animationController; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the StackedBar100 series renderer + final StackedBar100SeriesRenderer seriesRenderer; + + /// Specifies the painter key value + final PainterKey painterKey; + + /// Painter method for stacked bar 100 series + @override + void paint(Canvas canvas, Size size) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + _stackedBarPainter(canvas, seriesRenderer, seriesRendererDetails, + stateProperties, painterKey); } @override - bool shouldRepaint(_StackedBarChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(StackedBar100ChartPainter oldDelegate) => isRepaint; +} + +/// Rect painter for stacked series +void _stackedBarPainter( + Canvas canvas, + dynamic seriesRenderer, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, + PainterKey painterKey) { + if (seriesRendererDetails.visible! == true) { + canvas.save(); + Rect clipRect, axisClipRect; + CartesianChartPoint point; + final XyDataSeries series = + seriesRendererDetails.series as XyDataSeries; + final RenderingDetails _renderingDetails = stateProperties.renderingDetails; + final int seriesIndex = painterKey.index; + final Animation seriesAnimation = + seriesRendererDetails.seriesAnimation!; + final Animation chartElementAnimation = + seriesRendererDetails.seriesElementAnimation!; + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + double animationFactor; + // ignore: unnecessary_null_comparison + animationFactor = seriesAnimation != null && + (seriesRendererDetails.reAnimate == true || + (!(_renderingDetails.widgetNeedUpdate || + _renderingDetails.isLegendToggled))) + ? seriesAnimation.value + : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + + /// Clip rect will be added for series. + axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.clipRect(axisClipRect); + int segmentIndex = -1; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; + } + for (int pointIndex = 0; + pointIndex < seriesRendererDetails.dataPoints.length; + pointIndex++) { + point = seriesRendererDetails.dataPoints[pointIndex]; + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); + if (point.isVisible && !point.isGap) { + seriesRendererDetails.drawSegment( + canvas, + seriesRenderer._createSegments( + point, segmentIndex += 1, painterKey.index, animationFactor)); + } + } + clipRect = calculatePlotOffset( + Rect.fromLTRB( + stateProperties.chartAxis.axisClipRect.left - + series.markerSettings.width, + stateProperties.chartAxis.axisClipRect.top - + series.markerSettings.height, + stateProperties.chartAxis.axisClipRect.right + + series.markerSettings.width, + stateProperties.chartAxis.axisClipRect.bottom + + series.markerSettings.height), + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.restore(); + if ((series.animationDuration <= 0 || + !_renderingDetails.initialRender! || + animationFactor >= stateProperties.seriesDurationFactor) && + (series.markerSettings.isVisible || + series.dataLabelSettings.isVisible)) { + canvas.clipRect(clipRect); + seriesRendererDetails.renderSeriesElements( + stateProperties.chart, canvas, chartElementAnimation); + } + if (animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart index 6e4912b0e..746f52788 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_column_painter.dart @@ -1,30 +1,407 @@ -part of charts; +import 'dart:ui'; -class _StackedColummnChartPainter extends CustomPainter { - _StackedColummnChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/stacked_column_segment.dart'; +import '../chart_segment/stackedcolumn100_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/stacked_column_series.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/stackedcolumn100_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Stacked column series +class StackedColumnSeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedColumnSeriesRenderer class. + StackedColumnSeriesRenderer(); + + /// Stacked Bar segment is created here. + // ignore: unused_element + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedColumnSegment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final StackedColumnSeries _stackedColumnSeries = + seriesRendererDetails.series as StackedColumnSeries; + seriesRendererDetails.isRectSeries = true; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segmentProperties.seriesRenderer = this; + segmentProperties.series = _stackedColumnSeries; + segmentProperties.currentPoint = currentPoint; + segment.animationFactor = animateFactor; + segmentProperties.path = findingRectSeriesDashedBorder( + currentPoint, _stackedColumnSeries.borderWidth); + segment.segmentRect = getRRectFromRect( + currentPoint.region!, _stackedColumnSeries.borderRadius); + + //Tracker rect + if (_stackedColumnSeries.isTrackVisible) { + segmentProperties.trackRect = getRRectFromRect( + currentPoint.trackerRectRegion!, _stackedColumnSeries.borderRadius); + segmentProperties.trackerFillPaint = + segmentProperties.getTrackerFillPaint(); + segmentProperties.trackerStrokePaint = + segmentProperties.getTrackerStrokePaint(); + } + segmentProperties.segmentRect = segment.segmentRect; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked column series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + @override + StackedColumnSegment createSegment() => StackedColumnSegment(); + + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); + + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } +} + +/// Represents the StackedColumn Chart painter +class StackedColummnChartPainter extends CustomPainter { + /// Calling the default constructor of StackedColummnChartPainter class. + StackedColummnChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies the Cartesian chart point. CartesianChartPoint? point; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the StackedColumn series renderer final StackedColumnSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for stacked column series @override void paint(Canvas canvas, Size size) { - _stackedRectPainter(canvas, seriesRenderer, chartState, painterKey); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + _stackedRectPainter(canvas, seriesRenderer, seriesRendererDetails, + stateProperties, painterKey); } @override - bool shouldRepaint(_StackedColummnChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(StackedColummnChartPainter oldDelegate) => isRepaint; +} + +/// Creates series renderer for Stacked column 100 series +class StackedColumn100SeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedColumn100SeriesRenderer class. + StackedColumn100SeriesRenderer(); + + /// Stacked Column 100 segment is created here + // ignore: unused_element + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedColumn100Segment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final StackedColumn100Series _stackedColumn100Series = + seriesRendererDetails.series + as StackedColumn100Series; + seriesRendererDetails.isRectSeries = true; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points.add( + Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segmentProperties.seriesRenderer = this; + segmentProperties.series = _stackedColumn100Series; + segmentProperties.currentPoint = currentPoint; + segment.animationFactor = animateFactor; + segmentProperties.path = findingRectSeriesDashedBorder( + currentPoint, _stackedColumn100Series.borderWidth); + segment.segmentRect = getRRectFromRect( + currentPoint.region!, _stackedColumn100Series.borderRadius); + segmentProperties.segmentRect = segment.segmentRect; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked column 100 series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + StackedColumn100Segment createSegment() => StackedColumn100Segment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final StackedColumn100Segment column100Segment = + segment as StackedColumn100Segment; + final SegmentProperties column100SegmentProperties = + SegmentHelper.getSegmentProperties(column100Segment); + column100SegmentProperties.color = + column100SegmentProperties.currentPoint!.pointColorMapper ?? + SeriesHelper.getSeriesRendererDetails( + column100SegmentProperties.seriesRenderer) + .seriesColor; + column100SegmentProperties.strokeColor = + column100SegmentProperties.series.borderColor; + column100SegmentProperties.strokeWidth = + column100SegmentProperties.series.borderWidth; + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); + + /// Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } +} + +/// Represents the StacedColumn100 Chart painter +class StackedColumn100ChartPainter extends CustomPainter { + /// Calling the default constructor of StackedColumn100ChartPainter class. + StackedColumn100ChartPainter({ + required this.stateProperties, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required this.painterKey, + required ValueNotifier notifier, + }) : chart = stateProperties.chart, + super(repaint: notifier); + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. + final SfCartesianChart chart; + + /// Specifies the Cartesian chart point. + CartesianChartPoint? point; + + /// Specifies whether to repaint the series. + final bool isRepaint; + + /// Specifies the value of animation controller. + final AnimationController animationController; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the StackedColumn100 series renderer + final StackedColumn100SeriesRenderer seriesRenderer; + + /// Specifies the painter key value + final PainterKey painterKey; + + /// Painter method for stacked column 100 series + @override + void paint(Canvas canvas, Size size) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + _stackedRectPainter(canvas, seriesRenderer, seriesRendererDetails, + stateProperties, painterKey); + } + + @override + bool shouldRepaint(StackedColumn100ChartPainter oldDelegate) => isRepaint; +} + +/// Rect painter for stacked series +void _stackedRectPainter( + Canvas canvas, + dynamic seriesRenderer, + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties, + PainterKey painterKey) { + if (seriesRendererDetails.visible! == true) { + canvas.save(); + Rect clipRect, axisClipRect; + CartesianChartPoint point; + final XyDataSeries series = + seriesRendererDetails.series as XyDataSeries; + final RenderingDetails _renderingDetails = stateProperties.renderingDetails; + final int seriesIndex = painterKey.index; + final Animation seriesAnimation = + seriesRendererDetails.seriesAnimation!; + final Animation chartElementAnimation = + seriesRendererDetails.seriesElementAnimation!; + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + double animationFactor; + // ignore: unnecessary_null_comparison + animationFactor = seriesAnimation != null && + (seriesRendererDetails.reAnimate == true || + (!(_renderingDetails.widgetNeedUpdate || + _renderingDetails.isLegendToggled))) + ? seriesAnimation.value + : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + // Clip rect will be added for series. + axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.clipRect(axisClipRect); + int segmentIndex = -1; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; + } + for (int pointIndex = 0; + pointIndex < seriesRendererDetails.dataPoints.length; + pointIndex++) { + point = seriesRendererDetails.dataPoints[pointIndex]; + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); + if (point.isVisible && !point.isGap) { + seriesRendererDetails.drawSegment( + canvas, + seriesRenderer._createSegments( + point, segmentIndex += 1, painterKey.index, animationFactor)); + } + } + clipRect = calculatePlotOffset( + Rect.fromLTRB( + stateProperties.chartAxis.axisClipRect.left - + series.markerSettings.width, + stateProperties.chartAxis.axisClipRect.top - + series.markerSettings.height, + stateProperties.chartAxis.axisClipRect.right + + series.markerSettings.width, + stateProperties.chartAxis.axisClipRect.bottom + + series.markerSettings.height), + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.restore(); + if ((series.animationDuration <= 0 || + !_renderingDetails.initialRender! || + animationFactor >= stateProperties.seriesDurationFactor) && + (series.markerSettings.isVisible || + series.dataLabelSettings.isVisible)) { + canvas.clipRect(clipRect); + seriesRendererDetails.renderSeriesElements( + stateProperties.chart, canvas, chartElementAnimation); + } + if (animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart index d8b404d58..0db9d47f4 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stacked_line_painter.dart @@ -1,29 +1,481 @@ -part of charts; +import 'dart:ui'; -class _StackedLineChartPainter extends CustomPainter { - _StackedLineChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/stacked_line_segment.dart'; +import '../chart_segment/stackedline100_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Stacked line series +class StackedLineSeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedLineSeriesRenderer class. + StackedLineSeriesRenderer(); + + ///Stacked line segment is created here + // ignore: unused_element + ChartSegment _createSegments( + CartesianChartPoint currentPoint, + CartesianChartPoint _nextPoint, + int pointIndex, + int seriesIndex, + double animationFactor, + double currentCummulativePos, + double nextCummulativePos) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedLineSegment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final List? _oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + seriesRendererDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesRenderer = this; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segmentProperties.seriesIndex = seriesIndex; + segmentProperties.currentPoint = currentPoint; + segment.currentSegmentIndex = pointIndex; + segmentProperties.nextPoint = _nextPoint; + segment.animationFactor = animationFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + segmentProperties.currentCummulativePos = currentCummulativePos; + segmentProperties.nextCummulativePos = nextCummulativePos; + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && + seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && + _oldSeriesRenderers != null && + _oldSeriesRenderers.isNotEmpty && + _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers[segmentProperties.seriesIndex]; + SegmentHelper.setSegmentProperties(segment, segmentProperties); + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + } + segment.calculateSegmentPoints(); + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(segment); + segment.points.add( + Offset(currentSegmentProperties.x1, currentSegmentProperties.y1)); + segment.points.add( + Offset(currentSegmentProperties.x2, currentSegmentProperties.y2)); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked line series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + StackedLineSegment createSegment() => StackedLineSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = seriesRendererDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the StackedLine Chart painter +class StackedLineChartPainter extends CustomPainter { + /// Calling the default constructor of StackedLineChartPainter class. + StackedLineChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; + + /// Specifies the StacledLine series renderer final StackedLineSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for stacked line series @override void paint(Canvas canvas, Size size) { - _stackedLinePainter(canvas, seriesRenderer, seriesRenderer._seriesAnimation, - chartState, seriesRenderer._seriesElementAnimation, painterKey); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + _stackedLinePainter( + canvas, + seriesRenderer, + seriesRendererDetails, + seriesRendererDetails.seriesAnimation, + stateProperties, + seriesRendererDetails.seriesElementAnimation!, + painterKey); } @override - bool shouldRepaint(_StackedLineChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(StackedLineChartPainter oldDelegate) => isRepaint; +} + +/// Creates series renderer for Stacked line 100 series +class StackedLine100SeriesRenderer extends StackedSeriesRenderer { + /// Calling the default constructor of StackedLine100SeriesRenderer class. + StackedLine100SeriesRenderer(); + + ///Stacked line segment is created here + // ignore: unused_element + ChartSegment _createSegments( + CartesianChartPoint currentPoint, + CartesianChartPoint _nextPoint, + int pointIndex, + int seriesIndex, + double animationFactor, + double currentCummulativePos, + double nextCummulativePos) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StackedLine100Segment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final List _oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + seriesRendererDetails.isRectSeries = false; + // ignore: unnecessary_null_comparison + if (segment != null) { + segmentProperties.seriesRenderer = this; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segmentProperties.seriesIndex = seriesIndex; + segmentProperties.currentPoint = currentPoint; + segment.currentSegmentIndex = pointIndex; + segmentProperties.nextPoint = _nextPoint; + segment.animationFactor = animationFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + segmentProperties.currentCummulativePos = currentCummulativePos; + segmentProperties.nextCummulativePos = nextCummulativePos; + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && + seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && + // ignore: unnecessary_null_comparison + _oldSeriesRenderers != null && + _oldSeriesRenderers.isNotEmpty && + _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers[segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + } + segment.calculateSegmentPoints(); + segment.points.add(Offset(segmentProperties.x1, segmentProperties.y1)); + segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + } + return segment; + } + + /// To render stacked line 100 series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + StackedLine100Segment createSegment() => StackedLine100Segment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = seriesRendererDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the StackedLine100 Chart painter +class StackedLine100ChartPainter extends CustomPainter { + /// Calling the default constructor of StackedLine100ChartPainter class. + StackedLine100ChartPainter( + {required this.stateProperties, + required this.seriesRenderer, + required this.isRepaint, + required this.animationController, + required ValueNotifier notifier, + required this.painterKey}) + : chart = stateProperties.chart, + super(repaint: notifier); + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. + final SfCartesianChart chart; + + /// Specifies whether to repaint the series. + final bool isRepaint; + + /// Specifies the value of animation controller. + final AnimationController animationController; + + /// Specifies the StackedLine100 series renderer + final StackedLine100SeriesRenderer seriesRenderer; + + /// Specifies the painter key value + final PainterKey painterKey; + + /// Painter method for stacked line 100 series + @override + void paint(Canvas canvas, Size size) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + _stackedLinePainter( + canvas, + seriesRenderer, + seriesRendererDetails, + seriesRendererDetails.seriesAnimation, + stateProperties, + seriesRendererDetails.seriesElementAnimation!, + painterKey); + } + + @override + bool shouldRepaint(StackedLine100ChartPainter oldDelegate) => isRepaint; +} + +/// Painter for stacked line series +void _stackedLinePainter( + Canvas canvas, + dynamic seriesRenderer, + SeriesRendererDetails seriesRendererDetails, + Animation? seriesAnimation, + CartesianStateProperties stateProperties, + Animation chartElementAnimation, + PainterKey painterKey) { + Rect clipRect; + double animationFactor; + final RenderingDetails _renderingDetails = stateProperties.renderingDetails; + if (seriesRendererDetails.visible! == true) { + final XyDataSeries series = + seriesRendererDetails.series as XyDataSeries; + canvas.save(); + animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final int seriesIndex = painterKey.index; + StackedValues? stackedValues; + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + if ((seriesRenderer is StackedLine100SeriesRenderer || + seriesRenderer is StackedLineSeriesRenderer) && + seriesRendererDetails.stackingValues.isNotEmpty == true) { + stackedValues = seriesRendererDetails.stackingValues[0]; + } + final Rect rect = + seriesRendererDetails.stateProperties.chartAxis.axisClipRect; + + // Clip rect will be added for series. + final Rect axisClipRect = calculatePlotOffset( + rect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.clipRect(axisClipRect); + if (seriesRendererDetails.reAnimate == true || + ((!(_renderingDetails.widgetNeedUpdate || + _renderingDetails.isLegendToggled) || + !stateProperties.oldSeriesKeys.contains(series.key)) && + series.animationDuration > 0)) { + performLinearAnimation(stateProperties, + seriesRendererDetails.xAxisDetails!.axis, canvas, animationFactor); + } + + int segmentIndex = -1; + double? currentCummulativePos, nextCummulativePos; + CartesianChartPoint? startPoint, + endPoint, + currentPoint, + _nextPoint; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; + } + for (int pointIndex = 0; + pointIndex < seriesRendererDetails.dataPoints.length; + pointIndex++) { + currentPoint = seriesRendererDetails.dataPoints[pointIndex]; + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, seriesIndex, currentPoint, pointIndex); + if ((currentPoint.isVisible && !currentPoint.isGap) && + startPoint == null && + stackedValues != null) { + startPoint = currentPoint; + currentCummulativePos = stackedValues.endValues[pointIndex]; + } + if (pointIndex + 1 < seriesRendererDetails.dataPoints.length) { + _nextPoint = seriesRendererDetails.dataPoints[pointIndex + 1]; + if (startPoint != null && _nextPoint.isGap) { + startPoint = null; + } else if (_nextPoint.isVisible && + !_nextPoint.isGap && + stackedValues != null) { + endPoint = _nextPoint; + nextCummulativePos = stackedValues.endValues[pointIndex + 1]; + } + } + + if (startPoint != null && endPoint != null) { + seriesRendererDetails.drawSegment( + canvas, + seriesRenderer._createSegments( + startPoint, + endPoint, + segmentIndex += 1, + seriesIndex, + animationFactor, + currentCummulativePos!, + nextCummulativePos!)); + endPoint = startPoint = null; + } + } + clipRect = calculatePlotOffset( + Rect.fromLTRB( + rect.left - series.markerSettings.width, + rect.top - series.markerSettings.height, + rect.right + series.markerSettings.width, + rect.bottom + series.markerSettings.height), + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + canvas.restore(); + if ((series.animationDuration <= 0 || + !_renderingDetails.initialRender! || + animationFactor >= stateProperties.seriesDurationFactor) && + (series.markerSettings.isVisible || + series.dataLabelSettings.isVisible)) { + canvas.clipRect(clipRect); + seriesRendererDetails.renderSeriesElements( + stateProperties.chart, canvas, chartElementAnimation); + } + if (animationFactor >= 1) { + stateProperties.setPainterKey(seriesIndex, painterKey.name, true); + } + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart deleted file mode 100644 index 8a261ad85..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedarea100_painter.dart +++ /dev/null @@ -1,34 +0,0 @@ -part of charts; - -class _StackedArea100ChartPainter extends CustomPainter { - _StackedArea100ChartPainter( - {required this.chartState, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier}) - : chart = chartState._chart, - super(repaint: notifier); - final SfCartesianChartState chartState; - final SfCartesianChart chart; - final bool isRepaint; - final Animation animationController; - final StackedArea100SeriesRenderer seriesRenderer; - final _PainterKey painterKey; - - /// Painter method for stacked area 100 series - @override - void paint(Canvas canvas, Size size) { - _stackedAreaPainter( - canvas, - seriesRenderer, - chartState, - seriesRenderer._seriesAnimation, - seriesRenderer._seriesElementAnimation, - painterKey); - } - - @override - bool shouldRepaint(_StackedArea100ChartPainter oldDelegate) => isRepaint; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart deleted file mode 100644 index d321e94d3..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedbar100_painter.dart +++ /dev/null @@ -1,33 +0,0 @@ -part of charts; - -class _StackedBar100ChartPainter extends CustomPainter { - _StackedBar100ChartPainter({ - required this.chartState, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier, - }) : chart = chartState._chart, - super(repaint: notifier); - - final SfCartesianChartState chartState; - final SfCartesianChart chart; - CartesianChartPoint? point; - final bool isRepaint; - final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; - final StackedBar100SeriesRenderer seriesRenderer; - final _PainterKey painterKey; - //ignore: unused_field - // static double animation; - - /// Painter method for stacked bar 100 series - @override - void paint(Canvas canvas, Size size) { - _stackedRectPainter(canvas, seriesRenderer, chartState, painterKey); - } - - @override - bool shouldRepaint(_StackedBar100ChartPainter oldDelegate) => isRepaint; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart deleted file mode 100644 index b1b36d0b7..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedcolumn100_painter.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of charts; - -class _StackedColumn100ChartPainter extends CustomPainter { - _StackedColumn100ChartPainter({ - required this.chartState, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required this.painterKey, - required ValueNotifier notifier, - }) : chart = chartState._chart, - super(repaint: notifier); - final SfCartesianChartState chartState; - final SfCartesianChart chart; - CartesianChartPoint? point; - final bool isRepaint; - final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; - final StackedColumn100SeriesRenderer seriesRenderer; - final _PainterKey painterKey; - - /// Painter method for stacked column 100 series - @override - void paint(Canvas canvas, Size size) { - _stackedRectPainter(canvas, seriesRenderer, chartState, painterKey); - } - - @override - bool shouldRepaint(_StackedColumn100ChartPainter oldDelegate) => isRepaint; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart deleted file mode 100644 index e2b5ce39b..000000000 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stackedline100_painter.dart +++ /dev/null @@ -1,29 +0,0 @@ -part of charts; - -class _StackedLine100ChartPainter extends CustomPainter { - _StackedLine100ChartPainter( - {required this.chartState, - required this.seriesRenderer, - required this.isRepaint, - required this.animationController, - required ValueNotifier notifier, - required this.painterKey}) - : chart = chartState._chart, - super(repaint: notifier); - final SfCartesianChartState chartState; - final SfCartesianChart chart; - final bool isRepaint; - final AnimationController animationController; - final StackedLine100SeriesRenderer seriesRenderer; - final _PainterKey painterKey; - - /// Painter method for stacked line 100 series - @override - void paint(Canvas canvas, Size size) { - _stackedLinePainter(canvas, seriesRenderer, seriesRenderer._seriesAnimation, - chartState, seriesRenderer._seriesElementAnimation, painterKey); - } - - @override - bool shouldRepaint(_StackedLine100ChartPainter oldDelegate) => isRepaint; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart index 314d5bf36..0d24df9a3 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/step_area_painter.dart @@ -1,47 +1,173 @@ -part of charts; +import 'dart:math' as math_lib; +import 'dart:ui'; -class _StepAreaChartPainter extends CustomPainter { - _StepAreaChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/step_area_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/step_area_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Step area series +class StepAreaSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of StepAreaSeriesRenderer class. + StepAreaSeriesRenderer(); + + /// StepArea segment is created here + ChartSegment _createSegments( + Path path, Path strokePath, int seriesIndex, double animateFactor, + [List? _points]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StepAreaSegment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + seriesRendererDetails.isRectSeries = false; + segmentProperties.path = path; + segmentProperties.strokePath = strokePath; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = 0; + segmentProperties.seriesRenderer = this; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + if (_points != null) { + segment.points = _points; + } + segment.animationFactor = animateFactor; + segment.calculateSegmentPoints(); + segmentProperties.oldSegmentIndex = 0; + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + return segment; + } + + /// To render step area series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[0], seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + StepAreaSegment createSegment() => StepAreaSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + } + + /// Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the StepArea Chart painter +class StepAreaChartPainter extends CustomPainter { + /// Calling the default constructor of StepAreaChartPainter class. + StepAreaChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final Animation animationController; + + /// Specifies the Step area series renderer final StepAreaSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for step area series @override void paint(Canvas canvas, Size size) { double animationFactor; CartesianChartPoint? prevPoint, point, oldChartPoint; - _ChartLocation? currentPoint, + ChartLocation? currentPoint, originPoint, previousPoint, oldPoint, prevOldPoint; CartesianSeriesRenderer? oldSeriesRenderer; + late SeriesRendererDetails oldSeriesRendererDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final List oldSeriesRenderers = - chartState._oldSeriesRenderers; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + seriesRendererDetails.stateProperties.oldSeriesRenderers; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final StepAreaSeries _series = - seriesRenderer._series as StepAreaSeries; + seriesRendererDetails.series as StepAreaSeries; final Path _path = Path(), _strokePath = Path(); final List _points = []; - final num? crossesAt = _getCrossesAtValue(seriesRenderer, chartState); + final num? crossesAt = getCrossesAtValue(seriesRenderer, stateProperties); final num origin = crossesAt ?? 0; /// Clip rect will be added for series. - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(_series.animationDuration != null) || @@ -49,84 +175,95 @@ class _StepAreaChartPainter extends CustomPainter { 'The animation duration of the step area series must be greater or equal to 0.'); canvas.save(); final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final int seriesIndex = painterKey.index; final StepAreaSeries series = - seriesRenderer._series as StepAreaSeries; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); + seriesRendererDetails.series as StepAreaSeries; + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); canvas.clipRect(axisClipRect); - oldSeriesRenderer = _getOldSeriesRenderer( - chartState, seriesRenderer, seriesIndex, oldSeriesRenderers); + oldSeriesRenderer = getOldSeriesRenderer(stateProperties, + seriesRendererDetails, seriesIndex, oldSeriesRenderers); - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + if (oldSeriesRenderer != null) { + oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer); + } + + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - if (seriesRenderer._reAnimate || + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys - .contains(seriesRenderer._series.key)) && - seriesRenderer._series.animationDuration > 0)) { - _performLinearAnimation(chartState, - seriesRenderer._xAxisRenderer!._axis, canvas, animationFactor); + !stateProperties.oldSeriesKeys + .contains(seriesRendererDetails.series.key)) && + (seriesRendererDetails.series.animationDuration > 0) == true)) { + performLinearAnimation(stateProperties, + seriesRendererDetails.xAxisDetails!.axis, canvas, animationFactor); } - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (point.isVisible) { - oldChartPoint = _getOldChartPoint( - chartState, - seriesRenderer, + oldChartPoint = getOldChartPoint( + stateProperties, + seriesRendererDetails, StepAreaSegment, seriesIndex, pointIndex, oldSeriesRenderer, oldSeriesRenderers); oldPoint = oldChartPoint != null - ? _calculatePoint( + ? calculatePoint( oldChartPoint.xValue, oldChartPoint.yValue, - oldSeriesRenderer!._xAxisRenderer!, - oldSeriesRenderer._yAxisRenderer!, + oldSeriesRendererDetails.xAxisDetails!, + oldSeriesRendererDetails.yAxisDetails!, chart.isTransposed, - oldSeriesRenderer._series, + oldSeriesRendererDetails.series, axisClipRect) : null; - currentPoint = _calculatePoint( + currentPoint = calculatePoint( point.xValue, point.yValue, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState!._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, series, axisClipRect); previousPoint = prevPoint != null - ? _calculatePoint( + ? calculatePoint( prevPoint.xValue, prevPoint.yValue, - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState!._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, series, axisClipRect) : null; - originPoint = _calculatePoint( + originPoint = calculatePoint( point.xValue, - math_lib.max(yAxisRenderer._visibleRange!.minimum, origin), - xAxisRenderer, - yAxisRenderer, - seriesRenderer._chartState!._requireInvertedAxis, + math_lib.max(yAxisDetails.visibleRange!.minimum, origin), + xAxisDetails, + yAxisDetails, + seriesRendererDetails.stateProperties.requireInvertedAxis, series, axisClipRect); _points.add(Offset(currentPoint.x, currentPoint.y)); @@ -141,7 +278,8 @@ class _StepAreaChartPainter extends CustomPainter { prevOldPoint, pointIndex, animationFactor, - _series); + _series, + seriesRendererDetails); prevPoint = point; prevOldPoint = oldPoint; } @@ -153,92 +291,94 @@ class _StepAreaChartPainter extends CustomPainter { seriesRenderer._createSegments(_path, _strokePath, painterKey.index, animationFactor, _points)); } - _drawSeries(canvas, animationFactor); + _drawSeries(canvas, animationFactor, seriesRendererDetails); } } ///Draw series elements and add cliprect - void _drawSeries(Canvas canvas, double animationFactor) { + void _drawSeries(Canvas canvas, double animationFactor, + SeriesRendererDetails seriesRendererDetails) { final StepAreaSeries series = - seriesRenderer._series as StepAreaSeries; - final Rect clipRect = _calculatePlotOffset( + seriesRendererDetails.series as StepAreaSeries; + final Rect clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - !chartState._renderingDetails.initialRender! || - animationFactor >= chartState._seriesDurationFactor) && + !stateProperties.renderingDetails.initialRender! || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The step area series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } @override - bool shouldRepaint(_StepAreaChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(StepAreaChartPainter oldDelegate) => isRepaint; /// To draw the step area path void _drawStepAreaPath( Path _path, Path _strokePath, CartesianChartPoint? prevPoint, - _ChartLocation currentPoint, - _ChartLocation originPoint, - _ChartLocation? previousPoint, - _ChartLocation? oldPoint, - _ChartLocation? prevOldPoint, + ChartLocation currentPoint, + ChartLocation originPoint, + ChartLocation? previousPoint, + ChartLocation? oldPoint, + ChartLocation? prevOldPoint, int pointIndex, double animationFactor, - StepAreaSeries stepAreaSeries) { + StepAreaSeries stepAreaSeries, + SeriesRendererDetails seriesRendererDetails) { double x = currentPoint.x; double y = currentPoint.y; double? previousPointY = previousPoint?.y; final bool closed = stepAreaSeries.emptyPointSettings.mode == EmptyPointMode.drop && - _getSeriesVisibility(seriesRenderer._dataPoints, pointIndex); + _getSeriesVisibility(seriesRendererDetails.dataPoints, pointIndex); if (oldPoint != null) { - if (chartState._chart.isTransposed) { - x = _getAnimateValue( - animationFactor, x, oldPoint.x, currentPoint.x, seriesRenderer); + if (stateProperties.chart.isTransposed) { + x = getAnimateValue(animationFactor, x, oldPoint.x, currentPoint.x, + seriesRendererDetails); } else { - y = _getAnimateValue( - animationFactor, y, oldPoint.y, currentPoint.y, seriesRenderer); + y = getAnimateValue(animationFactor, y, oldPoint.y, currentPoint.y, + seriesRendererDetails); previousPointY = previousPointY != null - ? _getAnimateValue(animationFactor, previousPointY, prevOldPoint?.y, - previousPoint?.y, seriesRenderer) + ? getAnimateValue(animationFactor, previousPointY, prevOldPoint?.y, + previousPoint?.y, seriesRendererDetails) : previousPointY; } } if (prevPoint == null || - seriesRenderer._dataPoints[pointIndex - 1].isGap == true || - (seriesRenderer._dataPoints[pointIndex].isGap == true) || - (seriesRenderer._dataPoints[pointIndex - 1].isVisible == false && + seriesRendererDetails.dataPoints[pointIndex - 1].isGap == true || + (seriesRendererDetails.dataPoints[pointIndex].isGap == true) || + (seriesRendererDetails.dataPoints[pointIndex - 1].isVisible == false && stepAreaSeries.emptyPointSettings.mode == EmptyPointMode.gap)) { _path.moveTo(originPoint.x, originPoint.y); if (stepAreaSeries.borderDrawMode == BorderDrawMode.excludeBottom) { - if (seriesRenderer._dataPoints[pointIndex].isGap != true) { + if (seriesRendererDetails.dataPoints[pointIndex].isGap != true) { _strokePath.moveTo(originPoint.x, originPoint.y); _strokePath.lineTo(x, y); } } else if (stepAreaSeries.borderDrawMode == BorderDrawMode.all) { - if (seriesRenderer._dataPoints[pointIndex].isGap != true) { + if (seriesRendererDetails.dataPoints[pointIndex].isGap != true) { _strokePath.moveTo(originPoint.x, originPoint.y); _strokePath.lineTo(x, y); } @@ -246,8 +386,8 @@ class _StepAreaChartPainter extends CustomPainter { _strokePath.moveTo(x, y); } _path.lineTo(x, y); - } else if (pointIndex == seriesRenderer._dataPoints.length - 1 || - seriesRenderer._dataPoints[pointIndex + 1].isGap == true) { + } else if (pointIndex == seriesRendererDetails.dataPoints.length - 1 || + seriesRendererDetails.dataPoints[pointIndex + 1].isGap == true) { _strokePath.lineTo(x, previousPointY!); _strokePath.lineTo(x, y); if (stepAreaSeries.borderDrawMode == BorderDrawMode.excludeBottom) { diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart index 5ebba2847..da78bcc78 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/stepline_painter.dart @@ -1,56 +1,209 @@ -part of charts; +import 'dart:ui'; -class _StepLineChartPainter extends CustomPainter { - _StepLineChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/stepline_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/stepline_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for Step line series +class StepLineSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of StepLineSeriesRenderer class. + StepLineSeriesRenderer(); + + /// StepLine segment is created here + ChartSegment _createSegments( + CartesianChartPoint currentPoint, + num midX, + num midY, + CartesianChartPoint _nextPoint, + int pointIndex, + int seriesIndex, + double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final StepLineSegment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + final List _oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + seriesRendererDetails.isRectSeries = false; + segment.currentSegmentIndex = pointIndex; + segmentProperties.seriesIndex = seriesIndex; + segmentProperties.seriesRenderer = this; + segmentProperties.series = + seriesRendererDetails.series as XyDataSeries; + segmentProperties.currentPoint = currentPoint; + segmentProperties.midX = midX; + segmentProperties.midY = midY; + segmentProperties.nextPoint = _nextPoint; + segment.animationFactor = animateFactor; + segmentProperties.pointColorMapper = currentPoint.pointColorMapper; + if (seriesRendererDetails + .stateProperties.renderingDetails.widgetNeedUpdate == + true && + // ignore: unnecessary_null_comparison + _oldSeriesRenderers != null && + _oldSeriesRenderers.isNotEmpty && + _oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + _oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .seriesName) { + segmentProperties.oldSeriesRenderer = + _oldSeriesRenderers[segmentProperties.seriesIndex]; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + } + segment.calculateSegmentPoints(); + segment.points.add(Offset(segmentProperties.x1, segmentProperties.y1)); + segment.points.add(Offset(segmentProperties.x2, segmentProperties.y2)); + customizeSegment(segment); + segment.strokePaint = segment.getStrokePaint(); + segment.fillPaint = segment.getFillPaint(); + seriesRendererDetails.segments.add(segment); + return segment; + } + + /// To render step line series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + StepLineSegment createSegment() => StepLineSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = seriesRendererDetails.seriesColor; + segmentProperties.strokeColor = seriesRendererDetails.seriesColor; + segmentProperties.strokeWidth = segmentProperties.series.width; + } + + ///Draws marker with different shape and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the StepLine Chart painter +class StepLineChartPainter extends CustomPainter { + /// Calling the default constructor of StepLineChartPainter class. + StepLineChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final Animation animationController; + + /// Specifies the step line series renderer final StepLineSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for step line series @override void paint(Canvas canvas, Size size) { double animationFactor; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final StepLineSeries series = - seriesRenderer._series as StepLineSeries; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + seriesRendererDetails.series as StepLineSeries; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; - if (seriesRenderer._visible!) { + seriesRendererDetails.dataPoints; + if (seriesRendererDetails.visible! == true) { canvas.save(); assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, 'The animation duration of the fast line series must be greater or equal to 0.'); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || ((!(renderingDetails.widgetNeedUpdate || renderingDetails.isLegendToggled) || - !chartState._oldSeriesKeys.contains(series.key)) && + !stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation( - chartState, xAxisRenderer._axis, canvas, animationFactor); + performLinearAnimation( + stateProperties, xAxisDetails.axis, canvas, animationFactor); } int segmentIndex = -1; CartesianChartPoint? startPoint, @@ -59,9 +212,10 @@ class _StepLineChartPainter extends CustomPainter { _nextPoint; num? midX, midY; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { @@ -85,9 +239,9 @@ class _StepLineChartPainter extends CustomPainter { midY = currentPoint.yValue; } } - seriesRenderer._calculateRegionData( - chartState, - seriesRenderer, + seriesRendererDetails.calculateRegionData( + stateProperties, + seriesRendererDetails, seriesIndex, currentPoint, pointIndex, @@ -99,50 +253,51 @@ class _StepLineChartPainter extends CustomPainter { endPoint != null && midX != null && midY != null) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments(startPoint, midX, midY, endPoint, segmentIndex += 1, seriesIndex, animationFactor)); endPoint = startPoint = midX = midY = null; } } - _drawSeries(canvas, animationFactor); + _drawSeries(canvas, animationFactor, seriesRendererDetails); } } ///Draw series elements and add cliprect - void _drawSeries(Canvas canvas, double animationFactor) { + void _drawSeries(Canvas canvas, double animationFactor, + SeriesRendererDetails seriesRendererDetails) { final StepLineSeries series = - seriesRenderer._series as StepLineSeries; - final Rect clipRect = _calculatePlotOffset( + seriesRendererDetails.series as StepLineSeries; + final Rect clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + (!stateProperties.renderingDetails.initialRender! && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The step line series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } - if (seriesRenderer._visible! && animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + if (seriesRendererDetails.visible! == true && animationFactor >= 1) { + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } @@ -160,5 +315,5 @@ class _StepLineChartPainter extends CustomPainter { } @override - bool shouldRepaint(_StepLineChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(StepLineChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart index 2040dfe7f..36ec2781c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/series_painter/waterfall_painter.dart @@ -1,37 +1,233 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; -class _WaterfallChartPainter extends CustomPainter { - _WaterfallChartPainter( - {required this.chartState, +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; + +import '../../../charts.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/waterfall_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/waterfall_series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/segment_properties.dart'; +import '../utils/helper.dart'; + +/// Creates series renderer for waterfall series +class WaterfallSeriesRenderer extends XyDataSeriesRenderer { + /// Calling the default constructor of WaterfallSeriesRenderer class. + WaterfallSeriesRenderer(); + + late WaterfallSeries _waterfallSeries; + + /// To add waterfall segments in segments list + ChartSegment _createSegments(CartesianChartPoint currentPoint, + int pointIndex, int seriesIndex, double animateFactor) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + final WaterfallSegment segment = createSegment(); + final SegmentProperties segmentProperties = + SegmentProperties(seriesRendererDetails.stateProperties, segment); + SegmentHelper.setSegmentProperties(segment, segmentProperties); + + final List? oldSeriesRenderers = + seriesRendererDetails.stateProperties.oldSeriesRenderers; + _waterfallSeries = + seriesRendererDetails.series as WaterfallSeries; + final BorderRadius borderRadius = _waterfallSeries.borderRadius; + segmentProperties.seriesIndex = seriesIndex; + segment.currentSegmentIndex = pointIndex; + segment.points + .add(Offset(currentPoint.markerPoint!.x, currentPoint.markerPoint!.y)); + segmentProperties.seriesRenderer = this; + segmentProperties.series = _waterfallSeries; + segment.animationFactor = animateFactor; + segmentProperties.currentPoint = currentPoint; + if (seriesRendererDetails.stateProperties.renderingDetails.widgetNeedUpdate == true && + ZoomPanBehaviorHelper.getRenderingDetails(seriesRendererDetails + .stateProperties.zoomPanBehaviorRenderer) + .isPinching != + true && + seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + false && + oldSeriesRenderers != null && + oldSeriesRenderers.isNotEmpty && + oldSeriesRenderers.length - 1 >= segmentProperties.seriesIndex && + SeriesHelper.getSeriesRendererDetails( + oldSeriesRenderers[segmentProperties.seriesIndex]) + .seriesName == + SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .seriesName) { + segmentProperties.oldSeriesRenderer = + oldSeriesRenderers[segmentProperties.seriesIndex]; + final SeriesRendererDetails oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + segmentProperties + .oldPoint = (oldSeriesRendererDetails.segments.isNotEmpty == true && + oldSeriesRendererDetails.segments[0] is WaterfallSegment && + (oldSeriesRendererDetails.dataPoints.length - 1 >= pointIndex) == + true) + ? oldSeriesRendererDetails.dataPoints[pointIndex] + : null; + segmentProperties.oldSegmentIndex = getOldSegmentIndex(segment); + } else if (seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled == + true && + // ignore: unnecessary_null_comparison + seriesRendererDetails.stateProperties.segments != null && + seriesRendererDetails.stateProperties.segments.isNotEmpty == true) { + segmentProperties.oldSeriesVisible = seriesRendererDetails + .stateProperties.oldSeriesVisible[segmentProperties.seriesIndex]; + WaterfallSegment oldSegment; + for (int i = 0; + i < seriesRendererDetails.stateProperties.segments.length; + i++) { + oldSegment = seriesRendererDetails.stateProperties.segments[i] + as WaterfallSegment; + if (oldSegment.currentSegmentIndex == segment.currentSegmentIndex && + SegmentHelper.getSegmentProperties(oldSegment).seriesIndex == + segmentProperties.seriesIndex) { + segmentProperties.oldRegion = oldSegment.segmentRect.outerRect; + } + } + } + segmentProperties.path = findingRectSeriesDashedBorder( + currentPoint, _waterfallSeries.borderWidth); + // ignore: unnecessary_null_comparison + if (borderRadius != null) { + segment.segmentRect = + getRRectFromRect(currentPoint.region!, borderRadius); + } + segmentProperties.segmentRect = segment.segmentRect; + customizeSegment(segment); + seriesRendererDetails.segments.add(segment); + return segment; + } + + /// To render waterfall series segments + //ignore: unused_element + void _drawSegment(Canvas canvas, ChartSegment segment) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(this); + if (seriesRendererDetails.isSelectionEnable == true) { + final SelectionBehaviorRenderer? selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[segment.currentSegmentIndex!], + seriesRendererDetails.chart); + } + segment.onPaint(canvas); + } + + /// Creates a segment for a data point in the series. + @override + WaterfallSegment createSegment() => WaterfallSegment(); + + /// Changes the series color, border color, and border width. + @override + void customizeSegment(ChartSegment segment) { + final WaterfallSegment waterfallSegment = segment as WaterfallSegment; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + segmentProperties.color = + SeriesHelper.getSeriesRendererDetails(segmentProperties.seriesRenderer) + .seriesColor; + segmentProperties.negativePointsColor = + _waterfallSeries.negativePointsColor; + segmentProperties.intermediateSumColor = + _waterfallSeries.intermediateSumColor; + segmentProperties.totalSumColor = _waterfallSeries.totalSumColor; + segmentProperties.strokeColor = segmentProperties.series.borderColor; + segmentProperties.strokeWidth = segmentProperties.series.borderWidth; + waterfallSegment.strokePaint = waterfallSegment.getStrokePaint(); + waterfallSegment.fillPaint = waterfallSegment.getFillPaint(); + waterfallSegment.connectorLineStrokePaint = + segmentProperties.getConnectorLineStrokePaint(); + } + + ///Draws the marker with different shapes and color of the appropriate data point in the series. + @override + void drawDataMarker(int index, Canvas canvas, Paint fillPaint, + Paint strokePaint, double pointX, double pointY, + [CartesianSeriesRenderer? seriesRenderer]) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer!); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, fillPaint); + canvas.drawPath(seriesRendererDetails.markerShapes[index]!, strokePaint); + } + + /// Draws data label text of the appropriate data point in a series. + @override + void drawDataLabel(int index, Canvas canvas, String dataLabel, double pointX, + double pointY, int angle, TextStyle style) => + drawText(canvas, dataLabel, Offset(pointX, pointY), style, angle); +} + +/// Represents the waterfall Chart painter +class WaterfallChartPainter extends CustomPainter { + /// Calling the default constructor of WaterfallChartPainter class. + WaterfallChartPainter( + {required this.stateProperties, required this.seriesRenderer, required this.isRepaint, required this.animationController, required ValueNotifier notifier, required this.painterKey}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Represents the Cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the Cartesian chart. final SfCartesianChart chart; + + /// Specifies whether to repaint the series. final bool isRepaint; + + /// Specifies the value of animation controller. final AnimationController animationController; - List<_ChartLocation> currentChartLocations = <_ChartLocation>[]; + + /// Specifies the list of current chart location + List currentChartLocations = []; + + /// Specifies the Waterfall series renderer WaterfallSeriesRenderer seriesRenderer; - final _PainterKey painterKey; + + /// Specifies the painter key value + final PainterKey painterKey; /// Painter method for waterfall series @override void paint(Canvas canvas, Size size) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final ChartAxisRenderer yAxisRenderer = seriesRenderer._yAxisRenderer!; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final List> dataPoints = - seriesRenderer._dataPoints; + seriesRendererDetails.dataPoints; final WaterfallSeries series = - seriesRenderer._series as WaterfallSeries; - final num origin = math.max(yAxisRenderer._visibleRange!.minimum, 0); + seriesRendererDetails.series as WaterfallSeries; + final num origin = math.max(yAxisDetails.visibleRange!.minimum, 0); num currentEndValue = 0, intermediateOrigin = 0, prevEndValue = 0; num originValue = 0; - if (seriesRenderer._visible!) { + if (seriesRendererDetails.visible! == true) { assert( // ignore: unnecessary_null_comparison !(series.animationDuration != null) || series.animationDuration >= 0, @@ -41,19 +237,23 @@ class _WaterfallChartPainter extends CustomPainter { CartesianChartPoint? point; canvas.save(); final int seriesIndex = painterKey.index; - seriesRenderer._storeSeriesProperties(chartState, seriesIndex); - axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); + seriesRendererDetails.storeSeriesProperties(stateProperties, seriesIndex); + axisClipRect = calculatePlotOffset(stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); canvas.clipRect(axisClipRect); - animationFactor = seriesRenderer._seriesAnimation != null - ? seriesRenderer._seriesAnimation!.value + animationFactor = seriesRendererDetails.seriesAnimation != null + ? seriesRendererDetails.seriesAnimation!.value : 1; + stateProperties.shader = null; + if (series.onCreateShader != null) { + stateProperties.shader = series.onCreateShader!( + ShaderDetails(stateProperties.chartAxis.axisClipRect, 'series')); + } int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.isNotEmpty == true) { + seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; @@ -71,14 +271,14 @@ class _WaterfallChartPainter extends CustomPainter { : point.yValue; point.endValue = currentEndValue; point.originValue = originValue; - seriesRenderer._calculateRegionData( - chartState, seriesRenderer, painterKey.index, point, pointIndex); + seriesRendererDetails.calculateRegionData(stateProperties, + seriesRendererDetails, painterKey.index, point, pointIndex); if (renderingDetails.templates.isNotEmpty) { renderingDetails.templates[pointIndex].location = Offset(point.markerPoint!.x, point.markerPoint!.y); } if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); @@ -88,45 +288,46 @@ class _WaterfallChartPainter extends CustomPainter { } prevEndValue = currentEndValue; } - _drawSeries(canvas, animationFactor); + _drawSeries(canvas, animationFactor, seriesRendererDetails); } } ///Draw series elements and add cliprect - void _drawSeries(Canvas canvas, double animationFactor) { + void _drawSeries(Canvas canvas, double animationFactor, + SeriesRendererDetails seriesRendererDetails) { final WaterfallSeries series = - seriesRenderer._series as WaterfallSeries; - final Rect clipRect = _calculatePlotOffset( + seriesRendererDetails.series as WaterfallSeries; + final Rect clipRect = calculatePlotOffset( Rect.fromLTRB( - chartState._chartAxis._axisClipRect.left - + stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - chartState._chartAxis._axisClipRect.top - + stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - chartState._chartAxis._axisClipRect.right + + stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - chartState._chartAxis._axisClipRect.bottom + + stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || - (!chartState._renderingDetails.initialRender! && - !seriesRenderer._needAnimateSeriesElements) || - animationFactor >= chartState._seriesDurationFactor) && + (!stateProperties.renderingDetails.initialRender! && + seriesRendererDetails.needAnimateSeriesElements == false) || + animationFactor >= stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { // ignore: unnecessary_null_comparison assert(seriesRenderer != null, 'The waterfall series should be available to render a marker on it.'); canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, seriesRenderer._seriesElementAnimation); + seriesRendererDetails.renderSeriesElements( + chart, canvas, seriesRendererDetails.seriesElementAnimation); } if (animationFactor >= 1) { - chartState._setPainterKey(painterKey.index, painterKey.name, true); + stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } @override - bool shouldRepaint(_WaterfallChartPainter oldDelegate) => isRepaint; + bool shouldRepaint(WaterfallChartPainter oldDelegate) => isRepaint; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart index b787c2512..dd1591351 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/accumulation_distribution_indicator.dart @@ -1,4 +1,11 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; /// This class holds the properties of the Accumulation Distribution Indicator. /// @@ -19,6 +26,7 @@ class AccumulationDistributionIndicator String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -42,6 +50,7 @@ class AccumulationDistributionIndicator seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -97,6 +106,7 @@ class AccumulationDistributionIndicator other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -120,6 +130,7 @@ class AccumulationDistributionIndicator seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -135,61 +146,4 @@ class AccumulationDistributionIndicator ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - TechnicalIndicators indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - TechnicalIndicators indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final List> validData = - technicalIndicatorsRenderer._dataPoints!; - if (validData.isNotEmpty && - indicator is AccumulationDistributionIndicator) { - _calculateADPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } - } - - /// To calculate the rendering points of the accumulation distribution indicator - void _calculateADPoints( - AccumulationDistributionIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - num sum = 0, value = 0, high = 0, low = 0, close = 0; - for (int i = 0; i < validData.length; i++) { - high = validData[i].high ?? 0; - low = validData[i].low ?? 0; - close = validData[i].close ?? 0; - value = ((close - low) - (high - close)) / (high - low); - sum = sum + value * validData[i].volume!; - point = technicalIndicatorsRenderer._getDataPoint( - validData[i].x, sum, validData[i], points.length); - points.add(point); - xValues.add(point.x); - } - technicalIndicatorsRenderer._renderPoints = points; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'AD', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer._setSeriesRange(points, indicator, xValues); - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart index 75a47258d..821c66816 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/atr_indicator.dart @@ -1,4 +1,9 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; /// This class holds the properties of the Average True Range Indicator. /// @@ -19,6 +24,7 @@ class AtrIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -39,6 +45,7 @@ class AtrIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -69,6 +76,7 @@ class AtrIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -92,6 +100,7 @@ class AtrIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -107,92 +116,4 @@ class AtrIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - TechnicalIndicators indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - TechnicalIndicators indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> validData = - technicalIndicatorsRenderer._dataPoints!; - if (validData.isNotEmpty && - validData.length > indicator.period && - indicator is AtrIndicator) { - _calculateATRPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } - } - - /// To calculate the rendering points of the ATR indicator - void _calculateATRPoints( - AtrIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - num average = 0; - num highLow = 0, highClose = 0, lowClose = 0, trueRange = 0, tempRange = 0; - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - final List<_TempData> temp = <_TempData>[]; - final num period = indicator.period; - num sum = 0; - for (int i = 0; i < validData.length; i++) { - if (!validData[i].isDrop && !validData[i].isGap) { - highLow = validData[i].high - validData[i].low; - if (i > 0) { - highClose = (validData[i].high - (validData[i - 1].close ?? 0)).abs(); - lowClose = (validData[i].low - (validData[i - 1].close ?? 0)).abs(); - } - tempRange = math.max(highLow, highClose); - trueRange = math.max(tempRange, lowClose); - sum = sum + trueRange; - if (i >= period && period > 0) { - average = - (temp[temp.length - 1].y * (period - 1) + trueRange) / period; - point = technicalIndicatorsRenderer._getDataPoint( - validData[i].x, average, validData[i], points.length); - points.add(point); - xValues.add(point.x); - } else { - average = sum / period; - if (i == period - 1) { - point = technicalIndicatorsRenderer._getDataPoint( - validData[i].x, average, validData[i], points.length); - points.add(point); - xValues.add(point.x); - } - } - temp.add(_TempData(validData[i].x, average)); - } - } - technicalIndicatorsRenderer._renderPoints = points; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'ATR', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer._setSeriesRange(points, indicator, xValues); - } -} - -class _TempData { - _TempData(this.x, this.y); - final dynamic x; - final num y; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart index 0ffcf77b6..8721626d5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/bollinger_bands_indicator.dart @@ -1,4 +1,11 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; /// This class has the property of BollingerBand Indicator. /// @@ -18,6 +25,7 @@ class BollingerBandIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? closeValueMapper, @@ -42,6 +50,7 @@ class BollingerBandIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, closeValueMapper: closeValueMapper, @@ -56,7 +65,7 @@ class BollingerBandIndicator extends TechnicalIndicators { /// Standard Deviation value of the bollinger bands /// - /// Defaluts to `2` + /// Defaults to `2` /// ///```dart ///Widget build(BuildContext context) { @@ -73,7 +82,7 @@ class BollingerBandIndicator extends TechnicalIndicators { /// UpperLine Color of the bollinger bands. /// - /// Defaluts to `Colors.red` + /// Defaults to `Colors.red` /// ///```dart ///Widget build(BuildContext context) { @@ -90,7 +99,7 @@ class BollingerBandIndicator extends TechnicalIndicators { /// UpperLine width value of the bollinger bands. /// - /// Defaluts to `2` + /// Defaults to `2` /// ///```dart ///Widget build(BuildContext context) { @@ -107,7 +116,7 @@ class BollingerBandIndicator extends TechnicalIndicators { /// LowerLine Color value of the bollinger bands /// - /// Defaluts to `Colors.green` + /// Defaults to `Colors.green` /// ///```dart ///Widget build(BuildContext context) { @@ -124,7 +133,7 @@ class BollingerBandIndicator extends TechnicalIndicators { /// LowerLine Width value of the bollinger bands /// - /// Defaluts to `2` + /// Defaults to `2` /// ///```dart ///Widget build(BuildContext context) { @@ -141,7 +150,7 @@ class BollingerBandIndicator extends TechnicalIndicators { /// Band Color of the Bollinger Band /// - /// Defaluts to `Colors.grey.withOpacity(0.25)` + /// Defaults to `Colors.grey.withOpacity(0.25)` /// ///```dart ///Widget build(BuildContext context) { @@ -172,6 +181,7 @@ class BollingerBandIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.closeValueMapper == closeValueMapper && @@ -199,6 +209,7 @@ class BollingerBandIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, closeValueMapper, @@ -218,175 +229,4 @@ class BollingerBandIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - BollingerBandIndicator indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - BollingerBandIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final bool enableBand = indicator.bandColor != Colors.transparent && - // ignore: unnecessary_null_comparison - indicator.bandColor != null; - final int start = enableBand ? 1 : 0; - final List> signalCollection = - >[], - upperCollection = >[], - lowerCollection = >[], - bandCollection = >[]; - final List xValues = []; - - //prepare data - final List> validData = - technicalIndicatorsRenderer._dataPoints!; - if (validData.isNotEmpty && - validData.length >= indicator.period && - indicator.period > 0) { - num sum = 0, deviationSum = 0; - final num multiplier = indicator.standardDeviation; - final int limit = validData.length, length = indicator.period.round(); - // This has been null before - final List smaPoints = List.filled(limit, -1), - deviations = List.filled(limit, -1); - final List<_BollingerData> bollingerPoints = List<_BollingerData>.filled( - limit, - _BollingerData( - x: -1, midBand: -1, lowBand: -1, upBand: -1, visible: false)); - - for (int i = 0; i < length; i++) { - sum += validData[i].close ?? 0; - } - num sma = sum / indicator.period; - for (int i = 0; i < limit; i++) { - final num y = validData[i].close ?? 0; - if (i >= length - 1 && i < limit) { - if (i - indicator.period >= 0) { - final num diff = y - validData[i - length].close; - sum = sum + diff; - sma = sum / (indicator.period); - smaPoints[i] = sma; - deviations[i] = math.pow(y - sma, 2); - deviationSum += deviations[i] - deviations[i - length]; - } else { - smaPoints[i] = sma; - deviations[i] = math.pow(y - sma, 2); - deviationSum += deviations[i]; - } - final num range = math.sqrt(deviationSum / (indicator.period)); - final num lowerBand = smaPoints[i] - (multiplier * range); - final num upperBand = smaPoints[i] + (multiplier * range); - if (i + 1 == length) { - for (int j = 0; j < length - 1; j++) { - bollingerPoints[j] = _BollingerData( - x: validData[j].xValue, - midBand: smaPoints[i], - lowBand: lowerBand, - upBand: upperBand, - visible: true); - } - } - bollingerPoints[i] = _BollingerData( - x: validData[i].xValue, - midBand: smaPoints[i], - lowBand: lowerBand, - upBand: upperBand, - visible: true); - } else { - if (i < indicator.period - 1) { - smaPoints[i] = sma; - deviations[i] = math.pow(y - sma, 2); - deviationSum += deviations[i]; - } - } - } - int i = -1, j = -1; - for (int k = 0; k < limit; k++) { - if (k >= (length - 1)) { - xValues.add(validData[k].x); - upperCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[k].x, - bollingerPoints[k].upBand, - validData[k], - upperCollection.length)); - lowerCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[k].x, - bollingerPoints[k].lowBand, - validData[k], - lowerCollection.length)); - signalCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[k].x, - bollingerPoints[k].midBand, - validData[k], - signalCollection.length)); - if (enableBand) { - bandCollection.add(technicalIndicatorsRenderer._getRangePoint( - validData[k].x, - upperCollection[++i].y, - lowerCollection[++j].y, - validData[k], - bandCollection.length)); - } - } - } - } - technicalIndicatorsRenderer._renderPoints = signalCollection; - technicalIndicatorsRenderer._bollingerUpper = upperCollection; - technicalIndicatorsRenderer._bollingerLower = lowerCollection; - // Decides the type of renderer class to be used - bool isLine, isRangeArea; - if (indicator.bandColor != Colors.transparent && - // ignore: unnecessary_null_comparison - indicator.bandColor != null) { - isLine = false; - isRangeArea = true; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'rangearea', - indicator.bandColor, 0, chart, isLine, isRangeArea); - } - isLine = true; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'BollingerBand', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); - if (enableBand) { - technicalIndicatorsRenderer._setSeriesRange(bandCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[0]); - } - technicalIndicatorsRenderer._setSeriesRange(signalCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[start]); - technicalIndicatorsRenderer._setSeriesRange(upperCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[start + 1]); - technicalIndicatorsRenderer._setSeriesRange(lowerCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[start + 2]); - } -} - -class _BollingerData { - _BollingerData( - {this.x, - required this.midBand, - required this.lowBand, - required this.upBand, - required this.visible}); - num? x; - num midBand; - num lowBand; - num upBand; - bool visible; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart index fb786ce93..c7c07f113 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/ema_indicator.dart @@ -1,4 +1,9 @@ -part of charts; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; ///Renders EMA indicator /// @@ -16,6 +21,7 @@ class EmaIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -39,6 +45,7 @@ class EmaIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -89,6 +96,7 @@ class EmaIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -114,6 +122,7 @@ class EmaIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -131,83 +140,4 @@ class EmaIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - TechnicalIndicators indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - TechnicalIndicators indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final List> validData = - technicalIndicatorsRenderer._dataPoints!; - if (validData.isNotEmpty && - validData.length > indicator.period && - indicator is EmaIndicator && - indicator.period > 0) { - _calculateEMAPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } - } - - /// To calculate the rendering points of the EMA indicator - void _calculateEMAPoints( - EmaIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final int period = indicator.period; - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - if (validData.length >= period && period > 0) { - num sum = 0, average = 0; - final num k = 2 / (period + 1); - for (int i = 0; i < period; i++) { - sum += technicalIndicatorsRenderer._getFieldValue( - validData, i, valueField); - } - average = sum / period; - point = technicalIndicatorsRenderer._getDataPoint(validData[period - 1].x, - average, validData[period - 1], points.length); - points.add(point); - xValues.add(point.x); - int index = period; - while (index < validData.length) { - if (validData[index].isVisible || - validData[index].isGap == true || - validData[index].isDrop == true) { - final num prevAverage = points[index - period].y; - final num yValue = (technicalIndicatorsRenderer._getFieldValue( - validData, index, valueField) - - prevAverage) * - k + - prevAverage; - point = technicalIndicatorsRenderer._getDataPoint( - validData[index].x, yValue, validData[index], points.length); - points.add(point); - xValues.add(point.x); - } - index++; - } - } - technicalIndicatorsRenderer._renderPoints = points; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'EMA', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer._setSeriesRange(points, indicator, xValues); - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart index ca239ecbf..b4fda5f5f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/macd_indicator.dart @@ -1,4 +1,12 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../utils/enum.dart'; +import 'technical_indicator.dart'; /// This class Holds the properties of the Macd Indicator. /// @@ -20,6 +28,7 @@ class MacdIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? closeValueMapper, @@ -45,6 +54,7 @@ class MacdIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, closeValueMapper: closeValueMapper, @@ -192,6 +202,7 @@ class MacdIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.closeValueMapper == closeValueMapper && @@ -220,6 +231,7 @@ class MacdIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, closeValueMapper, @@ -240,235 +252,4 @@ class MacdIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - MacdIndicator indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - MacdIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - List> signalCollection = - >[]; - final num fastPeriod = indicator.longPeriod; - final num slowPeriod = indicator.shortPeriod; - final num trigger = indicator.period; - final num length = fastPeriod + trigger; - List> macdCollection = - >[], - histogramCollection = >[]; - final List> validData = - technicalIndicatorsRenderer._dataPoints!; - List signalX = [], - histogramX = [], - macdX = [], - collection; - CartesianSeriesRenderer? histogramSeriesRenderer, macdLineSeriesRenderer; - - if (validData.isNotEmpty && - length < validData.length && - slowPeriod <= fastPeriod && - slowPeriod > 0 && - indicator.period > 0 && - (length - 2) >= 0) { - final List shortEMA = _calculateEMAValues( - slowPeriod, validData, 'close', technicalIndicatorsRenderer); - final List longEMA = _calculateEMAValues( - fastPeriod, validData, 'close', technicalIndicatorsRenderer); - final List macdValues = _getMACDVales(indicator, shortEMA, longEMA); - collection = _getMACDPoints( - indicator, macdValues, validData, technicalIndicatorsRenderer); - macdCollection = collection[0]; - macdX = collection[1]; - final List signalEMA = _calculateEMAValues( - trigger, macdCollection, 'y', technicalIndicatorsRenderer); - collection = _getSignalPoints( - indicator, signalEMA, validData, technicalIndicatorsRenderer); - signalCollection = collection[0]; - signalX = collection[1]; - if (indicator.macdType == MacdType.histogram || - indicator.macdType == MacdType.both) { - collection = _getHistogramPoints(indicator, macdValues, signalEMA, - validData, technicalIndicatorsRenderer); - histogramCollection = collection[0]; - histogramX = collection[1]; - } - } - technicalIndicatorsRenderer._renderPoints = signalCollection; - technicalIndicatorsRenderer._macdHistogram = histogramCollection; - technicalIndicatorsRenderer._macdLine = macdCollection; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'MACD', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - // To describe the type of series renderer to be assigned - bool isLine, isRangeArea, isHistogram; - if (indicator.macdType == MacdType.line || - indicator.macdType == MacdType.both) { - // Decides the type of renderer class to be used - isLine = true; - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'MacdLine', - indicator.macdLineColor, indicator.macdLineWidth, chart, isLine); - } - if (indicator.macdType == MacdType.histogram || - indicator.macdType == MacdType.both) { - isLine = false; - isRangeArea = false; - isHistogram = true; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - 'Histogram', - indicator.histogramPositiveColor, - indicator.signalLineWidth / 2, - chart, - isLine, - isRangeArea, - isHistogram); - } - if (indicator.macdType == MacdType.histogram) { - histogramSeriesRenderer = - technicalIndicatorsRenderer._targetSeriesRenderers[1]; - } else { - macdLineSeriesRenderer = - technicalIndicatorsRenderer._targetSeriesRenderers[1]; - if (indicator.macdType == MacdType.both) { - histogramSeriesRenderer = - technicalIndicatorsRenderer._targetSeriesRenderers[2]; - } - } - technicalIndicatorsRenderer._setSeriesRange(signalCollection, indicator, - signalX, technicalIndicatorsRenderer._targetSeriesRenderers[0]); - if (histogramSeriesRenderer != null) { - technicalIndicatorsRenderer._setSeriesRange( - histogramCollection, indicator, histogramX, histogramSeriesRenderer); - } - if (macdLineSeriesRenderer != null) { - technicalIndicatorsRenderer._setSeriesRange( - macdCollection, indicator, macdX, macdLineSeriesRenderer); - } - } - - /// Calculates the EMA values for the given period - List _calculateEMAValues( - num period, - List> validData, - String valueField, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - num sum = 0, initialEMA = 0; - final List emaValues = []; - final num emaPercent = 2 / (period + 1); - for (int i = 0; i < period; i++) { - sum += - technicalIndicatorsRenderer._getFieldValue(validData, i, valueField); - } - initialEMA = sum / period; - emaValues.add(initialEMA); - num emaAvg = initialEMA; - for (int j = period.toInt(); j < validData.length; j++) { - emaAvg = (technicalIndicatorsRenderer._getFieldValue( - validData, j, valueField) - - emaAvg) * - emaPercent + - emaAvg; - emaValues.add(emaAvg); - } - return emaValues; - } - - ///Defines the MACD Points - List _getMACDPoints( - MacdIndicator indicator, - List macdPoints, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - final List> macdCollection = - >[]; - final List xValues = []; - int dataMACDIndex = indicator.longPeriod - 1, macdIndex = 0; - while (dataMACDIndex < validData.length) { - macdCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[dataMACDIndex].x, - macdPoints[macdIndex], - validData[dataMACDIndex], - macdCollection.length)); - xValues.add(validData[dataMACDIndex].x); - dataMACDIndex++; - macdIndex++; - } - return [macdCollection, xValues]; - } - - ///Calculates the signal points - List _getSignalPoints( - MacdIndicator indicator, - List signalEma, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - int dataSignalIndex = indicator.longPeriod + indicator.period - 2; - int signalIndex = 0; - final List xValues = []; - final List> signalCollection = - >[]; - while (dataSignalIndex < validData.length) { - signalCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[dataSignalIndex].x, - signalEma[signalIndex], - validData[dataSignalIndex], - signalCollection.length)); - xValues.add(validData[dataSignalIndex].x); - dataSignalIndex++; - signalIndex++; - } - return [signalCollection, xValues]; - } - - ///Calculates the MACD values - List _getMACDVales(MacdIndicator indicator, - List shortEma, List longEma) { - final List macdPoints = []; - final int diff = indicator.longPeriod - indicator.shortPeriod; - for (int i = 0; i < longEma.length; i++) { - macdPoints.add(shortEma[i + diff] - longEma[i]); - } - return macdPoints; - } - - ///Calculates the Histogram Points - List _getHistogramPoints( - MacdIndicator indicator, - List macdPoints, - List signalEma, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - int dataHistogramIndex = indicator.longPeriod + indicator.period - 2; - int histogramIndex = 0; - final List> histogramCollection = - >[]; - final List xValues = []; - while (dataHistogramIndex < validData.length) { - histogramCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[dataHistogramIndex].x, - macdPoints[histogramIndex + (indicator.period - 1)] - - signalEma[histogramIndex], - validData[dataHistogramIndex], - histogramCollection.length, - indicator)); - xValues.add(validData[dataHistogramIndex].x); - dataHistogramIndex++; - histogramIndex++; - } - return [histogramCollection, xValues]; - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart index c22375203..43f4665f8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/momentum_indicator.dart @@ -1,11 +1,18 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; /// Renders the momentum indicator. /// -/// This class renders the momentum indicator, it also has a centerline. The [centerLineColor] and [centerLineWidth] -/// property is used to define centerline. +/// This class renders the momentum indicator, it also has a center line. The [centerLineColor] and [centerLineWidth] +/// property is used to define center line. /// -/// Provides the options for visibility, centerline color, centerline width, and period values to customize the appearance. +/// Provides the options for visibility, center line color, center line width, and period values to customize the appearance. /// @immutable class MomentumIndicator extends TechnicalIndicators { @@ -17,6 +24,7 @@ class MomentumIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -40,6 +48,7 @@ class MomentumIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -105,6 +114,7 @@ class MomentumIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -131,6 +141,7 @@ class MomentumIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -149,68 +160,4 @@ class MomentumIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - MomentumIndicator indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - MomentumIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> signalCollection = - >[], - centerLineCollection = >[], - validData = technicalIndicatorsRenderer._dataPoints!; - final List centerXValues = [], xValues = []; - num value; - - if (validData.isNotEmpty) { - final int length = indicator.period; - if (validData.length >= indicator.period && indicator.period > 0) { - for (int i = 0; i < validData.length; i++) { - centerLineCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[i].x, 100, validData[i], centerLineCollection.length)); - centerXValues.add(validData[i].x); - if (!(i < length)) { - value = (validData[i].close ?? 0) / - (validData[i - length].close ?? 1) * - 100; - signalCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[i].x, value, validData[i], signalCollection.length)); - xValues.add(validData[i].x); - } - } - } - technicalIndicatorsRenderer._renderPoints = signalCollection; - technicalIndicatorsRenderer._momentumCenterLineValue = - centerLineCollection.first.y.toDouble(); - // Decides the type of renderer class to be used - const bool isLine = true; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'Momentum', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'CenterLine', - indicator.centerLineColor, indicator.centerLineWidth, chart, isLine); - technicalIndicatorsRenderer._setSeriesRange(signalCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[0]); - technicalIndicatorsRenderer._setSeriesRange( - centerLineCollection, - indicator, - centerXValues, - technicalIndicatorsRenderer._targetSeriesRenderers[1]); - } - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart index cdd03ca1e..cd3b143d8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/rsi_indicator.dart @@ -1,4 +1,11 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; ///Renders relative strength index (RSI) indicator. /// @@ -19,6 +26,7 @@ class RsiIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -46,6 +54,7 @@ class RsiIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -195,6 +204,7 @@ class RsiIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -225,6 +235,7 @@ class RsiIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -247,117 +258,4 @@ class RsiIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - RsiIndicator indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - RsiIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> signalCollection = - >[], - lowerCollection = >[], - upperCollection = >[], - validData = technicalIndicatorsRenderer._dataPoints!; - - final List xValues = [], signalXValues = []; - - if (validData.isNotEmpty && - validData.length >= indicator.period && - indicator.period > 0) { - if (indicator.showZones) { - for (int i = 0; i < validData.length; i++) { - upperCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[i].x, - indicator.overbought, - validData[i], - upperCollection.length)); - lowerCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[i].x, - indicator.oversold, - validData[i], - lowerCollection.length)); - xValues.add(validData[i].x); - } - } - num prevClose = validData[0].close ?? 0, gain = 0, loss = 0; - for (int i = 1; i <= indicator.period; i++) { - final num close = validData[i].close ?? 0.0; - if (close > prevClose) { - gain += close - prevClose; - } else { - loss += prevClose - close; - } - prevClose = close; - } - gain = gain / indicator.period; - loss = loss / indicator.period; - - signalCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[indicator.period].x, - 100 - (100 / (1 + (gain / loss))), - validData[indicator.period], - signalCollection.length)); - signalXValues.add(validData[indicator.period].x); - - for (int j = indicator.period + 1; j < validData.length; j++) { - if (!validData[j].isGap && !validData[j].isDrop) { - final num close = validData[j].close; - if (close > prevClose) { - gain = (gain * (indicator.period - 1) + (close - prevClose)) / - indicator.period; - loss = (loss * (indicator.period - 1)) / indicator.period; - } else if (close < prevClose) { - loss = (loss * (indicator.period - 1) + (prevClose - close)) / - indicator.period; - gain = (gain * (indicator.period - 1)) / indicator.period; - } - prevClose = close; - signalCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[j].x, - 100 - (100 / (1 + (gain / loss))), - validData[j], - signalCollection.length)); - signalXValues.add(validData[j].x); - } - } - } - technicalIndicatorsRenderer._renderPoints = signalCollection; - // Decides the type of renderer class to be used - const bool isLine = true; - // final CartesianSeriesRenderer signalSeriesRenderer = - // technicalIndicatorsRenderer._targetSeriesRenderers[0]; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'RSI', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - if (indicator.showZones == true) { - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); - } - - technicalIndicatorsRenderer._setSeriesRange(signalCollection, indicator, - signalXValues, technicalIndicatorsRenderer._targetSeriesRenderers[0]); - if (indicator.showZones) { - technicalIndicatorsRenderer._setSeriesRange(upperCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[1]); - technicalIndicatorsRenderer._setSeriesRange(lowerCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[2]); - } - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart index d04944a04..6325e7d59 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/sma_indicator.dart @@ -1,4 +1,11 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; ///Renders simple moving average (SMA) indicator. /// @@ -16,6 +23,7 @@ class SmaIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -39,6 +47,7 @@ class SmaIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -56,7 +65,7 @@ class SmaIndicator extends TechnicalIndicators { ///ValueField value for sma indicator. /// - ///Valuefield detemines the field for the rendering of sma indicator. + ///Value field determines the field for the rendering of sma indicator. /// ///Defaults to `close`. /// @@ -89,6 +98,7 @@ class SmaIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -114,6 +124,7 @@ class SmaIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -131,75 +142,4 @@ class SmaIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - TechnicalIndicators indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - SmaIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> smaPoints = - >[]; - final List> points = - technicalIndicatorsRenderer._dataPoints!; - final List xValues = []; - CartesianChartPoint point; - if (points.isNotEmpty) { - final List> validData = points; - - if (validData.length >= indicator.period && indicator.period > 0) { - num average = 0, sum = 0; - - for (int i = 0; i < indicator.period; i++) { - sum += technicalIndicatorsRenderer._getFieldValue( - validData, i, valueField); - } - - average = sum / indicator.period; - point = technicalIndicatorsRenderer._getDataPoint( - validData[indicator.period - 1].x, - average, - validData[indicator.period - 1], - smaPoints.length); - smaPoints.add(point); - xValues.add(point.x); - - int index = indicator.period; - while (index < validData.length) { - sum -= technicalIndicatorsRenderer._getFieldValue( - validData, index - indicator.period, valueField); - sum += technicalIndicatorsRenderer._getFieldValue( - validData, index, valueField); - average = sum / indicator.period; - point = technicalIndicatorsRenderer._getDataPoint( - validData[index].x, average, validData[index], smaPoints.length); - smaPoints.add(point); - xValues.add(point.x); - index++; - } - } - technicalIndicatorsRenderer._renderPoints = smaPoints; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'SMA', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - // final CartesianSeriesRenderer signalSeriesRenderer = - // technicalIndicatorsRenderer._targetSeriesRenderers[0]; - technicalIndicatorsRenderer._setSeriesRange( - smaPoints, indicator, xValues); - } - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart index 8a2e954e2..76f5da478 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/stochastic_indicator.dart @@ -1,4 +1,11 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; ///Renders stochastic indicator. /// @@ -17,6 +24,7 @@ class StochasticIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -49,6 +57,7 @@ class StochasticIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -267,6 +276,7 @@ class StochasticIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -302,6 +312,7 @@ class StochasticIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -329,187 +340,4 @@ class StochasticIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - StochasticIndicator indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators -// ignore:unused_element - void _initDataSource( - StochasticIndicator indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - List> signalCollection = - >[], - source = >[], - periodCollection = >[]; - final List> lowerCollection = - >[], - upperCollection = >[]; - final List> validData = - technicalIndicatorsRenderer._dataPoints!; - final List xValues = []; - late List collection, signalX, periodX; - if (validData.isNotEmpty && - validData.length >= indicator.period && - indicator.period > 0) { - if (indicator.showZones) { - for (int i = 0; i < validData.length; i++) { - upperCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[i].x, - indicator.overbought, - validData[i], - upperCollection.length)); - lowerCollection.add(technicalIndicatorsRenderer._getDataPoint( - validData[i].x, - indicator.oversold, - validData[i], - lowerCollection.length)); - xValues.add(validData[i].x); - } - } - source = _calculatePeriod(indicator.period, indicator.kPeriod.toInt(), - validData, technicalIndicatorsRenderer); - collection = _stochasticCalculation(indicator.period, - indicator.kPeriod.toInt(), source, technicalIndicatorsRenderer); - periodCollection = collection[0]; - periodX = collection[1]; - collection = _stochasticCalculation( - (indicator.period + indicator.kPeriod - 1).toInt(), - indicator.dPeriod.toInt(), - source, - technicalIndicatorsRenderer); - signalCollection = collection[0]; - signalX = collection[1]; - } - technicalIndicatorsRenderer._renderPoints = signalCollection; - technicalIndicatorsRenderer._stochasticperiod = periodCollection; - // Decides the type of renderer class to be used - const bool isLine = true; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'Stocastic', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'PeriodLine', - indicator.periodLineColor, indicator.periodLineWidth, chart, isLine); - if (showZones) { - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'UpperLine', - indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); - technicalIndicatorsRenderer._setSeriesProperties(indicator, 'LowerLine', - indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); - } - technicalIndicatorsRenderer._setSeriesRange(signalCollection, indicator, - signalX, technicalIndicatorsRenderer._targetSeriesRenderers[0]); - technicalIndicatorsRenderer._setSeriesRange(periodCollection, indicator, - periodX, technicalIndicatorsRenderer._targetSeriesRenderers[1]); - if (indicator.showZones) { - technicalIndicatorsRenderer._setSeriesRange(upperCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[2]); - technicalIndicatorsRenderer._setSeriesRange(lowerCollection, indicator, - xValues, technicalIndicatorsRenderer._targetSeriesRenderers[3]); - } - } - - /// To calculate the values of the stochastic indicator - List _stochasticCalculation( - int period, - int kPeriod, - List> data, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - final List> pointCollection = - >[]; - final List xValues = []; - if (data.length >= period + kPeriod && kPeriod > 0) { - final int count = period + (kPeriod - 1); - final List temp = [], values = []; - for (int i = 0; i < data.length; i++) { - final num value = data[i].y; - temp.add(value); - } - num length = temp.length; - while (length >= count) { - num sum = 0; - for (int i = period - 1; i < (period + kPeriod - 1); i++) { - sum = sum + temp[i]; - } - sum = sum / kPeriod; - final String _sum = sum.toStringAsFixed(2); - values.add(double.parse(_sum)); - temp.removeRange(0, 1); - length = temp.length; - } - final int len = count - 1; - for (int i = 0; i < data.length; i++) { - if (!(i < len)) { - pointCollection.add(technicalIndicatorsRenderer._getDataPoint( - data[i].x, values[i - len], data[i], pointCollection.length)); - xValues.add(data[i].x); - data[i].y = values[i - len]; - } - } - } - - return [pointCollection, xValues]; - } - - /// To return list of stochastic indicator points - List> _calculatePeriod( - int period, - int kPeriod, - List> data, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - // This has been null before - final List lowValue = List.filled(data.length, -1); - final List highValue = List.filled(data.length, -1); - final List closeValue = List.filled(data.length, -1); - final List> modifiedSource = - >[]; - - for (int j = 0; j < data.length; j++) { - lowValue[j] = data[j].low ?? 0; - highValue[j] = data[j].high ?? 0; - closeValue[j] = data[j].close ?? 0; - } - if (data.length > period) { - final List mins = [], maxs = []; - for (int i = 0; i < period - 1; ++i) { - maxs.add(0); - mins.add(0); - modifiedSource.add(technicalIndicatorsRenderer._getDataPoint( - data[i].x, data[i].close, data[i], modifiedSource.length)); - } - num? min, max; - for (int i = period - 1; i < data.length; ++i) { - for (int j = 0; j < period; ++j) { - min ??= lowValue[i - j]; - max ??= highValue[i - j]; - min = math.min(min, lowValue[i - j]); - max = math.max(max, highValue[i - j]); - } - maxs.add(max!); - mins.add(min!); - min = null; - max = null; - } - - for (int i = period - 1; i < data.length; ++i) { - num top = 0, bottom = 0; - top += closeValue[i] - mins[i]; - bottom += maxs[i] - mins[i]; - modifiedSource.add(technicalIndicatorsRenderer._getDataPoint( - data[i].x, (top / bottom) * 100, data[i], modifiedSource.length)); - } - } - return modifiedSource; - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart index 01811fb91..fbda6decf 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/technical_indicator.dart @@ -1,4 +1,34 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/event_args.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/chart_base.dart'; +import '../chart_series/column_series.dart'; +import '../chart_series/line_series.dart'; +import '../chart_series/range_area_series.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../series_painter/column_painter.dart'; +import '../series_painter/line_painter.dart'; +import '../series_painter/range_area_painter.dart'; +import '../utils/enum.dart'; +import 'accumulation_distribution_indicator.dart'; +import 'atr_indicator.dart'; +import 'bollinger_bands_indicator.dart'; +import 'ema_indicator.dart'; +import 'macd_indicator.dart'; +import 'momentum_indicator.dart'; +import 'rsi_indicator.dart'; +import 'sma_indicator.dart'; +import 'stochastic_indicator.dart'; +import 'tma_indicator.dart'; /// Customize the technical indicators. /// @@ -7,7 +37,7 @@ part of charts; /// /// Indicators generally overlay the chart data to show the data flow over a period of time. /// -/// _Note:_ This propertty is applicable only for financial chart series types. +/// _Note:_ This property is applicable only for financial chart series types. class TechnicalIndicators { /// Creating an argument constructor of TechnicalIndicators class. TechnicalIndicators( @@ -17,6 +47,7 @@ class TechnicalIndicators { this.seriesName, List? dashArray, double? animationDuration, + double? animationDelay, this.dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? lowValueMapper, @@ -34,6 +65,7 @@ class TechnicalIndicators { : isVisible = isVisible ?? true, dashArray = dashArray ?? [0, 0], animationDuration = animationDuration ?? 1500, + animationDelay = animationDelay ?? 0, isVisibleInLegend = isVisibleInLegend ?? true, legendIconType = legendIconType ?? LegendIconType.seriesType, signalLineColor = signalLineColor ?? Colors.blue, @@ -154,6 +186,27 @@ class TechnicalIndicators { ///``` final double animationDuration; + /// Delay duration of the technical indicator's animation. + /// It takes a millisecond value as input. + /// By default, the technical indicator will get animated for the specified duration. + /// If animationDelay is specified, then the technical indicator will begin to animate + /// after the specified duration. + /// + /// Defaults to '0'. + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// indicators: >[ + /// SmaIndicator( + /// animationDelay : 500, + /// ), + /// ], + /// )); + ///} + ///``` + final double? animationDelay; + /// Property to provide data for the technical indicators without any series. /// ///```dart @@ -285,7 +338,7 @@ class TechnicalIndicators { /// Property to provide icon type for the technical indicators legend. /// - /// Deafults to `LegendIconType.seriesType`. + /// Defaults to `LegendIconType.seriesType`. ///```dart ///Widget build(BuildContext context) { /// return Container( @@ -387,62 +440,70 @@ class TechnicalIndicators { ///Technical indicator renderer class for mutable fields and methods class TechnicalIndicatorsRenderer { /// Creates an argument constructor for TechnicalIndicator renderer class - TechnicalIndicatorsRenderer(this._technicalIndicatorRenderer); + TechnicalIndicatorsRenderer( + this.technicalIndicatorRenderer, this.stateProperties); + + /// Specifies the technical indicator renderer + final TechnicalIndicators technicalIndicatorRenderer; + + /// Holds the state properties + final CartesianStateProperties stateProperties; + + /// Specifies the name value + late String name; - final TechnicalIndicators _technicalIndicatorRenderer; + /// Specifies whether the indicator is visible + bool? visible; - late String _name; - bool? _visible; - //ignore: prefer_final_fields - bool _isIndicator = true; - final String _seriesType = 'indicator'; - late String _indicatorType; - //ignore: unused_field - late int _index; + /// Specifies whether it is indicator + bool isIndicator = true; - //ignore: prefer_final_fields - List _targetSeriesRenderers = + /// Represents the series type + final String seriesType = 'indicator'; + + /// Represents the indicator type + late String indicatorType; + + /// Specifies the value of index + late int index; + + /// Specifies the list of target series renderer + List targetSeriesRenderers = []; - //ignore: prefer_final_fields - List>? _dataPoints = + + /// Specifies the list of data points + List>? dataPoints = >[]; ///used for test case - //ignore: prefer_final_fields - List> _renderPoints = + List> renderPoints = >[]; ///used for events - //ignore: prefer_final_fields - List>? _bollingerUpper = + List>? bollingerUpper = >[]; ///used for events - //ignore: prefer_final_fields - List>? _bollingerLower = + List>? bollingerLower = >[]; ///used for events - //ignore: prefer_final_fields - List>? _macdLine = + List>? macdLine = >[]; ///used for events - //ignore: prefer_final_fields - List>? _macdHistogram = + List>? macdHistogram = >[]; ///used for events - //ignore: prefer_final_fields - List>? _stochasticperiod = + List>? stochasticperiod = >[]; ///used for events - //ignore: prefer_final_fields - double? _momentumCenterLineValue; + double? momentumCenterLineValue; /// To get and return CartesianChartPoint - CartesianChartPoint _getDataPoint( + CartesianChartPoint getDataPoint( dynamic x, num y, CartesianChartPoint sourcePoint, int index, [TechnicalIndicators? indicator]) { final CartesianChartPoint point = @@ -463,7 +524,7 @@ class TechnicalIndicatorsRenderer { } /// To get chart point of range type series - CartesianChartPoint _getRangePoint(dynamic x, num high, num low, + CartesianChartPoint getRangePoint(dynamic x, num high, num low, CartesianChartPoint sourcePoint, int index, //ignore: unused_element [TechnicalIndicators? indicator]) { @@ -478,7 +539,7 @@ class TechnicalIndicatorsRenderer { } /// To set properties of technical indicators - void _setSeriesProperties(TechnicalIndicators indicator, + void setSeriesProperties(TechnicalIndicators indicator, String name, Color color, double width, SfCartesianChart chart, [bool isLine = false, bool isRangeArea = false, @@ -491,28 +552,28 @@ class TechnicalIndicatorsRenderer { TechnicalIndicatorRenderDetails indicators; if (indicator is BollingerBandIndicator) { final BollingerBandIndicatorRenderParams indicatorRenderParams = - BollingerBandIndicatorRenderParams(_bollingerUpper, _bollingerLower, - _renderPoints, name, width, color, indicator.dashArray); + BollingerBandIndicatorRenderParams(bollingerUpper, bollingerLower, + renderPoints, name, width, color, indicator.dashArray); indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); } else if (indicator is MomentumIndicator) { final MomentumIndicatorRenderParams indicatorRenderParams = - MomentumIndicatorRenderParams(_momentumCenterLineValue, - _renderPoints, name, width, color, indicator.dashArray); + MomentumIndicatorRenderParams(momentumCenterLineValue, renderPoints, + name, width, color, indicator.dashArray); indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); } else if (indicator is StochasticIndicator) { final StochasticIndicatorRenderParams indicatorRenderParams = - StochasticIndicatorRenderParams(_stochasticperiod, _renderPoints, + StochasticIndicatorRenderParams(stochasticperiod, renderPoints, name, width, color, indicator.dashArray); indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); } else if (indicator is MacdIndicator) { final MacdIndicatorRenderParams indicatorRenderParams = - MacdIndicatorRenderParams(_macdLine, _macdHistogram, _renderPoints, + MacdIndicatorRenderParams(macdLine, macdHistogram, renderPoints, name, width, color, indicator.dashArray); indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); } else { final IndicatorRenderParams indicatorRenderParams = IndicatorRenderParams( - _renderPoints, name, width, color, indicator.dashArray); + renderPoints, name, width, color, indicator.dashArray); indicators = indicator.onRenderDetailsUpdate!(indicatorRenderParams); } @@ -528,6 +589,7 @@ class TechnicalIndicatorsRenderer { borderWidth: width, xAxisName: indicator.xAxisName, animationDuration: indicator.animationDuration, + animationDelay: indicator.animationDelay, yAxisName: indicator.yAxisName, enableTooltip: false, //ignore: always_specify_types @@ -544,6 +606,7 @@ class TechnicalIndicatorsRenderer { width: width, xAxisName: indicator.xAxisName, animationDuration: indicator.animationDuration, + animationDelay: indicator.animationDelay, yAxisName: indicator.yAxisName, //ignore: always_specify_types xValueMapper: (dynamic, _) => null, @@ -557,6 +620,7 @@ class TechnicalIndicatorsRenderer { width: width, xAxisName: indicator.xAxisName, animationDuration: indicator.animationDuration, + animationDelay: indicator.animationDelay, yAxisName: indicator.yAxisName, //ignore: always_specify_types xValueMapper: (dynamic, _) => null, @@ -566,32 +630,40 @@ class TechnicalIndicatorsRenderer { final CartesianSeriesRenderer seriesRenderer = isRangeArea == true ? RangeAreaSeriesRenderer() : (isHistogram == true ? ColumnSeriesRenderer() : LineSeriesRenderer()); - seriesRenderer._series = series; - seriesRenderer._visible = _visible; - seriesRenderer._chart = chart; - seriesRenderer._seriesType = isRangeArea == true - ? 'rangearea' - : (isHistogram == true ? 'column' : 'line'); - seriesRenderer._isIndicator = true; - seriesRenderer._seriesName = _name; - _targetSeriesRenderers.add(seriesRenderer); + final SeriesRendererDetails seriesRendererDetails = + SeriesRendererDetails(seriesRenderer); + SeriesHelper.setSeriesRendererDetails( + seriesRenderer, seriesRendererDetails); + seriesRendererDetails.stateProperties = stateProperties; + seriesRendererDetails.series = series; + seriesRendererDetails.visible = visible; + seriesRendererDetails.chart = chart; + seriesRendererDetails.seriesType = + isRangeArea ? 'rangearea' : (isHistogram ? 'column' : 'line'); + seriesRendererDetails.isIndicator = true; + seriesRendererDetails.seriesName = name; + targetSeriesRenderers.add(seriesRenderer); } /// Set series range of technical indicators - void _setSeriesRange(List> points, + void setSeriesRange(List> points, TechnicalIndicators indicator, List xValues, [CartesianSeriesRenderer? seriesRenderer]) { if (seriesRenderer == null) { - _targetSeriesRenderers[0]._dataPoints = points; - _targetSeriesRenderers[0]._xValues = xValues; + SeriesHelper.getSeriesRendererDetails(targetSeriesRenderers[0]) + .dataPoints = points; + SeriesHelper.getSeriesRendererDetails(targetSeriesRenderers[0]).xValues = + xValues; } else { - seriesRenderer._dataPoints = points; - seriesRenderer._xValues = xValues; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + seriesRendererDetails.dataPoints = points; + seriesRendererDetails.xValues = xValues; } } /// To get the value field value of technical indicators - num _getFieldValue(List?> dataPoints, int index, + num getFieldValue(List?> dataPoints, int index, String valueField) { num? val; if (valueField == 'low') { @@ -610,4 +682,1035 @@ class TechnicalIndicatorsRenderer { val = val ?? 0; return val; } + + /// To initialize data source of technical indicators + void initDataSource( + TechnicalIndicators indicator, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart) { + technicalIndicatorsRenderer.targetSeriesRenderers = + []; + final List> validData = + technicalIndicatorsRenderer.dataPoints!; + if (validData.isNotEmpty && + indicator is AccumulationDistributionIndicator) { + _calculateADPoints( + indicator, validData, technicalIndicatorsRenderer, chart); + } else if (validData.isNotEmpty && + validData.length > indicator.period && + indicator is AtrIndicator) { + _calculateATRPoints( + indicator, validData, technicalIndicatorsRenderer, chart); + } else if (indicator is BollingerBandIndicator) { + _calculateBollingerBandPoints( + indicator, technicalIndicatorsRenderer, chart); + } else if (validData.isNotEmpty && + validData.length > indicator.period && + indicator is EmaIndicator && + indicator.period > 0) { + _calculateEMAPoints( + indicator, validData, technicalIndicatorsRenderer, chart); + } else if (indicator is MacdIndicator) { + _calculateMacdPoints(indicator, technicalIndicatorsRenderer, chart); + } else if (indicator is MomentumIndicator) { + _calculateMomentumIndicatorPoints( + indicator, technicalIndicatorsRenderer, chart); + } else if (indicator is RsiIndicator) { + _calculateRSIPoints(indicator, technicalIndicatorsRenderer, chart); + } else if (indicator is SmaIndicator) { + _calculateSMAPoints(indicator, technicalIndicatorsRenderer, chart); + } else if (indicator is StochasticIndicator) { + _calculateStochasticIndicatorPoints( + indicator, technicalIndicatorsRenderer, chart); + } else if (validData.isNotEmpty && + validData.length > indicator.period && + indicator is TmaIndicator) { + _calculateTMAPoints( + indicator, validData, technicalIndicatorsRenderer, chart); + } + } + + /// To calculate the rendering points of the accumulation distribution indicator + void _calculateADPoints( + AccumulationDistributionIndicator indicator, + List> validData, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart) { + final List> points = + >[]; + final List xValues = []; + CartesianChartPoint point; + num sum = 0, value = 0, high = 0, low = 0, close = 0; + for (int i = 0; i < validData.length; i++) { + high = validData[i].high ?? 0; + low = validData[i].low ?? 0; + close = validData[i].close ?? 0; + value = ((close - low) - (high - close)) / (high - low); + sum = sum + value * validData[i].volume!; + point = technicalIndicatorsRenderer.getDataPoint( + validData[i].x, sum, validData[i], points.length); + points.add(point); + xValues.add(point.x); + } + technicalIndicatorsRenderer.renderPoints = points; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'AD', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); + } + + /// To calculate the rendering points of the ATR indicator + void _calculateATRPoints( + AtrIndicator indicator, + List> validData, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart, + ) { + num average = 0; + num highLow = 0, highClose = 0, lowClose = 0, trueRange = 0, tempRange = 0; + final List> points = + >[]; + final List xValues = []; + CartesianChartPoint point; + final List<_TempData> temp = <_TempData>[]; + final num period = indicator.period; + num sum = 0; + for (int i = 0; i < validData.length; i++) { + if (!validData[i].isDrop && !validData[i].isGap) { + highLow = validData[i].high - validData[i].low; + if (i > 0) { + highClose = (validData[i].high - (validData[i - 1].close ?? 0)).abs(); + lowClose = (validData[i].low - (validData[i - 1].close ?? 0)).abs(); + } + tempRange = math.max(highLow, highClose); + trueRange = math.max(tempRange, lowClose); + sum = sum + trueRange; + if (i >= period && period > 0) { + average = + (temp[temp.length - 1].y * (period - 1) + trueRange) / period; + point = technicalIndicatorsRenderer.getDataPoint( + validData[i].x, average, validData[i], points.length); + points.add(point); + xValues.add(point.x); + } else { + average = sum / period; + if (i == period - 1) { + point = technicalIndicatorsRenderer.getDataPoint( + validData[i].x, average, validData[i], points.length); + points.add(point); + xValues.add(point.x); + } + } + temp.add(_TempData(validData[i].x, average)); + } + } + technicalIndicatorsRenderer.renderPoints = points; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'ATR', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); + } + + void _calculateBollingerBandPoints( + BollingerBandIndicator indicator, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart, + ) { + final bool enableBand = indicator.bandColor != Colors.transparent && + // ignore: unnecessary_null_comparison + indicator.bandColor != null; + final int start = enableBand ? 1 : 0; + final List> signalCollection = + >[], + upperCollection = >[], + lowerCollection = >[], + bandCollection = >[]; + final List xValues = []; + + //prepare data + final List> validData = + technicalIndicatorsRenderer.dataPoints!; + if (validData.isNotEmpty && + validData.length >= indicator.period && + indicator.period > 0) { + num sum = 0, deviationSum = 0; + final num multiplier = indicator.standardDeviation; + final int limit = validData.length, length = indicator.period.round(); + // This has been null before + final List smaPoints = List.filled(limit, -1), + deviations = List.filled(limit, -1); + final List<_BollingerData> bollingerPoints = List<_BollingerData>.filled( + limit, + _BollingerData( + x: -1, midBand: -1, lowBand: -1, upBand: -1, visible: false)); + + for (int i = 0; i < length; i++) { + sum += validData[i].close ?? 0; + } + num sma = sum / indicator.period; + for (int i = 0; i < limit; i++) { + final num y = validData[i].close ?? 0; + if (i >= length - 1 && i < limit) { + if (i - indicator.period >= 0) { + final num diff = y - validData[i - length].close; + sum = sum + diff; + sma = sum / (indicator.period); + smaPoints[i] = sma; + deviations[i] = math.pow(y - sma, 2); + deviationSum += deviations[i] - deviations[i - length]; + } else { + smaPoints[i] = sma; + deviations[i] = math.pow(y - sma, 2); + deviationSum += deviations[i]; + } + final num range = math.sqrt(deviationSum / (indicator.period)); + final num lowerBand = smaPoints[i] - (multiplier * range); + final num upperBand = smaPoints[i] + (multiplier * range); + if (i + 1 == length) { + for (int j = 0; j < length - 1; j++) { + bollingerPoints[j] = _BollingerData( + x: validData[j].xValue, + midBand: smaPoints[i], + lowBand: lowerBand, + upBand: upperBand, + visible: true); + } + } + bollingerPoints[i] = _BollingerData( + x: validData[i].xValue, + midBand: smaPoints[i], + lowBand: lowerBand, + upBand: upperBand, + visible: true); + } else { + if (i < indicator.period - 1) { + smaPoints[i] = sma; + deviations[i] = math.pow(y - sma, 2); + deviationSum += deviations[i]; + } + } + } + int i = -1, j = -1; + for (int k = 0; k < limit; k++) { + if (k >= (length - 1)) { + xValues.add(validData[k].x); + upperCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[k].x, + bollingerPoints[k].upBand, + validData[k], + upperCollection.length)); + lowerCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[k].x, + bollingerPoints[k].lowBand, + validData[k], + lowerCollection.length)); + signalCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[k].x, + bollingerPoints[k].midBand, + validData[k], + signalCollection.length)); + if (enableBand) { + bandCollection.add(technicalIndicatorsRenderer.getRangePoint( + validData[k].x, + upperCollection[++i].y, + lowerCollection[++j].y, + validData[k], + bandCollection.length)); + } + } + } + } + technicalIndicatorsRenderer.renderPoints = signalCollection; + technicalIndicatorsRenderer.bollingerUpper = upperCollection; + technicalIndicatorsRenderer.bollingerLower = lowerCollection; + // Decides the type of renderer class to be used + bool isLine, isRangeArea; + if (indicator.bandColor != Colors.transparent && + // ignore: unnecessary_null_comparison + indicator.bandColor != null) { + isLine = false; + isRangeArea = true; + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'rangearea', + indicator.bandColor, 0, chart, isLine, isRangeArea); + } + isLine = true; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'BollingerBand', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'UpperLine', + indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'LowerLine', + indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); + if (enableBand) { + technicalIndicatorsRenderer.setSeriesRange(bandCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[0]); + } + technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[start]); + technicalIndicatorsRenderer.setSeriesRange(upperCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[start + 1]); + technicalIndicatorsRenderer.setSeriesRange(lowerCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[start + 2]); + } + + /// To calculate the rendering points of the EMA indicator + void _calculateEMAPoints( + EmaIndicator indicator, + List> validData, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart) { + final int period = indicator.period; + final List> points = + >[]; + final List xValues = []; + CartesianChartPoint point; + if (validData.length >= period && period > 0) { + num sum = 0, average = 0; + final num k = 2 / (period + 1); + for (int i = 0; i < period; i++) { + sum += technicalIndicatorsRenderer.getFieldValue( + validData, i, indicator.valueField); + } + average = sum / period; + point = technicalIndicatorsRenderer.getDataPoint(validData[period - 1].x, + average, validData[period - 1], points.length); + points.add(point); + xValues.add(point.x); + int index = period; + while (index < validData.length) { + if (validData[index].isVisible || + validData[index].isGap == true || + validData[index].isDrop == true) { + final num prevAverage = points[index - period].y; + final num yValue = (technicalIndicatorsRenderer.getFieldValue( + validData, index, indicator.valueField) - + prevAverage) * + k + + prevAverage; + point = technicalIndicatorsRenderer.getDataPoint( + validData[index].x, yValue, validData[index], points.length); + points.add(point); + xValues.add(point.x); + } + index++; + } + } + technicalIndicatorsRenderer.renderPoints = points; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'EMA', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); + } + + void _calculateMacdPoints( + MacdIndicator indicator, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart, + ) { + List> signalCollection = + >[]; + final num fastPeriod = indicator.longPeriod; + final num slowPeriod = indicator.shortPeriod; + final num trigger = indicator.period; + final num length = fastPeriod + trigger; + List> macdCollection = + >[], + histogramCollection = >[]; + final List> validData = + technicalIndicatorsRenderer.dataPoints!; + List signalX = [], + histogramX = [], + macdX = [], + collection; + CartesianSeriesRenderer? histogramSeriesRenderer, macdLineSeriesRenderer; + + if (validData.isNotEmpty && + length < validData.length && + slowPeriod <= fastPeriod && + slowPeriod > 0 && + indicator.period > 0 && + (length - 2) >= 0) { + final List shortEMA = _calculateEMAValues( + slowPeriod, validData, 'close', technicalIndicatorsRenderer); + final List longEMA = _calculateEMAValues( + fastPeriod, validData, 'close', technicalIndicatorsRenderer); + final List macdValues = _getMACDVales(indicator, shortEMA, longEMA); + collection = _getMACDPoints( + indicator, macdValues, validData, technicalIndicatorsRenderer); + macdCollection = collection[0]; + macdX = collection[1]; + final List signalEMA = _calculateEMAValues( + trigger, macdCollection, 'y', technicalIndicatorsRenderer); + collection = _getSignalPoints( + indicator, signalEMA, validData, technicalIndicatorsRenderer); + signalCollection = collection[0]; + signalX = collection[1]; + if (indicator.macdType == MacdType.histogram || + indicator.macdType == MacdType.both) { + collection = _getHistogramPoints(indicator, macdValues, signalEMA, + validData, technicalIndicatorsRenderer); + histogramCollection = collection[0]; + histogramX = collection[1]; + } + } + technicalIndicatorsRenderer.renderPoints = signalCollection; + technicalIndicatorsRenderer.macdHistogram = histogramCollection; + technicalIndicatorsRenderer.macdLine = macdCollection; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'MACD', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + // To describe the type of series renderer to be assigned + bool isLine, isRangeArea, isHistogram; + if (indicator.macdType == MacdType.line || + indicator.macdType == MacdType.both) { + // Decides the type of renderer class to be used + isLine = true; + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'MacdLine', + indicator.macdLineColor, indicator.macdLineWidth, chart, isLine); + } + if (indicator.macdType == MacdType.histogram || + indicator.macdType == MacdType.both) { + isLine = false; + isRangeArea = false; + isHistogram = true; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + 'Histogram', + indicator.histogramPositiveColor, + indicator.signalLineWidth / 2, + chart, + isLine, + isRangeArea, + isHistogram); + } + if (indicator.macdType == MacdType.histogram) { + histogramSeriesRenderer = + technicalIndicatorsRenderer.targetSeriesRenderers[1]; + } else { + macdLineSeriesRenderer = + technicalIndicatorsRenderer.targetSeriesRenderers[1]; + if (indicator.macdType == MacdType.both) { + histogramSeriesRenderer = + technicalIndicatorsRenderer.targetSeriesRenderers[2]; + } + } + technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, + signalX, technicalIndicatorsRenderer.targetSeriesRenderers[0]); + if (histogramSeriesRenderer != null) { + technicalIndicatorsRenderer.setSeriesRange( + histogramCollection, indicator, histogramX, histogramSeriesRenderer); + } + if (macdLineSeriesRenderer != null) { + technicalIndicatorsRenderer.setSeriesRange( + macdCollection, indicator, macdX, macdLineSeriesRenderer); + } + } + + /// Calculates the EMA values for the given period + List _calculateEMAValues( + num period, + List> validData, + String valueField, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + num sum = 0, initialEMA = 0; + final List emaValues = []; + final num emaPercent = 2 / (period + 1); + for (int i = 0; i < period; i++) { + sum += + technicalIndicatorsRenderer.getFieldValue(validData, i, valueField); + } + initialEMA = sum / period; + emaValues.add(initialEMA); + num emaAvg = initialEMA; + for (int j = period.toInt(); j < validData.length; j++) { + emaAvg = + (technicalIndicatorsRenderer.getFieldValue(validData, j, valueField) - + emaAvg) * + emaPercent + + emaAvg; + emaValues.add(emaAvg); + } + return emaValues; + } + + ///Defines the MACD Points + List _getMACDPoints( + MacdIndicator indicator, + List macdPoints, + List> validData, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + final List> macdCollection = + >[]; + final List xValues = []; + int dataMACDIndex = indicator.longPeriod - 1, macdIndex = 0; + while (dataMACDIndex < validData.length) { + macdCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[dataMACDIndex].x, + macdPoints[macdIndex], + validData[dataMACDIndex], + macdCollection.length)); + xValues.add(validData[dataMACDIndex].x); + dataMACDIndex++; + macdIndex++; + } + return [macdCollection, xValues]; + } + + ///Calculates the signal points + List _getSignalPoints( + MacdIndicator indicator, + List signalEma, + List> validData, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + int dataSignalIndex = indicator.longPeriod + indicator.period - 2; + int signalIndex = 0; + final List xValues = []; + final List> signalCollection = + >[]; + while (dataSignalIndex < validData.length) { + signalCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[dataSignalIndex].x, + signalEma[signalIndex], + validData[dataSignalIndex], + signalCollection.length)); + xValues.add(validData[dataSignalIndex].x); + dataSignalIndex++; + signalIndex++; + } + return [signalCollection, xValues]; + } + + ///Calculates the MACD values + List _getMACDVales(MacdIndicator indicator, + List shortEma, List longEma) { + final List macdPoints = []; + final int diff = indicator.longPeriod - indicator.shortPeriod; + for (int i = 0; i < longEma.length; i++) { + macdPoints.add(shortEma[i + diff] - longEma[i]); + } + return macdPoints; + } + + ///Calculates the Histogram Points + List _getHistogramPoints( + MacdIndicator indicator, + List macdPoints, + List signalEma, + List> validData, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + int dataHistogramIndex = indicator.longPeriod + indicator.period - 2; + int histogramIndex = 0; + final List> histogramCollection = + >[]; + final List xValues = []; + while (dataHistogramIndex < validData.length) { + histogramCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[dataHistogramIndex].x, + macdPoints[histogramIndex + (indicator.period - 1)] - + signalEma[histogramIndex], + validData[dataHistogramIndex], + histogramCollection.length, + indicator)); + xValues.add(validData[dataHistogramIndex].x); + dataHistogramIndex++; + histogramIndex++; + } + return [histogramCollection, xValues]; + } + + void _calculateMomentumIndicatorPoints( + MomentumIndicator indicator, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart, + ) { + final List> signalCollection = + >[], + centerLineCollection = >[], + validData = technicalIndicatorsRenderer.dataPoints!; + final List centerXValues = [], xValues = []; + num value; + + if (validData.isNotEmpty) { + final int length = indicator.period; + if (validData.length >= indicator.period && indicator.period > 0) { + for (int i = 0; i < validData.length; i++) { + centerLineCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[i].x, 100, validData[i], centerLineCollection.length)); + centerXValues.add(validData[i].x); + if (!(i < length)) { + value = (validData[i].close ?? 0) / + (validData[i - length].close ?? 1) * + 100; + signalCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[i].x, value, validData[i], signalCollection.length)); + xValues.add(validData[i].x); + } + } + } + technicalIndicatorsRenderer.renderPoints = signalCollection; + technicalIndicatorsRenderer.momentumCenterLineValue = + centerLineCollection.first.y.toDouble(); + // Decides the type of renderer class to be used + const bool isLine = true; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'Momentum', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'CenterLine', + indicator.centerLineColor, indicator.centerLineWidth, chart, isLine); + technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[0]); + technicalIndicatorsRenderer.setSeriesRange( + centerLineCollection, + indicator, + centerXValues, + technicalIndicatorsRenderer.targetSeriesRenderers[1]); + } + } + + void _calculateRSIPoints( + RsiIndicator indicator, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart, + ) { + final List> signalCollection = + >[], + lowerCollection = >[], + upperCollection = >[], + validData = technicalIndicatorsRenderer.dataPoints!; + + final List xValues = [], signalXValues = []; + + if (validData.isNotEmpty && + validData.length >= indicator.period && + indicator.period > 0) { + if (indicator.showZones) { + for (int i = 0; i < validData.length; i++) { + upperCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[i].x, + indicator.overbought, + validData[i], + upperCollection.length)); + lowerCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[i].x, + indicator.oversold, + validData[i], + lowerCollection.length)); + xValues.add(validData[i].x); + } + } + num prevClose = validData[0].close ?? 0, gain = 0, loss = 0; + for (int i = 1; i <= indicator.period; i++) { + final num close = validData[i].close ?? 0.0; + if (close > prevClose) { + gain += close - prevClose; + } else { + loss += prevClose - close; + } + prevClose = close; + } + gain = gain / indicator.period; + loss = loss / indicator.period; + + signalCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[indicator.period].x, + 100 - (100 / (1 + (gain / loss))), + validData[indicator.period], + signalCollection.length)); + signalXValues.add(validData[indicator.period].x); + + for (int j = indicator.period + 1; j < validData.length; j++) { + if (!validData[j].isGap && !validData[j].isDrop) { + final num close = validData[j].close; + if (close > prevClose) { + gain = (gain * (indicator.period - 1) + (close - prevClose)) / + indicator.period; + loss = (loss * (indicator.period - 1)) / indicator.period; + } else if (close < prevClose) { + loss = (loss * (indicator.period - 1) + (prevClose - close)) / + indicator.period; + gain = (gain * (indicator.period - 1)) / indicator.period; + } + prevClose = close; + signalCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[j].x, + 100 - (100 / (1 + (gain / loss))), + validData[j], + signalCollection.length)); + signalXValues.add(validData[j].x); + } + } + } + technicalIndicatorsRenderer.renderPoints = signalCollection; + // Decides the type of renderer class to be used + const bool isLine = true; + // final CartesianSeriesRenderer signalSeriesRenderer = + // technicalIndicatorsRenderer.targetSeriesRenderers[0]; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'RSI', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + if (indicator.showZones == true) { + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'UpperLine', + indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'LowerLine', + indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); + } + + technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, + signalXValues, technicalIndicatorsRenderer.targetSeriesRenderers[0]); + if (indicator.showZones) { + technicalIndicatorsRenderer.setSeriesRange(upperCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[1]); + technicalIndicatorsRenderer.setSeriesRange(lowerCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[2]); + } + } + + void _calculateSMAPoints( + SmaIndicator indicator, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart, + ) { + final List> smaPoints = + >[]; + final List> points = + technicalIndicatorsRenderer.dataPoints!; + final List xValues = []; + CartesianChartPoint point; + if (points.isNotEmpty) { + final List> validData = points; + + if (validData.length >= indicator.period && indicator.period > 0) { + num average = 0, sum = 0; + + for (int i = 0; i < indicator.period; i++) { + sum += technicalIndicatorsRenderer.getFieldValue( + validData, i, indicator.valueField); + } + + average = sum / indicator.period; + point = technicalIndicatorsRenderer.getDataPoint( + validData[indicator.period - 1].x, + average, + validData[indicator.period - 1], + smaPoints.length); + smaPoints.add(point); + xValues.add(point.x); + + int index = indicator.period; + while (index < validData.length) { + sum -= technicalIndicatorsRenderer.getFieldValue( + validData, index - indicator.period, indicator.valueField); + sum += technicalIndicatorsRenderer.getFieldValue( + validData, index, indicator.valueField); + average = sum / indicator.period; + point = technicalIndicatorsRenderer.getDataPoint( + validData[index].x, average, validData[index], smaPoints.length); + smaPoints.add(point); + xValues.add(point.x); + index++; + } + } + technicalIndicatorsRenderer.renderPoints = smaPoints; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'SMA', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + // final CartesianSeriesRenderer signalSeriesRenderer = + // technicalIndicatorsRenderer.targetSeriesRenderers[0]; + technicalIndicatorsRenderer.setSeriesRange(smaPoints, indicator, xValues); + } + } + + /// To calculate the stochastic indicator points + void _calculateStochasticIndicatorPoints( + StochasticIndicator indicator, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart, + ) { + List> signalCollection = + >[], + source = >[], + periodCollection = >[]; + final List> lowerCollection = + >[], + upperCollection = >[]; + final List> validData = + technicalIndicatorsRenderer.dataPoints!; + final List xValues = []; + late List collection, signalX, periodX; + if (validData.isNotEmpty && + validData.length >= indicator.period && + indicator.period > 0) { + if (indicator.showZones) { + for (int i = 0; i < validData.length; i++) { + upperCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[i].x, + indicator.overbought, + validData[i], + upperCollection.length)); + lowerCollection.add(technicalIndicatorsRenderer.getDataPoint( + validData[i].x, + indicator.oversold, + validData[i], + lowerCollection.length)); + xValues.add(validData[i].x); + } + } + source = calculatePeriod(indicator.period, indicator.kPeriod.toInt(), + validData, technicalIndicatorsRenderer); + collection = _stochasticCalculation(indicator.period, + indicator.kPeriod.toInt(), source, technicalIndicatorsRenderer); + periodCollection = collection[0]; + periodX = collection[1]; + collection = _stochasticCalculation( + (indicator.period + indicator.kPeriod - 1).toInt(), + indicator.dPeriod.toInt(), + source, + technicalIndicatorsRenderer); + signalCollection = collection[0]; + signalX = collection[1]; + } + technicalIndicatorsRenderer.renderPoints = signalCollection; + technicalIndicatorsRenderer.stochasticperiod = periodCollection; + // Decides the type of renderer class to be used + const bool isLine = true; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'Stocastic', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'PeriodLine', + indicator.periodLineColor, indicator.periodLineWidth, chart, isLine); + if (indicator.showZones) { + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'UpperLine', + indicator.upperLineColor, indicator.upperLineWidth, chart, isLine); + technicalIndicatorsRenderer.setSeriesProperties(indicator, 'LowerLine', + indicator.lowerLineColor, indicator.lowerLineWidth, chart, isLine); + } + technicalIndicatorsRenderer.setSeriesRange(signalCollection, indicator, + signalX, technicalIndicatorsRenderer.targetSeriesRenderers[0]); + technicalIndicatorsRenderer.setSeriesRange(periodCollection, indicator, + periodX, technicalIndicatorsRenderer.targetSeriesRenderers[1]); + if (indicator.showZones) { + technicalIndicatorsRenderer.setSeriesRange(upperCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[2]); + technicalIndicatorsRenderer.setSeriesRange(lowerCollection, indicator, + xValues, technicalIndicatorsRenderer.targetSeriesRenderers[3]); + } + } + + /// To calculate the values of the stochastic indicator + List _stochasticCalculation( + int period, + int kPeriod, + List> data, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + final List> pointCollection = + >[]; + final List xValues = []; + if (data.length >= period + kPeriod && kPeriod > 0) { + final int count = period + (kPeriod - 1); + final List temp = [], values = []; + for (int i = 0; i < data.length; i++) { + final num value = data[i].y; + temp.add(value); + } + num length = temp.length; + while (length >= count) { + num sum = 0; + for (int i = period - 1; i < (period + kPeriod - 1); i++) { + sum = sum + temp[i]; + } + sum = sum / kPeriod; + final String _sum = sum.toStringAsFixed(2); + values.add(double.parse(_sum)); + temp.removeRange(0, 1); + length = temp.length; + } + final int len = count - 1; + for (int i = 0; i < data.length; i++) { + if (!(i < len)) { + pointCollection.add(technicalIndicatorsRenderer.getDataPoint( + data[i].x, values[i - len], data[i], pointCollection.length)); + xValues.add(data[i].x); + data[i].y = values[i - len]; + } + } + } + + return [pointCollection, xValues]; + } + + /// To return list of stochastic indicator points + List> calculatePeriod( + int period, + int kPeriod, + List> data, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { + // This has been null before + final List lowValue = List.filled(data.length, -1); + final List highValue = List.filled(data.length, -1); + final List closeValue = List.filled(data.length, -1); + final List> modifiedSource = + >[]; + + for (int j = 0; j < data.length; j++) { + lowValue[j] = data[j].low ?? 0; + highValue[j] = data[j].high ?? 0; + closeValue[j] = data[j].close ?? 0; + } + if (data.length > period) { + final List mins = [], maxs = []; + for (int i = 0; i < period - 1; ++i) { + maxs.add(0); + mins.add(0); + modifiedSource.add(technicalIndicatorsRenderer.getDataPoint( + data[i].x, data[i].close, data[i], modifiedSource.length)); + } + num? min, max; + for (int i = period - 1; i < data.length; ++i) { + for (int j = 0; j < period; ++j) { + min ??= lowValue[i - j]; + max ??= highValue[i - j]; + min = math.min(min, lowValue[i - j]); + max = math.max(max, highValue[i - j]); + } + maxs.add(max!); + mins.add(min!); + min = null; + max = null; + } + + for (int i = period - 1; i < data.length; ++i) { + num top = 0, bottom = 0; + top += closeValue[i] - mins[i]; + bottom += maxs[i] - mins[i]; + modifiedSource.add(technicalIndicatorsRenderer.getDataPoint( + data[i].x, (top / bottom) * 100, data[i], modifiedSource.length)); + } + } + return modifiedSource; + } + + /// To calculate the values of the TMA indicator + void _calculateTMAPoints( + TmaIndicator indicator, + List> validData, + TechnicalIndicatorsRenderer technicalIndicatorsRenderer, + SfCartesianChart chart) { + final num period = indicator.period; + final List> points = + >[]; + final List xValues = []; + CartesianChartPoint point; + if (validData.isNotEmpty && + validData.length >= indicator.period && + period > 0) { + //prepare data + if (validData.isNotEmpty && validData.length >= period) { + num sum = 0; + int index = 0; + List smaValues = []; + int length = validData.length; + + while (length >= period) { + sum = 0; + index = validData.length - length; + for (int j = index; j < index + period; j++) { + sum += technicalIndicatorsRenderer.getFieldValue( + validData, j, indicator.valueField); + } + sum = sum / period; + smaValues.add(sum); + length--; + } + //initial values + for (int k = 0; k < period - 1; k++) { + sum = 0; + for (int j = 0; j < k + 1; j++) { + sum += technicalIndicatorsRenderer.getFieldValue( + validData, j, indicator.valueField); + } + sum = sum / (k + 1); + smaValues = _splice(smaValues, k, sum); + } + + index = indicator.period; + while (index <= smaValues.length) { + sum = 0; + for (int j = index - indicator.period; j < index; j++) { + sum = sum + smaValues[j]; + } + sum = sum / indicator.period; + point = technicalIndicatorsRenderer.getDataPoint( + validData[index - 1].x, sum, validData[index - 1], points.length); + points.add(point); + xValues.add(point.x); + index++; + } + } + } + technicalIndicatorsRenderer.renderPoints = points; + technicalIndicatorsRenderer.setSeriesProperties( + indicator, + indicator.name ?? 'TMA', + indicator.signalLineColor, + indicator.signalLineWidth, + chart); + // final CartesianSeriesRenderer signalSeriesRenderer = + // technicalIndicatorsRenderer.targetSeriesRenderers[0]; + technicalIndicatorsRenderer.setSeriesRange(points, indicator, xValues); + } + + /// To return list of spliced values + List _splice(List list, int index, num? elements) { + if (elements != null) { + list.insertAll(index, [elements]); + } + return list; + } +} + +class _TempData { + _TempData(this.x, this.y); + final dynamic x; + final num y; +} + +class _BollingerData { + _BollingerData( + {this.x, + required this.midBand, + required this.lowBand, + required this.upBand, + required this.visible}); + num? x; + num midBand; + num lowBand; + num upBand; + bool visible; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart index 62e137aaf..c37ba3bde 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/technical_indicators/tma_indicator.dart @@ -1,4 +1,11 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'technical_indicator.dart'; ///Renders Triangular Moving Average (TMA) indicator. /// @@ -16,6 +23,7 @@ class TmaIndicator extends TechnicalIndicators { String? seriesName, List? dashArray, double? animationDuration, + double? animationDelay, List? dataSource, ChartValueMapper? xValueMapper, ChartValueMapper? highValueMapper, @@ -39,6 +47,7 @@ class TmaIndicator extends TechnicalIndicators { seriesName: seriesName, dashArray: dashArray, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: xValueMapper, highValueMapper: highValueMapper, @@ -89,6 +98,7 @@ class TmaIndicator extends TechnicalIndicators { other.seriesName == seriesName && other.dashArray == dashArray && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.dataSource == dataSource && other.xValueMapper == xValueMapper && other.highValueMapper == highValueMapper && @@ -114,6 +124,7 @@ class TmaIndicator extends TechnicalIndicators { seriesName, dashArray, animationDuration, + animationDelay, dataSource, xValueMapper, highValueMapper, @@ -131,109 +142,4 @@ class TmaIndicator extends TechnicalIndicators { ]; return hashList(values); } - - /// To initialise indicators collections - // ignore:unused_element - void _initSeriesCollection( - TechnicalIndicators indicator, - SfCartesianChart chart, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer) { - technicalIndicatorsRenderer._targetSeriesRenderers = - []; - } - - /// To initialise data source of technical indicators - // ignore:unused_element - void _initDataSource( - TechnicalIndicators indicator, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart, - ) { - final List> validData = - technicalIndicatorsRenderer._dataPoints!; - if (validData.isNotEmpty && - validData.length > indicator.period && - indicator is TmaIndicator) { - _calculateTMAPoints( - indicator, validData, technicalIndicatorsRenderer, chart); - } - } - - /// To calculate the values of the TMA indicator - void _calculateTMAPoints( - TmaIndicator indicator, - List> validData, - TechnicalIndicatorsRenderer technicalIndicatorsRenderer, - SfCartesianChart chart) { - final num period = indicator.period; - final List> points = - >[]; - final List xValues = []; - CartesianChartPoint point; - if (validData.isNotEmpty && - validData.length >= indicator.period && - period > 0) { - //prepare data - if (validData.isNotEmpty && validData.length >= period) { - num sum = 0; - int index = 0; - List smaValues = []; - int length = validData.length; - - while (length >= period) { - sum = 0; - index = validData.length - length; - for (int j = index; j < index + period; j++) { - sum += technicalIndicatorsRenderer._getFieldValue( - validData, j, valueField); - } - sum = sum / period; - smaValues.add(sum); - length--; - } - //initial values - for (int k = 0; k < period - 1; k++) { - sum = 0; - for (int j = 0; j < k + 1; j++) { - sum += technicalIndicatorsRenderer._getFieldValue( - validData, j, valueField); - } - sum = sum / (k + 1); - smaValues = _splice(smaValues, k, sum); - } - - index = indicator.period; - while (index <= smaValues.length) { - sum = 0; - for (int j = index - indicator.period; j < index; j++) { - sum = sum + smaValues[j]; - } - sum = sum / indicator.period; - point = technicalIndicatorsRenderer._getDataPoint( - validData[index - 1].x, sum, validData[index - 1], points.length); - points.add(point); - xValues.add(point.x); - index++; - } - } - } - technicalIndicatorsRenderer._renderPoints = points; - technicalIndicatorsRenderer._setSeriesProperties( - indicator, - indicator.name ?? 'TMA', - indicator.signalLineColor, - indicator.signalLineWidth, - chart); - // final CartesianSeriesRenderer signalSeriesRenderer = - // technicalIndicatorsRenderer._targetSeriesRenderers[0]; - technicalIndicatorsRenderer._setSeriesRange(points, indicator, xValues); - } - - /// To return list of spliced values - List _splice(List list, int index, num? elements) { - if (elements != null) { - list.insertAll(index, [elements]); - } - return list; - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart index f771246a1..4f57fd6b5 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines.dart @@ -1,4 +1,34 @@ -part of charts; +import 'dart:math'; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../base/chart_base.dart'; +import '../chart_series/candle_series.dart'; +import '../chart_series/hilo_series.dart'; +import '../chart_series/hiloopenclose_series.dart'; +import '../chart_series/range_area_series.dart'; +import '../chart_series/range_column_series.dart'; +import '../chart_series/stacked_series_base.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../trendlines/trendlines.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; ///Renders the chart trend line /// @@ -22,12 +52,14 @@ class Trendline { this.isVisible = true, this.width = 2, this.animationDuration = 1500, + this.animationDelay = 0, this.valueField = 'high', this.isVisibleInLegend = true, this.legendIconType = LegendIconType.horizontalLine, this.markerSettings = const MarkerSettings(), this.polynomialOrder = 2, - this.period = 2}); + this.period = 2, + this.onRenderDetailsUpdate}); ///Determines the animation time of trendline. /// @@ -48,6 +80,29 @@ class Trendline { ///``` final double animationDuration; + /// Delay duration of the trendline animation. + /// It takes a millisecond value as input. + /// By default,the trendline will get animated for the specified duration. + /// If animationDelay is specified, then the trendline will begin to animate + /// after the specified duration. + /// + /// Defaults to '0'. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// trendlines: [ + /// Trendline(animationDelay: 500) + /// ]) + /// ] + /// )); + ///} + ///``` + final double? animationDelay; + ///Specifies the backward forecasting of trendlines. /// ///Defaults to `0`. @@ -162,7 +217,7 @@ class Trendline { ///``` final bool enableTooltip; - ///Color of the trenline. + ///Color of the trendline. /// ///Defaults to `Colors.blue`. /// @@ -219,7 +274,7 @@ class Trendline { ///``` final double? intercept; - ///Determines the visiblity of the trendline. + ///Determines the visibility of the trendline. /// ///Defaults to `true`. /// @@ -314,7 +369,7 @@ class Trendline { ///``` final MarkerSettings markerSettings; - ///Show/hides the legend for trenline. + ///Show/hides the legend for trendline. /// ///Defaults to `true`. /// @@ -333,7 +388,7 @@ class Trendline { ///``` final bool isVisibleInLegend; - ///Specifies the order of te polynomial for polynomial trendline. + ///Specifies the order of the polynomial for polynomial trendline. /// ///Defaults to `2`. /// @@ -375,6 +430,34 @@ class Trendline { ///``` final int period; + /// Callback which gets called while rendering the trendline. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// dataSource: chartData, + /// trendlines: [ + /// Trendline( + /// type: TrendlineType.linear, + /// onRenderDetailsUpdate: (TrendlineRenderParams args) { + /// print('Slope value: ' + args.slope[0]); + /// print('r-squared value: ' + args.rSquaredValue); + /// print('Intercept value (x): ' + args.intercept); + /// }), + /// ], + /// xValueMapper: (ChartSample data, _) => data.x, + /// yValueMapper: (ChartSample data, _) => data.y) + /// ], + /// ) + /// ); + ///} + ///``` + /// + final ChartTrendlineRenderCallback? onRenderDetailsUpdate; + @override // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) { @@ -436,64 +519,95 @@ class Trendline { ///Trendline renderer class for mutable fields and methods class TrendlineRenderer { /// Creates an argument constructor for Trendline renderer class - TrendlineRenderer(this._trendline) { - _opacity = _trendline.opacity; - _dashArray = _trendline.dashArray; - _fillColor = _trendline.color; - _visible = _trendline.isVisible; - _name = _trendline.name; + TrendlineRenderer(this.trendline) { + opacity = trendline.opacity; + dashArray = trendline.dashArray; + fillColor = trendline.color; + visible = trendline.isVisible; + name = trendline.name; } - final Trendline _trendline; - - /// Holds the collection of cartesian data points - List>? _pointsData; - late CartesianSeriesRenderer _seriesRenderer; - late _SlopeIntercept _slopeIntercept; - List? _polynomialSlopes; - late List _markerShapes; - late List _points; - //ignore: prefer_final_fields - late double _opacity; - //ignore: prefer_final_fields - List? _dashArray; - //ignore: prefer_final_fields - late Color _fillColor; - //ignore: prefer_final_fields - late bool _visible; - //ignore: prefer_final_fields - String? _name; - late bool _isNeedRender; - late AnimationController _animationController; - //ignore: prefer_final_fields - bool _isTrendlineRenderEvent = false; + /// Holds the value of trendline + final Trendline trendline; + + /// Holds the collection of Cartesian data points + List>? pointsData; + + /// Holds the slope intercept + _SlopeIntercept slopeIntercept = _SlopeIntercept(); + + /// Holds the slope intercept value for equation + _SlopeIntercept slopeInterceptData = _SlopeIntercept(); + + /// Holds the polynomial slopes + List? polynomialSlopes; + + /// Holds the polynomial slopes for equation + List? polynomialSlopesData; + + /// Holds the list of marker shapes + late List markerShapes; + + /// Holds the list of point value + late List points; + + /// Holds the opacity value + late double opacity; + + /// Holds the list of dash array + List? dashArray; + + /// Holds the fill color value + late Color fillColor; + + /// Specifies whether the trendline is visible + late bool visible; + + /// Specifies the value of name + String? name; + + /// Specifies whether the renderer is needed + late bool isNeedRender; + + /// Holds the value of animation controller + late AnimationController animationController; + + /// Checks whether the trendline rendered event is specified + bool isTrendlineRenderEvent = false; + + late SeriesRendererDetails _seriesRendererDetails; + + // In excel, the date is considered from Jan 1, 1900. To achieve the same scenario, we have considered the year from 1900. + // Reference link: https://support.microsoft.com/en-us/office/datevalue-function-df8b07d4-7761-4a93-bc33-b7471bbff252 + /// Holds the excel starting date value + final DateTime excelDate = DateTime(1900, 01, 01); /// Defines the data point of trendline CartesianChartPoint getDataPoint( dynamic x, num y, CartesianChartPoint sourcePoint, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, int index) { final CartesianChartPoint trendPoint = CartesianChartPoint(x, y); - trendPoint.x = (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) + trendPoint.x = (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) ? DateTime.fromMillisecondsSinceEpoch(x.floor()) : x; trendPoint.y = y; trendPoint.xValue = x; - trendPoint.pointColorMapper = _seriesRenderer._series.color; + trendPoint.pointColorMapper = _seriesRendererDetails.series.color; // trendPoint.index = index; trendPoint.yValue = y; trendPoint.isVisible = true; - seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX!, trendPoint.xValue); - seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY!, trendPoint.yValue); - seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX!, trendPoint.xValue); - seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY!, trendPoint.yValue); + seriesRendererDetails.minimumX = + math.min(seriesRendererDetails.minimumX!, trendPoint.xValue); + seriesRendererDetails.minimumY = + math.min(seriesRendererDetails.minimumY!, trendPoint.yValue); + seriesRendererDetails.maximumX = + math.max(seriesRendererDetails.maximumX!, trendPoint.xValue); + seriesRendererDetails.maximumY = + math.max(seriesRendererDetails.maximumY!, trendPoint.yValue); return trendPoint; } @@ -502,59 +616,68 @@ class TrendlineRenderer { List> points, dynamic xValues, List yValues, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, _SlopeIntercept slopeInterceptLinear) { num x1Linear, x2Linear; final List> pts = >[]; - if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { + if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { x1Linear = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[0], - -_trendline.backwardForecast); + -trendline.backwardForecast); x2Linear = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[xValues.length - 1], - _trendline.forwardForecast); + trendline.forwardForecast); } else { - x1Linear = xValues[0] - _trendline.backwardForecast; - x2Linear = xValues[xValues.length - 1] + _trendline.forwardForecast; + x1Linear = xValues[0] - trendline.backwardForecast; + x2Linear = xValues[xValues.length - 1] + trendline.forwardForecast; } - final num y1Linear = - slopeInterceptLinear.slope * x1Linear + slopeInterceptLinear.intercept; - final num y2Linear = - slopeInterceptLinear.slope * x2Linear + slopeInterceptLinear.intercept; + final num y1Linear = slopeInterceptLinear.slope! * x1Linear + + slopeInterceptLinear.intercept!; + final num y2Linear = slopeInterceptLinear.slope! * x2Linear + + slopeInterceptLinear.intercept!; pts.add(getDataPoint( - x1Linear, y1Linear, points[0], seriesRenderer, pts.length)); + x1Linear, y1Linear, points[0], seriesRendererDetails, pts.length)); pts.add(getDataPoint(x2Linear, y2Linear, points[points.length - 1], - seriesRenderer, pts.length)); + seriesRendererDetails, pts.length)); return pts; } /// Setting the linear range for trendline series void _setLinearRange(List> points, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final List xValues = []; + final List slopeInterceptXValues = []; final List yValues = []; int index = 0; + const int startXValue = 1; while (index < points.length) { final CartesianChartPoint point = points[index]; xValues.add(point.xValue ?? point.x); - (!(seriesRenderer._series is RangeAreaSeries || - seriesRenderer._series is RangeColumnSeries || - seriesRenderer._series is HiloSeries || - seriesRenderer._series is HiloOpenCloseSeries || - seriesRenderer._series is CandleSeries)) + slopeInterceptXValues.add((seriesRendererDetails + .xAxisDetails?.axisRenderer is DateTimeAxisRenderer) + ? point.x.difference(excelDate).inDays + : startXValue + index); + (!(seriesRendererDetails.series is RangeAreaSeries || + seriesRendererDetails.series is RangeColumnSeries || + seriesRendererDetails.series is HiloSeries || + seriesRendererDetails.series is HiloOpenCloseSeries || + seriesRendererDetails.series is CandleSeries)) ? yValues.add(point.yValue ?? point.y) - : yValues.add(_trendline.valueField.toLowerCase() == 'low' + : yValues.add(trendline.valueField.toLowerCase() == 'low' ? point.low : point.high); index++; } - _slopeIntercept = _findSlopeIntercept(xValues, yValues, points); - _pointsData = getLinearPoints( - points, xValues, yValues, seriesRenderer, _slopeIntercept); + slopeIntercept = _findSlopeIntercept(xValues, yValues, points); + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) + pointsData = getLinearPoints( + points, xValues, yValues, seriesRendererDetails, slopeIntercept); + slopeInterceptData = + _findSlopeIntercept(slopeInterceptXValues, yValues, points); } ///Defines Exponential Points @@ -562,68 +685,88 @@ class TrendlineRenderer { List> points, dynamic xValues, List yValues, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, _SlopeIntercept slopeInterceptExpo) { num x1, x2, x3; final int midPoint = (points.length / 2).round(); final List> ptsExpo = >[]; - if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { + if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { x1 = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[0], - -_trendline.backwardForecast); + -trendline.backwardForecast); x2 = xValues[midPoint - 1]; x3 = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[xValues.length - 1], - _trendline.forwardForecast); + trendline.forwardForecast); } else { - x1 = xValues[0] - _trendline.backwardForecast; + x1 = xValues[0] - trendline.backwardForecast; x2 = xValues[midPoint - 1]; - x3 = xValues[xValues.length - 1] + _trendline.forwardForecast; + x3 = xValues[xValues.length - 1] + trendline.forwardForecast; + } + final num y1 = slopeInterceptExpo.intercept! * + math.exp(slopeInterceptExpo.slope! * x1); + + final num y2 = slopeInterceptExpo.intercept! * + math.exp(slopeInterceptExpo.slope! * x2); + + final num y3 = slopeInterceptExpo.intercept! * + math.exp(slopeInterceptExpo.slope! * x3); + ptsExpo.add(getDataPoint(x1, y1.isNaN ? 0 : y1, points[0], + seriesRendererDetails, ptsExpo.length)); + ptsExpo.add(getDataPoint(x2, y2.isNaN ? 0 : y2, points[midPoint - 1], + seriesRendererDetails, ptsExpo.length)); + ptsExpo.add(getDataPoint(x2, y2.isNaN ? 0 : y2, points[midPoint - 1], + seriesRendererDetails, ptsExpo.length)); + ptsExpo.add(getDataPoint(x3, y3.isNaN ? 0 : y3, points[points.length - 1], + seriesRendererDetails, ptsExpo.length)); + // avoid rendering trendline when values are NaN + if (y1.isNaN || y2.isNaN || y3.isNaN) { + for (int i = 0; i < ptsExpo.length; i++) { + ptsExpo[i].x = 0; + ptsExpo[i].y = 0; + } } - final num y1 = - slopeInterceptExpo.intercept * math.exp(slopeInterceptExpo.slope * x1); - - final num y2 = - slopeInterceptExpo.intercept * math.exp(slopeInterceptExpo.slope * x2); - - final num y3 = - slopeInterceptExpo.intercept * math.exp(slopeInterceptExpo.slope * x3); - ptsExpo - .add(getDataPoint(x1, y1, points[0], seriesRenderer, ptsExpo.length)); - ptsExpo.add(getDataPoint( - x2, y2, points[midPoint - 1], seriesRenderer, ptsExpo.length)); - ptsExpo.add(getDataPoint( - x3, y3, points[points.length - 1], seriesRenderer, ptsExpo.length)); return ptsExpo; } /// Setting the exponential range for trendline series void _setExponentialRange(List> points, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final List xValues = []; + final List slopeInterceptXValues = []; final List yValues = []; int index = 0; + const int startXValue = 1; while (index < points.length) { final CartesianChartPoint point = points[index]; xValues.add(point.xValue ?? point.x); - (!(seriesRenderer._series is RangeAreaSeries || - seriesRenderer._series is RangeColumnSeries || - seriesRenderer._series is HiloSeries || - seriesRenderer._series is HiloOpenCloseSeries || - seriesRenderer._series is CandleSeries)) + slopeInterceptXValues.add(seriesRendererDetails.xAxisDetails?.axisRenderer + is DateTimeAxisRenderer + ? seriesRendererDetails.dataPoints[index].x + .difference(excelDate) + .inDays + : startXValue + index); + (!(seriesRendererDetails.series is RangeAreaSeries || + seriesRendererDetails.series is RangeColumnSeries || + seriesRendererDetails.series is HiloSeries || + seriesRendererDetails.series is HiloOpenCloseSeries || + seriesRendererDetails.series is CandleSeries)) ? yValues.add(math.log(point.yValue ?? point.y)) - : yValues.add(_trendline.valueField.toLowerCase() == 'low' + : yValues.add(trendline.valueField.toLowerCase() == 'low' ? math.log(point.low) : math.log(point.high)); index++; } - _slopeIntercept = _findSlopeIntercept(xValues, yValues, points); - _pointsData = getExponentialPoints( - points, xValues, yValues, seriesRenderer, _slopeIntercept); + slopeIntercept = _findSlopeIntercept(xValues, yValues, points); + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) + pointsData = getExponentialPoints( + points, xValues, yValues, seriesRendererDetails, slopeIntercept); + slopeInterceptData = + _findSlopeIntercept(slopeInterceptXValues, yValues, points); } ///Defines Power Points @@ -631,75 +774,96 @@ class TrendlineRenderer { List> points, dynamic xValues, List yValues, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, _SlopeIntercept slopeInterceptPow) { num x1, x2, x3; final int midPoint = (points.length / 2).round(); final List> ptsPow = >[]; - if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { + if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { x1 = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[0], - -_trendline.backwardForecast); + -trendline.backwardForecast); x2 = xValues[midPoint - 1]; x3 = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[xValues.length - 1], - _trendline.forwardForecast); + trendline.forwardForecast); } else { - x1 = xValues[0] - _trendline.backwardForecast; + x1 = xValues[0] - trendline.backwardForecast; x1 = x1 > -1 ? x1 : 0; x2 = xValues[midPoint - 1]; - x3 = xValues[xValues.length - 1] + _trendline.forwardForecast; + x3 = xValues[xValues.length - 1] + trendline.forwardForecast; } final num y1 = x1 == 0 ? 0 - : slopeInterceptPow.intercept * math.pow(x1, slopeInterceptPow.slope); + : slopeInterceptPow.intercept! * math.pow(x1, slopeInterceptPow.slope!); final num y2 = - slopeInterceptPow.intercept * math.pow(x2, slopeInterceptPow.slope); + slopeInterceptPow.intercept! * math.pow(x2, slopeInterceptPow.slope!); final num y3 = - slopeInterceptPow.intercept * math.pow(x3, slopeInterceptPow.slope); - ptsPow.add(getDataPoint(x1, y1, points[0], seriesRenderer, ptsPow.length)); - ptsPow.add(getDataPoint( - x2, y2, points[midPoint - 1], seriesRenderer, ptsPow.length)); - ptsPow.add(getDataPoint( - x3, y3, points[points.length - 1], seriesRenderer, ptsPow.length)); + slopeInterceptPow.intercept! * math.pow(x3, slopeInterceptPow.slope!); + ptsPow.add(getDataPoint(x1, y1.isNaN ? 0 : y1, points[0], + seriesRendererDetails, ptsPow.length)); + ptsPow.add(getDataPoint(x2, y2.isNaN ? 0 : y2, points[midPoint - 1], + seriesRendererDetails, ptsPow.length)); + ptsPow.add(getDataPoint(x3, y3.isNaN ? 0 : y3, points[points.length - 1], + seriesRendererDetails, ptsPow.length)); + // avoid rendering trendline when values are NaN + if (y1.isNaN || y2.isNaN || y3.isNaN) { + for (int i = 0; i < ptsPow.length; i++) { + ptsPow[i].x = 0; + ptsPow[i].y = 0; + } + } return ptsPow; } /// Setting the power range values for trendline series void _setPowerRange(List> points, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final List xValues = []; + final List slopeInterceptXValues = []; final List yValues = []; final List powerPoints = []; int index = 0; + const int startXValue = 1; while (index < points.length) { final CartesianChartPoint point = points[index]; powerPoints.add(point.xValue ?? point.x); - final dynamic xVal = point.xValue != null && - (math.log(point.xValue)).isFinite - ? math.log(point.xValue) - : (seriesRenderer._xAxisRenderer is CategoryAxisRenderer || - seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) - ? point.xValue - : point.x; + final dynamic xVal = + point.xValue != null && (math.log(point.xValue)).isFinite + ? math.log(point.xValue) + : (seriesRendererDetails.xAxisDetails?.axisRenderer + is CategoryAxisRenderer || + seriesRendererDetails.xAxisDetails?.axisRenderer + is DateTimeCategoryAxisRenderer) + ? point.xValue + : point.x; xValues.add(xVal); - (!(seriesRenderer._series is RangeAreaSeries || - seriesRenderer._series is RangeColumnSeries || - seriesRenderer._series is HiloSeries || - seriesRenderer._series is HiloOpenCloseSeries || - seriesRenderer._series is CandleSeries)) + slopeInterceptXValues.add(math.log(seriesRendererDetails + .xAxisDetails?.axisRenderer is DateTimeAxisRenderer + ? seriesRendererDetails.dataPoints[index].x + .difference(excelDate) + .inDays + : startXValue + index)); + (!(seriesRendererDetails.series is RangeAreaSeries || + seriesRendererDetails.series is RangeColumnSeries || + seriesRendererDetails.series is HiloSeries || + seriesRendererDetails.series is HiloOpenCloseSeries || + seriesRendererDetails.series is CandleSeries)) ? yValues.add(math.log(point.yValue ?? point.y)) - : yValues.add(_trendline.valueField.toLowerCase() == 'low' + : yValues.add(trendline.valueField.toLowerCase() == 'low' ? math.log(point.low) : math.log(point.high)); index++; } - _slopeIntercept = _findSlopeIntercept(xValues, yValues, points); - _pointsData = getPowerPoints( - points, powerPoints, yValues, seriesRenderer, _slopeIntercept); + slopeIntercept = _findSlopeIntercept(xValues, yValues, points); + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) + pointsData = getPowerPoints( + points, powerPoints, yValues, seriesRendererDetails, slopeIntercept); + slopeInterceptData = + _findSlopeIntercept(slopeInterceptXValues, yValues, points); } ///Defines Logarithmic Points @@ -707,76 +871,88 @@ class TrendlineRenderer { List> points, dynamic xValues, List yValues, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, _SlopeIntercept slopeInterceptLog) { num x1, x2, x3; final int midPoint = (points.length / 2).round(); final List> ptsLog = >[]; - if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { + if (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) { x1 = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[0], - -_trendline.backwardForecast); + -trendline.backwardForecast); x2 = xValues[midPoint - 1]; x3 = _increaseDateTimeForecast( - seriesRenderer._xAxisRenderer as DateTimeAxisRenderer, + seriesRendererDetails.xAxisDetails as DateTimeAxisRenderer, xValues[xValues.length - 1], - _trendline.forwardForecast); + trendline.forwardForecast); } else { - x1 = xValues[0] - _trendline.backwardForecast; + x1 = xValues[0] - trendline.backwardForecast; x2 = xValues[midPoint - 1]; - x3 = xValues[xValues.length - 1] + _trendline.forwardForecast; + x3 = xValues[xValues.length - 1] + trendline.forwardForecast; } - final num y1 = slopeInterceptLog.intercept + - (slopeInterceptLog.slope * + final num y1 = slopeInterceptLog.intercept! + + (slopeInterceptLog.slope! * ((math.log(x1)).isFinite ? math.log(x1) : x1)); - final num y2 = slopeInterceptLog.intercept + - (slopeInterceptLog.slope * + final num y2 = slopeInterceptLog.intercept! + + (slopeInterceptLog.slope! * ((math.log(x2)).isFinite ? math.log(x2) : x2)); - final num y3 = slopeInterceptLog.intercept + - (slopeInterceptLog.slope * + final num y3 = slopeInterceptLog.intercept! + + (slopeInterceptLog.slope! * ((math.log(x3)).isFinite ? math.log(x3) : x3)); - ptsLog.add(getDataPoint(x1, y1, points[0], seriesRenderer, ptsLog.length)); - ptsLog.add(getDataPoint( - x2, y2, points[midPoint - 1], seriesRenderer, ptsLog.length)); + ptsLog.add( + getDataPoint(x1, y1, points[0], seriesRendererDetails, ptsLog.length)); ptsLog.add(getDataPoint( - x3, y3, points[points.length - 1], seriesRenderer, ptsLog.length)); + x2, y2, points[midPoint - 1], seriesRendererDetails, ptsLog.length)); + ptsLog.add(getDataPoint(x3, y3, points[points.length - 1], + seriesRendererDetails, ptsLog.length)); return ptsLog; } - /// Setting the logarithmic range for _trendline series + /// Setting the logarithmic range for trendline series void _setLogarithmicRange(List> points, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final List xLogValue = []; + final List slopeInterceptXLogValue = []; final List yLogValue = []; final List xPointsLgr = []; int index = 0; + const int startXValue = 1; while (index < points.length) { final CartesianChartPoint point = points[index]; xPointsLgr.add(point.xValue ?? point.x); - final dynamic xVal = (point.xValue != null && - (math.log(point.xValue)).isFinite) - ? math.log(point.xValue) - : (seriesRenderer._xAxisRenderer is CategoryAxisRenderer || - seriesRenderer._xAxisRenderer is DateTimeCategoryAxisRenderer) - ? point.xValue - : point.x; + final dynamic xVal = + (point.xValue != null && (math.log(point.xValue)).isFinite) + ? math.log(point.xValue) + : (seriesRendererDetails.xAxisDetails?.axisRenderer + is CategoryAxisRenderer || + seriesRendererDetails.xAxisDetails?.axisRenderer + is DateTimeCategoryAxisRenderer) + ? point.xValue + : point.x; xLogValue.add(xVal); - (!(seriesRenderer._series is RangeAreaSeries || - seriesRenderer._series is RangeColumnSeries || - seriesRenderer._series is HiloSeries || - seriesRenderer._series is HiloOpenCloseSeries || - seriesRenderer._series is CandleSeries)) + slopeInterceptXLogValue.add(math.log(seriesRendererDetails + .xAxisDetails?.axisRenderer is DateTimeAxisRenderer + ? points[index].x.difference(excelDate).inDays + : startXValue + index)); + (!(seriesRendererDetails.series is RangeAreaSeries || + seriesRendererDetails.series is RangeColumnSeries || + seriesRendererDetails.series is HiloSeries || + seriesRendererDetails.series is HiloOpenCloseSeries || + seriesRendererDetails.series is CandleSeries)) ? yLogValue.add(point.yValue ?? point.y) - : yLogValue.add(_trendline.valueField.toLowerCase() == 'low' + : yLogValue.add(trendline.valueField.toLowerCase() == 'low' ? point.low : point.high); index++; } - _slopeIntercept = _findSlopeIntercept(xLogValue, yLogValue, points); - _pointsData = getLogarithmicPoints( - points, xPointsLgr, yLogValue, seriesRenderer, _slopeIntercept); + slopeIntercept = _findSlopeIntercept(xLogValue, yLogValue, points); + if (!slopeIntercept.slope!.isNaN && !slopeIntercept.intercept!.isNaN) + pointsData = getLogarithmicPoints( + points, xPointsLgr, yLogValue, seriesRendererDetails, slopeIntercept); + slopeInterceptData = + _findSlopeIntercept(slopeInterceptXLogValue, yLogValue, points); } ///Defines Polynomial points @@ -784,29 +960,68 @@ class TrendlineRenderer { List> points, dynamic xValues, List yValues, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { //ignore: unused_local_variable final int midPoint = (points.length / 2).round(); + const int startXValue = 1; List> pts = >[]; - _polynomialSlopes = - List.filled(_trendline.polynomialOrder + 1, null); + polynomialSlopes = + List.filled(trendline.polynomialOrder + 1, null); for (int i = 0; i < xValues.length; i++) { final dynamic xVal = xValues[i]; final num yVal = yValues[i]; - for (int j = 0; j <= _trendline.polynomialOrder; j++) { - _polynomialSlopes![j] ??= 0; - _polynomialSlopes![j] += pow(xVal.toDouble(), j) * yVal; + for (int j = 0; j <= trendline.polynomialOrder; j++) { + polynomialSlopes![j] ??= 0; + polynomialSlopes![j] += pow(xVal.toDouble(), j) * yVal; + } + } + + final List matrix = _getMatrix(trendline, xValues); + if (!_gaussJordanElimination(matrix, polynomialSlopes!)) { + //The trendline will not be generated if there is just one data point or if the x and y values are the same, + //for example (1,1), (1,1). So, the line was commented. And now marker alone will be rendered in this case. + // _polynomialSlopes = null; + } + pts = _getPoints(points, xValues, yValues, seriesRendererDetails); + if (trendline.onRenderDetailsUpdate != null) { + polynomialSlopesData = + List.filled(trendline.polynomialOrder + 1, 0); + + for (int i = 0; i < xValues.length; i++) { + final num yVal = yValues[i]; + for (int j = 0; j <= trendline.polynomialOrder; j++) { + polynomialSlopesData![j] += pow( + seriesRendererDetails.xAxisDetails?.axisRenderer + is DateTimeAxisRenderer + ? points[i].x.difference(excelDate).inDays + : startXValue + i.toDouble(), + j) * + yVal; + } + } + final List xData = []; + for (int i = 0; i < xValues.length; i++) { + xData.add(seriesRendererDetails.xAxisDetails?.axisRenderer + is DateTimeAxisRenderer + ? points[i].x.difference(excelDate).inDays + : startXValue + i.toDouble()); } + final List matrix = _getMatrix(trendline, xData); + // To find the prompt polynomial slopes for the trendline equation, gaussJordanElimination method is used here. + if (!_gaussJordanElimination(matrix, polynomialSlopesData!)) {} } + return pts; + } + /// Get matrix values for polynomial type + List _getMatrix(Trendline trendline, dynamic xValues) { final List numArray = - List.filled(2 * _trendline.polynomialOrder + 1, null); + List.filled(2 * trendline.polynomialOrder + 1, null); final List matrix = - List.filled(_trendline.polynomialOrder + 1, null); - - for (int i = 0; i <= _trendline.polynomialOrder; i++) { - matrix[i] = List.filled(_trendline.polynomialOrder + 1, null); + List.filled(trendline.polynomialOrder + 1, null); + for (int i = 0; i <= trendline.polynomialOrder; i++) { + matrix[i] = List.filled(trendline.polynomialOrder + 1, null); } num num1 = 0; @@ -820,42 +1035,36 @@ class TrendlineRenderer { } } - for (int i = 0; i <= _trendline.polynomialOrder; i++) { - for (int j = 0; j <= _trendline.polynomialOrder; j++) { + for (int i = 0; i <= trendline.polynomialOrder; i++) { + for (int j = 0; j <= trendline.polynomialOrder; j++) { matrix[i][j] = numArray[i + j]; } } - if (!_gaussJordanElimination(matrix, _polynomialSlopes!)) { - //The trendline will not be generated if there is just one data point or if the x and y values are the same, - //for example (1,1), (1,1). So, the line was commented. And now marker alone will be rendered in this case. - // _polynomialSlopes = null; - } - pts = _getPoints(points, xValues, yValues, seriesRenderer); - return pts; + return matrix; } /// Setting the polynomial range for trendline series void _setPolynomialRange(List> points, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final List xPolyValues = []; final List yPolyValues = []; int index = 0; while (index < points.length) { final CartesianChartPoint point = points[index]; xPolyValues.add(point.xValue ?? point.x); - (!(seriesRenderer._series is RangeAreaSeries || - seriesRenderer._series is RangeColumnSeries || - seriesRenderer._series is HiloSeries || - seriesRenderer._series is HiloOpenCloseSeries || - seriesRenderer._series is CandleSeries)) + (!(seriesRendererDetails.series is RangeAreaSeries || + seriesRendererDetails.series is RangeColumnSeries || + seriesRendererDetails.series is HiloSeries || + seriesRendererDetails.series is HiloOpenCloseSeries || + seriesRendererDetails.series is CandleSeries)) ? yPolyValues.add(point.yValue ?? point.y) - : yPolyValues.add(_trendline.valueField.toLowerCase() == 'low' + : yPolyValues.add(trendline.valueField.toLowerCase() == 'low' ? point.low : point.high); index++; } - _pointsData = - _getPolynomialPoints(points, xPolyValues, yPolyValues, seriesRenderer); + pointsData = _getPolynomialPoints( + points, xPolyValues, yPolyValues, seriesRendererDetails); } /// To return points list @@ -863,10 +1072,10 @@ class TrendlineRenderer { List> points, dynamic xValues, List yValues, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { //ignore: unused_local_variable final int midPoint = (points.length / 2).round(); - final List _polynomialSlopesList = _polynomialSlopes!; + final List _polynomialSlopesList = polynomialSlopes!; final List> pts = >[]; @@ -874,32 +1083,32 @@ class TrendlineRenderer { dynamic xVal; num yVal; final num _backwardForecast = - seriesRenderer._xAxisRenderer is DateTimeAxisRenderer - ? _getForecastDate(seriesRenderer._xAxisRenderer!, false) - : _trendline.backwardForecast; + seriesRendererDetails.xAxisDetails is DateTimeAxisDetails + ? _getForecastDate(seriesRendererDetails.xAxisDetails!, false) + : trendline.backwardForecast; final num _forwardForecast = - seriesRenderer._xAxisRenderer is DateTimeAxisRenderer - ? _getForecastDate(seriesRenderer._xAxisRenderer!, true) - : _trendline.forwardForecast; + seriesRendererDetails.xAxisDetails is DateTimeAxisDetails + ? _getForecastDate(seriesRendererDetails.xAxisDetails!, true) + : trendline.forwardForecast; for (int index = 1; index <= _polynomialSlopesList.length; index++) { if (index == 1) { xVal = xValues[0] - _backwardForecast.toDouble(); yVal = _getPolynomialYValue(_polynomialSlopesList, xVal); - pts.add( - getDataPoint(xVal, yVal, points[0], seriesRenderer, pts.length)); + pts.add(getDataPoint( + xVal, yVal, points[0], seriesRendererDetails, pts.length)); } else if (index == _polynomialSlopesList.length) { xVal = xValues[points.length - 1] + _forwardForecast.toDouble(); yVal = _getPolynomialYValue(_polynomialSlopesList, xVal); - pts.add(getDataPoint( - xVal, yVal, points[points.length - 1], seriesRenderer, pts.length)); + pts.add(getDataPoint(xVal, yVal, points[points.length - 1], + seriesRendererDetails, pts.length)); } else { - x1 += (points.length + _trendline.forwardForecast) / + x1 += (points.length + trendline.forwardForecast) / _polynomialSlopesList.length; xVal = xValues[x1.floor() - 1] * 1.0; yVal = _getPolynomialYValue(_polynomialSlopesList, xVal); - pts.add(getDataPoint( - xVal, yVal, points[x1.floor() - 1], seriesRenderer, pts.length)); + pts.add(getDataPoint(xVal, yVal, points[x1.floor() - 1], + seriesRendererDetails, pts.length)); } } return pts; @@ -919,12 +1128,12 @@ class TrendlineRenderer { List> points, List xValues, List yValues, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final List> pts = >[]; - int periods = _trendline.period >= points.length + int periods = trendline.period >= points.length ? points.length - 1 - : _trendline.period; + : trendline.period; periods = max(2, periods); int? y; dynamic x; @@ -941,8 +1150,8 @@ class TrendlineRenderer { y = ((periods - nullCount) <= 0) ? null : (y! ~/ (periods - nullCount)); if (y != null && !y.isNaN && index + periods < xValues.length + 1) { x = xValues[periods - 1 + index]; - pts.add(getDataPoint( - x, y, points[periods - 1 + index], seriesRenderer, pts.length)); + pts.add(getDataPoint(x, y, points[periods - 1 + index], + seriesRendererDetails, pts.length)); } } return pts; @@ -950,7 +1159,7 @@ class TrendlineRenderer { /// Setting the moving average range for trendline series void _setMovingAverageRange(List> points, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final List xValues = [], xAvgValues = []; final List yValues = []; @@ -958,18 +1167,18 @@ class TrendlineRenderer { final dynamic point = points[index]; xAvgValues.add(point.xValue ?? point.x); xValues.add(index + 1); - (!(seriesRenderer._series is RangeAreaSeries || - seriesRenderer._series is RangeColumnSeries || - seriesRenderer._series is HiloSeries || - seriesRenderer._series is HiloOpenCloseSeries || - seriesRenderer._series is CandleSeries)) + (!(seriesRendererDetails.series is RangeAreaSeries || + seriesRendererDetails.series is RangeColumnSeries || + seriesRendererDetails.series is HiloSeries || + seriesRendererDetails.series is HiloOpenCloseSeries || + seriesRendererDetails.series is CandleSeries)) ? yValues.add(point.yValue ?? point.y) - : yValues.add(_trendline.valueField.toLowerCase() == 'low' + : yValues.add(trendline.valueField.toLowerCase() == 'low' ? point.low : point.high); } - _pointsData = - getMovingAveragePoints(points, xAvgValues, yValues, seriesRenderer); + pointsData = getMovingAveragePoints( + points, xAvgValues, yValues, seriesRendererDetails); } /// Setting the slope intercept for trendline series @@ -988,12 +1197,12 @@ class TrendlineRenderer { xxAvg += xValues[index].toDouble() * xValues[index].toDouble(); index++; } - if (_trendline.intercept != null && - _trendline.intercept != 0 && - (_trendline.type == TrendlineType.linear || - _trendline.type == TrendlineType.exponential)) { - intercept = _trendline.intercept!.toDouble(); - switch (_trendline.type) { + if (trendline.intercept != null && + trendline.intercept != 0 && + (trendline.type == TrendlineType.linear || + trendline.type == TrendlineType.exponential)) { + intercept = trendline.intercept!.toDouble(); + switch (trendline.type) { case TrendlineType.linear: slope = (xyAvg - (intercept * xAvg)) / xxAvg; break; @@ -1007,39 +1216,38 @@ class TrendlineRenderer { slope = ((points.length * xyAvg) - (xAvg * yAvg)) / ((points.length * xxAvg) - (xAvg * xAvg)); - intercept = (_trendline.type == TrendlineType.exponential || - _trendline.type == TrendlineType.power) + intercept = (trendline.type == TrendlineType.exponential || + trendline.type == TrendlineType.power) ? math.exp((yAvg - (slope * xAvg)) / points.length) : (yAvg - (slope * xAvg)) / points.length; } - final _SlopeIntercept _slopeIntercept = _SlopeIntercept(); - _slopeIntercept.slope = slope; - _slopeIntercept.intercept = intercept; - return _slopeIntercept; + slopeIntercept.slope = slope; + slopeIntercept.intercept = intercept; + return slopeIntercept; } /// To set initial data source for trendlines void _initDataSource( - SfCartesianChart chart, CartesianSeriesRenderer seriesRenderer) { - if (_pointsData!.isNotEmpty) { - switch (_trendline.type) { + SfCartesianChart chart, SeriesRendererDetails seriesRendererDetails) { + if (pointsData!.isNotEmpty) { + switch (trendline.type) { case TrendlineType.linear: - _setLinearRange(_pointsData!, seriesRenderer); + _setLinearRange(pointsData!, seriesRendererDetails); break; case TrendlineType.exponential: - _setExponentialRange(_pointsData!, seriesRenderer); + _setExponentialRange(pointsData!, seriesRendererDetails); break; case TrendlineType.power: - _setPowerRange(_pointsData!, seriesRenderer); + _setPowerRange(pointsData!, seriesRendererDetails); break; case TrendlineType.logarithmic: - _setLogarithmicRange(_pointsData!, seriesRenderer); + _setLogarithmicRange(pointsData!, seriesRendererDetails); break; case TrendlineType.polynomial: - _setPolynomialRange(_pointsData!, seriesRenderer); + _setPolynomialRange(pointsData!, seriesRendererDetails); break; case TrendlineType.movingAverage: - _setMovingAverageRange(_pointsData!, seriesRenderer); + _setMovingAverageRange(pointsData!, seriesRendererDetails); break; default: break; @@ -1048,71 +1256,73 @@ class TrendlineRenderer { } /// To find the actual points of trend line series - void calculateTrendlinePoints(CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState) { - final Rect rect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); - _points = []; - if (seriesRenderer._series.trendlines != null && _pointsData != null) { - for (int i = 0; i < _pointsData!.length; i++) { - if (_pointsData![i].x != null && _pointsData![i].y != null) { - final _ChartLocation currentChartPoint = _pointsData![i].markerPoint = - _calculatePoint( - (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) - ? _pointsData![i].xValue - : _pointsData![i].x, - _pointsData![i].y, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + void calculateTrendlinePoints(SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties) { + final Rect rect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + points = []; + if (seriesRendererDetails.series.trendlines != null && pointsData != null) { + for (int i = 0; i < pointsData!.length; i++) { + if (pointsData![i].x != null && pointsData![i].y != null) { + final ChartLocation currentChartPoint = pointsData![i].markerPoint = + calculatePoint( + (seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer) + ? pointsData![i].xValue + : pointsData![i].x, + pointsData![i].y, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - _points.add(Offset(currentChartPoint.x, currentChartPoint.y)); - _pointsData![i].region = Rect.fromLTRB( - _points[i].dx, _points[i].dy, _points[i].dx, _points[i].dy); + points.add(Offset(currentChartPoint.x, currentChartPoint.y)); + pointsData![i].region = Rect.fromLTRB( + points[i].dx, points[i].dy, points[i].dx, points[i].dy); } } - _calculateMarkerShapesPoint(seriesRenderer); + _calculateMarkerShapesPoint(seriesRendererDetails); } } /// Calculate marker shapes for trendlines - void _calculateMarkerShapesPoint(CartesianSeriesRenderer seriesRenderer) { - _markerShapes = []; - for (int i = 0; i < _pointsData!.length; i++) { - final CartesianChartPoint point = _pointsData![i]; - final DataMarkerType markerType = _trendline.markerSettings.shape; - final Size size = Size( - _trendline.markerSettings.width, _trendline.markerSettings.height); - _markerShapes.add(_getMarkerShapesPath( + void _calculateMarkerShapesPoint( + SeriesRendererDetails seriesRendererDetails) { + markerShapes = []; + for (int i = 0; i < pointsData!.length; i++) { + final CartesianChartPoint point = pointsData![i]; + final DataMarkerType markerType = trendline.markerSettings.shape; + final Size size = + Size(trendline.markerSettings.width, trendline.markerSettings.height); + markerShapes.add(getMarkerShapesPath( markerType, Offset(point.markerPoint!.x, point.markerPoint!.y), size, - seriesRenderer)); + seriesRendererDetails)); } } /// To set data source for trendlines - void _setDataSource( - CartesianSeriesRenderer? seriesRenderer, SfCartesianChart chart) { - if (seriesRenderer?._series != null) { - _seriesRenderer = seriesRenderer!; - _pointsData = seriesRenderer._dataPoints; - if (seriesRenderer is _StackedSeriesRenderer) { - for (int i = 0; i < _pointsData!.length; i++) { - _pointsData![i].y = seriesRenderer._stackingValues[0].endValues[i]; - _pointsData![i].yValue = - seriesRenderer._stackingValues[0].endValues[i]; + void setDataSource( + SeriesRendererDetails? seriesRendererDetails, SfCartesianChart chart) { + if (seriesRendererDetails?.series != null) { + _seriesRendererDetails = seriesRendererDetails!; + pointsData = seriesRendererDetails.dataPoints; + if (seriesRendererDetails.renderer is StackedSeriesRenderer) { + for (int i = 0; i < pointsData!.length; i++) { + pointsData![i].y = + seriesRendererDetails.stackingValues[0].endValues[i]; + pointsData![i].yValue = + seriesRendererDetails.stackingValues[0].endValues[i]; } } - _initDataSource(chart, _seriesRenderer); + _initDataSource(chart, _seriesRendererDetails); } } /// To obtain control points for type curve trendlines - List _getControlPoints(List _dataPoints, int index) { + List getControlPoints(List _dataPoints, int index) { List yCoef = []; final List controlPoints = []; final List xValues = [], yValues = []; @@ -1120,16 +1330,17 @@ class TrendlineRenderer { xValues.add(_dataPoints[i].dx); yValues.add(_dataPoints[i].dy); } - yCoef = _naturalSpline( + yCoef = naturalSpline( xValues, yValues, yCoef, xValues.length, SplineType.natural); - return _calculateControlPoints(xValues, yValues, yCoef[index]!.toDouble(), + return calculateControlPoints(xValues, yValues, yCoef[index]!.toDouble(), yCoef[index + 1]!.toDouble(), index, controlPoints); } /// It returns the date-time values of trendline series int _increaseDateTimeForecast( DateTimeAxisRenderer axisRenderer, int value, num interval) { - final DateTimeAxis axis = axisRenderer._axis as DateTimeAxis; + final DateTimeAxis axis = + AxisHelper.getAxisRendererDetails(axisRenderer).axis as DateTimeAxis; DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(value); switch (axis.intervalType) { case DateTimeIntervalType.years: @@ -1217,9 +1428,9 @@ class TrendlineRenderer { matrix[index3][index4_1] = num2; ++index4_1; } - final num num3 = _polynomialSlopes![index2]; - _polynomialSlopes![index2] = _polynomialSlopes![index3]; - _polynomialSlopes![index3] = num3; + final num num3 = _polynomialSlopesList[index2]; + _polynomialSlopesList[index2] = _polynomialSlopesList[index3]; + _polynomialSlopesList[index3] = num3; } numArray2[index1] = index2; numArray1[index1] = index3; @@ -1233,7 +1444,7 @@ class TrendlineRenderer { matrix[index3][iindex4] *= num4; ++iindex4; } - _polynomialSlopes![index3] *= num4; + _polynomialSlopesList[index3] *= num4; int iandex4 = 0; while (iandex4 < length) { if (iandex4 != index3) { @@ -1244,7 +1455,8 @@ class TrendlineRenderer { matrix[iandex4][index5] -= matrix[index3][index5] * num2; ++index5; } - _polynomialSlopes![iandex4] -= _polynomialSlopes![index3] * num2; + _polynomialSlopesList[iandex4] -= + _polynomialSlopesList[index3] * num2; } ++iandex4; } @@ -1266,36 +1478,39 @@ class TrendlineRenderer { /// It returns the polynomial points List getPolynomialCurve( List> points, - CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState) { + SeriesRendererDetails seriesRendererDetails, + CartesianStateProperties stateProperties) { final List polyPoints = []; - final dynamic start = seriesRenderer._xAxisRenderer is DateTimeAxisRenderer - ? points[0].xValue - : points[0].x; - final dynamic end = seriesRenderer._xAxisRenderer is DateTimeAxisRenderer - ? points[points.length - 1].xValue - : points[points.length - 1].xValue; + final dynamic start = + seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer + ? points[0].xValue + : points[0].x; + final dynamic end = + seriesRendererDetails.xAxisDetails is DateTimeAxisRenderer + ? points[points.length - 1].xValue + : points[points.length - 1].xValue; for (dynamic x = start; polyPoints.length <= 100; x += (end - start) / 100) { - final double y = _getPolynomialYValue(_polynomialSlopes!, x); - final _ChartLocation position = _calculatePoint( + final double y = _getPolynomialYValue(polynomialSlopes!, x); + final ChartLocation position = calculatePoint( x, y, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, - _chartState._chartAxis._axisClipRect); + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + stateProperties.chartAxis.axisClipRect); polyPoints.add(Offset(position.x, position.y)); } return polyPoints; } /// To return predicted forecast values - int _getForecastDate(ChartAxisRenderer axisRenderer, bool _isForward) { + int _getForecastDate( + ChartAxisRendererDetails axisRendererDetails, bool _isForward) { Duration duration = const Duration(seconds: 0); - final DateTimeAxis axis = axisRenderer._axis as DateTimeAxis; + final DateTimeAxis axis = axisRendererDetails.axis as DateTimeAxis; switch (axis.intervalType) { case DateTimeIntervalType.auto: duration = const Duration(seconds: 0); @@ -1304,51 +1519,51 @@ class TrendlineRenderer { duration = Duration( days: (365.25 * (_isForward - ? _trendline.forwardForecast - : _trendline.backwardForecast)) + ? trendline.forwardForecast + : trendline.backwardForecast)) .round()); break; case DateTimeIntervalType.months: duration = Duration( days: 31 * (_isForward - ? _trendline.forwardForecast - : _trendline.backwardForecast) + ? trendline.forwardForecast + : trendline.backwardForecast) .round()); break; case DateTimeIntervalType.days: duration = Duration( days: (_isForward - ? _trendline.forwardForecast - : _trendline.backwardForecast) + ? trendline.forwardForecast + : trendline.backwardForecast) .round()); break; case DateTimeIntervalType.hours: duration = Duration( hours: (_isForward - ? _trendline.forwardForecast - : _trendline.backwardForecast) + ? trendline.forwardForecast + : trendline.backwardForecast) .round()); break; case DateTimeIntervalType.minutes: duration = Duration( minutes: (_isForward - ? _trendline.forwardForecast - : _trendline.backwardForecast) + ? trendline.forwardForecast + : trendline.backwardForecast) .round()); break; case DateTimeIntervalType.seconds: duration = Duration( seconds: (_isForward - ? _trendline.forwardForecast - : _trendline.backwardForecast) + ? trendline.forwardForecast + : trendline.backwardForecast) .round()); break; case DateTimeIntervalType.milliseconds: duration = Duration( milliseconds: (_isForward - ? _trendline.forwardForecast - : _trendline.backwardForecast) + ? trendline.forwardForecast + : trendline.backwardForecast) .round()); } return duration.inMilliseconds; @@ -1356,6 +1571,6 @@ class TrendlineRenderer { } class _SlopeIntercept { - late num slope; - late num intercept; + num? slope; + num? intercept; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart index 697c97896..ce66e1b69 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/trendlines/trendlines_painter.dart @@ -1,12 +1,36 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../common/event_args.dart'; +import '../../common/rendering_details.dart'; +import '../base/chart_base.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/renderer.dart'; +import '../trendlines/trendlines.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; -class _TrendlinePainter extends CustomPainter { - _TrendlinePainter( - {required this.chartState, +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; + +/// Represents the trend line painter +class TrendlinePainter extends CustomPainter { + /// Creates an instance for trend line painter + TrendlinePainter( + {required this.stateProperties, required this.trendlineAnimations, required ValueNotifier notifier}) : super(repaint: notifier); - final SfCartesianChartState chartState; + + /// Holds the Cartesian state properties value + final CartesianStateProperties stateProperties; + + /// Holds the list of trend line animation final Map> trendlineAnimations; @override @@ -14,105 +38,125 @@ class _TrendlinePainter extends CustomPainter { double animationFactor; Animation? trendlineAnimation; for (int i = 0; - i < chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[i]; - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + final RenderingDetails renderingDetails = + stateProperties.renderingDetails; final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; TrendlineRenderer trendlineRenderer; Trendline trendline; List controlPoints; + const int minimumDataLength = 2; if (series.trendlines != null) { for (int j = 0; j < series.trendlines!.length; j++) { trendline = series.trendlines![j]; - trendlineRenderer = seriesRenderer._trendlineRenderer[j]; + trendlineRenderer = seriesRendererDetails.trendlineRenderer[j]; assert(trendline.width >= 0, 'The width of the trendlines must be greater or equal to 0.'); assert(trendline.animationDuration >= 0, 'The animation duration time for trendlines should be greater than or equal to 0.'); trendlineAnimation = trendlineAnimations['$i-$j']; - if (trendlineRenderer._isNeedRender && + if (trendlineRenderer.isNeedRender && trendline.isVisible && - trendlineRenderer._pointsData != null && - trendlineRenderer._pointsData!.isNotEmpty) { + trendlineRenderer.pointsData != null && + trendlineRenderer.pointsData!.isNotEmpty) { canvas.save(); - animationFactor = (renderingDetails.isLegendToggled && - (seriesRenderer._oldSeries == null)) && + animationFactor = (!renderingDetails.isLegendToggled && + (seriesRendererDetails.oldSeries == null)) && trendlineAnimation != null ? trendlineAnimation.value : 1; - final Rect axisClipRect = _calculatePlotOffset( - chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); + final Rect axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); canvas.clipRect(axisClipRect); final Path path = Path(); final Paint paint = Paint(); paint.strokeWidth = trendline.width; - if (seriesRenderer._reAnimate || + if (seriesRendererDetails.reAnimate == true || (trendline.animationDuration > 0 && - seriesRenderer._oldSeries == null)) { - _performLinearAnimation( - chartState, - seriesRenderer._xAxisRenderer!._axis, + seriesRendererDetails.oldSeries == null)) { + performLinearAnimation( + stateProperties, + seriesRendererDetails.xAxisDetails!.axis, canvas, animationFactor); } renderTrendlineEvent( - chartState._chart, + stateProperties.chart, trendline, series.trendlines!.indexOf(trendline), - chartState._chartSeries.visibleSeriesRenderers - .indexOf(seriesRenderer), - seriesRenderer._seriesName!); - paint.color = trendlineRenderer._fillColor - .withOpacity(trendlineRenderer._opacity); + stateProperties.chartSeries.visibleSeriesRenderers + .indexOf(seriesRendererDetails.renderer), + seriesRendererDetails.seriesName!); + paint.color = trendlineRenderer.fillColor + .withOpacity(trendlineRenderer.opacity); paint.style = PaintingStyle.stroke; - if (trendline.type == TrendlineType.linear) { - path.moveTo(trendlineRenderer._points[0].dx, - trendlineRenderer._points[0].dy); - path.lineTo(trendlineRenderer._points[1].dx, - trendlineRenderer._points[1].dy); + // The first two points are always accessed to generate the linear trend line, + // so a single data point throws an exception, thus ensured the data length is more than or equal to 2. + if (trendline.type == TrendlineType.linear && + trendlineRenderer.points.length >= minimumDataLength) { + path.moveTo(trendlineRenderer.points[0].dx, + trendlineRenderer.points[0].dy); + path.lineTo(trendlineRenderer.points[1].dx, + trendlineRenderer.points[1].dy); } else if (trendline.type == TrendlineType.exponential || trendline.type == TrendlineType.power || trendline.type == TrendlineType.logarithmic) { - path.moveTo(trendlineRenderer._points[0].dx, - trendlineRenderer._points[0].dy); - for (int i = 0; i < trendlineRenderer._points.length - 1; i++) { - controlPoints = trendlineRenderer._getControlPoints( - trendlineRenderer._points, i); + path.moveTo(trendlineRenderer.points[0].dx, + trendlineRenderer.points[0].dy); + for (int i = 0; i < trendlineRenderer.points.length - 1; i++) { + controlPoints = trendlineRenderer.getControlPoints( + trendlineRenderer.points, i); + path.cubicTo( + controlPoints[0].dx, + controlPoints[0].dy, + controlPoints[1].dx, + controlPoints[1].dy, + trendlineRenderer.points[i + 1].dx, + trendlineRenderer.points[i + 1].dy); + } + } else if (trendline.type == TrendlineType.polynomial) { + path.moveTo(trendlineRenderer.points[0].dx, + trendlineRenderer.points[0].dy); + for (int i = 0; i < trendlineRenderer.points.length - 1; i++) { + controlPoints = trendlineRenderer.getControlPoints( + trendlineRenderer.points, i); path.cubicTo( controlPoints[0].dx, controlPoints[0].dy, controlPoints[1].dx, controlPoints[1].dy, - trendlineRenderer._points[i + 1].dx, - trendlineRenderer._points[i + 1].dy); + trendlineRenderer.points[i + 1].dx, + trendlineRenderer.points[i + 1].dy); } } else if (trendline.type == TrendlineType.polynomial && - trendlineRenderer._pointsData != null && - trendlineRenderer._pointsData!.isNotEmpty) { + trendlineRenderer.pointsData != null && + trendlineRenderer.pointsData!.isNotEmpty) { final List polynomialPoints = trendlineRenderer.getPolynomialCurve( - trendlineRenderer._pointsData!, - seriesRenderer, - chartState); + trendlineRenderer.pointsData!, + seriesRendererDetails, + stateProperties); path.moveTo(polynomialPoints[0].dx, polynomialPoints[0].dy); for (int i = 1; i < polynomialPoints.length; i++) { path.lineTo(polynomialPoints[i].dx, polynomialPoints[i].dy); } } else if (trendline.type == TrendlineType.movingAverage) { - path.moveTo(trendlineRenderer._points[0].dx, - trendlineRenderer._points[0].dy); - for (int i = 1; i < trendlineRenderer._points.length; i++) { - path.lineTo(trendlineRenderer._points[i].dx, - trendlineRenderer._points[i].dy); + path.moveTo(trendlineRenderer.points[0].dx, + trendlineRenderer.points[0].dy); + for (int i = 1; i < trendlineRenderer.points.length; i++) { + path.lineTo(trendlineRenderer.points[i].dx, + trendlineRenderer.points[i].dy); } } - _drawTrendlineMarker(trendlineRenderer, trendline, seriesRenderer, - animationFactor, canvas, path, paint); + _drawTrendlineMarker(trendlineRenderer, trendline, + seriesRendererDetails, animationFactor, canvas, path, paint); } } } @@ -123,44 +167,44 @@ class _TrendlinePainter extends CustomPainter { void _drawTrendlineMarker( TrendlineRenderer trendlineRenderer, Trendline trendline, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, double animationFactor, Canvas canvas, Path path, Paint paint, ) { Rect clipRect; - final Rect _axisClipRect = chartState._chartAxis._axisClipRect; - final _RenderingDetails renderingDetails = chartState._renderingDetails; - (trendlineRenderer._dashArray != null) - ? _drawDashedLine(canvas, trendlineRenderer._dashArray!, paint, path) + final Rect _axisClipRect = stateProperties.chartAxis.axisClipRect; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; + (trendlineRenderer.dashArray != null) + ? drawDashedLine(canvas, trendlineRenderer.dashArray!, paint, path) : canvas.drawPath(path, paint); - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( _axisClipRect.left - trendline.markerSettings.width, _axisClipRect.top - trendline.markerSettings.height, _axisClipRect.right + trendline.markerSettings.width, _axisClipRect.bottom + trendline.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); canvas.restore(); - if (trendlineRenderer._visible && - (animationFactor > chartState._trendlineDurationFactor)) { + if (trendlineRenderer.visible && + (animationFactor > stateProperties.trendlineDurationFactor)) { canvas.clipRect(clipRect); if (trendline.markerSettings.isVisible) { for (final CartesianChartPoint point - in trendlineRenderer._pointsData!) { + in trendlineRenderer.pointsData!) { if (point.isVisible && point.isGap != true) { if (trendline.markerSettings.shape == DataMarkerType.image) { - _drawImageMarker(seriesRenderer, canvas, point.markerPoint!.x, - point.markerPoint!.y); + drawImageMarker(seriesRendererDetails, canvas, + point.markerPoint!.x, point.markerPoint!.y); } final Paint strokePaint = Paint() ..color = trendline.markerSettings.borderWidth == 0 ? Colors.transparent : trendline.markerSettings.borderColor ?? - trendlineRenderer._fillColor + trendlineRenderer.fillColor ..strokeWidth = trendline.markerSettings.borderWidth ..style = PaintingStyle.stroke; @@ -170,10 +214,9 @@ class _TrendlinePainter extends CustomPainter { ? Colors.white : Colors.black) ..style = PaintingStyle.fill; - final int index = trendlineRenderer._pointsData!.indexOf(point); - canvas.drawPath( - trendlineRenderer._markerShapes[index], strokePaint); - canvas.drawPath(trendlineRenderer._markerShapes[index], fillPaint); + final int index = trendlineRenderer.pointsData!.indexOf(point); + canvas.drawPath(trendlineRenderer.markerShapes[index], strokePaint); + canvas.drawPath(trendlineRenderer.markerShapes[index], fillPaint); } } } @@ -183,29 +226,36 @@ class _TrendlinePainter extends CustomPainter { /// Setting the values of render trend line event void renderTrendlineEvent(SfCartesianChart chart, Trendline trendline, int trendlineIndex, int seriesIndex, String seriesName) { - TrendlineRenderArgs args; - final TrendlineRenderer trendlineRenderer = chartState._chartSeries - .visibleSeriesRenderers[seriesIndex]._trendlineRenderer[trendlineIndex]; - if (chart.onTrendlineRender != null && - !trendlineRenderer._isTrendlineRenderEvent) { - trendlineRenderer._isTrendlineRenderEvent = true; - args = TrendlineRenderArgs( - trendline.intercept, - trendlineIndex, + TrendlineRenderParams args; + final TrendlineRenderer trendlineRenderer = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]) + .trendlineRenderer[trendlineIndex]; + + final List? slope = trendline.type == TrendlineType.polynomial + ? trendlineRenderer.polynomialSlopesData + : trendline.type == TrendlineType.movingAverage + ? null + : [trendlineRenderer.slopeInterceptData.slope!.toDouble()]; + if (trendline.onRenderDetailsUpdate != null && + !trendlineRenderer.isTrendlineRenderEvent) { + trendlineRenderer.isTrendlineRenderEvent = true; + args = TrendlineRenderParams( + trendlineRenderer.slopeIntercept.intercept?.toDouble(), seriesIndex, - trendlineRenderer._name!, + trendlineRenderer.name!, seriesName, - trendlineRenderer._pointsData!); - args.color = trendlineRenderer._fillColor; - args.opacity = trendlineRenderer._opacity; - args.dashArray = trendlineRenderer._dashArray; - chart.onTrendlineRender!(args); - trendlineRenderer._fillColor = args.color; - trendlineRenderer._opacity = args.opacity; - trendlineRenderer._dashArray = args.dashArray; + trendlineRenderer.points, + slope, + getRSquaredValue( + stateProperties.seriesRenderers[seriesIndex], + trendline, + slope, + trendlineRenderer.slopeIntercept.intercept?.toDouble())); + trendline.onRenderDetailsUpdate!(args); } } @override - bool shouldRepaint(_TrendlinePainter oldDelegate) => true; + bool shouldRepaint(TrendlinePainter oldDelegate) => true; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart index 2ef3ff3fd..2bf8db564 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair.dart @@ -1,4 +1,20 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../common/utils/helper.dart'; +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../base/chart_base.dart'; +import '../chart_behavior/chart_behavior.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'crosshair_painter.dart'; /// This class has the properties of the crosshair behavior. /// @@ -185,7 +201,8 @@ class CrosshairBehavior { return hashList(values); } - SfCartesianChartState? _chartState; + /// Represents the cartesian state properties + late CartesianStateProperties _stateProperties; /// Displays the crosshair at the specified x and y-positions. /// @@ -195,42 +212,11 @@ class CrosshairBehavior { /// coordinateUnit - specify the type of x and y values given.'pixel' or 'point' for logica pixel and chart data point respectively. /// Defaults to `'point'`. void show(dynamic x, double y, [String coordinateUnit = 'point']) { - final SfCartesianChartState chartState = _chartState!; final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - chartState._crosshairBehaviorRenderer; - if (coordinateUnit != 'pixel') { - final CartesianSeriesRenderer seriesRenderer = crosshairBehaviorRenderer - ._crosshairPainter!.chartState._chartSeries.visibleSeriesRenderers[0]; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; - final _ChartLocation location = _calculatePoint( - (x is DateTime && xAxisRenderer is! DateTimeCategoryAxisRenderer) - ? x.millisecondsSinceEpoch - : ((x is DateTime && - xAxisRenderer is DateTimeCategoryAxisRenderer) - ? xAxisRenderer._labels - .indexOf(xAxisRenderer._dateFormat.format(x)) - : ((x is String && xAxisRenderer is CategoryAxisRenderer) - ? xAxisRenderer._labels.indexOf(x) - : x)), - y, - xAxisRenderer, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, - seriesRenderer._chartState!._chartAxis._axisClipRect); - x = location.x; - y = location.y.truncateToDouble(); - } - - if (crosshairBehaviorRenderer._crosshairPainter != null && - activationMode != ActivationMode.none && - x != null) { - crosshairBehaviorRenderer._crosshairPainter! - ._generateAllPoints(Offset(x.toDouble(), y)); - crosshairBehaviorRenderer._crosshairPainter!.canResetPath = false; - crosshairBehaviorRenderer._crosshairPainter!.chartState - ._repaintNotifiers['crosshair']!.value++; - } + _stateProperties.crosshairBehaviorRenderer; + final CrosshairRenderingDetails renderingDetails = + CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); + renderingDetails.internalShow(x, y, coordinateUnit); } /// Displays the crosshair at the specified point index. @@ -238,53 +224,58 @@ class CrosshairBehavior { /// /// pointIndex - index of point at which the crosshair needs to be shown. void showByIndex(int pointIndex) { - final SfCartesianChartState chartState = _chartState!; final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - chartState._crosshairBehaviorRenderer; - if (_validIndex( - pointIndex, 0, crosshairBehaviorRenderer._crosshairPainter!.chart)) { - if (crosshairBehaviorRenderer._crosshairPainter != null && + _stateProperties.crosshairBehaviorRenderer; + final CrosshairRenderingDetails renderingDetails = + CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); + if (validIndex(pointIndex, 0, renderingDetails.crosshairPainter!.chart)) { + if (renderingDetails.crosshairPainter != null && activationMode != ActivationMode.none) { final List visibleSeriesRenderer = - crosshairBehaviorRenderer._crosshairPainter!.chartState._chartSeries + renderingDetails.crosshairPainter!.stateProperties.chartSeries .visibleSeriesRenderers; - final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[0]; - crosshairBehaviorRenderer._crosshairPainter!._generateAllPoints(Offset( - seriesRenderer._dataPoints[pointIndex].markerPoint!.x, - seriesRenderer._dataPoints[pointIndex].markerPoint!.y)); - crosshairBehaviorRenderer._crosshairPainter!.canResetPath = false; - crosshairBehaviorRenderer._crosshairPainter!.chartState - ._repaintNotifiers['crosshair']!.value++; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderer[0]); + renderingDetails.crosshairPainter!.generateAllPoints(Offset( + seriesRendererDetails.dataPoints[pointIndex].markerPoint!.x, + seriesRendererDetails.dataPoints[pointIndex].markerPoint!.y)); + renderingDetails.crosshairPainter!.canResetPath = false; + renderingDetails.crosshairPainter!.stateProperties + .repaintNotifiers['crosshair']!.value++; } } } /// Hides the crosshair if it is displayed. void hide() { - final SfCartesianChartState chartState = _chartState!; final CrosshairBehaviorRenderer crosshairBehaviorRenderer = - chartState._crosshairBehaviorRenderer; - if (crosshairBehaviorRenderer._crosshairPainter != null) { - crosshairBehaviorRenderer._crosshairPainter!.canResetPath = false; - ValueNotifier(crosshairBehaviorRenderer._crosshairPainter!.chartState - ._repaintNotifiers['crosshair']!.value++); - crosshairBehaviorRenderer._crosshairPainter!.timer?.cancel(); - if (!chartState._isTouchUp) { - crosshairBehaviorRenderer._crosshairPainter!.chartState - ._repaintNotifiers['crosshair']!.value++; - crosshairBehaviorRenderer._crosshairPainter!.canResetPath = true; + _stateProperties.crosshairBehaviorRenderer; + final CrosshairRenderingDetails renderingDetails = + CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); + if (renderingDetails.crosshairPainter != null) { + renderingDetails.crosshairPainter!.canResetPath = false; + ValueNotifier(renderingDetails.crosshairPainter!.stateProperties + .repaintNotifiers['crosshair']!.value++); + renderingDetails.crosshairPainter!.timer?.cancel(); + if (!_stateProperties.isTouchUp) { + renderingDetails.crosshairPainter!.stateProperties + .repaintNotifiers['crosshair']!.value++; + renderingDetails.crosshairPainter!.canResetPath = true; + renderingDetails.position = null; } else { if (!shouldAlwaysShow) { final double duration = (hideDelay == 0 && - crosshairBehaviorRenderer - ._crosshairPainter!.chartState._enableDoubleTap) + renderingDetails + .crosshairPainter!.stateProperties.enableDoubleTap == + true) ? 200 : hideDelay; - crosshairBehaviorRenderer._crosshairPainter!.timer = + renderingDetails.crosshairPainter!.timer = Timer(Duration(milliseconds: duration.toInt()), () { - crosshairBehaviorRenderer._crosshairPainter!.chartState - ._repaintNotifiers['crosshair']!.value++; - crosshairBehaviorRenderer._crosshairPainter!.canResetPath = true; + renderingDetails.crosshairPainter!.stateProperties + .repaintNotifiers['crosshair']!.value++; + renderingDetails.crosshairPainter!.canResetPath = true; + renderingDetails.position = null; }); } } @@ -292,76 +283,149 @@ class CrosshairBehavior { } } -/// Cross hair renderer class for mutable fields and methods +/// Crosshair renderer class for mutable fields and methods class CrosshairBehaviorRenderer with ChartBehavior { /// Creates an argument constructor for Crosshair renderer class - CrosshairBehaviorRenderer(this._chartState); - - SfCartesianChart get _chart => _chartState._chart; - - final SfCartesianChartState _chartState; - - CrosshairBehavior get _crosshairBehavior => _chart.crosshairBehavior; - - /// Touch position - Offset? _position; + CrosshairBehaviorRenderer(this._stateProperties) { + _crosshairRenderingDetails = CrosshairRenderingDetails(_stateProperties); + } - /// Holds the instance of CrosshairPainter - _CrosshairPainter? _crosshairPainter; + final CartesianStateProperties _stateProperties; - /// Check whether long press activated or not. - //ignore: prefer_final_fields - bool _isLongPressActivated = false; + /// Specifies the value of crosshair rendering details + late CrosshairRenderingDetails _crosshairRenderingDetails; /// Enables the crosshair on double tap. @override void onDoubleTap(double xPos, double yPos) => - _crosshairBehavior.show(xPos, yPos, 'pixel'); + _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); /// Enables the crosshair on long press. @override void onLongPress(double xPos, double yPos) => - _crosshairBehavior.show(xPos, yPos, 'pixel'); + _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); /// Enables the crosshair on touch down. @override void onTouchDown(double xPos, double yPos) => - _crosshairBehavior.show(xPos, yPos, 'pixel'); + _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); /// Enables the crosshair on touch move. @override void onTouchMove(double xPos, double yPos) => - _crosshairBehavior.show(xPos, yPos, 'pixel'); + _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); /// Enables the crosshair on touch up. @override - void onTouchUp(double xPos, double yPos) => _crosshairBehavior.hide(); + void onTouchUp(double xPos, double yPos) => + _crosshairRenderingDetails._crosshairBehavior.hide(); /// Enables the crosshair on mouse hover. @override void onEnter(double xPos, double yPos) => - _crosshairBehavior.show(xPos, yPos, 'pixel'); + _crosshairRenderingDetails._crosshairBehavior.show(xPos, yPos, 'pixel'); /// Disables the crosshair on mouse exit. @override - void onExit(double xPos, double yPos) => _crosshairBehavior.hide(); + void onExit(double xPos, double yPos) => + _crosshairRenderingDetails._crosshairBehavior.hide(); /// Draws the crosshair. @override void onPaint(Canvas canvas) { - if (_crosshairPainter != null) { - _crosshairPainter!._drawCrosshair(canvas); + if (_crosshairRenderingDetails.crosshairPainter != null) { + _crosshairRenderingDetails.crosshairPainter!.drawCrosshair(canvas); } } +} + +/// Represents the class that holds the rendering details of cross hair +class CrosshairRenderingDetails { + /// Creates an instance of cross hair rendering details + CrosshairRenderingDetails(this._stateProperties); + + final CartesianStateProperties _stateProperties; + + SfCartesianChart get _chart => _stateProperties.chart; + + CrosshairBehavior get _crosshairBehavior => _chart.crosshairBehavior; + + /// Touch position + Offset? position; + + /// Holds the instance of CrosshairPainter + CrosshairPainter? crosshairPainter; + + /// Check whether long press activated or not. + bool isLongPressActivated = false; /// To draw cross hair line - void _drawLine(Canvas canvas, Paint? paint, int? seriesIndex) { + void drawLine(Canvas canvas, Paint? paint, int? seriesIndex) { assert(_crosshairBehavior.lineWidth >= 0, 'Line width value of crosshair should be greater than 0.'); - if (_crosshairPainter != null && paint != null) { - _crosshairPainter!._drawCrosshairLine(canvas, paint, seriesIndex); + if (crosshairPainter != null && paint != null) { + crosshairPainter!.drawCrosshairLine(canvas, paint, seriesIndex); + } + } + + /// To get the paint value for the crosshair. + Paint? linePainter(Paint paint) => crosshairPainter?.getLinePainter(paint); + + /// To show the crosshair with provided coordinates + void internalShow(dynamic x, double y, [String coordinateUnit = 'point']) { + final CrosshairBehaviorRenderer crosshairBehaviorRenderer = + _stateProperties.crosshairBehaviorRenderer; + if (coordinateUnit != 'pixel') { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _stateProperties.chartSeries.visibleSeriesRenderers[0]); + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartLocation location = calculatePoint( + (x is DateTime && xAxisDetails is! DateTimeCategoryAxisDetails) + ? x.millisecondsSinceEpoch + : ((x is DateTime && xAxisDetails is DateTimeCategoryAxisDetails) + ? xAxisDetails.labels + .indexOf(xAxisDetails.dateFormat.format(x)) + : ((x is String && xAxisDetails is CategoryAxisDetails) + ? xAxisDetails.labels.indexOf(x) + : x)), + y, + xAxisDetails, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + seriesRendererDetails.stateProperties.chartAxis.axisClipRect); + x = location.x; + y = location.y.truncateToDouble(); + } + + final CrosshairRenderingDetails renderingDetails = + CrosshairHelper.getRenderingDetails(crosshairBehaviorRenderer); + if (renderingDetails.crosshairPainter != null && + _chart.crosshairBehavior.activationMode != ActivationMode.none && + x != null) { + renderingDetails.crosshairPainter! + .generateAllPoints(Offset(x.toDouble(), y)); + renderingDetails.crosshairPainter!.canResetPath = false; + renderingDetails.crosshairPainter!.stateProperties + .repaintNotifiers['crosshair']!.value++; } } +} + +// ignore: avoid_classes_with_only_static_members +/// Helper class to get the crosshair rendering details instance from its renderer +class CrosshairHelper { + /// Returns the crosshair rendering details instance from its renderer + static CrosshairRenderingDetails getRenderingDetails( + CrosshairBehaviorRenderer renderer) { + return renderer._crosshairRenderingDetails; + } - Paint? _linePainter(Paint paint) => _crosshairPainter?._getLinePainter(paint); + /// Method to set the cartesian state properties + static void setStateProperties(CrosshairBehavior crosshairBehavior, + CartesianStateProperties stateProperties) { + crosshairBehavior._stateProperties = stateProperties; + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart index 54167aa4a..ea711b8f8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/crosshair_painter.dart @@ -1,51 +1,109 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../common/event_args.dart'; +import '../../common/rendering_details.dart'; +import '../axis/axis.dart'; +import '../axis/category_axis.dart'; +import '../base/chart_base.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'crosshair.dart'; -class _CrosshairPainter extends CustomPainter { - _CrosshairPainter({required this.chartState, required this.valueNotifier}) - : chart = chartState._chart, +/// Represents the crosshair painter +class CrosshairPainter extends CustomPainter { + /// Calling the default constructor of CrosshairPainter class. + CrosshairPainter({required this.stateProperties, required this.valueNotifier}) + : chart = stateProperties.chart, super(repaint: valueNotifier); - final SfCartesianChartState chartState; + + /// Represents the cartesian state properties + final CartesianStateProperties stateProperties; + + /// Represents the cartesian chart. final SfCartesianChart chart; - _RenderingDetails get _renderingDetails => chartState._renderingDetails; + + RenderingDetails get _renderingDetails => stateProperties.renderingDetails; + + /// Represents the value of timer. Timer? timer; + + /// Repaint notifier for crosshair. ValueNotifier valueNotifier; + // double pointerLength; // double pointerWidth; + + /// Represents the nose point y value. double nosePointY = 0; + + /// Represents the nose point x value. double nosePointX = 0; + + /// Represents the total width value. double totalWidth = 0; + // double x; // double y; // double xPos; // double yPos; + + /// Represents the value of isTop. bool isTop = false; + // double cornerRadius; + + /// Represents the background path value for crosshair. Path backgroundPath = Path(); + + /// Represents the canResetPath value of crosshair. bool canResetPath = true; + + /// Represents the value of isleft. bool isLeft = false; + + /// Represents the value of isRight. bool isRight = false; + // bool enable; + + /// Represents the padding value for crosshair. double padding = 0; + + /// Specifies the list of string value for the crosshair tooltip. List stringValue = []; + + /// Represents the boundary rect for crosshair. Rect boundaryRect = const Rect.fromLTWH(0, 0, 0, 0); + + /// Represents the left padding for crosshair. double leftPadding = 0; + + /// Represents the top padding for crosshair. double topPadding = 0; + + /// Specifies whether the orientation is horizontal or not. bool isHorizontalOrientation = false; + // TextStyle labelStyle; @override void paint(Canvas canvas, Size size) { if (!canResetPath) { - chartState._crosshairBehaviorRenderer.onPaint(canvas); + stateProperties.crosshairBehaviorRenderer.onPaint(canvas); } } @override bool shouldRepaint(CustomPainter oldDelegate) => true; - /// calculate trackball points - void _generateAllPoints(Offset position) { - final Rect _axisClipRect = chartState._chartAxis._axisClipRect; + /// Calculate trackball points + void generateAllPoints(Offset position) { + final Rect _axisClipRect = stateProperties.chartAxis.axisClipRect; double dx, dy; dx = position.dx > _axisClipRect.right ? _axisClipRect.right @@ -57,47 +115,50 @@ class _CrosshairPainter extends CustomPainter { : position.dy < _axisClipRect.top ? _axisClipRect.top : position.dy; - chartState._crosshairBehaviorRenderer._position = Offset(dx, dy); + CrosshairHelper.getRenderingDetails( + stateProperties.crosshairBehaviorRenderer) + .position = Offset(dx, dy); } - /// Get line painter paint - Paint _getLinePainter(Paint crosshairLinePaint) => crosshairLinePaint; + /// Gets the line painter + Paint getLinePainter(Paint crosshairLinePaint) => crosshairLinePaint; - /// Draw the path of the cross hair line - void _drawCrosshairLine(Canvas canvas, Paint paint, int? index) { - if (chartState._crosshairBehaviorRenderer._position != null) { + /// Draw the path of the crosshair line + void drawCrosshairLine(Canvas canvas, Paint paint, int? index) { + final CrosshairRenderingDetails renderingDetails = + CrosshairHelper.getRenderingDetails( + stateProperties.crosshairBehaviorRenderer); + if (renderingDetails.position != null) { final Path dashArrayPath = Path(); if ((chart.crosshairBehavior.lineType == CrosshairLineType.horizontal || chart.crosshairBehavior.lineType == CrosshairLineType.both) && chart.crosshairBehavior.lineWidth != 0) { - dashArrayPath.moveTo(chartState._chartAxis._axisClipRect.left, - chartState._crosshairBehaviorRenderer._position!.dy); - dashArrayPath.lineTo(chartState._chartAxis._axisClipRect.right, - chartState._crosshairBehaviorRenderer._position!.dy); + dashArrayPath.moveTo(stateProperties.chartAxis.axisClipRect.left, + renderingDetails.position!.dy); + dashArrayPath.lineTo(stateProperties.chartAxis.axisClipRect.right, + renderingDetails.position!.dy); chart.crosshairBehavior.lineDashArray != null - ? _drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray!, + ? drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray!, paint, dashArrayPath) : canvas.drawPath(dashArrayPath, paint); } if ((chart.crosshairBehavior.lineType == CrosshairLineType.vertical || chart.crosshairBehavior.lineType == CrosshairLineType.both) && chart.crosshairBehavior.lineWidth != 0) { - dashArrayPath.moveTo( - chartState._crosshairBehaviorRenderer._position!.dx, - chartState._chartAxis._axisClipRect.top); - dashArrayPath.lineTo( - chartState._crosshairBehaviorRenderer._position!.dx, - chartState._chartAxis._axisClipRect.bottom); + dashArrayPath.moveTo(renderingDetails.position!.dx, + stateProperties.chartAxis.axisClipRect.top); + dashArrayPath.lineTo(renderingDetails.position!.dx, + stateProperties.chartAxis.axisClipRect.bottom); chart.crosshairBehavior.lineDashArray != null - ? _drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray!, + ? drawDashedLine(canvas, chart.crosshairBehavior.lineDashArray!, paint, dashArrayPath) : canvas.drawPath(dashArrayPath, paint); } } } - /// To draw cross hair - void _drawCrosshair(Canvas canvas) { + /// To draw crosshair + void drawCrosshair(Canvas canvas) { final Paint fillPaint = Paint() ..color = _renderingDetails.chartTheme.crosshairBackgroundColor ..strokeCap = StrokeCap.butt @@ -114,8 +175,11 @@ class _CrosshairPainter extends CustomPainter { : strokePaint.color = strokePaint.color; final Paint crosshairLinePaint = Paint(); - if (chartState._crosshairBehaviorRenderer._position != null) { - final Offset position = chartState._crosshairBehaviorRenderer._position!; + final CrosshairRenderingDetails renderingDetails = + CrosshairHelper.getRenderingDetails( + stateProperties.crosshairBehaviorRenderer); + if (renderingDetails.position != null) { + final Offset position = renderingDetails.position!; crosshairLinePaint.color = chart.crosshairBehavior.lineColor ?? _renderingDetails.chartTheme.crosshairLineColor; @@ -124,15 +188,13 @@ class _CrosshairPainter extends CustomPainter { CrosshairRenderArgs crosshairEventArgs; if (chart.onCrosshairPositionChanging != null) { crosshairEventArgs = CrosshairRenderArgs(); + crosshairEventArgs.text = ''; crosshairEventArgs.lineColor = crosshairLinePaint.color; chart.onCrosshairPositionChanging!(crosshairEventArgs); crosshairLinePaint.color = crosshairEventArgs.lineColor; } - chartState._crosshairBehaviorRenderer._drawLine( - canvas, - chartState._crosshairBehaviorRenderer - ._linePainter(crosshairLinePaint), - null); + renderingDetails.drawLine( + canvas, renderingDetails.linePainter(crosshairLinePaint), null); _drawBottomAxesTooltip(canvas, position, strokePaint, fillPaint); _drawTopAxesTooltip(canvas, position, strokePaint, fillPaint); @@ -141,10 +203,10 @@ class _CrosshairPainter extends CustomPainter { } } - /// draw bottom axes tooltip + /// Draw bottom axes tooltip void _drawBottomAxesTooltip( Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { - ChartAxisRenderer axisRenderer; + ChartAxisRendererDetails axisDetails; String value; Size labelSize; Rect labelRect, validatedRect; @@ -154,20 +216,21 @@ class _CrosshairPainter extends CustomPainter { const double padding = 10; CrosshairRenderArgs crosshairEventArgs; for (int index = 0; - index < chartState._chartAxis._bottomAxesCount.length; + index < stateProperties.chartAxis.bottomAxesCount.length; index++) { - axisRenderer = chartState._chartAxis._bottomAxesCount[index].axisRenderer; - final ChartAxis axis = axisRenderer._axis; - if (_needToAddTooltip(axisRenderer)) { + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.bottomAxesCount[index].axisRenderer); + final ChartAxis axis = axisDetails.axis; + if (_needToAddTooltip(axisDetails)) { fillPaint.color = axis.interactiveTooltip.color ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.color = axis.interactiveTooltip.borderColor ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getXValue(axisRenderer, position); + value = _getXValue(axisDetails.axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { crosshairEventArgs = CrosshairRenderArgs( - axis, value, axisRenderer._name, axisRenderer._orientation); + axis, value, axisDetails.name, axisDetails.orientation); crosshairEventArgs.text = value; crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? _renderingDetails.chartTheme.crosshairLineColor; @@ -178,17 +241,17 @@ class _CrosshairPainter extends CustomPainter { labelSize = measureText(value, axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( position.dx - (labelSize.width / 2 + padding / 2), - axisRenderer._bounds.top + axis.interactiveTooltip.arrowLength, + axisDetails.bounds.top + axis.interactiveTooltip.arrowLength, labelSize.width + padding, labelSize.height + padding); - labelRect = _validateRectBounds( - labelRect, _renderingDetails.chartContainerRect); - validatedRect = _validateRectXPosition(labelRect, chartState); + labelRect = + validateRectBounds(labelRect, _renderingDetails.chartContainerRect); + validatedRect = validateRectXPosition(labelRect, stateProperties); backgroundPath.reset(); - tooltipRect = _getRoundedCornerRect( + tooltipRect = getRoundedCornerRect( validatedRect, axis.interactiveTooltip.borderRadius); backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( + drawTooltipArrowhead( canvas, backgroundPath, fillPaint, @@ -209,11 +272,11 @@ class _CrosshairPainter extends CustomPainter { } } - /// draw top axes tooltip + /// Draw top axes tooltip void _drawTopAxesTooltip( Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { ChartAxis axis; - ChartAxisRenderer axisRenderer; + ChartAxisRendererDetails axisDetails; String value; Size labelSize; Rect labelRect, validatedRect; @@ -223,20 +286,21 @@ class _CrosshairPainter extends CustomPainter { Color? color; CrosshairRenderArgs crosshairEventArgs; for (int index = 0; - index < chartState._chartAxis._topAxesCount.length; + index < stateProperties.chartAxis.topAxesCount.length; index++) { - axisRenderer = chartState._chartAxis._topAxesCount[index].axisRenderer; - axis = axisRenderer._axis; - if (_needToAddTooltip(axisRenderer)) { + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.topAxesCount[index].axisRenderer); + axis = axisDetails.axis; + if (_needToAddTooltip(axisDetails)) { fillPaint.color = axis.interactiveTooltip.color ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.color = axis.interactiveTooltip.borderColor ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getXValue(axisRenderer, position); + value = _getXValue(axisDetails.axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { - crosshairEventArgs = CrosshairRenderArgs(axisRenderer._axis, value, - axisRenderer._name, axisRenderer._orientation); + crosshairEventArgs = CrosshairRenderArgs(axisDetails.axis, value, + axisDetails.name, axisDetails.orientation); crosshairEventArgs.text = value; crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? _renderingDetails.chartTheme.crosshairLineColor; @@ -247,19 +311,19 @@ class _CrosshairPainter extends CustomPainter { labelSize = measureText(value, axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( position.dx - (labelSize.width / 2 + padding / 2), - axisRenderer._bounds.top - + axisDetails.bounds.top - (labelSize.height + padding) - axis.interactiveTooltip.arrowLength, labelSize.width + padding, labelSize.height + padding); - labelRect = _validateRectBounds( - labelRect, _renderingDetails.chartContainerRect); - validatedRect = _validateRectXPosition(labelRect, chartState); + labelRect = + validateRectBounds(labelRect, _renderingDetails.chartContainerRect); + validatedRect = validateRectXPosition(labelRect, stateProperties); backgroundPath.reset(); - tooltipRect = _getRoundedCornerRect( + tooltipRect = getRoundedCornerRect( validatedRect, axis.interactiveTooltip.borderRadius); backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( + drawTooltipArrowhead( canvas, backgroundPath, fillPaint, @@ -280,11 +344,11 @@ class _CrosshairPainter extends CustomPainter { } } - /// draw left axes tooltip + /// Draw left axes tooltip void _drawLeftAxesTooltip( Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { ChartAxis axis; - ChartAxisRenderer axisRenderer; + ChartAxisRendererDetails axisDetails; String value; Size labelSize; Rect labelRect, validatedRect; @@ -294,20 +358,21 @@ class _CrosshairPainter extends CustomPainter { Color? color; CrosshairRenderArgs crosshairEventArgs; for (int index = 0; - index < chartState._chartAxis._leftAxesCount.length; + index < stateProperties.chartAxis.leftAxesCount.length; index++) { - axisRenderer = chartState._chartAxis._leftAxesCount[index].axisRenderer; - axis = axisRenderer._axis; - if (_needToAddTooltip(axisRenderer)) { + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.leftAxesCount[index].axisRenderer); + axis = axisDetails.axis; + if (_needToAddTooltip(axisDetails)) { fillPaint.color = axis.interactiveTooltip.color ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.color = axis.interactiveTooltip.borderColor ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getYValue(axisRenderer, position); + value = _getYValue(axisDetails.axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { crosshairEventArgs = CrosshairRenderArgs( - axis, value, axisRenderer._name, axisRenderer._orientation); + axis, value, axisDetails.name, axisDetails.orientation); crosshairEventArgs.text = value; crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? _renderingDetails.chartTheme.crosshairLineColor; @@ -317,21 +382,21 @@ class _CrosshairPainter extends CustomPainter { } labelSize = measureText(value, axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( - axisRenderer._bounds.left - + axisDetails.bounds.left - (labelSize.width + padding) - axis.interactiveTooltip.arrowLength, position.dy - (labelSize.height + padding) / 2, labelSize.width + padding, labelSize.height + padding); - labelRect = _validateRectBounds( - labelRect, _renderingDetails.chartContainerRect); - validatedRect = _validateRectYPosition(labelRect, chartState); + labelRect = + validateRectBounds(labelRect, _renderingDetails.chartContainerRect); + validatedRect = validateRectYPosition(labelRect, stateProperties); backgroundPath.reset(); - tooltipRect = _getRoundedCornerRect( + tooltipRect = getRoundedCornerRect( validatedRect, axis.interactiveTooltip.borderRadius); backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( + drawTooltipArrowhead( canvas, backgroundPath, fillPaint, @@ -355,11 +420,11 @@ class _CrosshairPainter extends CustomPainter { } } - /// draw right axes tooltip + /// Draw right axes tooltip void _drawRightAxesTooltip( Canvas canvas, Offset position, Paint strokePaint, Paint fillPaint) { ChartAxis axis; - ChartAxisRenderer axisRenderer; + ChartAxisRendererDetails axisDetails; String value; Size labelSize; Rect labelRect, validatedRect; @@ -369,20 +434,21 @@ class _CrosshairPainter extends CustomPainter { Color? color; const double padding = 10; for (int index = 0; - index < chartState._chartAxis._rightAxesCount.length; + index < stateProperties.chartAxis.rightAxesCount.length; index++) { - axisRenderer = chartState._chartAxis._rightAxesCount[index].axisRenderer; - axis = axisRenderer._axis; - if (_needToAddTooltip(axisRenderer)) { + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.rightAxesCount[index].axisRenderer); + axis = axisDetails.axis; + if (_needToAddTooltip(axisDetails)) { fillPaint.color = axis.interactiveTooltip.color ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.color = axis.interactiveTooltip.borderColor ?? _renderingDetails.chartTheme.crosshairBackgroundColor; strokePaint.strokeWidth = axis.interactiveTooltip.borderWidth; - value = _getYValue(axisRenderer, position); + value = _getYValue(axisDetails.axisRenderer, position); if (chart.onCrosshairPositionChanging != null) { crosshairEventArgs = CrosshairRenderArgs( - axis, value, axisRenderer._name, axisRenderer._orientation); + axis, value, axisDetails.name, axisDetails.orientation); crosshairEventArgs.text = value; crosshairEventArgs.lineColor = chart.crosshairBehavior.lineColor ?? _renderingDetails.chartTheme.crosshairLineColor; @@ -392,18 +458,18 @@ class _CrosshairPainter extends CustomPainter { } labelSize = measureText(value, axis.interactiveTooltip.textStyle); labelRect = Rect.fromLTWH( - axisRenderer._bounds.left + axis.interactiveTooltip.arrowLength, + axisDetails.bounds.left + axis.interactiveTooltip.arrowLength, position.dy - (labelSize.height / 2 + padding / 2), labelSize.width + padding, labelSize.height + padding); - labelRect = _validateRectBounds( - labelRect, _renderingDetails.chartContainerRect); - validatedRect = _validateRectYPosition(labelRect, chartState); + labelRect = + validateRectBounds(labelRect, _renderingDetails.chartContainerRect); + validatedRect = validateRectYPosition(labelRect, stateProperties); backgroundPath.reset(); - tooltipRect = _getRoundedCornerRect( + tooltipRect = getRoundedCornerRect( validatedRect, axis.interactiveTooltip.borderRadius); backgroundPath.addRRect(tooltipRect); - _drawTooltipArrowhead( + drawTooltipArrowhead( canvas, backgroundPath, fillPaint, @@ -428,7 +494,7 @@ class _CrosshairPainter extends CustomPainter { void _drawTooltipText(Canvas canvas, String text, TextStyle textStyle, RRect tooltipRect, Size labelSize) { - _drawText( + drawText( canvas, text, Offset((tooltipRect.left + tooltipRect.width / 2) - labelSize.width / 2, @@ -462,20 +528,22 @@ class _CrosshairPainter extends CustomPainter { /// To find the x value of crosshair String _getXValue(ChartAxisRenderer axisRenderer, Offset position) { - final num value = _pointToXVal( + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final num value = pointToXVal( chart, - axisRenderer, - axisRenderer._bounds, + axisDetails.axisRenderer, + axisDetails.bounds, position.dx - - (chartState._chartAxis._axisClipRect.left + - axisRenderer._axis.plotOffset), + (stateProperties.chartAxis.axisClipRect.left + + axisDetails.axis.plotOffset), position.dy - - (chartState._chartAxis._axisClipRect.top + - axisRenderer._axis.plotOffset)); + (stateProperties.chartAxis.axisClipRect.top + + axisDetails.axis.plotOffset)); String resultantString = - _getInteractiveTooltipLabel(value, axisRenderer).toString(); - if (axisRenderer._axis.interactiveTooltip.format != null) { - final String stringValue = axisRenderer._axis.interactiveTooltip.format! + getInteractiveTooltipLabel(value, axisDetails.axisRenderer).toString(); + if (axisDetails.axis.interactiveTooltip.format != null) { + final String stringValue = axisDetails.axis.interactiveTooltip.format! .replaceAll('{value}', resultantString); resultantString = stringValue; } @@ -484,20 +552,22 @@ class _CrosshairPainter extends CustomPainter { /// To find the y value of crosshair String _getYValue(ChartAxisRenderer axisRenderer, Offset position) { - final num value = _pointToYVal( + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final num value = pointToYVal( chart, - axisRenderer, - axisRenderer._bounds, + axisDetails.axisRenderer, + axisDetails.bounds, position.dx - - (chartState._chartAxis._axisClipRect.left + - axisRenderer._axis.plotOffset), + (stateProperties.chartAxis.axisClipRect.left + + axisDetails.axis.plotOffset), position.dy - - (chartState._chartAxis._axisClipRect.top + - axisRenderer._axis.plotOffset)); + (stateProperties.chartAxis.axisClipRect.top + + axisDetails.axis.plotOffset)); String resultantString = - _getInteractiveTooltipLabel(value, axisRenderer).toString(); - if (axisRenderer._axis.interactiveTooltip.format != null) { - final String stringValue = axisRenderer._axis.interactiveTooltip.format! + getInteractiveTooltipLabel(value, axisDetails.axisRenderer).toString(); + if (axisDetails.axis.interactiveTooltip.format != null) { + final String stringValue = axisDetails.axis.interactiveTooltip.format! .replaceAll('{value}', resultantString); resultantString = stringValue; } @@ -505,11 +575,11 @@ class _CrosshairPainter extends CustomPainter { } /// To add the tooltip for crosshair - bool _needToAddTooltip(ChartAxisRenderer axisRenderer) { - return axisRenderer._axis.interactiveTooltip.enable && - ((axisRenderer is! CategoryAxisRenderer && - axisRenderer._visibleLabels.isNotEmpty) || - (axisRenderer is CategoryAxisRenderer && - axisRenderer._labels.isNotEmpty)); + bool _needToAddTooltip(ChartAxisRendererDetails axisDetails) { + return axisDetails.axis.interactiveTooltip.enable && + ((axisDetails is! CategoryAxisDetails && + axisDetails.visibleLabels.isNotEmpty) || + (axisDetails is CategoryAxisDetails && + axisDetails.labels.isNotEmpty)); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart index 21e7a5279..5dc8c15ca 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/selection_renderer.dart @@ -1,28 +1,109 @@ -part of charts; - -class _SelectionRenderer { - _SelectionRenderer(); +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/segment_properties.dart'; + +import '../../circular_chart/base/circular_base.dart'; +import '../../common/event_args.dart'; + +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/helper.dart'; +import '../../common/utils/typedef.dart'; +import '../../funnel_chart/base/funnel_base.dart'; + +import '../base/chart_base.dart'; + +import '../chart_segment/box_and_whisker_segment.dart'; +import '../chart_segment/candle_segment.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_segment/error_bar_segment.dart'; +import '../chart_segment/fastline_segment.dart'; +import '../chart_segment/hilo_segment.dart'; +import '../chart_segment/hiloopenclose_segment.dart'; +import '../chart_segment/line_segment.dart'; +import '../chart_segment/spline_segment.dart'; +import '../chart_segment/stacked_line_segment.dart'; +import '../chart_segment/stackedline100_segment.dart'; +import '../chart_segment/stepline_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; + +import '../common/common.dart'; + +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Creates a selection renderer for selection behavior +class SelectionRenderer { + /// Calling the default constructor of SelectionRenderer class. + SelectionRenderer(); + + /// Represents the point index of the data points. int? pointIndex; + + /// Represents the series index of the cartesian series. int? seriesIndex; + + /// Represens the value of cartesian series index late int cartesianSeriesIndex; + + /// Represens the value of cartesian point index int? cartesianPointIndex; + + /// Specifies whether the selection is done or not. late bool isSelection; - ChartSegment? selectedSegment, currentSegment; + + /// Specifies the value of selected and current segment. + SegmentProperties? selectedSegmentProperties, currentSegmentProperties; + + /// Holds the list of default selected segments. final List defaultselectedSegments = []; + + /// Holds the list of default unselected segments. final List defaultunselectedSegments = []; + + /// Specifies whether the segmens is selected or not. bool isSelected = false; + + /// Represents the value of chart. late dynamic chart; - late dynamic _chartState; - dynamic seriesRenderer; + + /// Represents the chart state properties. + late dynamic stateProperties; + + /// Represents the chart series renderer + dynamic seriesRendererDetails; + + /// Represents the fill and stroke color for selection painter. late Color fillColor, strokeColor; - late double fillOpacity, strokeOpacity, strokeWidth; + + /// Represents the value of fill and stroke opacity. + late double fillOpacity, strokeOpacity; + + /// Represents the value of stroke width. + late double strokeWidth; + + /// Specifies the value of selection args SelectionArgs? selectionArgs; + + /// Specifies the value of selected segments late List selectedSegments; + + /// Specifies the value of unselected segments List? unselectedSegments; + + /// Specifies the data point selection type SelectionType? selectionType; + + /// Represents the value of viewport point index. int? viewportIndex; + + /// Specifies whether the point is selected or not. bool selected = false; - bool _isInteraction = false; + + /// Specifies the user interaction is performed or not for selection. + bool isInteraction = false; ////Selects or deselects the specified data point in the series. /// @@ -44,116 +125,127 @@ class _SelectionRenderer { void selectDataPoints(int? pointIndex, [int? seriesIndex]) { if (chart is SfCartesianChart) { - if (_validIndex(pointIndex, seriesIndex, chart)) { + if (validIndex(pointIndex, seriesIndex, chart)) { bool select = false; final List seriesRenderList = - _chartState._chartSeries.visibleSeriesRenderers; + stateProperties.chartSeries.visibleSeriesRenderers; final CartesianSeriesRenderer seriesRender = seriesRenderList[seriesIndex!]; selected = pointIndex != null; - viewportIndex = _getVisibleDataPointIndex(pointIndex, seriesRender); - final String seriesType = seriesRenderer._seriesType; + viewportIndex = getVisibleDataPointIndex( + pointIndex, SeriesHelper.getSeriesRendererDetails(seriesRender)); + final String seriesType = seriesRendererDetails.seriesType; final SelectionBehaviorRenderer selectionBehaviorRenderer = - seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer!._isInteraction = true; - if (_isLineTypeSeries(seriesType) || + seriesRendererDetails.selectionBehaviorRenderer; + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); + selectionDetails.selectionRenderer!.isInteraction = true; + if (isLineTypeSeries(seriesType) || seriesType.contains('hilo') || seriesType == 'candle' || seriesType.contains('boxandwhisker')) { - if (seriesRenderer._isSelectionEnable = true) { - selectionBehaviorRenderer._selectionRenderer!.cartesianPointIndex = + if (seriesRendererDetails.isSelectionEnable = true) { + selectionDetails.selectionRenderer!.cartesianPointIndex = pointIndex; - selectionBehaviorRenderer._selectionRenderer!.cartesianSeriesIndex = + selectionDetails.selectionRenderer!.cartesianSeriesIndex = seriesIndex; - select = selectionBehaviorRenderer._selectionRenderer! - .isCartesianSelection( - chart, seriesRender, pointIndex, seriesIndex); + select = selectionDetails.selectionRenderer!.isCartesianSelection( + chart, seriesRender, pointIndex, seriesIndex); } } else { - _chartState._renderDatalabelRegions = []; + stateProperties.renderDatalabelRegions = []; + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); if (seriesType.contains('area') || seriesType == 'fastline') { - selectionBehaviorRenderer._selectionRenderer!.seriesIndex = - seriesIndex; + selectionDetails.selectionRenderer!.seriesIndex = seriesIndex; } else { - selectionBehaviorRenderer._selectionRenderer!.seriesIndex = - seriesIndex; - selectionBehaviorRenderer._selectionRenderer!.pointIndex = - pointIndex; + selectionDetails.selectionRenderer!.seriesIndex = seriesIndex; + selectionDetails.selectionRenderer!.pointIndex = pointIndex; } - select = selectionBehaviorRenderer._selectionRenderer! - .isCartesianSelection( - chart, seriesRender, pointIndex, seriesIndex); + select = selectionDetails.selectionRenderer!.isCartesianSelection( + chart, seriesRender, pointIndex, seriesIndex); } if (select) { for (final CartesianSeriesRenderer _seriesRenderer - in _chartState._chartSeries.visibleSeriesRenderers) { - ValueNotifier(_seriesRenderer._repaintNotifier.value++); + in stateProperties.chartSeries.visibleSeriesRenderers) { + ValueNotifier( + SeriesHelper.getSeriesRendererDetails(_seriesRenderer) + .repaintNotifier + .value++); } } selectionType = null; } } else if (chart is SfCircularChart) { - if (_validIndex(pointIndex!, seriesIndex!, chart)) { - _chartState._chartSeries._seriesPointSelection( + if (validIndex(pointIndex!, seriesIndex!, chart)) { + stateProperties.chartSeries.seriesPointSelection( null, chart.selectionGesture, pointIndex, seriesIndex); } } else if (chart is SfFunnelChart) { if (pointIndex! < chart.series.dataSource.length) { - seriesRenderer._chartState!._chartSeries - ._seriesPointSelection(pointIndex, chart.selectionGesture); + stateProperties.chartSeries + .seriesPointSelection(pointIndex, chart.selectionGesture); } } else { if (pointIndex! < chart.series.dataSource.length) { - seriesRenderer._chartState!._chartSeries - ._seriesPointSelection(pointIndex, chart.selectionGesture); + seriesRendererDetails.stateProperties.chartSeries + .seriesPointSelection(pointIndex, chart.selectionGesture); } } } - /// selection for selected dataPoint index + /// Selection for selected dataPoint index void selectedDataPointIndex( CartesianSeriesRenderer seriesRenderer, List selectedData) { for (int data = 0; data < selectedData.length; data++) { final int selectedItem = selectedData[data]; if (chart.onSelectionChanged != null) {} - for (int j = 0; j < seriesRenderer._segments.length; j++) { - currentSegment = seriesRenderer._segments[j]; - currentSegment!.currentSegmentIndex == selectedItem - ? selectedSegments.add(seriesRenderer._segments[j]) - : unselectedSegments!.add(seriesRenderer._segments[j]); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + for (int j = 0; j < seriesRendererDetails.segments.length; j++) { + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[j]); + currentSegmentProperties!.segment.currentSegmentIndex == selectedItem + ? selectedSegments.add(seriesRendererDetails.segments[j]) + : unselectedSegments!.add(seriesRendererDetails.segments[j]); } } - _selectedSegmentsColors(selectedSegments); - _unselectedSegmentsColors(unselectedSegments!); + selectedSegmentsColors(selectedSegments); + unselectedSegmentsColors(unselectedSegments!); } ///Paint method for default fill color settings Paint getDefaultFillColor(List>? points, int? point, ChartSegment segment) { - final String seriesType = seriesRenderer._seriesType; + final String seriesType = seriesRendererDetails.seriesType; final Paint selectedFillPaint = Paint(); - if (seriesRenderer._series is CartesianSeries) { - seriesRenderer._seriesType == 'line' || + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + if (seriesRendererDetails.series is CartesianSeries) { + seriesRendererDetails.seriesType == 'line' || seriesType == 'spline' || seriesType == 'stepline' || seriesType == 'fastline' || seriesType == 'stackedline' || seriesType == 'stackedline100' || seriesType.contains('hilo') - ? selectedFillPaint.color = segment._defaultStrokeColor!.color - : selectedFillPaint.color = segment._defaultFillColor!.color; - if (segment._defaultFillColor?.shader != null) { - selectedFillPaint.shader = segment._defaultFillColor!.shader; + ? selectedFillPaint.color = + segmentProperties.defaultStrokeColor!.color + : selectedFillPaint.color = segmentProperties.defaultFillColor!.color; + if (segmentProperties.defaultFillColor?.shader != null) { + selectedFillPaint.shader = segmentProperties.defaultFillColor!.shader; } } - if (seriesRenderer._seriesType == 'candle') { - if (segment is CandleSegment && segment._isSolid) { + if (seriesRendererDetails.seriesType == 'candle') { + if (segment is CandleSegment && segmentProperties.isSolid == true) { selectedFillPaint.style = PaintingStyle.fill; - selectedFillPaint.strokeWidth = seriesRenderer._series.borderWidth; + selectedFillPaint.strokeWidth = + seriesRendererDetails.series.borderWidth; } else { selectedFillPaint.style = PaintingStyle.stroke; - selectedFillPaint.strokeWidth = seriesRenderer._series.borderWidth; + selectedFillPaint.strokeWidth = + seriesRendererDetails.series.borderWidth; } } else { selectedFillPaint.style = PaintingStyle.fill; @@ -164,11 +256,13 @@ class _SelectionRenderer { ///Paint method for default stroke color settings Paint getDefaultStrokeColor(List>? points, int? point, ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); final Paint selectedStrokePaint = Paint(); - if (seriesRenderer._series is CartesianSeries) { - selectedStrokePaint.color = segment._defaultStrokeColor!.color; + if (seriesRendererDetails.series is CartesianSeries) { + selectedStrokePaint.color = segmentProperties.defaultStrokeColor!.color; selectedStrokePaint.strokeWidth = - segment._defaultStrokeColor!.strokeWidth; + segmentProperties.defaultStrokeColor!.strokeWidth; } selectedStrokePaint.style = PaintingStyle.stroke; return selectedStrokePaint; @@ -176,8 +270,11 @@ class _SelectionRenderer { /// Paint method with selected fill color values Paint getFillColor(bool isSelection, ChartSegment segment) { - final dynamic selectionBehavior = seriesRenderer._selectionBehavior; - final CartesianSeries series = seriesRenderer._series; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final dynamic selectionBehavior = seriesRendererDetails.selectionBehavior; + final CartesianSeries series = + seriesRendererDetails.series; assert( ((selectionBehavior.selectedOpacity != null) == false) || selectionBehavior.selectedOpacity >= 0 == true && @@ -196,19 +293,19 @@ class _SelectionRenderer { selectionArgs != null && selectionArgs!.selectedColor != null ? selectionArgs!.selectedColor - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.selectedColor != null - ? _chartState._selectionArgs!.selectedColor + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.selectedColor != null + ? stateProperties.selectionArgs!.selectedColor : selectionBehavior.selectedColor ?? - segment._defaultFillColor!.color; + segmentProperties.defaultFillColor!.color; } fillOpacity = chartEventSelection != null && selectionArgs != null && selectionArgs!.selectedColor != null ? selectionArgs!.selectedColor!.opacity - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.selectedColor != null - ? _chartState._selectionArgs!.selectedColor.opacity + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.selectedColor != null + ? stateProperties.selectionArgs!.selectedColor.opacity : selectionBehavior.selectedOpacity ?? series.opacity; } else { if (series is CartesianSeries) { @@ -216,25 +313,25 @@ class _SelectionRenderer { selectionArgs != null && selectionArgs!.unselectedColor != null ? selectionArgs!.unselectedColor - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.unselectedColor != null - ? _chartState._selectionArgs!.unselectedColor + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.unselectedColor != null + ? stateProperties.selectionArgs!.unselectedColor : selectionBehavior.unselectedColor ?? - segment._defaultFillColor!.color; + segmentProperties.defaultFillColor!.color; } fillOpacity = chartEventSelection != null && selectionArgs != null && selectionArgs!.unselectedColor != null ? selectionArgs!.unselectedColor!.opacity - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.unselectedColor != null - ? _chartState._selectionArgs!.unselectedColor.opacity + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.unselectedColor != null + ? stateProperties.selectionArgs!.unselectedColor.opacity : selectionBehavior.unselectedOpacity ?? series.opacity; } final Paint selectedFillPaint = Paint(); selectedFillPaint.color = fillColor.withOpacity(fillOpacity); - if (seriesRenderer._seriesType == 'candle') { - if (segment is CandleSegment && segment._isSolid) { + if (seriesRendererDetails.seriesType == 'candle') { + if (segment is CandleSegment && segmentProperties.isSolid == true) { selectedFillPaint.style = PaintingStyle.fill; selectedFillPaint.strokeWidth = series.borderWidth; } else { @@ -250,9 +347,12 @@ class _SelectionRenderer { /// Paint method with selected stroke color values Paint getStrokeColor(bool isSelection, ChartSegment segment, [CartesianChartPoint? point]) { - final CartesianSeries series = seriesRenderer._series; - final String seriesType = seriesRenderer._seriesType; - final dynamic selectionBehavior = seriesRenderer._selectionBehavior; + final CartesianSeries series = + seriesRendererDetails.series; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + final String seriesType = seriesRendererDetails.seriesType; + final dynamic selectionBehavior = seriesRendererDetails.selectionBehavior; final ChartSelectionCallback? chartEventSelection = chart.onSelectionChanged; if (isSelection) { @@ -270,18 +370,19 @@ class _SelectionRenderer { selectionArgs != null && selectionArgs!.selectedColor != null ? selectionArgs!.selectedColor - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.selectedColor != null - ? _chartState._selectionArgs!.selectedColor + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.selectedColor != null + ? stateProperties.selectionArgs!.selectedColor : selectionBehavior.selectedColor ?? - segment._defaultFillColor!.color + segmentProperties.defaultFillColor!.color : strokeColor = chartEventSelection != null && selectionArgs != null && selectionArgs!.selectedBorderColor != null ? selectionArgs!.selectedBorderColor - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.selectedBorderColor != null - ? _chartState._selectionArgs!.selectedBorderColor + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.selectedBorderColor != + null + ? stateProperties.selectionArgs!.selectedBorderColor : selectionBehavior.selectedBorderColor ?? series.borderColor; } @@ -290,18 +391,18 @@ class _SelectionRenderer { selectionArgs != null && selectionArgs!.selectedBorderColor != null ? selectionArgs!.selectedBorderColor!.opacity - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.selectedBorderColor != null - ? _chartState._selectionArgs!.selectedBorderColor.opacity + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.selectedBorderColor != null + ? stateProperties.selectionArgs!.selectedBorderColor.opacity : selectionBehavior.selectedOpacity ?? series.opacity; strokeWidth = chartEventSelection != null && selectionArgs != null && selectionArgs!.selectedBorderWidth != null ? selectionArgs!.selectedBorderWidth - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.selectedBorderWidth != null - ? _chartState._selectionArgs!.selectedBorderWidth + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.selectedBorderWidth != null + ? stateProperties.selectionArgs!.selectedBorderWidth : selectionBehavior.selectedBorderWidth ?? series.borderWidth; } else { if (series is CartesianSeries) { @@ -318,19 +419,19 @@ class _SelectionRenderer { selectionArgs != null && selectionArgs!.unselectedColor != null ? selectionArgs!.unselectedColor - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.unselectedColor != null - ? _chartState._selectionArgs!.unselectedColor + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.unselectedColor != null + ? stateProperties.selectionArgs!.unselectedColor : selectionBehavior.unselectedColor ?? - segment._defaultFillColor!.color + segmentProperties.defaultFillColor!.color : strokeColor = chartEventSelection != null && selectionArgs != null && selectionArgs!.unselectedBorderColor != null ? selectionArgs!.unselectedBorderColor - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.unselectedBorderColor != + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.unselectedBorderColor != null - ? _chartState._selectionArgs!.unselectedBorderColor + ? stateProperties.selectionArgs!.unselectedBorderColor : selectionBehavior.unselectedBorderColor ?? series.borderColor; } @@ -338,17 +439,17 @@ class _SelectionRenderer { selectionArgs != null && selectionArgs!.unselectedColor != null ? selectionArgs!.unselectedColor!.opacity - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.unselectedColor != null - ? _chartState._selectionArgs!.unselectedColor.opacity + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.unselectedColor != null + ? stateProperties.selectionArgs!.unselectedColor.opacity : selectionBehavior.unselectedOpacity ?? series.opacity; strokeWidth = chartEventSelection != null && selectionArgs != null && selectionArgs!.unselectedBorderWidth != null ? selectionArgs!.unselectedBorderWidth - : _chartState._selectionArgs != null && - _chartState._selectionArgs!.unselectedBorderWidth != null - ? _chartState._selectionArgs!.unselectedBorderWidth + : stateProperties.selectionArgs != null && + stateProperties.selectionArgs!.unselectedBorderWidth != null + ? stateProperties.selectionArgs!.unselectedBorderWidth : selectionBehavior.unselectedBorderWidth ?? series.borderWidth; } final Paint selectedStrokePaint = Paint(); @@ -360,36 +461,54 @@ class _SelectionRenderer { } /// Give selected color for selected segments - void _selectedSegmentsColors(List selectedSegments) { + void selectedSegmentsColors(List selectedSegments) { for (int i = 0; i < selectedSegments.length; i++) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[selectedSegments[i]._seriesIndex]; - if (seriesRenderer._seriesType.contains('area') == false && - seriesRenderer._seriesType != 'fastline') { - final ChartSegment currentSegment = - seriesRenderer._segments[selectedSegments[i].currentSegmentIndex]; - final Paint fillPaint = getFillColor(true, currentSegment); - currentSegment.fillPaint = seriesRenderer._selectionBehaviorRenderer - .getSelectedItemFill(fillPaint, selectedSegments[i]._seriesIndex, - selectedSegments[i].currentSegmentIndex, selectedSegments); - final Paint strokePaint = getStrokeColor(true, currentSegment); - currentSegment.strokePaint = seriesRenderer._selectionBehaviorRenderer + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegments[i]); + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries + .visibleSeriesRenderers[selectedSegmentProperties.seriesIndex]); + if (seriesRendererDetails.seriesType.contains('area') == false && + seriesRendererDetails.seriesType != 'fastline') { + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[selectedSegments[i].currentSegmentIndex]); + final Paint fillPaint = + getFillColor(true, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = + seriesRendererDetails.selectionBehaviorRenderer.getSelectedItemFill( + fillPaint, + selectedSegmentProperties.seriesIndex, + selectedSegments[i].currentSegmentIndex, + selectedSegments); + final Paint strokePaint = + getStrokeColor(true, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = seriesRendererDetails + .selectionBehaviorRenderer .getSelectedItemBorder( strokePaint, - selectedSegments[i]._seriesIndex, + selectedSegmentProperties.seriesIndex, selectedSegments[i].currentSegmentIndex, selectedSegments); } else { - final ChartSegment currentSegment = seriesRenderer._segments[0]; - final Paint fillPaint = getFillColor(true, currentSegment); - currentSegment.fillPaint = seriesRenderer._selectionBehaviorRenderer - .getSelectedItemFill(fillPaint, selectedSegments[i]._seriesIndex, - selectedSegments[i].currentSegmentIndex, selectedSegments); - final Paint strokePaint = getStrokeColor(true, currentSegment); - currentSegment.strokePaint = seriesRenderer._selectionBehaviorRenderer + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); + final Paint fillPaint = + getFillColor(true, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = + seriesRendererDetails.selectionBehaviorRenderer.getSelectedItemFill( + fillPaint, + selectedSegmentProperties.seriesIndex, + selectedSegments[i].currentSegmentIndex, + selectedSegments); + final Paint strokePaint = + getStrokeColor(true, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = seriesRendererDetails + .selectionBehaviorRenderer .getSelectedItemBorder( strokePaint, - selectedSegments[i]._seriesIndex, + selectedSegmentProperties.seriesIndex, selectedSegments[i].currentSegmentIndex, selectedSegments); } @@ -397,123 +516,152 @@ class _SelectionRenderer { } /// Give unselected color for unselected segments - void _unselectedSegmentsColors(List unselectedSegments) { + void unselectedSegmentsColors(List unselectedSegments) { for (int i = 0; i < unselectedSegments.length; i++) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[unselectedSegments[i]._seriesIndex]; - if (seriesRenderer._seriesType.contains('area') == false && - seriesRenderer._seriesType != 'fastline') { - final ChartSegment currentSegment = - seriesRenderer._segments[unselectedSegments[i].currentSegmentIndex]; - final Paint fillPaint = getFillColor(false, currentSegment); - currentSegment.fillPaint = seriesRenderer._selectionBehaviorRenderer - .getUnselectedItemFill( - fillPaint, - unselectedSegments[i]._seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); - final Paint strokePaint = getStrokeColor(false, currentSegment); - currentSegment.strokePaint = seriesRenderer._selectionBehaviorRenderer - .getUnselectedItemBorder( - strokePaint, - unselectedSegments[i]._seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); - } else { - final ChartSegment currentSegment = seriesRenderer._segments[0]; - final Paint fillPaint = getFillColor(false, currentSegment); - currentSegment.fillPaint = seriesRenderer._selectionBehaviorRenderer - .getUnselectedItemFill( - fillPaint, - unselectedSegments[i]._seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); - final Paint strokePaint = getStrokeColor(false, currentSegment); - currentSegment.strokePaint = seriesRenderer._selectionBehaviorRenderer - .getUnselectedItemBorder( - strokePaint, - unselectedSegments[i]._seriesIndex, - unselectedSegments[i].currentSegmentIndex, - unselectedSegments); + if (unselectedSegments[i] is! ErrorBarSegment) { + final SegmentProperties unselectedSegmentProperties = + SegmentHelper.getSegmentProperties(unselectedSegments[i]); + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[ + unselectedSegmentProperties.seriesIndex]); + if (seriesRendererDetails.seriesType.contains('area') == false && + seriesRendererDetails.seriesType != 'fastline') { + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[unselectedSegments[i].currentSegmentIndex]); + final Paint fillPaint = + getFillColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = seriesRendererDetails + .selectionBehaviorRenderer + .getUnselectedItemFill( + fillPaint, + unselectedSegmentProperties.seriesIndex, + unselectedSegments[i].currentSegmentIndex, + unselectedSegments); + final Paint strokePaint = + getStrokeColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = seriesRendererDetails + .selectionBehaviorRenderer + .getUnselectedItemBorder( + strokePaint, + unselectedSegmentProperties.seriesIndex, + unselectedSegments[i].currentSegmentIndex, + unselectedSegments); + } else { + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); + final Paint fillPaint = + getFillColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = seriesRendererDetails + .selectionBehaviorRenderer + .getUnselectedItemFill( + fillPaint, + unselectedSegmentProperties.seriesIndex, + unselectedSegments[i].currentSegmentIndex, + unselectedSegments); + final Paint strokePaint = + getStrokeColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = seriesRendererDetails + .selectionBehaviorRenderer + .getUnselectedItemBorder( + strokePaint, + unselectedSegmentProperties.seriesIndex, + unselectedSegments[i].currentSegmentIndex, + unselectedSegments); + } } } } - /// change color and removing unselected segments from list + /// Change color and removing unselected segments from list void changeColorAndPopUnselectedSegments( List unselectedSegments) { int k = unselectedSegments.length - 1; while (unselectedSegments.isNotEmpty) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[unselectedSegments[k]._seriesIndex]; - if (seriesRenderer._seriesType.contains('area') == false && - seriesRenderer._seriesType != 'fastline') { + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[ + SegmentHelper.getSegmentProperties(unselectedSegments[k]) + .seriesIndex]); + if (seriesRendererDetails.seriesType.contains('area') == false && + seriesRendererDetails.seriesType != 'fastline') { if (unselectedSegments[k].currentSegmentIndex! < - seriesRenderer._segments.length) { - final ChartSegment currentSegment = seriesRenderer - ._segments[unselectedSegments[k].currentSegmentIndex]; + seriesRendererDetails.segments.length && + unselectedSegments[k] is! ErrorBarSegment) { + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[unselectedSegments[k].currentSegmentIndex]); final Paint fillPaint = - getDefaultFillColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.strokePaint = strokePaint; + getDefaultFillColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + final Paint strokePaint = getDefaultStrokeColor( + null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = strokePaint; } unselectedSegments.remove(unselectedSegments[k]); k--; } else { - final ChartSegment currentSegment = seriesRenderer._segments[0]; - final Paint fillPaint = getDefaultFillColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); + final Paint fillPaint = + getDefaultFillColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.strokePaint = strokePaint; + getDefaultStrokeColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = strokePaint; unselectedSegments.remove(unselectedSegments[0]); k--; } } } - /// change color and remove selected segments from list + /// Change color and remove selected segments from list bool changeColorAndPopSelectedSegments( List selectedSegments, bool isSamePointSelect) { int j = selectedSegments.length - 1; while (selectedSegments.isNotEmpty) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[selectedSegments[j]._seriesIndex]; - if (seriesRenderer._seriesType.contains('area') == false && - seriesRenderer._seriesType != 'fastline') { + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegments[j]); + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries + .visibleSeriesRenderers[selectedSegmentProperties.seriesIndex]); + if (seriesRendererDetails.seriesType.contains('area') == false && + seriesRendererDetails.seriesType != 'fastline') { if (selectedSegments[j].currentSegmentIndex! < - seriesRenderer._segments.length) { - final ChartSegment currentSegment = - seriesRenderer._segments[selectedSegments[j].currentSegmentIndex]; + seriesRendererDetails.segments.length) { + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[selectedSegments[j].currentSegmentIndex]); final Paint fillPaint = - getDefaultFillColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.strokePaint = strokePaint; - if (seriesRenderer._seriesType == 'line' || - seriesRenderer._seriesType == 'spline' || - seriesRenderer._seriesType == 'stepline' || - seriesRenderer._seriesType == 'stackedline' || - seriesRenderer._seriesType == 'stackedline100' || - seriesRenderer._seriesType.contains('hilo') == true || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType.contains('boxandwhisker') == true) { - if (selectedSegments[j]._currentPoint!.overallDataPointIndex == + getDefaultFillColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + final Paint strokePaint = getDefaultStrokeColor( + null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = strokePaint; + if (seriesRendererDetails.seriesType == 'line' || + seriesRendererDetails.seriesType == 'spline' || + seriesRendererDetails.seriesType == 'stepline' || + seriesRendererDetails.seriesType == 'stackedline' || + seriesRendererDetails.seriesType == 'stackedline100' || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType.contains('boxandwhisker') == + true) { + if (selectedSegmentProperties.currentPoint!.overallDataPointIndex == cartesianPointIndex && - selectedSegments[j]._seriesIndex == cartesianSeriesIndex) { + selectedSegmentProperties.seriesIndex == cartesianSeriesIndex) { isSamePointSelect = true; } } else { - if ((currentSegment._currentPoint!.overallDataPointIndex == + if ((currentSegmentProperties.currentPoint!.overallDataPointIndex == pointIndex || - selectedSegments[j]._currentPoint!.overallDataPointIndex == + selectedSegmentProperties + .currentPoint!.overallDataPointIndex == pointIndex) && - currentSegment._oldSegmentIndex == - selectedSegments[j]._oldSegmentIndex && - selectedSegments[j]._seriesIndex == seriesIndex) { + currentSegmentProperties.oldSegmentIndex == + selectedSegmentProperties.oldSegmentIndex && + selectedSegmentProperties.seriesIndex == seriesIndex) { isSamePointSelect = true; } } @@ -521,13 +669,18 @@ class _SelectionRenderer { selectedSegments.remove(selectedSegments[j]); j--; } else { - final ChartSegment currentSegment = seriesRenderer._segments[0]; - final Paint fillPaint = getDefaultFillColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); + final Paint fillPaint = + getDefaultFillColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.strokePaint = strokePaint; - if (selectedSegments[0]._seriesIndex == seriesIndex) { + getDefaultStrokeColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = strokePaint; + if (SegmentHelper.getSegmentProperties(selectedSegments[0]) + .seriesIndex == + seriesIndex) { isSamePointSelect = true; } selectedSegments.remove(selectedSegments[0]); @@ -542,43 +695,54 @@ class _SelectionRenderer { for (int j = 0; j < selectedSegments.length && selectedSegments.isNotEmpty; j++) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[selectedSegments[j]._seriesIndex]; - if (seriesRenderer._seriesType.contains('area') == false && - seriesRenderer._seriesType != 'fastline') { + final SegmentProperties selectedSegmentProperties = + SegmentHelper.getSegmentProperties(selectedSegments[j]); + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries + .visibleSeriesRenderers[selectedSegmentProperties.seriesIndex]); + if (seriesRendererDetails.seriesType.contains('area') == false && + seriesRendererDetails.seriesType != 'fastline') { if (selectedSegments[j].currentSegmentIndex! < - seriesRenderer._segments.length) { - final ChartSegment currentSegment = - seriesRenderer._segments[selectedSegments[j].currentSegmentIndex]; - if (((seriesRenderer._seriesType.indexOf('line') >= 0) == true || - seriesRenderer._seriesType.contains('hilo') == true || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType.contains('boxandwhisker') == + seriesRendererDetails.segments.length) { + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[selectedSegments[j].currentSegmentIndex]); + if (((seriesRendererDetails.seriesType.indexOf('line') >= 0) == + true || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true) && - selectedSegments[j]._currentPoint!.overallDataPointIndex == + selectedSegmentProperties.currentPoint!.overallDataPointIndex == cartesianPointIndex && - selectedSegments[j]._seriesIndex == cartesianSeriesIndex) { + selectedSegmentProperties.seriesIndex == cartesianSeriesIndex) { isSamePointSelected = true; } else { - if ((currentSegment._currentPoint!.overallDataPointIndex == + if ((currentSegmentProperties.currentPoint!.overallDataPointIndex == pointIndex || - selectedSegments[j]._currentPoint!.overallDataPointIndex == + selectedSegmentProperties + .currentPoint!.overallDataPointIndex == pointIndex) && - currentSegment._oldSegmentIndex == - selectedSegments[j]._oldSegmentIndex && - selectedSegments[j]._seriesIndex == seriesIndex) { + currentSegmentProperties.oldSegmentIndex == + selectedSegmentProperties.oldSegmentIndex && + selectedSegmentProperties.seriesIndex == seriesIndex) { isSamePointSelected = true; } } } } else { - final ChartSegment currentSegment = seriesRenderer._segments[0]; - final Paint fillPaint = getDefaultFillColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); + final Paint fillPaint = + getDefaultFillColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.strokePaint = strokePaint; - if (selectedSegments[0]._seriesIndex == seriesIndex) { + getDefaultStrokeColor(null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = strokePaint; + if (SegmentHelper.getSegmentProperties(selectedSegments[0]) + .seriesIndex == + seriesIndex) { isSamePointSelected = true; } } @@ -586,59 +750,74 @@ class _SelectionRenderer { return isSamePointSelected; } + /// To get the selected segment on tap ChartSegment? getTappedSegment() { for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - for (int k = 0; k < seriesRenderer._segments.length; k++) { - if (!seriesRenderer._seriesType.contains('area') && - seriesRenderer._seriesType != 'fastline') { - currentSegment = seriesRenderer._segments[k]; - if (seriesRenderer._seriesType == 'line' || - seriesRenderer._seriesType == 'spline' || - seriesRenderer._seriesType == 'stepline' || - seriesRenderer._seriesType == 'stackedline' || - seriesRenderer._seriesType == 'stackedline100' || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType.contains('boxandwhisker')) { - if (currentSegment!.currentSegmentIndex == cartesianPointIndex && - currentSegment!._seriesIndex == cartesianSeriesIndex) { - selectedSegment = seriesRenderer._segments[k]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + for (int k = 0; k < seriesRendererDetails.segments.length; k++) { + if (seriesRendererDetails.seriesType.contains('area') == false && + seriesRendererDetails.seriesType != 'fastline') { + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); + if (seriesRendererDetails.seriesType == 'line' || + seriesRendererDetails.seriesType == 'spline' || + seriesRendererDetails.seriesType == 'stepline' || + seriesRendererDetails.seriesType == 'stackedline' || + seriesRendererDetails.seriesType == 'stackedline100' || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType.contains('boxandwhisker') == + true) { + if (currentSegmentProperties!.segment.currentSegmentIndex == + cartesianPointIndex && + currentSegmentProperties!.seriesIndex == cartesianSeriesIndex) { + selectedSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); } } else { - if (currentSegment!._currentPoint!.overallDataPointIndex == + if (currentSegmentProperties!.currentPoint!.overallDataPointIndex == pointIndex && - currentSegment!._seriesIndex == seriesIndex) { - selectedSegment = seriesRenderer._segments[k]; + currentSegmentProperties!.seriesIndex == seriesIndex) { + selectedSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); } } } else { - currentSegment = seriesRenderer._segments[0]; - if (currentSegment!._seriesIndex == seriesIndex) { - selectedSegment = seriesRenderer._segments[0]; + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); + if (currentSegmentProperties!.seriesIndex == + SelectionHelper.getRenderingDetails( + seriesRendererDetails.selectionBehaviorRenderer!) + .selectionRenderer! + .seriesIndex) { + selectedSegmentProperties = currentSegmentProperties; break; } } } } - return selectedSegment; + return selectedSegmentProperties?.segment; } + /// To check position of the selection segment. bool checkPosition() { outerLoop: for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - for (int k = 0; k < seriesRenderer._segments.length; k++) { - currentSegment = seriesRenderer._segments[k]; - if ((currentSegment!._currentPoint!.overallDataPointIndex == + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + for (int k = 0; k < seriesRendererDetails.segments.length; k++) { + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); + if ((currentSegmentProperties!.currentPoint!.overallDataPointIndex == pointIndex) && - currentSegment!._seriesIndex == seriesIndex) { + currentSegmentProperties!.seriesIndex == seriesIndex) { isSelected = true; break outerLoop; } else { @@ -653,25 +832,28 @@ class _SelectionRenderer { bool isCartesianSelection(SfCartesianChart chartAssign, CartesianSeriesRenderer seriesAssign, int? pointIndex, int? seriesIndex) { chart = chartAssign; - seriesRenderer = seriesAssign; + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails(seriesAssign); if (chart.onSelectionChanged != null && selected && - (!(seriesRenderer._selectionBehavior.toggleSelection == false && + (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && _isSamePointSelected(selectedSegments)))) { - chart.onSelectionChanged(getSelectionEventArgs(seriesRenderer._series, - seriesIndex!, viewportIndex!, seriesRenderer)); + chart.onSelectionChanged(getSelectionEventArgs( + seriesRendererDetails.series, + seriesIndex!, + viewportIndex!, + seriesRendererDetails)); selected = false; } /// Maintained the event arguments on zooming, device orientation change. if (selectionArgs != null) { - _chartState._selectionArgs = selectionArgs; + stateProperties.selectionArgs = selectionArgs; } /// For point mode if ((selectionType ?? chart.selectionType) == SelectionType.point) { - if (!(seriesRenderer._selectionBehavior.toggleSelection == false && + if (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && _isSamePointSelected(selectedSegments))) { bool isSamePointSelect = false; @@ -685,35 +867,44 @@ class _SelectionRenderer { if (chart.enableMultiSelection == true) { if (selectedSegments.isNotEmpty) { for (int i = - _chartState._chartSeries.visibleSeriesRenderers.length - 1; + stateProperties.chartSeries.visibleSeriesRenderers.length - + 1; i >= 0; i--) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); /// To identify the tapped segment - for (int k = 0; k < seriesRenderer._segments.length; k++) { - currentSegment = seriesRenderer._segments[k]; - if ((currentSegment!._currentPoint!.overallDataPointIndex == + for (int k = 0; k < seriesRendererDetails.segments.length; k++) { + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); + if ((currentSegmentProperties! + .currentPoint!.overallDataPointIndex == pointIndex) && - currentSegment!._seriesIndex == seriesIndex) { - selectedSegment = seriesRenderer._segments[k]; + currentSegmentProperties!.seriesIndex == seriesIndex) { + selectedSegmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); break; } } } /// To identify that tapped segment in any one of the selected segment - if (selectedSegment != null) { + if (selectedSegmentProperties != null) { for (int k = 0; k < selectedSegments.length; k++) { - if ((selectedSegment!.currentSegmentIndex == + if ((selectedSegmentProperties!.segment.currentSegmentIndex == selectedSegments[k].currentSegmentIndex || - selectedSegment!._currentPoint!.overallDataPointIndex == - selectedSegments[k] - ._currentPoint! + selectedSegmentProperties! + .currentPoint!.overallDataPointIndex == + SegmentHelper.getSegmentProperties( + selectedSegments[k]) + .currentPoint! .overallDataPointIndex) && - selectedSegment!._seriesIndex == - selectedSegments[k]._seriesIndex) { + selectedSegmentProperties!.seriesIndex == + SegmentHelper.getSegmentProperties(selectedSegments[k]) + .seriesIndex) { multiSelect = true; break; } @@ -723,62 +914,72 @@ class _SelectionRenderer { /// Executes when tapped again in one of the selected segments if (multiSelect) { for (int j = selectedSegments.length - 1; j >= 0; j--) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[selectedSegments[j]._seriesIndex]; - final ChartSegment currentSegment = seriesRenderer - ._segments[selectedSegments[j].currentSegmentIndex]; + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[ + selectedSegmentProperties!.seriesIndex]); + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[selectedSegments[j].currentSegmentIndex]); /// Applying default settings when last selected segment becomes unselected - if (((selectedSegment!.currentSegmentIndex == + if (((selectedSegmentProperties!.segment.currentSegmentIndex == selectedSegments[j].currentSegmentIndex || - selectedSegment! - ._currentPoint!.overallDataPointIndex == - selectedSegments[j] - ._currentPoint! + selectedSegmentProperties! + .currentPoint!.overallDataPointIndex == + SegmentHelper.getSegmentProperties( + selectedSegments[j]) + .currentPoint! .overallDataPointIndex) && - selectedSegment!._seriesIndex == - selectedSegments[j]._seriesIndex) && + selectedSegmentProperties!.seriesIndex == + SegmentHelper.getSegmentProperties( + selectedSegments[j]) + .seriesIndex) && (selectedSegments.length == 1)) { - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegment); - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - currentSegment.strokePaint = strokePaint; - - if ((currentSegment._currentPoint!.overallDataPointIndex == + final Paint fillPaint = getDefaultFillColor( + null, null, currentSegmentProperties.segment); + final Paint strokePaint = getDefaultStrokeColor( + null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + currentSegmentProperties.segment.strokePaint = strokePaint; + + if ((currentSegmentProperties + .currentPoint!.overallDataPointIndex == pointIndex || - selectedSegments[j] - ._currentPoint! + SegmentHelper.getSegmentProperties( + selectedSegments[j]) + .currentPoint! .overallDataPointIndex == pointIndex) && - selectedSegments[j]._seriesIndex == seriesIndex) { + selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } selectedSegments.remove(selectedSegments[j]); } /// Applying unselected color for unselected segments in multiSelect option - else if ((selectedSegment! - ._currentPoint!.overallDataPointIndex == - selectedSegments[j] - ._currentPoint! + else if ((selectedSegmentProperties! + .currentPoint!.overallDataPointIndex == + SegmentHelper.getSegmentProperties(selectedSegments[j]) + .currentPoint! .overallDataPointIndex) && - selectedSegment!._seriesIndex == - selectedSegments[j]._seriesIndex) { - final Paint fillPaint = getFillColor(false, currentSegment); - currentSegment.fillPaint = fillPaint; + selectedSegmentProperties!.seriesIndex == + selectedSegmentProperties!.seriesIndex) { + final Paint fillPaint = + getFillColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; final Paint strokePaint = - getStrokeColor(false, currentSegment); - currentSegment.strokePaint = strokePaint; + getStrokeColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.strokePaint = strokePaint; - if ((currentSegment._currentPoint!.overallDataPointIndex == + if ((currentSegmentProperties + .currentPoint!.overallDataPointIndex == pointIndex || - selectedSegments[j] - ._currentPoint! + SegmentHelper.getSegmentProperties( + selectedSegments[j]) + .currentPoint! .overallDataPointIndex == pointIndex) && - selectedSegments[j]._seriesIndex == seriesIndex) { + selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } unselectedSegments!.add(selectedSegments[j]); @@ -794,49 +995,59 @@ class _SelectionRenderer { } /// To check that the selection setting is enable or not - if (seriesRenderer._isSelectionEnable == true) { + if (seriesRendererDetails.isSelectionEnable == true) { if (!isSamePointSelect) { - seriesRenderer._seriesType == 'column' || - seriesRenderer._seriesType == 'bar' || - seriesRenderer._seriesType == 'scatter' || - seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType.contains('stackedcolumn') == + seriesRendererDetails.seriesType == 'column' || + seriesRendererDetails.seriesType == 'bar' || + seriesRendererDetails.seriesType == 'scatter' || + seriesRendererDetails.seriesType == 'bubble' || + seriesRendererDetails.seriesType + .contains('stackedcolumn') == + true || + seriesRendererDetails.seriesType.contains('stackedbar') == true || - seriesRenderer._seriesType.contains('stackedbar') == true || - seriesRenderer._seriesType == 'rangecolumn' || - seriesRenderer._seriesType == 'waterfall' + seriesRendererDetails.seriesType == 'rangecolumn' || + seriesRendererDetails.seriesType == 'waterfall' ? isSelected = checkPosition() : isSelected = true; unselectedSegments?.clear(); for (int i = - _chartState._chartSeries.visibleSeriesRenderers.length - 1; + stateProperties.chartSeries.visibleSeriesRenderers.length - + 1; i >= 0; i--) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); if (isSelected) { - for (int j = 0; j < seriesRenderer._segments.length; j++) { - currentSegment = seriesRenderer._segments[j]; - if (currentSegment!.currentSegmentIndex == null || + for (int j = 0; + j < seriesRendererDetails.segments.length; + j++) { + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[j]); + if (currentSegmentProperties!.segment.currentSegmentIndex == + null || pointIndex == null) { break; } - (seriesRenderer._seriesType.contains('area') - ? currentSegment!.currentSegmentIndex == + (seriesRendererDetails.seriesType.contains('area') == true + ? currentSegmentProperties! + .segment.currentSegmentIndex == pointIndex - : currentSegment! - ._currentPoint!.overallDataPointIndex == + : currentSegmentProperties! + .currentPoint!.overallDataPointIndex == pointIndex) && - currentSegment!._seriesIndex == seriesIndex - ? selectedSegments.add(seriesRenderer._segments[j]) - : unselectedSegments!.add(seriesRenderer._segments[j]); + currentSegmentProperties!.seriesIndex == seriesIndex + ? selectedSegments.add(seriesRendererDetails.segments[j]) + : unselectedSegments! + .add(seriesRendererDetails.segments[j]); } /// Giving color to unselected segments - _unselectedSegmentsColors(unselectedSegments!); + unselectedSegmentsColors(unselectedSegments!); /// Giving Color to selected segments - _selectedSegmentsColors(selectedSegments); + selectedSegmentsColors(selectedSegments); } } } else { @@ -846,30 +1057,35 @@ class _SelectionRenderer { } } - ///For Series Mode + ///For series mode else if ((selectionType ?? chart.selectionType) == SelectionType.series) { - if (!(seriesRenderer._selectionBehavior.toggleSelection == false && + if (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && _isSamePointSelected(selectedSegments))) { bool isSamePointSelect = false; for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - for (int k = 0; k < seriesRenderer._segments.length; k++) { - currentSegment = seriesRenderer._segments[k]; - final ChartSegment compareSegment = seriesRenderer._segments[k]; - if (currentSegment!.currentSegmentIndex != + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + for (int k = 0; k < seriesRendererDetails.segments.length; k++) { + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); + final ChartSegment compareSegment = + seriesRendererDetails.segments[k]; + if (currentSegmentProperties!.segment.currentSegmentIndex != compareSegment.currentSegmentIndex && - currentSegment!._seriesIndex != compareSegment._seriesIndex) { + currentSegmentProperties!.seriesIndex != + SegmentHelper.getSegmentProperties(compareSegment) + .seriesIndex) { isSelected = false; } } } /// Executes only when final selected segment became unselected - if (selectedSegments.length == seriesRenderer._segments.length) { + if (selectedSegments.length == seriesRendererDetails.segments.length) { changeColorAndPopUnselectedSegments(unselectedSegments!); } @@ -877,12 +1093,15 @@ class _SelectionRenderer { bool multiSelect = false; if (chart.enableMultiSelection == true) { if (selectedSegments.isNotEmpty) { - selectedSegment = getTappedSegment(); + selectedSegmentProperties = + SegmentHelper.getSegmentProperties(getTappedSegment()!); /// To identify that tapped again in any one of the selected segments - if (selectedSegment != null) { + if (selectedSegmentProperties != null) { for (int k = 0; k < selectedSegments.length; k++) { - if (seriesIndex == selectedSegments[k]._seriesIndex) { + if (seriesIndex == + SegmentHelper.getSegmentProperties(selectedSegments[k]) + .seriesIndex) { multiSelect = true; break; } @@ -891,85 +1110,91 @@ class _SelectionRenderer { /// Executes when tapped again in one of the selected segments if (multiSelect) { - ChartSegment currentSegment; + SegmentProperties currentSegmentProperties; for (int j = selectedSegments.length - 1; j >= 0; j--) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[selectedSegments[j]._seriesIndex]; - - currentSegment = - (seriesRenderer._seriesType.contains('area') == false && - seriesRenderer._seriesType != 'fastline') - ? seriesRenderer - ._segments[selectedSegments[j].currentSegmentIndex] - : seriesRenderer._segments[0]; + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[ + selectedSegmentProperties!.seriesIndex]); + + currentSegmentProperties = (seriesRendererDetails.seriesType + .contains('area') == + false && + seriesRendererDetails.seriesType != 'fastline') + ? SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[selectedSegments[j].currentSegmentIndex]) + : SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); /// Applying series fill when all last selected segment becomes unselected - if (seriesRenderer._seriesType.contains('area') == false && - seriesRenderer._seriesType != 'fastline') { - if ((selectedSegment!._seriesIndex == - selectedSegments[j]._seriesIndex) && + if (seriesRendererDetails.seriesType.contains('area') == + false && + seriesRendererDetails.seriesType != 'fastline') { + if ((selectedSegmentProperties!.seriesIndex == + selectedSegmentProperties!.seriesIndex) && (selectedSegments.length <= - seriesRenderer._segments.length)) { - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegment); - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - currentSegment.strokePaint = strokePaint; - if (selectedSegments[j] - ._currentPoint! + seriesRendererDetails.segments.length)) { + final Paint fillPaint = getDefaultFillColor( + null, null, currentSegmentProperties.segment); + final Paint strokePaint = getDefaultStrokeColor( + null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + currentSegmentProperties.segment.strokePaint = strokePaint; + if (SegmentHelper.getSegmentProperties(selectedSegments[j]) + .currentPoint! .overallDataPointIndex == pointIndex && - selectedSegments[j]._seriesIndex == seriesIndex) { + selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } selectedSegments.remove(selectedSegments[j]); } /// Applying unselected color for unselected segments in multiSelect option - else if (selectedSegment!._seriesIndex == - selectedSegments[j]._seriesIndex) { - final Paint fillPaint = getFillColor(false, currentSegment); + else if (selectedSegmentProperties!.seriesIndex == + selectedSegmentProperties!.seriesIndex) { + final Paint fillPaint = + getFillColor(false, currentSegmentProperties.segment); final Paint strokePaint = - getStrokeColor(false, currentSegment); - currentSegment.fillPaint = fillPaint; - currentSegment.strokePaint = strokePaint; - if (selectedSegments[j] - ._currentPoint! + getStrokeColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + currentSegmentProperties.segment.strokePaint = strokePaint; + if (SegmentHelper.getSegmentProperties(selectedSegments[j]) + .currentPoint! .overallDataPointIndex == pointIndex && - selectedSegments[j]._seriesIndex == seriesIndex) { + selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } unselectedSegments!.add(selectedSegments[j]); selectedSegments.remove(selectedSegments[j]); } } else { - if ((selectedSegment!._seriesIndex == - selectedSegments[j]._seriesIndex) && + if ((selectedSegmentProperties!.seriesIndex == + selectedSegmentProperties!.seriesIndex) && (selectedSegments.length <= - seriesRenderer._segments.length)) { - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegment); - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - currentSegment.strokePaint = strokePaint; - if (selectedSegments[j]._seriesIndex == seriesIndex) { + seriesRendererDetails.segments.length)) { + final Paint fillPaint = getDefaultFillColor( + null, null, currentSegmentProperties.segment); + final Paint strokePaint = getDefaultStrokeColor( + null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + currentSegmentProperties.segment.strokePaint = strokePaint; + if (selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } selectedSegments.remove(selectedSegments[j]); } /// Applying unselected color for unselected segments in multiSelect option - else if (selectedSegment!._seriesIndex == - selectedSegments[j]._seriesIndex) { - final Paint fillPaint = getFillColor(false, currentSegment); + else if (selectedSegmentProperties!.seriesIndex == + selectedSegmentProperties!.seriesIndex) { + final Paint fillPaint = + getFillColor(false, currentSegmentProperties.segment); final Paint strokePaint = - getStrokeColor(false, currentSegment); - currentSegment.fillPaint = fillPaint; - currentSegment.strokePaint = strokePaint; - if (selectedSegments[j]._seriesIndex == seriesIndex) { + getStrokeColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + currentSegmentProperties.segment.strokePaint = strokePaint; + if (selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } unselectedSegments!.add(selectedSegments[j]); @@ -986,51 +1211,63 @@ class _SelectionRenderer { selectedSegments, isSamePointSelect); } - /// To identify the Tapped segment - if (seriesRenderer._isSelectionEnable == true) { + /// To identify the tapped segment + if (seriesRendererDetails.isSelectionEnable == true) { if (!isSamePointSelect) { - seriesRenderer._seriesType == 'column' || - seriesRenderer._seriesType == 'bar' || - seriesRenderer._seriesType == 'scatter' || - seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType.contains('stackedcolumn') == + seriesRendererDetails.seriesType == 'column' || + seriesRendererDetails.seriesType == 'bar' || + seriesRendererDetails.seriesType == 'scatter' || + seriesRendererDetails.seriesType == 'bubble' || + seriesRendererDetails.seriesType + .contains('stackedcolumn') == true || - seriesRenderer._seriesType.contains('stackedbar') == true || - seriesRenderer._seriesType == 'rangecolumn' || - seriesRenderer._seriesType == 'waterfall' + seriesRendererDetails.seriesType.contains('stackedbar') == + true || + seriesRendererDetails.seriesType == 'rangecolumn' || + seriesRendererDetails.seriesType == 'waterfall' ? isSelected = checkPosition() : isSelected = true; - selectedSegment = getTappedSegment(); + selectedSegmentProperties = + SegmentHelper.getSegmentProperties(getTappedSegment()!); if (isSelected) { - /// To Push the Selected and Unselected segment + /// To push the selected and unselected segment for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - if (!seriesRenderer._seriesType.contains('area') && - seriesRenderer._seriesType != 'fastline') { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + if (seriesRendererDetails.seriesType.contains('area') == + false && + seriesRendererDetails.seriesType != 'fastline') { if (seriesIndex != null) { - for (int k = 0; k < seriesRenderer._segments.length; k++) { - currentSegment = seriesRenderer._segments[k]; - currentSegment!._seriesIndex == seriesIndex - ? selectedSegments.add(seriesRenderer._segments[k]) + for (int k = 0; + k < seriesRendererDetails.segments.length; + k++) { + currentSegmentProperties = + SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); + currentSegmentProperties!.seriesIndex == seriesIndex + ? selectedSegments + .add(seriesRendererDetails.segments[k]) : unselectedSegments! - .add(seriesRenderer._segments[k]); + .add(seriesRendererDetails.segments[k]); } } } else { - currentSegment = seriesRenderer._segments[0]; - currentSegment!._seriesIndex == seriesIndex - ? selectedSegments.add(seriesRenderer._segments[0]) - : unselectedSegments!.add(seriesRenderer._segments[0]); + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[0]); + currentSegmentProperties!.seriesIndex == seriesIndex + ? selectedSegments.add(seriesRendererDetails.segments[0]) + : unselectedSegments! + .add(seriesRendererDetails.segments[0]); } - /// Give Color to the Unselected segment - _unselectedSegmentsColors(unselectedSegments!); + /// Give color to the unselected segment + unselectedSegmentsColors(unselectedSegments!); - /// Give Color to the Selected segment - _selectedSegmentsColors(selectedSegments); + /// Give color to the selected segment + selectedSegmentsColors(selectedSegments); } } } else { @@ -1040,15 +1277,15 @@ class _SelectionRenderer { } } - /// For Cluster Mode + /// For cluster mode else if ((selectionType ?? chart.selectionType) == SelectionType.cluster) { - if (!(seriesRenderer._selectionBehavior.toggleSelection == false && + if (!(seriesRendererDetails.selectionBehavior.toggleSelection == false && _isSamePointSelected(selectedSegments))) { bool isSamePointSelect = false; /// Executes only when last selected segment became unselected if (selectedSegments.length == - _chartState._chartSeries.visibleSeriesRenderers.length) { + stateProperties.chartSeries.visibleSeriesRenderers.length) { changeColorAndPopUnselectedSegments(unselectedSegments!); } @@ -1056,12 +1293,13 @@ class _SelectionRenderer { bool multiSelect = false; if (chart.enableMultiSelection == true) { if (selectedSegments.isNotEmpty) { - selectedSegment = getTappedSegment(); + selectedSegmentProperties = + SegmentHelper.getSegmentProperties(getTappedSegment()!); /// To identify that tapped again in any one of the selected segment - if (selectedSegment != null) { + if (selectedSegmentProperties != null) { for (int k = 0; k < selectedSegments.length; k++) { - if (selectedSegment!.currentSegmentIndex == + if (selectedSegmentProperties!.segment.currentSegmentIndex == selectedSegments[k].currentSegmentIndex) { multiSelect = true; break; @@ -1072,26 +1310,28 @@ class _SelectionRenderer { /// Executes when tapped again in one of the selected segment if (multiSelect) { for (int j = selectedSegments.length - 1; j >= 0; j--) { - seriesRenderer = _chartState._chartSeries - .visibleSeriesRenderers[selectedSegments[j]._seriesIndex]; - final ChartSegment currentSegment = seriesRenderer - ._segments[selectedSegments[j].currentSegmentIndex]; + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[ + selectedSegmentProperties!.seriesIndex]); + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[selectedSegments[j].currentSegmentIndex]); /// Applying default settings when last selected segment becomes unselected - if ((selectedSegment!.currentSegmentIndex == + if ((selectedSegmentProperties!.segment.currentSegmentIndex == selectedSegments[j].currentSegmentIndex) && (selectedSegments.length <= - _chartState - ._chartSeries.visibleSeriesRenderers.length)) { - final Paint fillPaint = - getDefaultFillColor(null, null, currentSegment); - final Paint strokePaint = - getDefaultStrokeColor(null, null, currentSegment); - currentSegment.fillPaint = fillPaint; - currentSegment.strokePaint = strokePaint; + stateProperties + .chartSeries.visibleSeriesRenderers.length)) { + final Paint fillPaint = getDefaultFillColor( + null, null, currentSegmentProperties.segment); + final Paint strokePaint = getDefaultStrokeColor( + null, null, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + currentSegmentProperties.segment.strokePaint = strokePaint; if (selectedSegments[j].currentSegmentIndex == pointIndex && - selectedSegments[j]._seriesIndex == seriesIndex) { + selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } // if(isSamePointSelect == false && ) @@ -1099,16 +1339,18 @@ class _SelectionRenderer { } /// Applying unselected color for unselected segment in multiSelect option - else if (selectedSegment!.currentSegmentIndex == + else if (selectedSegmentProperties! + .segment.currentSegmentIndex == selectedSegments[j].currentSegmentIndex) { - final Paint fillPaint = getFillColor(false, currentSegment); + final Paint fillPaint = + getFillColor(false, currentSegmentProperties.segment); final Paint strokePaint = - getStrokeColor(false, currentSegment); - currentSegment.fillPaint = fillPaint; - currentSegment.strokePaint = strokePaint; + getStrokeColor(false, currentSegmentProperties.segment); + currentSegmentProperties.segment.fillPaint = fillPaint; + currentSegmentProperties.segment.strokePaint = strokePaint; if (selectedSegments[j].currentSegmentIndex == pointIndex && - selectedSegments[j]._seriesIndex == seriesIndex) { + selectedSegmentProperties!.seriesIndex == seriesIndex) { isSamePointSelect = true; } @@ -1126,53 +1368,66 @@ class _SelectionRenderer { selectedSegments, isSamePointSelect); } - /// To identify the Tapped segment - if (seriesRenderer._isSelectionEnable == true) { + /// To identify the tapped segment + if (seriesRendererDetails.isSelectionEnable == true) { if (!isSamePointSelect) { - final bool isSegmentSeries = seriesRenderer._seriesType == + final bool isSegmentSeries = seriesRendererDetails.seriesType == 'column' || - seriesRenderer._seriesType == 'bar' || - seriesRenderer._seriesType == 'scatter' || - seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType.contains('stackedcolumn') == true || - seriesRenderer._seriesType.contains('stackedbar') == true || - seriesRenderer._seriesType == 'rangecolumn' || - seriesRenderer._seriesType == 'waterfall'; - selectedSegment = getTappedSegment(); + seriesRendererDetails.seriesType == 'bar' || + seriesRendererDetails.seriesType == 'scatter' || + seriesRendererDetails.seriesType == 'bubble' || + seriesRendererDetails.seriesType.contains('stackedcolumn') == + true || + seriesRendererDetails.seriesType.contains('stackedbar') == + true || + seriesRendererDetails.seriesType == 'rangecolumn' || + seriesRendererDetails.seriesType == 'waterfall'; + selectedSegmentProperties = + SegmentHelper.getSegmentProperties(getTappedSegment()!); isSegmentSeries ? isSelected = checkPosition() : isSelected = true; if (isSelected) { /// To Push the Selected and Unselected segments for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - if (currentSegment!.currentSegmentIndex == null || + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + if (currentSegmentProperties!.segment.currentSegmentIndex == + null || pointIndex == null) { break; } - for (int k = 0; k < seriesRenderer._segments.length; k++) { - currentSegment = seriesRenderer._segments[k]; + for (int k = 0; + k < seriesRendererDetails.segments.length; + k++) { + currentSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); if (isSegmentSeries) { - currentSegment!._currentPoint!.xValue == - selectedSegment!._currentPoint!.xValue - ? selectedSegments.add(seriesRenderer._segments[k]) - : unselectedSegments!.add(seriesRenderer._segments[k]); + currentSegmentProperties!.currentPoint!.xValue == + selectedSegmentProperties!.currentPoint!.xValue + ? selectedSegments + .add(seriesRendererDetails.segments[k]) + : unselectedSegments! + .add(seriesRendererDetails.segments[k]); } else { - currentSegment!.currentSegmentIndex == - selectedSegment!.currentSegmentIndex - ? selectedSegments.add(seriesRenderer._segments[k]) - : unselectedSegments!.add(seriesRenderer._segments[k]); + currentSegmentProperties!.segment.currentSegmentIndex == + selectedSegmentProperties! + .segment.currentSegmentIndex + ? selectedSegments + .add(seriesRendererDetails.segments[k]) + : unselectedSegments! + .add(seriesRendererDetails.segments[k]); } } } /// Giving color to unselected segments - _unselectedSegmentsColors(unselectedSegments!); + unselectedSegmentsColors(unselectedSegments!); - /// Giving Color to selected segments - _selectedSegmentsColors(selectedSegments); + /// Giving color to selected segments + selectedSegmentsColors(selectedSegments); } } else { isSelected = true; @@ -1183,36 +1438,42 @@ class _SelectionRenderer { return isSelected; } -// To get point index and series index + /// To get point index and series index void getPointAndSeriesIndex(SfCartesianChart chart, Offset position, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRenderer._selectionBehaviorRenderer; + seriesRendererDetails.selectionBehaviorRenderer; if (selectionBehaviorRenderer == null) { return; } - ChartSegment currentSegment; - ChartSegment? selectedSegment; - for (int k = 0; k < seriesRenderer._segments.length; k++) { - currentSegment = seriesRenderer._segments[k]; - if (currentSegment._segmentRect!.contains(position)) { + SegmentProperties currentSegmentProperties; + SegmentProperties? selectedSegmentProperties; + for (int k = 0; k < seriesRendererDetails.segments.length; k++) { + currentSegmentProperties = + SegmentHelper.getSegmentProperties(seriesRendererDetails.segments[k]); + if (currentSegmentProperties.segmentRect!.contains(position) == true) { selected = true; - selectedSegment = seriesRenderer._segments[k]; - viewportIndex = selectedSegment._currentPoint?.visiblePointIndex; + selectedSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[k]); + viewportIndex = + selectedSegmentProperties.currentPoint?.visiblePointIndex; } } - if (selectedSegment == null) { - selectionBehaviorRenderer._selectionRenderer!.pointIndex = null; - selectionBehaviorRenderer._selectionRenderer!.seriesIndex = null; + + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); + if (selectedSegmentProperties == null) { + selectionDetails.selectionRenderer!.pointIndex = null; + selectionDetails.selectionRenderer!.seriesIndex = null; } else { - selectionBehaviorRenderer._selectionRenderer!.pointIndex = - selectedSegment._currentPoint?.overallDataPointIndex; - selectionBehaviorRenderer._selectionRenderer!.seriesIndex = - selectedSegment._seriesIndex; + selectionDetails.selectionRenderer!.pointIndex = + selectedSegmentProperties.currentPoint?.overallDataPointIndex; + selectionDetails.selectionRenderer!.seriesIndex = + selectedSegmentProperties.seriesIndex; } } -// To check that touch point is lies in segment + /// To check that touch point is lies in segment bool isLineIntersect( CartesianChartPoint segmentStartPoint, CartesianChartPoint segmentEndPoint, @@ -1247,60 +1508,69 @@ class _SelectionRenderer { } /// To identify that series contains a given point - bool _isSeriesContainsPoint( - CartesianSeriesRenderer seriesRenderer, Offset position) { + bool isSeriesContainsPoint( + SeriesRendererDetails seriesRendererDetails, Offset position) { int? dataPointIndex; - ChartSegment? startSegment; - ChartSegment? endSegment; + SegmentProperties? startSegmentProperties; + SegmentProperties? endSegmentProperties; final List>? nearestDataPoints = - _getNearestChartPoints( + getNearestChartPoints( position.dx, position.dy, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer); + seriesRendererDetails.xAxisDetails!.axisRenderer, + seriesRendererDetails.yAxisDetails!.axisRenderer, + seriesRendererDetails); if (nearestDataPoints == null) { return false; } for (final CartesianChartPoint dataPoint in nearestDataPoints) { - dataPointIndex = seriesRenderer - ._dataPoints[seriesRenderer._dataPoints.indexOf(dataPoint)] + dataPointIndex = seriesRendererDetails + .dataPoints[seriesRendererDetails.dataPoints.indexOf(dataPoint)] .visiblePointIndex; } - if (dataPointIndex != null && seriesRenderer._segments.isNotEmpty) { - if (seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType.contains('boxandwhisker')) { - startSegment = seriesRenderer._segments[dataPointIndex]; + if (dataPointIndex != null && + seriesRendererDetails.segments.isNotEmpty == true) { + if (seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { + startSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[dataPointIndex]); } else { if (dataPointIndex == 0 && - dataPointIndex < seriesRenderer._segments.length) { - startSegment = seriesRenderer._segments[dataPointIndex]; - } else if (dataPointIndex == seriesRenderer._dataPoints.length - 1 && - dataPointIndex - 1 < seriesRenderer._segments.length) { - startSegment = seriesRenderer._segments[dataPointIndex - 1]; + dataPointIndex < seriesRendererDetails.segments.length) { + startSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[dataPointIndex]); + } else if (dataPointIndex == + seriesRendererDetails.dataPoints.length - 1) { + startSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[dataPointIndex - 1]); } else { - if (dataPointIndex - 1 < seriesRenderer._segments.length) { - startSegment = seriesRenderer._segments[dataPointIndex - 1]; + if (dataPointIndex - 1 < seriesRendererDetails.segments.length) { + startSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[dataPointIndex - 1]); } - if (dataPointIndex < seriesRenderer._segments.length) { - endSegment = seriesRenderer._segments[dataPointIndex]; + if (dataPointIndex < seriesRendererDetails.segments.length) { + endSegmentProperties = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[dataPointIndex]); } } } // ignore: unnecessary_null_comparison - if (startSegment != null) { - cartesianSeriesIndex = startSegment._seriesIndex; - cartesianPointIndex = startSegment.currentSegmentIndex; - if (_isSegmentIntersect(startSegment, position.dx, position.dy)) { + if (startSegmentProperties != null) { + cartesianSeriesIndex = startSegmentProperties.seriesIndex; + cartesianPointIndex = + startSegmentProperties.segment.currentSegmentIndex; + if (_isSegmentIntersect( + startSegmentProperties.segment, position.dx, position.dy)) { return true; } - } else if (endSegment != null) { - cartesianSeriesIndex = endSegment._seriesIndex; - cartesianPointIndex = endSegment.currentSegmentIndex; - return _isSegmentIntersect(endSegment, position.dx, position.dy); + } else if (endSegmentProperties != null) { + cartesianSeriesIndex = endSegmentProperties.seriesIndex; + cartesianPointIndex = endSegmentProperties.segment.currentSegmentIndex; + return _isSegmentIntersect( + endSegmentProperties.segment, position.dx, position.dy); } } return false; @@ -1316,42 +1586,43 @@ class _SelectionRenderer { nextDataPointIndex, nearestDataPointIndex; final List>? nearestDataPoints = - _getNearestChartPoints( + getNearestChartPoints( position.dx, position.dy, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer); + seriesRendererDetails.xAxisDetails!.axisRenderer, + seriesRendererDetails.yAxisDetails!.axisRenderer, + seriesRendererDetails); for (final CartesianChartPoint dataPoint in nearestDataPoints!) { dataPointIndex = dataPoint.overallDataPointIndex; viewportIndex = dataPoint.visiblePointIndex; - previousIndex = seriesRenderer._dataPoints.indexOf(dataPoint) - 1; + previousIndex = seriesRendererDetails.dataPoints.indexOf(dataPoint) - 1; previousIndex < 0 ? previousDataPointIndex = dataPointIndex : previousDataPointIndex = previousIndex; - nextIndex = seriesRenderer._dataPoints.indexOf(dataPoint) + 1; - nextIndex > seriesRenderer._dataPoints.length - 1 + nextIndex = seriesRendererDetails.dataPoints.indexOf(dataPoint) + 1; + nextIndex > seriesRendererDetails.dataPoints.length - 1 ? nextDataPointIndex = dataPointIndex : nextDataPointIndex = nextIndex; } firstNearestDataPoints - .add(seriesRenderer._dataPoints[previousDataPointIndex]); - firstNearestDataPoints.add(seriesRenderer._dataPoints[nextDataPointIndex]); + .add(seriesRendererDetails.dataPoints[previousDataPointIndex]); + firstNearestDataPoints + .add(seriesRendererDetails.dataPoints[nextDataPointIndex]); final List>? firstNearestPoints = - _getNearestChartPoints( + getNearestChartPoints( position.dx, position.dy, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer, + seriesRendererDetails.xAxisDetails!.axisRenderer, + seriesRendererDetails.yAxisDetails!.axisRenderer, + seriesRendererDetails, firstNearestDataPoints); for (final CartesianChartPoint dataPoint in firstNearestPoints!) { - if (seriesRenderer._seriesType.contains('hilo') == true || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType.contains('boxandwhisker') == true) { + if (seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true) { nearestDataPointIndex = dataPointIndex; } else { if (dataPointIndex! < dataPoint.overallDataPointIndex!) { @@ -1365,7 +1636,10 @@ class _SelectionRenderer { } } } - seriesRenderer._selectionBehaviorRenderer._selectionRenderer + + SelectionHelper.getRenderingDetails( + seriesRendererDetails.selectionBehaviorRenderer) + .selectionRenderer! .cartesianPointIndex = nearestDataPointIndex; return nearestDataPointIndex; } @@ -1385,35 +1659,37 @@ class _SelectionRenderer { segment is BoxAndWhiskerSegment || segment is StackedLine100Segment) { currentSegment = segment; + currentSegmentProperties = + SegmentHelper.getSegmentProperties(currentSegment); } x1 = currentSegment is HiloSegment || currentSegment is HiloOpenCloseSegment || currentSegment is CandleSegment || currentSegment is BoxAndWhiskerSegment - ? currentSegment._x - : currentSegment._x1; + ? currentSegmentProperties!.x + : currentSegmentProperties!.x1; if (currentSegment is HiloSegment || currentSegment is HiloOpenCloseSegment || currentSegment is CandleSegment || currentSegment is BoxAndWhiskerSegment) { - x2 = currentSegment._x; + x2 = currentSegmentProperties!.x; if (currentSegment is BoxAndWhiskerSegment) { - y1 = currentSegment._min; - y2 = currentSegment._max; + y1 = currentSegmentProperties!.min; + y2 = currentSegmentProperties!.max; } else { - y1 = currentSegment._low; - y2 = currentSegment._high; + y1 = currentSegmentProperties!.low; + y2 = currentSegmentProperties!.high; } } else { y1 = currentSegment is StackedLineSegment || currentSegment is StackedLine100Segment - ? currentSegment._currentCummulativeValue - : currentSegment._y1; - x2 = currentSegment._x2; + ? currentSegmentProperties!.currentCummulativeValue + : currentSegmentProperties!.y1; + x2 = currentSegmentProperties!.x2; y2 = currentSegment is StackedLineSegment || currentSegment is StackedLine100Segment - ? currentSegment._nextCummulativeValue - : currentSegment._y2; + ? currentSegmentProperties!.nextCummulativeValue + : currentSegmentProperties!.y2; } final CartesianChartPoint leftPoint = @@ -1435,11 +1711,11 @@ class _SelectionRenderer { return true; } - if (seriesRenderer._seriesType == 'stepline') { - final num x3 = currentSegment._x3; - final num y3 = currentSegment._y3; - final num x2 = currentSegment._x2; - final num y2 = currentSegment._y2; + if (seriesRendererDetails.seriesType == 'stepline') { + final num x3 = currentSegmentProperties!.x3; + final num y3 = currentSegmentProperties!.y3; + final num x2 = currentSegmentProperties!.x2; + final num y2 = currentSegmentProperties!.y2; final CartesianChartPoint endSegment = CartesianChartPoint(x2, y2); final CartesianChartPoint midSegment = @@ -1454,20 +1730,22 @@ class _SelectionRenderer { /// To get the index of the selected segment void getSelectedSeriesIndex(SfCartesianChart chart, Offset position, - CartesianSeriesRenderer seriesRenderer) { + SeriesRendererDetails seriesRendererDetails) { Rect? currentSegment; int? seriesIndex; SelectionBehaviorRenderer? selectionBehaviorRenderer; CartesianChartPoint point; outerLoop: for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer!; - for (int j = 0; j < seriesRenderer._dataPoints.length; j++) { - point = seriesRenderer._dataPoints[j]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + selectionBehaviorRenderer = + seriesRendererDetails.selectionBehaviorRenderer!; + for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { + point = seriesRendererDetails.dataPoints[j]; currentSegment = point.region; if (currentSegment != null && currentSegment.contains(position)) { seriesIndex = i; @@ -1478,69 +1756,88 @@ class _SelectionRenderer { if (selectionBehaviorRenderer == null) { return; } - selectionBehaviorRenderer._selectionRenderer!.seriesIndex = seriesIndex; + + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer) + .selectionRenderer! + .seriesIndex = seriesIndex; } /// To do selection for cartesian type chart. void performSelection(Offset position) { bool select = false; bool isSelect = false; - _isInteraction = true; + isInteraction = true; int? cartesianPointIndex; - if (seriesRenderer._seriesType == 'line' || - seriesRenderer._seriesType == 'spline' || - seriesRenderer._seriesType == 'stepline' || - seriesRenderer._seriesType == 'stackedline' || - seriesRenderer._seriesType.contains('hilo') == true || - seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType.contains('boxandwhisker') == true || - seriesRenderer._seriesType == 'stackedline100') { - isSelect = seriesRenderer._isSelectionEnable == true && - _isSeriesContainsPoint(seriesRenderer, position); + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails( + seriesRendererDetails.selectionBehaviorRenderer); + if (seriesRendererDetails.seriesType == 'line' || + seriesRendererDetails.seriesType == 'spline' || + seriesRendererDetails.seriesType == 'stepline' || + seriesRendererDetails.seriesType == 'stackedline' || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true || + seriesRendererDetails.seriesType == 'stackedline100') { + isSelect = seriesRendererDetails.isSelectionEnable == true && + isSeriesContainsPoint(seriesRendererDetails, position); if (isSelect) { cartesianPointIndex = getCartesianPointIndex(position); selected = cartesianPointIndex != null; - select = seriesRenderer._selectionBehaviorRenderer._selectionRenderer - .isCartesianSelection(chart, seriesRenderer, cartesianPointIndex, - cartesianSeriesIndex); + select = selectionDetails.selectionRenderer!.isCartesianSelection( + chart, + seriesRendererDetails.renderer, + cartesianPointIndex, + cartesianSeriesIndex); } } else { - _chartState._renderDatalabelRegions = []; - (seriesRenderer._seriesType.contains('area') == true || - seriesRenderer._seriesType == 'fastline') - ? getSelectedSeriesIndex(chart, position, seriesRenderer) - : getPointAndSeriesIndex(chart, position, seriesRenderer); - - select = seriesRenderer._selectionBehaviorRenderer._selectionRenderer - .isCartesianSelection(chart, seriesRenderer, pointIndex, seriesIndex); + stateProperties.renderDatalabelRegions = []; + (seriesRendererDetails.seriesType.contains('area') == true || + seriesRendererDetails.seriesType == 'fastline') + ? getSelectedSeriesIndex(chart, position, seriesRendererDetails) + : getPointAndSeriesIndex(chart, position, seriesRendererDetails); + + select = selectionDetails.selectionRenderer!.isCartesianSelection( + chart, seriesRendererDetails.renderer, pointIndex, seriesIndex); } if (select) { for (final CartesianSeriesRenderer _seriesRenderer - in _chartState._chartSeries.visibleSeriesRenderers) { - ValueNotifier(_seriesRenderer._repaintNotifier.value++); + in stateProperties.chartSeries.visibleSeriesRenderers) { + ValueNotifier( + SeriesHelper.getSeriesRendererDetails(_seriesRenderer) + .repaintNotifier + .value++); } } } + /// To check whether the segments are selected or not. // ignore: unused_element - void _checkWithSelectionState( + void checkWithSelectionState( ChartSegment currentSegment, SfCartesianChart chart) { bool isSelected = false; + final SegmentProperties currentSegmentProperties = + SegmentHelper.getSegmentProperties(currentSegment); if (selectedSegments.isNotEmpty) { for (int i = 0; i < selectedSegments.length; i++) { - if (selectedSegments[i]._seriesIndex == currentSegment._seriesIndex && - (_isInteraction || currentSegment._oldSegmentIndex != -1) && - (seriesRenderer._seriesType.contains('area') == true + if (SegmentHelper.getSegmentProperties(selectedSegments[i]) + .seriesIndex == + currentSegmentProperties.seriesIndex && + (isInteraction || currentSegmentProperties.oldSegmentIndex != -1) && + (seriesRendererDetails.seriesType.contains('area') == true ? selectedSegments[i].currentSegmentIndex == currentSegment.currentSegmentIndex - : selectedSegments[i]._currentPoint!.overallDataPointIndex == - (_isInteraction - ? currentSegment._currentPoint!.overallDataPointIndex - : (currentSegment._oldSegmentIndex ?? - currentSegment - ._currentPoint!.overallDataPointIndex)))) { - _selectedSegmentsColors([currentSegment]); + : SegmentHelper.getSegmentProperties(selectedSegments[i]) + .currentPoint! + .overallDataPointIndex == + (isInteraction + ? currentSegmentProperties + .currentPoint!.overallDataPointIndex + : (currentSegmentProperties.oldSegmentIndex ?? + currentSegmentProperties + .currentPoint!.overallDataPointIndex)))) { + selectedSegmentsColors([currentSegment]); isSelected = true; break; } @@ -1549,17 +1846,21 @@ class _SelectionRenderer { if (!isSelected && unselectedSegments!.isNotEmpty) { for (int i = 0; i < unselectedSegments!.length; i++) { - if (unselectedSegments![i]._seriesIndex == - currentSegment._seriesIndex && - (currentSegment._oldSegmentIndex == -1 || - currentSegment._oldSegmentIndex != + if (SegmentHelper.getSegmentProperties(unselectedSegments![i]) + .seriesIndex == + currentSegmentProperties.seriesIndex && + (currentSegmentProperties.oldSegmentIndex == -1 || + currentSegmentProperties.oldSegmentIndex != currentSegment.currentSegmentIndex || - seriesRenderer._seriesType.contains('area') == true + seriesRendererDetails.seriesType.contains('area') == true ? unselectedSegments![i].currentSegmentIndex == currentSegment.currentSegmentIndex - : unselectedSegments![i]._currentPoint?.overallDataPointIndex == - currentSegment._currentPoint?.overallDataPointIndex)) { - _unselectedSegmentsColors([currentSegment]); + : SegmentHelper.getSegmentProperties(unselectedSegments![i]) + .currentPoint + ?.overallDataPointIndex == + currentSegmentProperties + .currentPoint?.overallDataPointIndex)) { + unselectedSegmentsColors([currentSegment]); isSelected = true; break; } @@ -1567,16 +1868,20 @@ class _SelectionRenderer { } } - SelectionArgs getSelectionEventArgs(CartesianSeries series, - int seriesIndex, int pointIndex, CartesianSeriesRenderer seriesRender) { + /// To get the selection event argument values. + SelectionArgs getSelectionEventArgs( + CartesianSeries series, + int seriesIndex, + int pointIndex, + SeriesRendererDetails seriesRenderDetails) { // ignore: unnecessary_null_comparison if (series != null) { selectionArgs = SelectionArgs( - seriesRenderer: seriesRenderer, + seriesRenderer: seriesRendererDetails.renderer, seriesIndex: seriesIndex, viewportPointIndex: pointIndex, - pointIndex: seriesRender - ._visibleDataPoints![pointIndex].overallDataPointIndex!); + pointIndex: seriesRendererDetails + .visibleDataPoints![pointIndex].overallDataPointIndex!); } return selectionArgs!; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart index 396b4edfa..9394b080c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball.dart @@ -1,4 +1,42 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../common/event_args.dart'; +import '../../common/rendering_details.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../../common/utils/typedef.dart'; +import '../axis/axis.dart'; +import '../axis/axis_panel.dart'; +import '../axis/category_axis.dart'; +import '../axis/datetime_axis.dart'; +import '../axis/datetime_category_axis.dart'; +import '../base/chart_base.dart'; +import '../chart_behavior/chart_behavior.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/financial_series_base.dart'; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../common/interactive_tooltip.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../common/trackball_marker_settings.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'trackball_marker_setting_renderer.dart'; +import 'trackball_painter.dart'; +import 'trackball_template.dart'; /// Customizes the trackball. /// @@ -124,7 +162,6 @@ class TrackballBehavior { ///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`. /// @@ -337,7 +374,8 @@ class TrackballBehavior { return hashList(values); } - SfCartesianChartState? _chartState; + /// Holds the value of cartesian state properties + late CartesianStateProperties _stateProperties; ///Options to customize the markers that are displayed when trackball is enabled. /// @@ -360,48 +398,51 @@ class TrackballBehavior { /// /// Defaults to 'point'. void show(dynamic x, double y, [String coordinateUnit = 'point']) { - final SfCartesianChartState chartState = _chartState!; - final TrackballBehaviorRenderer trackballBehaviorRenderer = - chartState._trackballBehaviorRenderer; + final CartesianStateProperties stateProperties = _stateProperties; + final TrackballRenderingDetails _trackballRenderingDetails = + TrackballHelper.getRenderingDetails( + _stateProperties.trackballBehaviorRenderer); final List visibleSeriesRenderer = - chartState._chartSeries.visibleSeriesRenderers; - final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderer[0]; - if ((trackballBehaviorRenderer._trackballPainter != null || + stateProperties.chartSeries.visibleSeriesRenderers; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderer[0]); + if ((_trackballRenderingDetails.trackballPainter != null || builder != null) && activationMode != ActivationMode.none) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; if (coordinateUnit != 'pixel') { - final _ChartLocation location = _calculatePoint( - (x is DateTime && (xAxisRenderer is! DateTimeCategoryAxisRenderer)) + final ChartLocation location = calculatePoint( + (x is DateTime && (xAxisDetails is! DateTimeCategoryAxisDetails)) ? x.millisecondsSinceEpoch : ((x is DateTime && - xAxisRenderer is DateTimeCategoryAxisRenderer) - ? xAxisRenderer._labels - .indexOf(xAxisRenderer._dateFormat.format(x)) - : ((x is String && xAxisRenderer is CategoryAxisRenderer) - ? xAxisRenderer._labels.indexOf(x) + xAxisDetails is DateTimeCategoryAxisDetails) + ? xAxisDetails.labels + .indexOf(xAxisDetails.dateFormat.format(x)) + : ((x is String && xAxisDetails is CategoryAxisDetails) + ? xAxisDetails.labels.indexOf(x) : x)), y, - xAxisRenderer, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, - seriesRenderer._chartState!._chartAxis._axisClipRect); + xAxisDetails, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, + seriesRendererDetails.stateProperties.chartAxis.axisClipRect); x = location.x; y = location.y; } - if (trackballBehaviorRenderer._trackballPainter != null) { - trackballBehaviorRenderer._isTrackballTemplate = false; - trackballBehaviorRenderer._generateAllPoints(Offset(x.toDouble(), y)); - } else if (builder != null && (!trackballBehaviorRenderer._isMoving)) { - trackballBehaviorRenderer - ._showTemplateTrackball(Offset(x.toDouble(), y)); + if (_trackballRenderingDetails.trackballPainter != null) { + _trackballRenderingDetails.isTrackballTemplate = false; + _trackballRenderingDetails.generateAllPoints(Offset(x.toDouble(), y)); + } else if (builder != null && (!_trackballRenderingDetails.isMoving)) { + _trackballRenderingDetails + .showTemplateTrackball(Offset(x.toDouble(), y)); } } - if (trackballBehaviorRenderer._trackballPainter != null) { - trackballBehaviorRenderer._trackballPainter!.canResetPath = false; - chartState._repaintNotifiers['trackball']!.value++; + if (_trackballRenderingDetails.trackballPainter != null) { + _trackballRenderingDetails.trackballPainter!.canResetPath = false; + stateProperties.repaintNotifiers['trackball']!.value++; } } @@ -409,100 +450,200 @@ class TrackballBehavior { /// /// * pointIndex - index of the point for which the trackball must be shown void showByIndex(int pointIndex) { - final SfCartesianChartState chartState = _chartState!; - final TrackballBehaviorRenderer trackballBehaviorRenderer = - chartState._trackballBehaviorRenderer; - if ((trackballBehaviorRenderer._trackballPainter != null || - builder != null) && - activationMode != ActivationMode.none) { - if (_validIndex(pointIndex, 0, chartState._chart)) { - trackballBehaviorRenderer._showTrackball( - chartState._chartSeries.visibleSeriesRenderers, - pointIndex, - trackballBehaviorRenderer); - } - if (trackballBehaviorRenderer._trackballPainter != null) { - trackballBehaviorRenderer._trackballPainter!.canResetPath = false; - trackballBehaviorRenderer._trackballPainter!.chartState - ._repaintNotifiers['trackball']!.value++; - } - } + final TrackballRenderingDetails _trackballRenderingDetails = + TrackballHelper.getRenderingDetails( + _stateProperties.trackballBehaviorRenderer); + _trackballRenderingDetails.internalShowByIndex( + pointIndex, + ); } /// Hides the trackball if it is displayed. void hide() { - final SfCartesianChartState chartState = _chartState!; - final TrackballBehaviorRenderer trackballBehaviorRenderer = - chartState._trackballBehaviorRenderer; - if (trackballBehaviorRenderer._trackballPainter != null && - !trackballBehaviorRenderer._isTrackballTemplate && + final CartesianStateProperties stateProperties = _stateProperties; + final TrackballRenderingDetails _trackballRenderingDetails = + TrackballHelper.getRenderingDetails( + _stateProperties.trackballBehaviorRenderer); + if (_trackballRenderingDetails.trackballPainter != null && + !_trackballRenderingDetails.isTrackballTemplate && activationMode != ActivationMode.none) { - if (chartState._chart.trackballBehavior.activationMode == + if (stateProperties.chart.trackballBehavior.activationMode == ActivationMode.doubleTap) { - trackballBehaviorRenderer._trackballPainter!.canResetPath = false; - ValueNotifier(trackballBehaviorRenderer._trackballPainter! - .chartState._repaintNotifiers['trackball']!.value++); - if (trackballBehaviorRenderer._trackballPainter!.timer != null) { - trackballBehaviorRenderer._trackballPainter!.timer?.cancel(); + _trackballRenderingDetails.trackballPainter!.canResetPath = false; + ValueNotifier(_trackballRenderingDetails.trackballPainter! + .stateProperties.repaintNotifiers['trackball']!.value++); + if (_trackballRenderingDetails.trackballPainter!.timer != null) { + _trackballRenderingDetails.trackballPainter!.timer?.cancel(); } } - if (!_chartState!._isTouchUp) { - trackballBehaviorRenderer._trackballPainter!.chartState - ._repaintNotifiers['trackball']!.value++; - trackballBehaviorRenderer._trackballPainter!.canResetPath = true; + if (!stateProperties.isTouchUp) { + _trackballRenderingDetails.trackballPainter!.stateProperties + .repaintNotifiers['trackball']!.value++; + _trackballRenderingDetails.chartPointInfo.clear(); + _trackballRenderingDetails.trackballPainter!.canResetPath = true; } else { final double duration = - (hideDelay == 0 && chartState._enableDoubleTap) ? 200 : hideDelay; + (hideDelay == 0 && stateProperties.enableDoubleTap) + ? 200 + : hideDelay; if (!shouldAlwaysShow) { - trackballBehaviorRenderer._trackballPainter!.timer = + _trackballRenderingDetails.trackballPainter!.timer = Timer(Duration(milliseconds: duration.toInt()), () { - trackballBehaviorRenderer._trackballPainter!.chartState - ._repaintNotifiers['trackball']!.value++; - trackballBehaviorRenderer._trackballPainter!.canResetPath = true; + _trackballRenderingDetails.trackballPainter!.stateProperties + .repaintNotifiers['trackball']!.value++; + _trackballRenderingDetails.trackballPainter!.canResetPath = true; + _trackballRenderingDetails.chartPointInfo.clear(); }); } } - } else if (trackballBehaviorRenderer._trackballTemplate != null) { - final GlobalKey key = - trackballBehaviorRenderer._trackballTemplate!.key as GlobalKey; - final _TrackballTemplateState? trackballTemplateState = - key.currentState as _TrackballTemplateState; - final double duration = - shouldAlwaysShow || (hideDelay == 0 && chartState._enableDoubleTap) - ? 200 - : hideDelay; - trackballTemplateState?._trackballTimer = - Timer(Duration(milliseconds: duration.toInt()), () { - trackballTemplateState.hideTrackballTemplate(); - }); + } else if (_trackballRenderingDetails.trackballTemplate != null) { + GlobalKey key = + _trackballRenderingDetails.trackballTemplate!.key as GlobalKey; + TrackballTemplateState? trackballTemplateState = + key.currentState as TrackballTemplateState; + final double duration = shouldAlwaysShow || + (hideDelay == 0 && stateProperties.enableDoubleTap) + ? 200 + : hideDelay; + if (!stateProperties.isTrackballOrientationChanged) { + stateProperties.trackballTimer = + Timer(Duration(milliseconds: duration.toInt()), () { + if (stateProperties.isTrackballOrientationChanged) { + key = + _trackballRenderingDetails.trackballTemplate!.key as GlobalKey; + trackballTemplateState = key.currentState as TrackballTemplateState; + } + trackballTemplateState!.hideTrackballTemplate(); + stateProperties.isTrackballOrientationChanged = false; + _trackballRenderingDetails.chartPointInfo.clear(); + }); + } } } } ///Trackball behavior renderer class for mutable fields and methods class TrackballBehaviorRenderer with ChartBehavior { - /// Creates an argument constructor for Trackball renderer class - TrackballBehaviorRenderer(this._chartState); - SfCartesianChart get _chart => _chartState._chart; - final SfCartesianChartState _chartState; - TrackballBehavior get _trackballBehavior => _chart.trackballBehavior; + /// Creates an argument constructor for trackball renderer class + TrackballBehaviorRenderer(this._stateProperties) { + _trackballRenderingDetails = TrackballRenderingDetails(_stateProperties); + } + final CartesianStateProperties _stateProperties; + + /// Specifies the value of trackball rendering details + late TrackballRenderingDetails _trackballRenderingDetails; + + /// Performs the double-tap action. + /// + /// Hits while double tapping on the chart. + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onDoubleTap(double xPos, double yPos) => + _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); + + /// Performs the long press action. + /// + /// Hits while a long tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onLongPress(double xPos, double yPos) => + _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); + + /// Performs the touch-down action. + /// + /// Hits while tapping on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onTouchDown(double xPos, double yPos) => + _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); + + /// Performs the touch-move action. + /// + /// Hits while tap and moving on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onTouchMove(double xPos, double yPos) => + _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); - /// Check whether long press activated or not . + /// Performs the touch-up action. + /// + /// Hits while release tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onTouchUp(double xPos, double yPos) => + _trackballRenderingDetails.trackballBehavior.hide(); + + /// Performs the mouse-hover action. + /// + /// Hits while enter tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onEnter(double xPos, double yPos) => + _trackballRenderingDetails.trackballBehavior.show(xPos, yPos, 'pixel'); + + /// Performs the mouse-exit action. + /// + /// Hits while exit tap on the chart. + /// + /// * xPos - X value of the touch position. + /// * yPos - Y value of the touch position. + @override + void onExit(double xPos, double yPos) => + _trackballRenderingDetails.trackballBehavior.hide(); + + /// Draws trackball + /// + /// * canvas - Canvas used to draw the track line on the chart. + @override + void onPaint(Canvas canvas) { + if (_trackballRenderingDetails.trackballPainter != null && + !_trackballRenderingDetails.trackballPainter!.canResetPath) { + _trackballRenderingDetails.trackballPainter!.drawTrackball(canvas); + } + } +} + +/// Represents the class that holds the rendering details of trackball +class TrackballRenderingDetails { + /// Creates an instance of trackball rendering details + TrackballRenderingDetails(this._stateProperties); + final CartesianStateProperties _stateProperties; + SfCartesianChart get _chart => _stateProperties.chart; + + /// Specifies the trackball behavior + TrackballBehavior get trackballBehavior => _chart.trackballBehavior; + + /// Check whether long press activated or not. // ignore: prefer_final_fields - bool _isLongPressActivated = false; + bool isLongPressActivated = false; - /// check whether onPointerMove or not. + /// Check whether onPointerMove or not. // ignore: prefer_final_fields - bool _isMoving = false; + bool isMoving = false; /// Touch position - late Offset _tapPosition; + late Offset tapPosition; - /// Holds the instance of trackballPainter!. - _TrackballPainter? _trackballPainter; - _TrackballTemplate? _trackballTemplate; + /// Holds the instance of trackball painter. + TrackballPainter? trackballPainter; + + /// Specifies the trackball template + TrackballTemplate? trackballTemplate; List _visibleLocation = []; - final List _markerShapes = []; + + /// Specifies the marker shape + final List markerShapes = []; late Rect _axisClipRect; //ignore: unused_field @@ -515,159 +656,183 @@ class TrackballBehaviorRenderer with ChartBehavior { List> _visibleSeriesList = >[]; late TrackballGroupingModeInfo _groupingModeInfo; - List<_ChartPointInfo> _chartPointInfo = <_ChartPointInfo>[]; - List _tooltipTop = []; - List _tooltipBottom = []; - final List _xAxesInfo = []; - final List _yAxesInfo = []; - List<_ClosestPoints> _visiblePoints = <_ClosestPoints>[]; - _TooltipPositions? _tooltipPosition; + + /// Specifies the chart point info + List chartPointInfo = []; + + /// Specifies the tooltip top + List tooltipTop = []; + + /// Specifies the tooltip bottom + List tooltipBottom = []; + + /// Specifies the xAxesInfo + final List xAxesInfo = []; + + /// Specifies the yAxesInfo + final List yAxesInfo = []; + + /// Specifies the visible points + List visiblePoints = []; + + /// Specifies the tooltip position + TooltipPositions? tooltipPosition; + late num _tooltipPadding; - bool _isRangeSeries = false; - bool _isBoxSeries = false; - bool _isTrackballTemplate = false; + + /// Specifies whether it is range series + bool isRangeSeries = false; + + /// Specifies whether it is box series + bool isBoxSeries = false; + + /// Specifies whether the trackball has template + bool isTrackballTemplate = false; /// To render the trackball marker for both tooltip and template - void _trackballMarker(int index) { - if (_trackballBehavior.markerSettings != null && - (_trackballBehavior.markerSettings!.markerVisibility == + void trackballMarker(int index) { + if (trackballBehavior.markerSettings != null && + (trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.auto - ? (_chartPointInfo[index] - .seriesRenderer! - ._series - .markerSettings - .isVisible) - : _trackballBehavior.markerSettings!.markerVisibility == + ? (chartPointInfo[index] + .seriesRendererDetails! + .series + .markerSettings + .isVisible == + true) + : trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.visible)) { - final MarkerSettings markerSettings = _trackballBehavior.markerSettings!; + final MarkerSettings markerSettings = trackballBehavior.markerSettings!; final DataMarkerType markerType = markerSettings.shape; final Size size = Size(markerSettings.width, markerSettings.height); final String seriesType = - _chartPointInfo[index].seriesRenderer!._seriesType; - _chartPointInfo[index].seriesRenderer!._isMarkerRenderEvent = true; - _markerShapes.add(_getMarkerShapesPath( + chartPointInfo[index].seriesRendererDetails!.seriesType; + chartPointInfo[index].seriesRendererDetails!.isMarkerRenderEvent = true; + markerShapes.add(getMarkerShapesPath( markerType, Offset( - _chartPointInfo[index].xPosition!, + chartPointInfo[index].xPosition!, seriesType.contains('range') || seriesType.contains('hilo') || seriesType == 'candle' - ? _chartPointInfo[index].highYPosition! + ? chartPointInfo[index].highYPosition! : seriesType == 'boxandwhisker' - ? _chartPointInfo[index].maxYPosition! - : _chartPointInfo[index].yPosition!), + ? chartPointInfo[index].maxYPosition! + : chartPointInfo[index].yPosition!), size, - _chartPointInfo[index].seriesRenderer, + chartPointInfo[index].seriesRendererDetails!, null, - _trackballBehavior)); + trackballBehavior)); } } - /// To show track ball based on point index - void _showTrackball(List visibleSeriesRenderers, + /// To show trackball based on point index + void showTrackball(List visibleSeriesRenderers, int pointIndex, TrackballBehaviorRenderer trackballBehaviorRenderer) { - _ChartLocation position; - final CartesianSeriesRenderer seriesRenderer = visibleSeriesRenderers[0]; - final Rect rect = seriesRenderer._chartState!._chartAxis._axisClipRect; + ChartLocation position; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderers[0]); + final Rect rect = + seriesRendererDetails.stateProperties.chartAxis.axisClipRect; final List> dataPoints = >[]; - for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { - if (seriesRenderer._dataPoints[i].isGap != true) { - dataPoints.add(seriesRenderer._dataPoints[i]); + for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { + if (seriesRendererDetails.dataPoints[i].isGap != true) { + dataPoints.add(seriesRendererDetails.dataPoints[i]); } } // ignore: unnecessary_null_comparison assert(pointIndex != null, 'Point index must not be null'); // ignore: unnecessary_null_comparison if (pointIndex != null && - pointIndex.abs() < seriesRenderer._dataPoints.length) { + pointIndex.abs() < seriesRendererDetails.dataPoints.length) { final int index = pointIndex; - final num xValue = seriesRenderer._dataPoints[index].xValue; - final num yValue = - seriesRenderer._series is _FinancialSeriesBase || - seriesRenderer._seriesType.contains('range') - ? seriesRenderer._dataPoints[index].high - : seriesRenderer._dataPoints[index].yValue; - position = _calculatePoint( + final num xValue = seriesRendererDetails.dataPoints[index].xValue; + final num yValue = seriesRendererDetails.series + is FinancialSeriesBase || + seriesRendererDetails.seriesType.contains('range') == true + ? seriesRendererDetails.dataPoints[index].high + : seriesRendererDetails.dataPoints[index].yValue; + position = calculatePoint( xValue, yValue, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - seriesRenderer._chartState!._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + seriesRendererDetails.stateProperties.requireInvertedAxis, + seriesRendererDetails.series, rect); - if (trackballBehaviorRenderer._trackballPainter != null) { - seriesRenderer._chartState!._trackballBehaviorRenderer - ._generateAllPoints(Offset(position.x, position.y)); - } else if (_trackballBehavior.builder != null) { - trackballBehaviorRenderer - ._showTemplateTrackball(Offset(position.x, position.y)); + if (trackballPainter != null) { + generateAllPoints(Offset(position.x, position.y)); + } else if (trackballBehavior.builder != null) { + showTemplateTrackball(Offset(position.x, position.y)); } } } - void _showTemplateTrackball(Offset position) { - final GlobalKey key = _trackballTemplate!.key as GlobalKey; - final _TrackballTemplateState trackballTemplateState = - key.currentState as _TrackballTemplateState; - _tapPosition = position; - trackballTemplateState._alwaysShow = _trackballBehavior.shouldAlwaysShow; - trackballTemplateState._duration = - _trackballBehavior.hideDelay == 0 ? 200 : _trackballBehavior.hideDelay; - _isTrackballTemplate = true; - _generateAllPoints(position); + /// Method to show the trackball with template + void showTemplateTrackball(Offset position) { + final GlobalKey key = trackballTemplate!.key as GlobalKey; + final TrackballTemplateState trackballTemplateState = + key.currentState as TrackballTemplateState; + tapPosition = position; + trackballTemplateState.alwaysShow = trackballBehavior.shouldAlwaysShow; + trackballTemplateState.duration = + trackballBehavior.hideDelay == 0 ? 200 : trackballBehavior.hideDelay; + isTrackballTemplate = true; + generateAllPoints(position); CartesianChartPoint dataPoint; - for (int index = 0; index < _chartPointInfo.length; index++) { - dataPoint = _chartPointInfo[index] - .seriesRenderer! - ._dataPoints[_chartPointInfo[index].dataPointIndex!]; - if (_trackballBehavior.tooltipDisplayMode == + for (int index = 0; index < chartPointInfo.length; index++) { + dataPoint = chartPointInfo[index] + .seriesRendererDetails! + .dataPoints[chartPointInfo[index].dataPointIndex!]; + if (trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { _points.add(dataPoint); - _currentPointIndices.add(_chartPointInfo[index].dataPointIndex!); - _visibleSeriesIndices.add(_chartState - ._chartSeries.visibleSeriesRenderers - .indexOf(_chartPointInfo[index].seriesRenderer!)); - _visibleSeriesList.add(_chartPointInfo[index].seriesRenderer!._series); + _currentPointIndices.add(chartPointInfo[index].dataPointIndex!); + _visibleSeriesIndices.add(_stateProperties + .chartSeries.visibleSeriesRenderers + .indexOf(chartPointInfo[index].seriesRendererDetails!.renderer)); + _visibleSeriesList + .add(chartPointInfo[index].seriesRendererDetails!.series); } - _trackballMarker(index); + trackballMarker(index); } _groupingModeInfo = TrackballGroupingModeInfo(_points, _currentPointIndices, _visibleSeriesIndices, _visibleSeriesList); assert(trackballTemplateState.mounted, 'Template state which must be mounted before accessing to avoid rebuilding'); if (trackballTemplateState.mounted && - _trackballBehavior.tooltipDisplayMode == + trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.groupAllPoints) { - trackballTemplateState._chartPointInfo = _chartPointInfo; + trackballTemplateState.chartPointInfo = chartPointInfo; trackballTemplateState.groupingModeInfo = _groupingModeInfo; - trackballTemplateState._markerShapes = _markerShapes; + trackballTemplateState.markerShapes = markerShapes; trackballTemplateState.refresh(); } else if (trackballTemplateState.mounted) { - trackballTemplateState._chartPointInfo = _chartPointInfo; - trackballTemplateState._markerShapes = _markerShapes; + trackballTemplateState.chartPointInfo = chartPointInfo; + trackballTemplateState.markerShapes = markerShapes; trackballTemplateState.refresh(); } _points = >[]; _currentPointIndices = []; _visibleSeriesIndices = []; _visibleSeriesList = >[]; - _tooltipTop.clear(); - _tooltipBottom.clear(); + tooltipTop.clear(); + tooltipBottom.clear(); } - /// calculate trackball points - void _generateAllPoints(Offset position) { - _axisClipRect = _chartState._chartAxis._axisClipRect; - _tooltipPadding = _chartState._requireInvertedAxis ? 8 : 5; - _chartPointInfo = <_ChartPointInfo>[]; - _visiblePoints = <_ClosestPoints>[]; - _markerShapes.clear(); - _tooltipTop = _tooltipBottom = _visibleLocation = []; - _trackballPainter?._tooltipTop = []; - _trackballPainter?._tooltipBottom = []; + /// Calculate trackball points + void generateAllPoints(Offset position) { + _axisClipRect = _stateProperties.chartAxis.axisClipRect; + _tooltipPadding = _stateProperties.requireInvertedAxis ? 8 : 5; + chartPointInfo = []; + visiblePoints = []; + markerShapes.clear(); + tooltipTop = tooltipBottom = _visibleLocation = []; + trackballPainter?.tooltipTop = []; + trackballPainter?.tooltipBottom = []; final Rect seriesBounds = _axisClipRect; - _tapPosition = position; + tapPosition = position; double? xPos = 0, yPos = 0, leastX = 0, @@ -687,12 +852,12 @@ class TrackballBehaviorRenderer with ChartBehavior { maxYPos, maxXPos; int seriesIndex = 0, index; - late CartesianSeriesRenderer cartesianSeriesRenderer; - ChartAxisRenderer chartAxisRenderer, xAxisRenderer, yAxisRenderer; + late SeriesRendererDetails cartesianSeriesRendererDetails; + ChartAxisRendererDetails chartAxisDetails, xAxisDetails, yAxisDetails; CartesianChartPoint chartDataPoint; - _ChartAxis chartAxis; + ChartAxisPanel chartAxis; String seriesType, labelValue, seriesName; - bool invertedAxis = _chartState._requireInvertedAxis; + bool invertedAxis = _stateProperties.requireInvertedAxis; CartesianSeries series; num? xValue, yValue, @@ -709,39 +874,42 @@ class TrackballBehaviorRenderer with ChartBehavior { cumulativeValue; Rect axisClipRect; final TrackballDisplayMode tooltipDisplayMode = - _chartState._chart.trackballBehavior.tooltipDisplayMode; - _ChartLocation highLocation, maxLocation; - chartAxisRenderer = _chartState._seriesRenderers[0]._xAxisRenderer!; + _stateProperties.chart.trackballBehavior.tooltipDisplayMode; + ChartLocation highLocation, maxLocation; + chartAxisDetails = SeriesHelper.getSeriesRendererDetails( + _stateProperties.seriesRenderers[0]) + .xAxisDetails!; for (final CartesianSeriesRenderer axisSeriesRenderer - in chartAxisRenderer._seriesRenderers) { - cartesianSeriesRenderer = axisSeriesRenderer; - seriesType = cartesianSeriesRenderer._seriesType; - _isRangeSeries = seriesType.contains('range') || + in chartAxisDetails.seriesRenderers) { + cartesianSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(axisSeriesRenderer); + seriesType = cartesianSeriesRendererDetails.seriesType; + isRangeSeries = seriesType.contains('range') || seriesType.contains('hilo') || seriesType == 'candle'; - _isBoxSeries = seriesType == 'boxandwhisker'; - if (axisSeriesRenderer._visible == false || - (axisSeriesRenderer._dataPoints.isEmpty && - !axisSeriesRenderer._isRectSeries)) { + isBoxSeries = seriesType == 'boxandwhisker'; + if (cartesianSeriesRendererDetails.visible == false || + (cartesianSeriesRendererDetails.dataPoints.isEmpty == true && + cartesianSeriesRendererDetails.isRectSeries == false)) { continue; } - if (cartesianSeriesRenderer._dataPoints.isNotEmpty) { + if (cartesianSeriesRendererDetails.dataPoints.isNotEmpty == true) { final List>? nearestDataPoints = - _getNearestChartPoints( + getNearestChartPoints( position.dx, position.dy, - cartesianSeriesRenderer._xAxisRenderer!, - cartesianSeriesRenderer._yAxisRenderer!, - cartesianSeriesRenderer); + cartesianSeriesRendererDetails.xAxisDetails!.axisRenderer, + cartesianSeriesRendererDetails.yAxisDetails!.axisRenderer, + cartesianSeriesRendererDetails); for (final CartesianChartPoint dataPoint in nearestDataPoints!) { - index = axisSeriesRenderer._dataPoints.indexOf(dataPoint); - chartDataPoint = cartesianSeriesRenderer._dataPoints[index]; - xAxisRenderer = cartesianSeriesRenderer._xAxisRenderer!; - yAxisRenderer = cartesianSeriesRenderer._yAxisRenderer!; - chartAxis = cartesianSeriesRenderer._chartState!._chartAxis; - invertedAxis = _chartState._requireInvertedAxis; - series = cartesianSeriesRenderer._series; + index = cartesianSeriesRendererDetails.dataPoints.indexOf(dataPoint); + chartDataPoint = cartesianSeriesRendererDetails.dataPoints[index]; + xAxisDetails = cartesianSeriesRendererDetails.xAxisDetails!; + yAxisDetails = cartesianSeriesRendererDetails.yAxisDetails!; + chartAxis = cartesianSeriesRendererDetails.stateProperties.chartAxis; + invertedAxis = _stateProperties.requireInvertedAxis; + series = cartesianSeriesRendererDetails.series; xValue = chartDataPoint.xValue; if (seriesType != 'boxandwhisker') { yValue = chartDataPoint.yValue; @@ -755,39 +923,39 @@ class TrackballBehaviorRenderer with ChartBehavior { lowValue = chartDataPoint.low; openValue = chartDataPoint.open; closeValue = chartDataPoint.close; - seriesName = - cartesianSeriesRenderer._series.name ?? 'Series $seriesIndex'; + seriesName = cartesianSeriesRendererDetails.series.name ?? + 'Series $seriesIndex'; bubbleSize = chartDataPoint.bubbleSize; cumulativeValue = chartDataPoint.cumulativeValue; - axisClipRect = _calculatePlotOffset( - chartAxis._axisClipRect, - Offset(xAxisRenderer._axis.plotOffset, - yAxisRenderer._axis.plotOffset)); - cummulativePos = _calculatePoint( + axisClipRect = calculatePlotOffset( + chartAxis.axisClipRect, + Offset( + xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); + cummulativePos = calculatePoint( xValue!, cumulativeValue, - xAxisRenderer, - yAxisRenderer, + xAxisDetails, + yAxisDetails, invertedAxis, series, axisClipRect) .y; - xPos = _calculatePoint( + xPos = calculatePoint( xValue, seriesType.contains('stacked') ? cumulativeValue : yValue, - xAxisRenderer, - yAxisRenderer, + xAxisDetails, + yAxisDetails, invertedAxis, series, axisClipRect) .x; if (!xPos.toDouble().isNaN) { if (seriesIndex == 0 || - ((leastX! - position.dx) > (leastX - xPos))) { + ((leastX! - position.dx).abs() > (xPos - position.dx).abs())) { leastX = xPos; } labelValue = _getTrackballLabelText( - cartesianSeriesRenderer, + cartesianSeriesRendererDetails, xValue, yValue, lowValue, @@ -805,15 +973,15 @@ class TrackballBehaviorRenderer with ChartBehavior { dataPoint); yPos = seriesType.contains('stacked') ? cummulativePos - : _calculatePoint(xValue, yValue, xAxisRenderer, yAxisRenderer, + : calculatePoint(xValue, yValue, xAxisDetails, yAxisDetails, invertedAxis, series, axisClipRect) .y; - if (_isRangeSeries) { - lowYPos = _calculatePoint(xValue, lowValue, xAxisRenderer, - yAxisRenderer, invertedAxis, series, axisClipRect) + if (isRangeSeries) { + lowYPos = calculatePoint(xValue, lowValue, xAxisDetails, + yAxisDetails, invertedAxis, series, axisClipRect) .y; - highLocation = _calculatePoint(xValue, highValue, xAxisRenderer, - yAxisRenderer, invertedAxis, series, axisClipRect); + highLocation = calculatePoint(xValue, highValue, xAxisDetails, + yAxisDetails, invertedAxis, series, axisClipRect); highYPos = highLocation.y; highXPos = highLocation.x; if (seriesType == 'hiloopenclose' || seriesType == 'candle') { @@ -823,11 +991,11 @@ class TrackballBehaviorRenderer with ChartBehavior { closeYPos = dataPoint.closePoint!.y; } } else if (seriesType == 'boxandwhisker') { - minYPos = _calculatePoint(xValue, minimumValue, xAxisRenderer, - yAxisRenderer, invertedAxis, series, axisClipRect) + minYPos = calculatePoint(xValue, minimumValue, xAxisDetails, + yAxisDetails, invertedAxis, series, axisClipRect) .y; - maxLocation = _calculatePoint(xValue, maximumValue, xAxisRenderer, - yAxisRenderer, invertedAxis, series, axisClipRect); + maxLocation = calculatePoint(xValue, maximumValue, xAxisDetails, + yAxisDetails, invertedAxis, series, axisClipRect); maxXPos = maxLocation.x; maxYPos = maxLocation.y; lowerXPos = dataPoint.lowerQuartilePoint!.x; @@ -837,38 +1005,38 @@ class TrackballBehaviorRenderer with ChartBehavior { } final Rect rect = seriesBounds.intersect(Rect.fromLTWH( xPos - 1, - _isRangeSeries + isRangeSeries ? highYPos! - 1 - : _isBoxSeries + : isBoxSeries ? maxYPos! - 1 : yPos - 1, 2, 2)); if (seriesBounds.contains(Offset( xPos, - _isRangeSeries + isRangeSeries ? highYPos! - : _isBoxSeries + : isBoxSeries ? maxYPos! : yPos)) || seriesBounds.overlaps(rect)) { - _visiblePoints.add(_ClosestPoints( - closestPointX: !_isRangeSeries + visiblePoints.add(ClosestPoints( + closestPointX: !isRangeSeries ? xPos - : _isBoxSeries + : isBoxSeries ? maxXPos! : highXPos!, - closestPointY: _isRangeSeries + closestPointY: isRangeSeries ? highYPos! - : _isBoxSeries + : isBoxSeries ? maxYPos! : yPos)); _addChartPointInfo( - cartesianSeriesRenderer, + cartesianSeriesRendererDetails, xPos, yPos, index, - !_isTrackballTemplate ? labelValue : null, + !isTrackballTemplate ? labelValue : null, seriesIndex, lowYPos, highXPos, @@ -894,28 +1062,31 @@ class TrackballBehaviorRenderer with ChartBehavior { seriesIndex++; } _validateNearestXValue( - leastX!, cartesianSeriesRenderer, position.dx, position.dy); + leastX!, cartesianSeriesRendererDetails, position.dx, position.dy); } - if (_visiblePoints.isNotEmpty) { + if (visiblePoints.isNotEmpty) { invertedAxis - ? _visiblePoints.sort((_ClosestPoints a, _ClosestPoints b) => + ? visiblePoints.sort((ClosestPoints a, ClosestPoints b) => a.closestPointX.compareTo(b.closestPointX)) - : _visiblePoints.sort((_ClosestPoints a, _ClosestPoints b) => + : visiblePoints.sort((ClosestPoints a, ClosestPoints b) => a.closestPointY.compareTo(b.closestPointY)); } - if (_chartPointInfo.isNotEmpty) { + if (chartPointInfo.isNotEmpty) { if (tooltipDisplayMode != TrackballDisplayMode.groupAllPoints) { invertedAxis - ? _chartPointInfo.sort((_ChartPointInfo a, _ChartPointInfo b) => + ? chartPointInfo.sort((ChartPointInfo a, ChartPointInfo b) => a.xPosition!.compareTo(b.xPosition!)) - : _chartPointInfo.sort((_ChartPointInfo a, _ChartPointInfo b) => - a.yPosition!.compareTo(b.yPosition!)); + : 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 || - (cartesianSeriesRenderer._isRectSeries && + (cartesianSeriesRendererDetails.isRectSeries == true && tooltipDisplayMode != TrackballDisplayMode.groupAllPoints)) { _validateNearestPointForAllSeries( - leastX!, _chartPointInfo, position.dx, position.dy); + leastX!, chartPointInfo, position.dx, position.dy); } } _triggerTrackballRenderCallback(); @@ -924,24 +1095,24 @@ class TrackballBehaviorRenderer with ChartBehavior { /// Event for trackball render void _triggerTrackballRenderCallback() { if (_chart.onTrackballPositionChanging != null) { - _chartState._chartPointInfo = - _chartState._trackballBehaviorRenderer._chartPointInfo; + _stateProperties.chartPointInfo = chartPointInfo; int index; - for (index = _chartState._chartPointInfo.length - 1; + for (index = _stateProperties.chartPointInfo.length - 1; index >= 0; index--) { TrackballArgs chartPoint; chartPoint = TrackballArgs(); - chartPoint.chartPointInfo = _chartState._chartPointInfo[index]; + chartPoint.chartPointInfo = _stateProperties.chartPointInfo[index]; _chart.onTrackballPositionChanging!(chartPoint); - _chartState._chartPointInfo[index].label = + _stateProperties.chartPointInfo[index].label = chartPoint.chartPointInfo.label; - _chartState._chartPointInfo[index].header = + _stateProperties.chartPointInfo[index].header = chartPoint.chartPointInfo.header; - if (_chartState._chartPointInfo[index].label == null || - _chartState._chartPointInfo[index].label == '') { - _chartState._chartPointInfo.removeAt(index); - _visiblePoints.removeAt(index); + if (!isTrackballTemplate && + _stateProperties.chartPointInfo[index].label == null || + _stateProperties.chartPointInfo[index].label == '') { + _stateProperties.chartPointInfo.removeAt(index); + visiblePoints.removeAt(index); } } } @@ -949,82 +1120,64 @@ class TrackballBehaviorRenderer with ChartBehavior { /// To validate the nearest point in all series for trackball void _validateNearestPointForAllSeries(double leastX, - List<_ChartPointInfo> trackballInfo, double touchXPos, double touchYPos) { + List trackballInfo, double touchXPos, double touchYPos) { double xPos = 0, yPos; - final List<_ChartPointInfo> tempTrackballInfo = - List<_ChartPointInfo>.from(trackballInfo); - _ChartPointInfo pointInfo, nextPointInfo, previousPointInfo; + final List tempTrackballInfo = + List.from(trackballInfo); + ChartPointInfo pointInfo; num? yValue; num xValue; Rect axisClipRect; - int pointInfoIndex; CartesianChartPoint dataPoint; - ChartAxisRenderer xAxisRenderer, yAxisRenderer; + ChartAxisRendererDetails xAxisDetails, yAxisDetails; int i; + for (i = 0; i < tempTrackballInfo.length; i++) { pointInfo = tempTrackballInfo[i]; - dataPoint = - pointInfo.seriesRenderer!._dataPoints[pointInfo.dataPointIndex!]; - xAxisRenderer = pointInfo.seriesRenderer!._xAxisRenderer!; - yAxisRenderer = pointInfo.seriesRenderer!._yAxisRenderer!; + dataPoint = pointInfo + .seriesRendererDetails!.dataPoints[pointInfo.dataPointIndex!]; + xAxisDetails = pointInfo.seriesRendererDetails!.xAxisDetails!; + yAxisDetails = pointInfo.seriesRendererDetails!.yAxisDetails!; xValue = dataPoint.xValue; - if (pointInfo.seriesRenderer!._seriesType != 'boxandwhisker') { + if (pointInfo.seriesRendererDetails!.seriesType != 'boxandwhisker') { yValue = dataPoint.yValue; } - axisClipRect = _calculatePlotOffset( - pointInfo.seriesRenderer!._chartState!._chartAxis._axisClipRect, - Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); - xPos = _calculatePoint( + axisClipRect = calculatePlotOffset( + pointInfo + .seriesRendererDetails!.stateProperties.chartAxis.axisClipRect, + Offset(xAxisDetails.axis.plotOffset, yAxisDetails.axis.plotOffset)); + xPos = calculatePoint( xValue, yValue, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, - pointInfo.seriesRenderer!._series, + xAxisDetails, + yAxisDetails, + _stateProperties.requireInvertedAxis, + pointInfo.seriesRendererDetails!.series, axisClipRect) .x; - if (_chartState._chart.trackballBehavior.tooltipDisplayMode != + if (_stateProperties.chart.trackballBehavior.tooltipDisplayMode != TrackballDisplayMode.floatAllPoints && - (!pointInfo.seriesRenderer!._chartState!._requireInvertedAxis)) { + (pointInfo + .seriesRendererDetails!.stateProperties.requireInvertedAxis == + false)) { if (leastX != xPos) { trackballInfo.remove(pointInfo); } - pointInfoIndex = tempTrackballInfo.indexOf(pointInfo); yPos = touchYPos; - if (pointInfoIndex < tempTrackballInfo.length - 1) { - nextPointInfo = tempTrackballInfo[pointInfoIndex + 1]; - if (pointInfo.yPosition! > yPos && pointInfoIndex == 0) { - continue; - } - if (!(yPos < - (nextPointInfo.yPosition! - - ((nextPointInfo.yPosition! - pointInfo.yPosition!) / 2)))) { - trackballInfo.remove(pointInfo); - } else if (pointInfoIndex != 0) { - previousPointInfo = tempTrackballInfo[pointInfoIndex - 1]; - if (yPos < - (pointInfo.yPosition! - - ((pointInfo.yPosition! - previousPointInfo.yPosition!) / - 2))) { - trackballInfo.remove(pointInfo); - } - } - } else { - if (pointInfoIndex != 0 && - pointInfoIndex == tempTrackballInfo.length - 1) { - previousPointInfo = tempTrackballInfo[pointInfoIndex - 1]; - if (yPos < previousPointInfo.yPosition!) { - trackballInfo.remove(pointInfo); - } - if (yPos < - (pointInfo.yPosition! - - ((pointInfo.yPosition! - previousPointInfo.yPosition!) / - 2))) { - trackballInfo.remove(pointInfo); - } + } + yPos = touchYPos; + if (_stateProperties.chart.trackballBehavior.tooltipDisplayMode != + TrackballDisplayMode.floatAllPoints) { + ChartPointInfo point = trackballInfo[0]; + for (i = 1; i < trackballInfo.length; i++) { + if ((point.yPosition! - yPos).abs() > + (trackballInfo[i].yPosition! - yPos).abs()) { + point = trackballInfo[i]; } } + trackballInfo + ..clear() + ..add(point); } } } @@ -1032,15 +1185,16 @@ class TrackballBehaviorRenderer with ChartBehavior { /// To find the nearest x value to render a trackball void _validateNearestXValue( double leastX, - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, double touchXPos, double touchYPos) { - final List<_ChartPointInfo> leastPointInfo = <_ChartPointInfo>[]; - final Rect axisClipRect = _calculatePlotOffset( - seriesRenderer._chartState!._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); - final bool invertedAxis = seriesRenderer._chartState!._requireInvertedAxis; + final List leastPointInfo = []; + final Rect axisClipRect = calculatePlotOffset( + seriesRendererDetails.stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + final bool invertedAxis = + seriesRendererDetails.stateProperties.requireInvertedAxis; double nearPointX = invertedAxis ? axisClipRect.top : axisClipRect.left; final double touchXValue = invertedAxis ? touchYPos : touchXPos; double delta = 0, currX; @@ -1048,20 +1202,20 @@ class TrackballBehaviorRenderer with ChartBehavior { num? yValue; CartesianChartPoint dataPoint; CartesianSeries series; - ChartAxisRenderer xAxisRenderer, yAxisRenderer; - _ChartLocation currXLocation; - for (final _ChartPointInfo pointInfo in _chartPointInfo) { - if (pointInfo.dataPointIndex! < seriesRenderer._dataPoints.length) { - dataPoint = seriesRenderer._dataPoints[pointInfo.dataPointIndex!]; - xAxisRenderer = pointInfo.seriesRenderer!._xAxisRenderer!; - yAxisRenderer = pointInfo.seriesRenderer!._yAxisRenderer!; + ChartAxisRendererDetails xAxisDetails, yAxisDetails; + ChartLocation currXLocation; + for (final ChartPointInfo pointInfo in chartPointInfo) { + if (pointInfo.dataPointIndex! < seriesRendererDetails.dataPoints.length) { + dataPoint = seriesRendererDetails.dataPoints[pointInfo.dataPointIndex!]; + xAxisDetails = pointInfo.seriesRendererDetails!.xAxisDetails!; + yAxisDetails = pointInfo.seriesRendererDetails!.yAxisDetails!; xValue = dataPoint.xValue; - if (seriesRenderer._seriesType != 'boxandwhisker') { + if (seriesRendererDetails.seriesType != 'boxandwhisker') { yValue = dataPoint.yValue; } - series = pointInfo.seriesRenderer!._series; - currXLocation = _calculatePoint(xValue, yValue, xAxisRenderer, - yAxisRenderer, invertedAxis, series, axisClipRect); + series = pointInfo.seriesRendererDetails!.series; + currXLocation = calculatePoint(xValue, yValue, xAxisDetails, + yAxisDetails, invertedAxis, series, axisClipRect); currX = invertedAxis ? currXLocation.y : currXLocation.x; if (delta == touchXValue - currX) { @@ -1074,14 +1228,15 @@ class TrackballBehaviorRenderer with ChartBehavior { leastPointInfo.add(pointInfo); } } - if (_chartPointInfo.isNotEmpty) { - if (_chartPointInfo[0].dataPointIndex! < - seriesRenderer._dataPoints.length) { - leastX = _getLeastX(_chartPointInfo[0], seriesRenderer, axisClipRect); + if (chartPointInfo.isNotEmpty) { + if (chartPointInfo[0].dataPointIndex! < + seriesRendererDetails.dataPoints.length) { + leastX = _getLeastX( + chartPointInfo[0], seriesRendererDetails, axisClipRect); } } - if (pointInfo.seriesRenderer!._seriesType.contains('bar') + if (pointInfo.seriesRendererDetails!.seriesType.contains('bar') == true ? invertedAxis : invertedAxis) { _yPos = leastX; @@ -1091,39 +1246,40 @@ class TrackballBehaviorRenderer with ChartBehavior { } } - /// To get the lowest X value to render trackball - double _getLeastX(_ChartPointInfo pointInfo, - CartesianSeriesRenderer seriesRenderer, Rect axisClipRect) { - return _calculatePoint( - seriesRenderer._dataPoints[pointInfo.dataPointIndex!].xValue, + /// To get the lowest x value to render trackball + double _getLeastX(ChartPointInfo pointInfo, + SeriesRendererDetails seriesRendererDetails, Rect axisClipRect) { + return calculatePoint( + seriesRendererDetails.dataPoints[pointInfo.dataPointIndex!].xValue, 0, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, - _chartState._requireInvertedAxis, - seriesRenderer._series, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, + _stateProperties.requireInvertedAxis, + seriesRendererDetails.series, axisClipRect) .x; } - // To render the trackball marker - void _renderTrackballMarker(CartesianSeriesRenderer seriesRenderer, + /// To render the trackball marker + void renderTrackballMarker(SeriesRendererDetails seriesRendererDetails, Canvas canvas, TrackballBehavior trackballBehavior, int index) { final CartesianChartPoint point = - seriesRenderer._dataPoints[index]; + seriesRendererDetails.dataPoints[index]; final TrackballMarkerSettings markerSettings = trackballBehavior.markerSettings!; - final _RenderingDetails renderingDetails = - seriesRenderer._renderingDetails!; + final RenderingDetails renderingDetails = + seriesRendererDetails.stateProperties.renderingDetails; if (markerSettings.shape == DataMarkerType.image) { - _drawImageMarker(null, canvas, _chartPointInfo[index].markerXPos!, - _chartPointInfo[index].markerYPos!, markerSettings, _chartState); + drawImageMarker(null, canvas, chartPointInfo[index].markerXPos!, + chartPointInfo[index].markerYPos!, markerSettings, _stateProperties); } final Paint strokePaint = Paint() ..color = trackballBehavior.markerSettings!.borderWidth == 0 ? Colors.transparent : ((point.pointColorMapper != null) ? point.pointColorMapper! - : markerSettings.borderColor ?? seriesRenderer._seriesColor!) + : markerSettings.borderColor ?? + seriesRendererDetails.seriesColor!) ..strokeWidth = markerSettings.borderWidth ..style = PaintingStyle.stroke; @@ -1133,13 +1289,18 @@ class TrackballBehaviorRenderer with ChartBehavior { ? Colors.white : Colors.black) ..style = PaintingStyle.fill; - canvas.drawPath(_markerShapes[index], strokePaint); - canvas.drawPath(_markerShapes[index], fillPaint); + canvas.drawPath(markerShapes[index], strokePaint); + canvas.drawPath(markerShapes[index], fillPaint); } /// To add chart point info - void _addChartPointInfo(CartesianSeriesRenderer seriesRenderer, double xPos, - double yPos, int dataPointIndex, String? label, int seriesIndex, + void _addChartPointInfo( + SeriesRendererDetails seriesRendererDetails, + double xPos, + double yPos, + int dataPointIndex, + String? label, + int seriesIndex, [double? lowYPos, double? highXPos, double? highYPos, @@ -1154,30 +1315,31 @@ class TrackballBehaviorRenderer with ChartBehavior { double? lowerYPos, double? upperXPos, double? upperYPos]) { - final _ChartPointInfo pointInfo = _ChartPointInfo(); + final ChartPointInfo pointInfo = ChartPointInfo(); - pointInfo.seriesRenderer = seriesRenderer; - pointInfo.series = seriesRenderer._series; + pointInfo.seriesRendererDetails = seriesRendererDetails; + pointInfo.series = seriesRendererDetails.series; pointInfo.markerXPos = xPos; pointInfo.markerYPos = yPos; pointInfo.xPosition = xPos; pointInfo.yPosition = yPos; pointInfo.seriesIndex = seriesIndex; - if (seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'candle') { + if (seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType == 'candle') { pointInfo.lowYPosition = lowYPos!; pointInfo.highXPosition = highXPos!; pointInfo.highYPosition = highYPos!; - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle') { + if (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType == 'candle') { pointInfo.openXPosition = openXPos!; pointInfo.openYPosition = openYPos!; pointInfo.closeXPosition = closeXPos!; pointInfo.closeYPosition = closeYPos!; } - } else if (seriesRenderer._seriesType.contains('boxandwhisker')) { + } else if (seriesRendererDetails.seriesType.contains('boxandwhisker') == + true) { pointInfo.minYPosition = minYPos!; pointInfo.maxYPosition = maxYPos!; pointInfo.maxXPosition = maxXPos!; @@ -1187,48 +1349,56 @@ class TrackballBehaviorRenderer with ChartBehavior { pointInfo.upperYPosition = upperYPos!; } - if (seriesRenderer._segments.length > dataPointIndex) { - pointInfo.color = seriesRenderer._segments[dataPointIndex]._color!; - } else if (seriesRenderer._segments.length > 1) { - pointInfo.color = - seriesRenderer._segments[seriesRenderer._segments.length - 1]._color!; + if (seriesRendererDetails.segments.length > dataPointIndex) { + pointInfo.color = SegmentHelper.getSegmentProperties( + seriesRendererDetails.segments[dataPointIndex]) + .color!; + } else if (seriesRendererDetails.segments.length > 1) { + pointInfo.color = SegmentHelper.getSegmentProperties(seriesRendererDetails + .segments[seriesRendererDetails.segments.length - 1]) + .color!; } - pointInfo.chartDataPoint = seriesRenderer._dataPoints[dataPointIndex]; + pointInfo.chartDataPoint = seriesRendererDetails.dataPoints[dataPointIndex]; pointInfo.dataPointIndex = dataPointIndex; - if (!_isTrackballTemplate) { + if (!isTrackballTemplate) { pointInfo.label = label!; pointInfo.header = _getHeaderText( - seriesRenderer._dataPoints[dataPointIndex], seriesRenderer); + seriesRendererDetails.dataPoints[dataPointIndex], + seriesRendererDetails); } - _chartPointInfo.add(pointInfo); + chartPointInfo.add(pointInfo); } - // Method to place the collided tooltips properly - _TooltipPositions _smartTooltipPositions( + /// Method to place the collided tooltips properly + TooltipPositions smartTooltipPositions( List tooltipTop, List tooltipBottom, List _xAxesInfo, List _yAxesInfo, - List<_ChartPointInfo> chartPointInfo, + List chartPointInfo, bool requireInvertedAxis, [bool? isPainterTooltip]) { - _tooltipPadding = _chartState._requireInvertedAxis ? 8 : 5; + _tooltipPadding = _stateProperties.requireInvertedAxis ? 8 : 5; num tooltipWidth = 0; - _TooltipPositions tooltipPosition; + TooltipPositions tooltipPosition; for (int i = 0; i < chartPointInfo.length; i++) { requireInvertedAxis ? _visibleLocation.add(chartPointInfo[i].xPosition!) : _visibleLocation.add((chartPointInfo[i] - .seriesRenderer! - ._seriesType - .contains('range') || + .seriesRendererDetails! + .seriesType + .contains('range') == + true || chartPointInfo[i] - .seriesRenderer! - ._seriesType - .contains('hilo') || - chartPointInfo[i].seriesRenderer!._seriesType == 'candle') + .seriesRendererDetails! + .seriesType + .contains('hilo') == + true || + chartPointInfo[i].seriesRendererDetails!.seriesType == + 'candle') ? chartPointInfo[i].highYPosition! - : chartPointInfo[i].seriesRenderer!._seriesType == 'boxandwhisker' + : chartPointInfo[i].seriesRendererDetails!.seriesType == + 'boxandwhisker' ? chartPointInfo[i].maxYPosition! : chartPointInfo[i].yPosition!); @@ -1246,21 +1416,21 @@ class TrackballBehaviorRenderer with ChartBehavior { return tooltipPosition; } - _TooltipPositions _verticalArrangements(_TooltipPositions tooltipPPosition, + TooltipPositions _verticalArrangements(TooltipPositions tooltipPPosition, List _xAxesInfo, List _yAxesInfo) { - final _TooltipPositions tooltipPosition = tooltipPPosition; + final TooltipPositions tooltipPosition = tooltipPPosition; num? startPos, chartHeight; - final bool isTransposed = _chartState._requireInvertedAxis; + final bool isTransposed = _stateProperties.requireInvertedAxis; num secWidth, width; final int length = tooltipPosition.tooltipTop.length; ChartAxisRenderer yAxisRenderer; final int axesLength = - _chartState._chartAxis._axisRenderersCollection.length; + _stateProperties.chartAxis.axisRenderersCollection.length; for (int i = length - 1; i >= 0; i--) { yAxisRenderer = _yAxesInfo[i]; for (int k = 0; k < axesLength; k++) { if (yAxisRenderer == - _chartState._chartAxis._axisRenderersCollection[k]) { + _stateProperties.chartAxis.axisRenderersCollection[k]) { if (isTransposed) { chartHeight = _axisClipRect.right; startPos = _axisClipRect.left; @@ -1295,7 +1465,7 @@ class TrackballBehaviorRenderer with ChartBehavior { yAxisRenderer = _yAxesInfo[i]; for (int k = 0; k < axesLength; k++) { if (yAxisRenderer == - _chartState._chartAxis._axisRenderersCollection[k]) { + _stateProperties.chartAxis.axisRenderersCollection[k]) { if (isTransposed) { chartHeight = _axisClipRect.right; startPos = _axisClipRect.left; @@ -1329,7 +1499,7 @@ class TrackballBehaviorRenderer with ChartBehavior { } // Method to identify the colliding trackball tooltips and return the new tooltip positions - _TooltipPositions _continuousOverlappingPoints(List tooltipTop, + TooltipPositions _continuousOverlappingPoints(List tooltipTop, List tooltipBottom, List visibleLocation) { num temp, count = 0, @@ -1413,12 +1583,12 @@ class TrackballBehaviorRenderer with ChartBehavior { startPoint = i + 1; } } - return _TooltipPositions(tooltipTop, tooltipBottom); + return TooltipPositions(tooltipTop, tooltipBottom); } /// To get and return label text of the trackball String _getTrackballLabelText( - CartesianSeriesRenderer seriesRenderer, + SeriesRendererDetails seriesRendererDetails, num? xValue, num? yValue, num? lowValue, @@ -1435,29 +1605,30 @@ class TrackballBehaviorRenderer with ChartBehavior { num? cumulativeValue, CartesianChartPoint dataPoint) { String labelValue; - final int digits = _trackballBehavior.tooltipSettings.decimalPlaces; - final ChartAxis yAxis = seriesRenderer._yAxisRenderer!._axis; - if (_trackballBehavior.tooltipSettings.format != null) { + final int digits = trackballBehavior.tooltipSettings.decimalPlaces; + final ChartAxis yAxis = seriesRendererDetails.yAxisDetails!.axis; + if (trackballBehavior.tooltipSettings.format != null) { dynamic x; - final ChartAxisRenderer axisRenderer = seriesRenderer._xAxisRenderer!; - if (axisRenderer is DateTimeAxisRenderer) { + final ChartAxisRendererDetails axisDetails = + seriesRendererDetails.xAxisDetails!; + if (axisDetails is DateTimeAxisDetails) { final DateFormat dateFormat = - (axisRenderer._axis as DateTimeAxis).dateFormat ?? - _getDateTimeLabelFormat(axisRenderer); + (axisDetails.axis as DateTimeAxis).dateFormat ?? + getDateTimeLabelFormat(axisDetails.axisRenderer); x = dateFormat .format(DateTime.fromMillisecondsSinceEpoch(xValue! as int)); - } else if (axisRenderer is CategoryAxisRenderer) { + } else if (axisDetails is CategoryAxisDetails) { x = dataPoint.x; - } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - x = axisRenderer._labels - .indexOf(axisRenderer._dateFormat.format(dataPoint.x)); + } else if (axisDetails is DateTimeCategoryAxisDetails) { + x = axisDetails.labels + .indexOf(axisDetails.dateFormat.format(dataPoint.x)); } - labelValue = seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker') - ? seriesRenderer._seriesType.contains('boxandwhisker') - ? (_trackballBehavior.tooltipSettings.format! + labelValue = seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == true + ? seriesRendererDetails.seriesType.contains('boxandwhisker') == true + ? (trackballBehavior.tooltipSettings.format! .replaceAll('point.x', (x ?? xValue).toString()) .replaceAll('point.minimum', minValue.toString()) .replaceAll('point.maximum', maxValue.toString()) @@ -1468,16 +1639,16 @@ class TrackballBehaviorRenderer with ChartBehavior { .replaceAll('{', '') .replaceAll('}', '') .replaceAll('series.name', seriesName)) - : seriesRenderer._seriesType == 'hilo' || - seriesRenderer._seriesType.contains('range') - ? (_trackballBehavior.tooltipSettings.format! + : seriesRendererDetails.seriesType == 'hilo' || + seriesRendererDetails.seriesType.contains('range') == true + ? (trackballBehavior.tooltipSettings.format! .replaceAll('point.x', (x ?? xValue).toString()) .replaceAll('point.high', highValue.toString()) .replaceAll('point.low', lowValue.toString()) .replaceAll('{', '') .replaceAll('}', '') .replaceAll('series.name', seriesName)) - : (_trackballBehavior.tooltipSettings.format! + : (trackballBehavior.tooltipSettings.format! .replaceAll('point.x', (x ?? xValue).toString()) .replaceAll('point.high', highValue.toString()) .replaceAll('point.low', lowValue.toString()) @@ -1486,208 +1657,154 @@ class TrackballBehaviorRenderer with ChartBehavior { .replaceAll('{', '') .replaceAll('}', '') .replaceAll('series.name', seriesName)) - : seriesRenderer._seriesType == 'bubble' - ? (_trackballBehavior.tooltipSettings.format! + : seriesRendererDetails.seriesType == 'bubble' + ? (trackballBehavior.tooltipSettings.format! .replaceAll('point.x', (x ?? xValue).toString()) .replaceAll( 'point.y', - _getLabelValue( - yValue, seriesRenderer._yAxisRenderer!._axis, digits)) + getLabelValue(yValue, + seriesRendererDetails.yAxisDetails!.axis, digits)) .replaceAll('{', '') .replaceAll('}', '') .replaceAll('series.name', seriesName) .replaceAll('point.size', bubbleSize.toString())) - : seriesRenderer._seriesType.contains('stacked') - ? (_trackballBehavior.tooltipSettings.format! + : seriesRendererDetails.seriesType.contains('stacked') == true + ? (trackballBehavior.tooltipSettings.format! .replaceAll('point.x', (x ?? xValue).toString()) - .replaceAll('point.y', _getLabelValue(yValue, seriesRenderer._yAxisRenderer!._axis, digits)) + .replaceAll('point.y', getLabelValue(yValue, seriesRendererDetails.yAxisDetails!.axis, digits)) .replaceAll('{', '') .replaceAll('}', '') .replaceAll('series.name', seriesName) .replaceAll('point.cumulativeValue', cumulativeValue.toString())) - : (_trackballBehavior.tooltipSettings.format!.replaceAll('point.x', (x ?? xValue).toString()).replaceAll('point.y', _getLabelValue(yValue, seriesRenderer._yAxisRenderer!._axis, digits)).replaceAll('{', '').replaceAll('}', '').replaceAll('series.name', seriesName)); + : (trackballBehavior.tooltipSettings.format!.replaceAll('point.x', (x ?? xValue).toString()).replaceAll('point.y', getLabelValue(yValue, seriesRendererDetails.yAxisDetails!.axis, digits)).replaceAll('{', '').replaceAll('}', '').replaceAll('series.name', seriesName)); } else { - labelValue = !seriesRenderer._seriesType.contains('range') && - !seriesRenderer._seriesType.contains('candle') && - !seriesRenderer._seriesType.contains('hilo') && - !seriesRenderer._seriesType.contains('boxandwhisker') - ? _getLabelValue(yValue, yAxis, digits) - : seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker') - ? seriesRenderer._seriesType.contains('boxandwhisker') + labelValue = seriesRendererDetails.seriesType.contains('range') == + false && + seriesRendererDetails.seriesType.contains('candle') == false && + seriesRendererDetails.seriesType.contains('hilo') == false && + seriesRendererDetails.seriesType.contains('boxandwhisker') == + false + ? getLabelValue(yValue, yAxis, digits) + : seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == + true + ? seriesRendererDetails.seriesType.contains('boxandwhisker') == + true ? 'Maximum : ' + - _getLabelValue(maxValue, yAxis) + + getLabelValue(maxValue, yAxis) + '\n' + 'Minimum : ' + - _getLabelValue(minValue, yAxis) + + getLabelValue(minValue, yAxis) + '\n' + 'LowerQuartile : ' + - _getLabelValue(lowerQuartileValue, yAxis) + + getLabelValue(lowerQuartileValue, yAxis) + '\n' + 'UpperQuartile : ' + - _getLabelValue(upperQuartileValue, yAxis) + getLabelValue(upperQuartileValue, yAxis) : 'High : ' + - _getLabelValue(highValue, yAxis) + + getLabelValue(highValue, yAxis) + '\n' + 'Low : ' + - _getLabelValue(lowValue, yAxis) + + getLabelValue(lowValue, yAxis) + '\n' + 'Open : ' + - _getLabelValue(openValue, yAxis) + + getLabelValue(openValue, yAxis) + '\n' + 'Close : ' + - _getLabelValue(closeValue, yAxis) + getLabelValue(closeValue, yAxis) : 'High : ' + - _getLabelValue(highValue, yAxis) + + getLabelValue(highValue, yAxis) + '\n' + 'Low : ' + - _getLabelValue(lowValue, yAxis); + getLabelValue(lowValue, yAxis); } return labelValue; } /// To get header text of trackball String _getHeaderText(CartesianChartPoint point, - CartesianSeriesRenderer seriesRenderer) { - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!; + SeriesRendererDetails seriesRendererDetails) { + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; String headerText; String? date; - if (xAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _xAxis = xAxisRenderer._axis as DateTimeAxis; - final DateFormat dateFormat = - _xAxis.dateFormat ?? _getDateTimeLabelFormat(xAxisRenderer); + if (xAxisDetails is DateTimeAxisDetails) { + final DateTimeAxis _xAxis = xAxisDetails.axis as DateTimeAxis; + final DateFormat dateFormat = _xAxis.dateFormat ?? + getDateTimeLabelFormat(xAxisDetails.axisRenderer); date = dateFormat .format(DateTime.fromMillisecondsSinceEpoch(point.xValue.floor())); } - headerText = xAxisRenderer is CategoryAxisRenderer + headerText = xAxisDetails is CategoryAxisDetails ? point.x.toString() - : xAxisRenderer is DateTimeAxisRenderer + : xAxisDetails is DateTimeAxisDetails ? date!.toString() - : (xAxisRenderer is DateTimeCategoryAxisRenderer - ? xAxisRenderer._getFormattedLabel( + : (xAxisDetails is DateTimeCategoryAxisDetails + ? xAxisDetails.getFormattedLabel( '${point.x.microsecondsSinceEpoch}', - xAxisRenderer._dateFormat) - : _getLabelValue(point.xValue, xAxisRenderer._axis, + xAxisDetails.dateFormat) + : getLabelValue(point.xValue, xAxisDetails.axis, _chart.tooltipBehavior.decimalPlaces)); return headerText; } - /// Performs the double-tap action. - /// - /// Hits while double tapping on the chart. - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onDoubleTap(double xPos, double yPos) => - _trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the long press action. - /// - /// Hits while a long tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onLongPress(double xPos, double yPos) => - _trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the touch-down action. - /// - /// Hits while tapping on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onTouchDown(double xPos, double yPos) => - _trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the touch-move action. - /// - /// Hits while tap and moving on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onTouchMove(double xPos, double yPos) => - _trackballBehavior.show(xPos, yPos, 'pixel'); - - /// Performs the touch-up action. - /// - /// Hits while release tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onTouchUp(double xPos, double yPos) => _trackballBehavior.hide(); - - /// Performs the mouse-hover action. - /// - /// Hits while enter tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onEnter(double xPos, double yPos) => - _trackballBehavior.show(xPos, yPos, 'pixel'); - - /// performs the mouse-exit action. - /// - /// Hits while exit tap on the chart. - /// - /// * xPos - X value of the touch position. - /// * yPos - Y value of the touch position. - @override - void onExit(double xPos, double yPos) => _trackballBehavior.hide(); - - /// Draws trackball - /// - /// * canvas - Canvas used to draw the Track line on the chart. - @override - void onPaint(Canvas canvas) { - if (_trackballPainter != null && !_trackballPainter!.canResetPath) { - _trackballPainter!._drawTrackball(canvas); - } - } - /// To draw trackball line - void _drawLine(Canvas canvas, Paint? paint, int seriesIndex) { - assert(_trackballBehavior.lineWidth >= 0, + void drawLine(Canvas canvas, Paint? paint, int seriesIndex) { + assert(trackballBehavior.lineWidth >= 0, 'Line width value of trackball should be greater than 0.'); - if (_trackballPainter != null && paint != null) { - _trackballPainter!._drawTrackBallLine(canvas, paint, seriesIndex); + if (trackballPainter != null && paint != null) { + trackballPainter!.drawTrackBallLine(canvas, paint, seriesIndex); } } - Paint? _linePainter(Paint paint) => _trackballPainter?._getLinePainter(paint); -} - -///Trackball marker renderer class for mutable fields and methods -class TrackballMarkerSettingsRenderer { - /// Creates an argument constructor for TrackballMarkerSettings class - TrackballMarkerSettingsRenderer(this._trackballMarkerSettings); - - ///ignore: unused_field - final TrackballMarkerSettings? _trackballMarkerSettings; - - dart_ui.Image? _image; + /// Returns the track line painter + Paint? linePainter(Paint paint) => trackballPainter?.getLinePainter(paint); + + /// Trackball show by index + void internalShowByIndex( + int pointIndex, + ) { + final CartesianStateProperties stateProperties = _stateProperties; + final TrackballRenderingDetails _trackballRenderingDetails = + TrackballHelper.getRenderingDetails( + _stateProperties.trackballBehaviorRenderer); + if ((_trackballRenderingDetails.trackballPainter != null || + _chart.trackballBehavior.builder != null) && + _chart.trackballBehavior.activationMode != ActivationMode.none) { + if (validIndex(pointIndex, 0, stateProperties.chart)) { + _trackballRenderingDetails.showTrackball( + stateProperties.chartSeries.visibleSeriesRenderers, + pointIndex, + _stateProperties.trackballBehaviorRenderer); + } + if (_trackballRenderingDetails.trackballPainter != null) { + _trackballRenderingDetails.trackballPainter!.canResetPath = false; + _trackballRenderingDetails.trackballPainter!.stateProperties + .repaintNotifiers['trackball']!.value++; + } + } + } } -/// Class to store the group mode details of trackball template. -class TrackballGroupingModeInfo { - /// Creates an argument constructor for TrackballGroupingModeInfo class. - TrackballGroupingModeInfo(this.points, this.currentPointIndices, - this.visibleSeriesIndices, this.visibleSeriesList); - - /// It specifies the cartesian chart points. - final List> points; - - /// It specifies the current point indices. - final List currentPointIndices; +// ignore: avoid_classes_with_only_static_members +/// Helper class to get the trackball rendering details instance from its renderer +class TrackballHelper { + /// Returns the trackball rendering details instance from its renderer + static TrackballRenderingDetails getRenderingDetails( + TrackballBehaviorRenderer renderer) { + return renderer._trackballRenderingDetails; + } - /// It specifies the visible series indices. - final List visibleSeriesIndices; + /// Returns the cartesian state properties from its instance + static CartesianStateProperties getStateProperties( + TrackballBehavior trackballBehavior) { + return trackballBehavior._stateProperties; + } - /// It specifies the cartesian visible series list. - final List> visibleSeriesList; + /// Method to set the cartesian state properties + static void setStateProperties(TrackballBehavior trackballBehavior, + CartesianStateProperties stateProperties) { + trackballBehavior._stateProperties = stateProperties; + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_marker_setting_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_marker_setting_renderer.dart new file mode 100644 index 000000000..c3fd866f8 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_marker_setting_renderer.dart @@ -0,0 +1,35 @@ +import 'dart:ui' as dart_ui; +import '../chart_series/series.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/trackball_marker_settings.dart'; + +/// Creates a class for TrackballMarkerSettingsRenderer +class TrackballMarkerSettingsRenderer { + /// Creates an argument constructor for TrackballMarkerSettings class + TrackballMarkerSettingsRenderer(this._trackballMarkerSettings); + + ///ignore: unused_field + final TrackballMarkerSettings? _trackballMarkerSettings; + + /// Specifies the marker image + dart_ui.Image? image; +} + +/// Class to store the group mode details of trackball template. +class TrackballGroupingModeInfo { + /// Creates an argument constructor for TrackballGroupingModeInfo class. + TrackballGroupingModeInfo(this.points, this.currentPointIndices, + this.visibleSeriesIndices, this.visibleSeriesList); + + /// Specifies the cartesian chart points. + final List> points; + + /// Specifies the current point indices. + final List currentPointIndices; + + /// Specifies the visible series indices. + final List visibleSeriesIndices; + + /// Specifies the cartesian visible series list. + final List> visibleSeriesList; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart index b7d52cd20..01fafb5cc 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_painter.dart @@ -1,73 +1,266 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; -class _TrackballPainter extends CustomPainter { - _TrackballPainter({required this.chartState, required this.valueNotifier}) - : chart = chartState._chart, +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../common/rendering_details.dart'; +import '../../common/utils/enum.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_segment/chart_segment.dart'; +import '../chart_series/series.dart'; +import '../chart_series/series_renderer_properties.dart'; +import '../chart_series/xy_data_series.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/interactive_tooltip.dart'; +import '../common/marker.dart'; +import '../common/renderer.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'trackball.dart'; +import 'trackball_template.dart'; + +/// Represents the Trackline painter +class TracklinePainter extends CustomPainter { + /// Creates constructor of TracklinePainter class. + TracklinePainter(this.trackballBehavior, this.stateProperties, + this.chartPointInfo, this.markerShapes); + + /// Represents the value trackball behavior. + TrackballBehavior trackballBehavior; + + /// Represents the cartesian state properties. + CartesianStateProperties stateProperties; + + /// Specifies the list of chart point information of data points. + List? chartPointInfo; + + /// Specifies the list of maker shape paths. + List? markerShapes; + + /// Specifies whether the trackline is drawn or not. + bool isTrackLineDrawn = false; + + @override + void paint(Canvas canvas, Size size) { + final Path dashArrayPath = Path(); + final Paint trackballLinePaint = Paint(); + trackballLinePaint.color = trackballBehavior.lineColor ?? + stateProperties.renderingDetails.chartTheme.crosshairLineColor; + trackballLinePaint.strokeWidth = trackballBehavior.lineWidth; + trackballLinePaint.style = PaintingStyle.stroke; + trackballBehavior.lineWidth == 0 + ? trackballLinePaint.color = Colors.transparent + : trackballLinePaint.color = trackballLinePaint.color; + final Rect boundaryRect = stateProperties.chartAxis.axisClipRect; + + if (chartPointInfo != null && chartPointInfo!.isNotEmpty) { + for (int index = 0; index < chartPointInfo!.length; index++) { + if (index == 0) { + if (chartPointInfo![index] + .seriesRendererDetails! + .seriesType + .contains('bar') == + true + ? stateProperties.requireInvertedAxis + : stateProperties.requireInvertedAxis) { + 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); + } + trackballBehavior.lineDashArray != null + ? drawDashedLine(canvas, trackballBehavior.lineDashArray!, + trackballLinePaint, dashArrayPath) + : canvas.drawPath(dashArrayPath, trackballLinePaint); + } + if (markerShapes != null && + markerShapes!.isNotEmpty && + markerShapes!.length > index) { + TrackballHelper.getRenderingDetails( + stateProperties.trackballBehaviorRenderer) + .renderTrackballMarker( + chartPointInfo![index].seriesRendererDetails!, + canvas, + trackballBehavior, + index); + } + } + } + } + + @override + bool shouldRepaint(TracklinePainter oldDelegate) => true; +} + +/// Represents the trackball painter. +class TrackballPainter extends CustomPainter { + /// Calling the default constructor of TrackballPainter class. + TrackballPainter({required this.stateProperties, required this.valueNotifier}) + : chart = stateProperties.chart, super(repaint: valueNotifier); - final SfCartesianChartState chartState; + + /// Represents the cartesian chart properties. + final CartesianStateProperties stateProperties; + + /// Represents the value of cartesian chart. final SfCartesianChart chart; + + /// Specifies the value of timer. Timer? timer; + + /// Repaint notifier for trackball. ValueNotifier valueNotifier; + + /// 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. double? x; + + /// Represents the value of y value. double? y; + + /// Represents the value of x position. double? xPos; + + /// Represents the value of y position. double? yPos; + + /// Represents the value of isTop. bool isTop = false; + + /// Represents the value of border radius. late double borderRadius; + + /// Represents the value of background path. Path backgroundPath = Path(); + + /// Represents the value for canResetPath for trackball. bool canResetPath = true; + + /// Represents the value of isleft. bool isLeft = false; + + /// Represents the value of isright. bool isRight = false; + + /// Specifies the padding value for group all dispaly mode. double groupAllPadding = 10; - List<_TrackballElement> stringValue = <_TrackballElement>[]; + + /// Specifies the list of string values for the trackball. + List stringValue = []; + + /// Represents the boundary rect for trackball. Rect boundaryRect = const Rect.fromLTWH(0, 0, 0, 0); + + /// Represents the value of left padding. double leftPadding = 0; + + /// Represents the value of top padding. double topPadding = 0; + + /// Specifies whether the orientation is horizontal or not. bool isHorizontalOrientation = false; + + /// Specifies whether the series is rect type or not. bool isRectSeries = false; + + /// Specifies the text style for label. late TextStyle labelStyle; + + /// Specifies whether the divider is needed or not. bool divider = true; + + /// Specifies the list of marker shaper paths. List? _markerShapes; - //ignore: prefer_final_fields - List _tooltipTop = []; - //ignore: prefer_final_fields - List _tooltipBottom = []; + + /// Specifies the list of tooltip top values. + List tooltipTop = []; + + /// Specifies the list of tooltip bottom values. + List tooltipBottom = []; + final List _xAxesInfo = []; + final List _yAxesInfo = []; - late List<_ChartPointInfo> chartPointInfo; - late List<_ClosestPoints> _visiblePoints; - _TooltipPositions? _tooltipPosition; + + /// Specifies the list of chart point infos + late List chartPointInfo; + + late List _visiblePoints; + + TooltipPositions? _tooltipPosition; + num _padding = 5; + late num _tooltipPadding; + + ///Specifies whether the series is range type or not. bool isRangeSeries = false; + + ///Specifies whether the series is box and whishers series or not. bool isBoxSeries = false; + + /// Represents the rect value of label. late Rect labelRect; + + /// Represents the value of marker size and padding. late num markerSize, markerPadding; + + /// Specifies whether the group mode is enabled or not. bool isGroupMode = false; + + /// Represents the value of last marker result height. late double lastMarkerResultHeight; - _ChartLocation? _minLocation, _maxLocation; + + ChartLocation? _minLocation, _maxLocation; + + /// Trackball rendering details + TrackballRenderingDetails get trackballRenderingDetails => + TrackballHelper.getRenderingDetails( + stateProperties.trackballBehaviorRenderer); @override - void paint(Canvas canvas, Size size) => - chartState._trackballBehaviorRenderer.onPaint(canvas); + void paint(Canvas canvas, Size size) { + stateProperties.trackballBehaviorRenderer.onPaint(canvas); + } - Paint _getLinePainter(Paint trackballLinePaint) => trackballLinePaint; + /// To get the paint for trackball line painter. + Paint getLinePainter(Paint trackballLinePaint) => trackballLinePaint; /// To draw the trackball for all series - void _drawTrackball(Canvas canvas) { - final _RenderingDetails renderingDetails = chartState._renderingDetails; + void drawTrackball(Canvas canvas) { + final RenderingDetails renderingDetails = stateProperties.renderingDetails; if (!_isSeriesAnimating()) { - chartPointInfo = chartState._trackballBehaviorRenderer._chartPointInfo; - _markerShapes = chartState._trackballBehaviorRenderer._markerShapes; - _visiblePoints = chartState._trackballBehaviorRenderer._visiblePoints; - isRangeSeries = chartState._trackballBehaviorRenderer._isRangeSeries; - isBoxSeries = chartState._trackballBehaviorRenderer._isBoxSeries; - _tooltipPadding = chartState._requireInvertedAxis ? 8 : 5; + chartPointInfo = trackballRenderingDetails.chartPointInfo; + _markerShapes = trackballRenderingDetails.markerShapes; + _visiblePoints = trackballRenderingDetails.visiblePoints; + isRangeSeries = trackballRenderingDetails.isRangeSeries; + isBoxSeries = trackballRenderingDetails.isBoxSeries; + _tooltipPadding = stateProperties.requireInvertedAxis ? 8 : 5; borderRadius = chart.trackballBehavior.tooltipSettings.borderRadius; pointerLength = chart.trackballBehavior.tooltipSettings.arrowLength; pointerWidth = chart.trackballBehavior.tooltipSettings.arrowWidth; @@ -77,7 +270,7 @@ class _TrackballPainter extends CustomPainter { isLeft = false; isRight = false; double height = 0, width = 0; - boundaryRect = chartState._chartAxis._axisClipRect; + boundaryRect = stateProperties.chartAxis.axisClipRect; totalWidth = boundaryRect.left + boundaryRect.width; labelStyle = TextStyle( color: chart.trackballBehavior.tooltipSettings.textStyle.color ?? @@ -119,13 +312,13 @@ class _TrackballPainter extends CustomPainter { chart.trackballBehavior.tooltipSettings.textStyle.debugLabel, fontFamilyFallback: chart .trackballBehavior.tooltipSettings.textStyle.fontFamilyFallback); - _ChartPointInfo? trackLinePoint = + ChartPointInfo? trackLinePoint = chartPointInfo.isNotEmpty ? chartPointInfo[0] : null; for (int index = 0; index < chartPointInfo.length; index++) { - final _ChartPointInfo next = chartPointInfo[index]; - final _ChartPointInfo pres = trackLinePoint!; - final Offset pos = chartState._trackballBehaviorRenderer._tapPosition; - if (chartState._requireInvertedAxis + final ChartPointInfo next = chartPointInfo[index]; + final ChartPointInfo pres = trackLinePoint!; + final Offset pos = trackballRenderingDetails.tapPosition; + if (stateProperties.requireInvertedAxis ? ((pos.dy - pres.yPosition!).abs() >= (pos.dy - next.yPosition!).abs()) : ((pos.dx - pres.xPosition!).abs() >= @@ -133,47 +326,56 @@ class _TrackballPainter extends CustomPainter { trackLinePoint = chartPointInfo[index]; } if (((chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('column') || - chartPointInfo[index].seriesRenderer!._seriesType == + .seriesRendererDetails! + .seriesType + .contains('column') == + true || + chartPointInfo[index].seriesRendererDetails!.seriesType == 'candle' || chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('boxandwhisker') || + .seriesRendererDetails! + .seriesType + .contains('boxandwhisker') == + true || chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('hilo')) && - !chartState._requireInvertedAxis) || + .seriesRendererDetails! + .seriesType + .contains('hilo') == + true) && + !stateProperties.requireInvertedAxis) || (chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('bar') && - chartState._requireInvertedAxis)) { + .seriesRendererDetails! + .seriesType + .contains('bar') == + true && + stateProperties.requireInvertedAxis)) { isHorizontalOrientation = true; } isRectSeries = false; if ((chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('column') || - chartPointInfo[index].seriesRenderer!._seriesType == 'candle' || + .seriesRendererDetails! + .seriesType + .contains('column') == + true || + chartPointInfo[index].seriesRendererDetails!.seriesType == + 'candle' || chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('hilo') || + .seriesRendererDetails! + .seriesType + .contains('hilo') == + true || chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('boxandwhisker') && - chartState._requireInvertedAxis) || + .seriesRendererDetails! + .seriesType + .contains('boxandwhisker') == + true && + stateProperties.requireInvertedAxis) || (chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('bar') && - !chartState._requireInvertedAxis)) { + .seriesRendererDetails! + .seriesType + .contains('bar') == + true && + !stateProperties.requireInvertedAxis)) { isRectSeries = true; } @@ -191,10 +393,11 @@ class _TrackballPainter extends CustomPainter { chart.trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.auto ? (chartPointInfo[index] - .seriesRenderer! - ._series - .markerSettings - .isVisible) + .seriesRendererDetails! + .series + .markerSettings + .isVisible == + true) : chart.trackballBehavior.markerSettings != null && chart.trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.visible) @@ -214,14 +417,14 @@ class _TrackballPainter extends CustomPainter { if (!canResetPath && chartPointInfo[index].label != null && chartPointInfo[index].label != '') { - _tooltipTop.add(chartState._requireInvertedAxis + tooltipTop.add(stateProperties.requireInvertedAxis ? _visiblePoints[index].closestPointX - _tooltipPadding - (width / 2) : _visiblePoints[index].closestPointY - _tooltipPadding - height / 2); - _tooltipBottom.add(chartState._requireInvertedAxis + tooltipBottom.add(stateProperties.requireInvertedAxis ? (_visiblePoints[index].closestPointX + _tooltipPadding + (width / 2)) + @@ -231,10 +434,14 @@ class _TrackballPainter extends CustomPainter { : _visiblePoints[index].closestPointY + _tooltipPadding + height / 2); - _xAxesInfo - .add(chartPointInfo[index].seriesRenderer!._xAxisRenderer!); - _yAxesInfo - .add(chartPointInfo[index].seriesRenderer!._yAxisRenderer!); + _xAxesInfo.add(chartPointInfo[index] + .seriesRendererDetails! + .xAxisDetails! + .axisRenderer); + _yAxesInfo.add(chartPointInfo[index] + .seriesRendererDetails! + .yAxisDetails! + .axisRenderer); } } } @@ -255,33 +462,31 @@ class _TrackballPainter extends CustomPainter { chart.trackballBehavior.lineWidth == 0 ? trackballLinePaint.color = Colors.transparent : trackballLinePaint.color = trackballLinePaint.color; - chartState._trackballBehaviorRenderer._drawLine( + trackballRenderingDetails.drawLine( canvas, - chartState._trackballBehaviorRenderer - ._linePainter(trackballLinePaint), + trackballRenderingDetails.linePainter(trackballLinePaint), chartPointInfo.indexOf(trackLinePoint)); } // ignore: unnecessary_null_comparison - if (_tooltipTop != null && _tooltipTop.isNotEmpty) { - _tooltipPosition = chartState._trackballBehaviorRenderer - ._smartTooltipPositions( - _tooltipTop, - _tooltipBottom, - _xAxesInfo, - _yAxesInfo, - chartPointInfo, - chartState._requireInvertedAxis, - true); + if (tooltipTop != null && tooltipTop.isNotEmpty) { + _tooltipPosition = trackballRenderingDetails.smartTooltipPositions( + tooltipTop, + tooltipBottom, + _xAxesInfo, + _yAxesInfo, + chartPointInfo, + stateProperties.requireInvertedAxis, + true); } for (int index = 0; index < chartPointInfo.length; index++) { - chartState._trackballBehaviorRenderer._trackballMarker(index); + trackballRenderingDetails.trackballMarker(index); if (_markerShapes != null && _markerShapes!.isNotEmpty && _markerShapes!.length > index) { - chartState._trackballBehaviorRenderer._renderTrackballMarker( - chartPointInfo[index].seriesRenderer!, + trackballRenderingDetails.renderTrackballMarker( + chartPointInfo[index].seriesRendererDetails!, canvas, chart.trackballBehavior, index); @@ -293,10 +498,11 @@ class _TrackballPainter extends CustomPainter { chart.trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.auto ? (chartPointInfo[index] - .seriesRenderer! - ._series - .markerSettings - .isVisible) + .seriesRendererDetails! + .series + .markerSettings + .isVisible == + true) : chart.trackballBehavior.markerSettings != null && chart.trackballBehavior.markerSettings!.markerVisibility == TrackballVisibilityMode.visible) @@ -316,8 +522,8 @@ class _TrackballPainter extends CustomPainter { _calculateTrackballRect( canvas, width, height, index, chartPointInfo, _tooltipPosition!); if (index == chartPointInfo.length - 1) { - _tooltipTop.clear(); - _tooltipBottom.clear(); + tooltipTop.clear(); + tooltipBottom.clear(); _tooltipPosition!.tooltipTop.clear(); _tooltipPosition!.tooltipBottom.clear(); _xAxesInfo.clear(); @@ -330,22 +536,28 @@ class _TrackballPainter extends CustomPainter { bool _isSeriesAnimating() { for (int i = 0; - i < chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[i]; - if (!(seriesRenderer._animationCompleted || - seriesRenderer._series.animationDuration == 0 || - !chartState._renderingDetails.initialRender!) && - seriesRenderer._series.isVisible) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + if (!(seriesRendererDetails.animationCompleted == true || + seriesRendererDetails.series.animationDuration == 0 || + !stateProperties.renderingDetails.initialRender!) && + seriesRendererDetails.series.isVisible == true) { return true; } } return false; } + /// 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; /// To get tooltip size @@ -353,14 +565,13 @@ class _TrackballPainter extends CustomPainter { final Offset position = Offset( chartPointInfo[index].xPosition!, chartPointInfo[index].yPosition!); Offset pos; - SfCartesianChartState? _chartState; - ChartAxisRenderer? xAxisRenderer, yAxisRenderer; - CartesianSeriesRenderer? seriesRender; + ChartAxisRendererDetails xAxisDetails, yAxisDetails; + SeriesRendererDetails? seriesRendererDetails; num? _minX, _maxX; - stringValue = <_TrackballElement>[]; + stringValue = []; final String? format = chartPointInfo[index] - .seriesRenderer! - ._chart + .seriesRendererDetails! + .chart .trackballBehavior .tooltipSettings .format; @@ -377,34 +588,33 @@ class _TrackballPainter extends CustomPainter { } if (chartPointInfo[index].header != null && chartPointInfo[index].header != '') { - stringValue.add(_TrackballElement(chartPointInfo[index].header!, null)); + stringValue.add(TrackballElement(chartPointInfo[index].header!, null)); } if (isGroupMode) { String str1 = ''; for (int i = 0; i < chartPointInfo.length; i++) { - pos = chartState._trackballBehaviorRenderer._tapPosition; - _chartState = chartPointInfo[i].seriesRenderer?._chartState; - xAxisRenderer = chartPointInfo[i].seriesRenderer?._xAxisRenderer; - seriesRender = chartPointInfo[i].seriesRenderer; - yAxisRenderer = chartPointInfo[i].seriesRenderer?._yAxisRenderer; - _minX = seriesRender?._minimumX; - _maxX = seriesRender?._maximumX; - _minLocation = _calculatePoint( + pos = trackballRenderingDetails.tapPosition; + xAxisDetails = chartPointInfo[i].seriesRendererDetails!.xAxisDetails!; + seriesRendererDetails = chartPointInfo[i].seriesRendererDetails; + yAxisDetails = chartPointInfo[i].seriesRendererDetails!.yAxisDetails!; + _minX = seriesRendererDetails!.minimumX; + _maxX = seriesRendererDetails.maximumX; + _minLocation = calculatePoint( _minX!, - seriesRender?._minimumY!, - xAxisRenderer!, - yAxisRenderer!, - _chartState!._requireInvertedAxis, + seriesRendererDetails.minimumY!, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, chartPointInfo[index].series, - _chartState._chartAxis._axisClipRect); - _maxLocation = _calculatePoint( + stateProperties.chartAxis.axisClipRect); + _maxLocation = calculatePoint( _maxX!, - seriesRender?._maximumY!, - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, + seriesRendererDetails.maximumY!, + xAxisDetails, + yAxisDetails, + stateProperties.requireInvertedAxis, chartPointInfo[index].series, - _chartState._chartAxis._axisClipRect); + stateProperties.chartAxis.axisClipRect); if (chartPointInfo[i].header != null && chartPointInfo[i].header!.contains(':')) { headerText = true; @@ -413,22 +623,24 @@ class _TrackballPainter extends CustomPainter { chartPointInfo[i].header != null && chartPointInfo[i].header != ''; bool isLabel = chartPointInfo[i].label != null && chartPointInfo[i].label != ''; - if (chartPointInfo[i].seriesRenderer!._isIndicator) { + if (chartPointInfo[i].seriesRendererDetails!.isIndicator == true) { isHeader = chartPointInfo[0].header != null && chartPointInfo[0].header != ''; isLabel = chartPointInfo[0].label != null && chartPointInfo[0].label != ''; } divider = isHeader && isLabel; - final String seriesType = chartPointInfo[i].seriesRenderer!._seriesType; - if (chartPointInfo[i].seriesRenderer!._isIndicator && + final String seriesType = + chartPointInfo[i].seriesRendererDetails!.seriesType; + if (chartPointInfo[i].seriesRendererDetails!.isIndicator == true && chartPointInfo[i] - .seriesRenderer! - ._series - .name! - .contains('rangearea')) { + .seriesRendererDetails! + .series + .name! + .contains('rangearea') == + true) { if (i == 0) { - stringValue.add(_TrackballElement('', null)); + stringValue.add(TrackballElement('', null)); } else { str1 = ''; } @@ -438,59 +650,60 @@ class _TrackballPainter extends CustomPainter { seriesType.contains('range') || seriesType == 'boxandwhisker') && chartPointInfo[i] - .seriesRenderer! - ._chart + .seriesRendererDetails! + .chart .trackballBehavior .tooltipSettings .format == null && isLabel) { - stringValue.add(_TrackballElement( + stringValue.add(TrackballElement( ((chartPointInfo[index].header == null || chartPointInfo[index].header == '') ? '' : i == 0 ? '\n' : '') + - '${chartPointInfo[i].seriesRenderer!._seriesName}\n${chartPointInfo[i].label}', - chartPointInfo[i].seriesRenderer!)); - } else if (chartPointInfo[i].seriesRenderer!._series.name != null) { + '${chartPointInfo[i].seriesRendererDetails!.seriesName}\n${chartPointInfo[i].label}', + chartPointInfo[i].seriesRendererDetails!.renderer)); + } else if (chartPointInfo[i].seriesRendererDetails!.series.name != + null) { if (chartPointInfo[i] - .seriesRenderer! - ._chart + .seriesRendererDetails! + .chart .trackballBehavior .tooltipSettings .format != null) { if (isHeader && isLabel && i == 0) { - stringValue.add(_TrackballElement('', null)); + stringValue.add(TrackballElement('', null)); } if (isLabel) { - stringValue.add(_TrackballElement( - chartPointInfo[i].label!, chartPointInfo[i].seriesRenderer!)); + stringValue.add(TrackballElement(chartPointInfo[i].label!, + chartPointInfo[i].seriesRendererDetails!.renderer)); } } else if (isLabel && chartPointInfo[i].label!.contains(':') && (chartPointInfo[i].header == null || chartPointInfo[i].header == '')) { - stringValue.add(_TrackballElement( - chartPointInfo[i].label!, chartPointInfo[i].seriesRenderer!)); + stringValue.add(TrackballElement(chartPointInfo[i].label!, + chartPointInfo[i].seriesRendererDetails!.renderer)); divider = false; } else { if (isHeader && isLabel && i == 0) { - stringValue.add(_TrackballElement('', null)); + stringValue.add(TrackballElement('', null)); } if (isLabel) { //ignore: avoid_bool_literals_in_conditional_expressions - if (chartPointInfo[i].seriesRenderer!._isIndicator + if (chartPointInfo[i].seriesRendererDetails!.isIndicator == true ? pos.dx >= _minLocation!.x && pos.dx <= _maxLocation!.x : true) { - stringValue.add(_TrackballElement( + stringValue.add(TrackballElement( str1 + - chartPointInfo[i].seriesRenderer!._series.name! + + chartPointInfo[i].seriesRendererDetails!.series.name! + ': ' + chartPointInfo[i].label!, - chartPointInfo[i].seriesRenderer!)); + chartPointInfo[i].seriesRendererDetails!.renderer)); } } divider = (chartPointInfo[0].header != null && @@ -503,10 +716,10 @@ class _TrackballPainter extends CustomPainter { } else { if (isLabel) { if (isHeader && i == 0) { - stringValue.add(_TrackballElement('', null)); + stringValue.add(TrackballElement('', null)); } - stringValue.add(_TrackballElement( - chartPointInfo[i].label!, chartPointInfo[i].seriesRenderer!)); + stringValue.add(TrackballElement(chartPointInfo[i].label!, + chartPointInfo[i].seriesRendererDetails!.renderer)); } } } @@ -531,11 +744,11 @@ class _TrackballPainter extends CustomPainter { y = boundaryRect.bottom; } } else { - stringValue = <_TrackballElement>[]; + stringValue = []; if (chartPointInfo[index].label != null && chartPointInfo[index].label != '') { - stringValue.add(_TrackballElement(chartPointInfo[index].label!, - chartPointInfo[index].seriesRenderer!)); + stringValue.add(TrackballElement(chartPointInfo[index].label!, + chartPointInfo[index].seriesRendererDetails!.renderer)); } String? measureString = @@ -551,19 +764,29 @@ class _TrackballPainter extends CustomPainter { height = size.height; if (chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('column') || - chartPointInfo[index].seriesRenderer!._seriesType.contains('bar') || - chartPointInfo[index].seriesRenderer!._seriesType == 'candle' || + .seriesRendererDetails! + .seriesType + .contains('column') == + true || chartPointInfo[index] - .seriesRenderer! - ._seriesType - .contains('boxandwhisker') || - chartPointInfo[index].seriesRenderer!._seriesType.contains('hilo')) { + .seriesRendererDetails! + .seriesType + .contains('bar') == + true || + chartPointInfo[index].seriesRendererDetails!.seriesType == 'candle' || + chartPointInfo[index] + .seriesRendererDetails! + .seriesType + .contains('boxandwhisker') == + true || + chartPointInfo[index] + .seriesRendererDetails! + .seriesType + .contains('hilo') == + true) { x = position.dx; y = position.dy; - } else if (chartPointInfo[index].seriesRenderer!._seriesType == + } else if (chartPointInfo[index].seriesRendererDetails!.seriesType == 'rangearea') { x = chartPointInfo[index].chartDataPoint!.markerPoint!.x; y = (chartPointInfo[index].chartDataPoint!.markerPoint!.y + @@ -580,10 +803,10 @@ class _TrackballPainter extends CustomPainter { /// To find the rect location of the trackball void _calculateTrackballRect( Canvas canvas, double width, double height, int index, - [List<_ChartPointInfo>? chartPointInfo, - _TooltipPositions? tooltipPosition]) { + [List? chartPointInfo, + TooltipPositions? tooltipPosition]) { final String seriesType = - chartPointInfo![index].seriesRenderer!._seriesType; + chartPointInfo![index].seriesRendererDetails!.seriesType; const double widthPadding = 17; markerSize = 10; Rect leftRect, rightRect; @@ -625,8 +848,8 @@ class _TrackballPainter extends CustomPainter { } else { isTop = false; if (seriesType.contains('bar') - ? chartState._requireInvertedAxis - : chartState._requireInvertedAxis) { + ? stateProperties.requireInvertedAxis + : stateProperties.requireInvertedAxis) { xPos = x! - (labelRect.width / 2); yPos = (y! + pointerLength) + _padding; nosePointX = labelRect.left; @@ -679,10 +902,10 @@ class _TrackballPainter extends CustomPainter { TrackballDisplayMode.nearestPoint ? Rect.fromLTWH(xPos!, yPos!, labelRect.width, labelRect.height) : Rect.fromLTWH( - chartState._requireInvertedAxis + stateProperties.requireInvertedAxis ? tooltipPosition!.tooltipTop[index].toDouble() : xPos!, - !chartState._requireInvertedAxis + !stateProperties.requireInvertedAxis ? tooltipPosition!.tooltipTop[index].toDouble() : yPos!, labelRect.width, @@ -702,7 +925,7 @@ class _TrackballPainter extends CustomPainter { null, null); } else { - if (chartState._requireInvertedAxis + if (stateProperties.requireInvertedAxis ? tooltipPosition!.tooltipTop[index] >= boundaryRect.left && tooltipPosition.tooltipBottom[index] <= boundaryRect.right : tooltipPosition!.tooltipTop[index] >= boundaryRect.top && @@ -739,14 +962,18 @@ class _TrackballPainter extends CustomPainter { /// To find the trackball tooltip size void _calculateTooltipSize( Rect labelRect, - List<_ChartPointInfo>? chartPointInfo, - _TooltipPositions? tooltipPositions, + List? chartPointInfo, + TooltipPositions? tooltipPositions, int index) { isTop = true; isRight = false; - if (chartPointInfo![index].seriesRenderer!._seriesType.contains('bar') - ? chartState._requireInvertedAxis - : chartState._requireInvertedAxis) { + if (chartPointInfo![index] + .seriesRendererDetails! + .seriesType + .contains('bar') == + true + ? stateProperties.requireInvertedAxis + : stateProperties.requireInvertedAxis) { xPos = x! - (labelRect.width / 2); yPos = (y! - labelRect.height) - _padding; nosePointY = labelRect.top - _padding; @@ -788,11 +1015,15 @@ class _TrackballPainter extends CustomPainter { } /// To draw the line for the trackball - void _drawTrackBallLine(Canvas canvas, Paint paint, int index) { + void drawTrackBallLine(Canvas canvas, Paint paint, int index) { final Path dashArrayPath = Path(); - if (chartPointInfo[index].seriesRenderer!._seriesType.contains('bar') - ? chartState._requireInvertedAxis - : chartState._requireInvertedAxis) { + if (chartPointInfo[index] + .seriesRendererDetails! + .seriesType + .contains('bar') == + true + ? stateProperties.requireInvertedAxis + : stateProperties.requireInvertedAxis) { dashArrayPath.moveTo(boundaryRect.left, chartPointInfo[index].yPosition!); dashArrayPath.lineTo( boundaryRect.right, chartPointInfo[index].yPosition!); @@ -802,12 +1033,12 @@ class _TrackballPainter extends CustomPainter { chartPointInfo[index].xPosition!, boundaryRect.bottom); } chart.trackballBehavior.lineDashArray != null - ? _drawDashedLine(canvas, chart.trackballBehavior.lineDashArray!, paint, + ? drawDashedLine(canvas, chart.trackballBehavior.lineDashArray!, paint, dashArrayPath) : canvas.drawPath(dashArrayPath, paint); } - /// To draw background of trackball tool tip + /// To draw background of trackball tooltip void _drawTooltipBackground( Canvas canvas, Rect labelRect, @@ -882,7 +1113,7 @@ class _TrackballPainter extends CustomPainter { chart.trackballBehavior.tooltipDisplayMode != TrackballDisplayMode.none) { if (!isGroupMode && !(xPosition == null || yPosition == null)) { - if (chartState._requireInvertedAxis) { + if (stateProperties.requireInvertedAxis) { if (isLeft) { startX = rectF.left + borderRadius; endX = startX + pointerWidth; @@ -922,10 +1153,10 @@ class _TrackballPainter extends CustomPainter { } } - /// draw trackball tooltip rect and text + /// Draw trackball tooltip rect and text void _drawRectandText( Canvas canvas, Path backgroundPath, Rect rect, int index) { - final _RenderingDetails renderingDetails = chartState._renderingDetails; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; final RRect tooltipRect = RRect.fromRectAndCorners( rect, bottomLeft: Radius.circular(borderRadius), @@ -1046,13 +1277,13 @@ class _TrackballPainter extends CustomPainter { tooltipRect, animationFactor, labelSize, - chartPointInfo[index].seriesRenderer!, + chartPointInfo[index].seriesRendererDetails!, i, null, null, eachTextHeight, index); - _drawText( + drawText( canvas, stringValue[i].label, Offset( @@ -1105,14 +1336,14 @@ class _TrackballPainter extends CustomPainter { tooltipRect, animationFactor, labelSize, - chartPointInfo[index].seriesRenderer!, + chartPointInfo[index].seriesRendererDetails!, i, null, width, eachTextHeight, index); } - _drawText( + drawText( canvas, str1[k], Offset( @@ -1163,7 +1394,7 @@ class _TrackballPainter extends CustomPainter { tooltipRect, animationFactor, labelSize, - chartPointInfo[index].seriesRenderer!, + chartPointInfo[index].seriesRendererDetails!, i, null, null, @@ -1179,7 +1410,7 @@ class _TrackballPainter extends CustomPainter { ? 7 : 0) : 0; - _drawText( + drawText( canvas, str1[str1.length - 1], Offset(markerPadding + tooltipRect.left + 4, @@ -1264,7 +1495,7 @@ class _TrackballPainter extends CustomPainter { tooltipRect, animationFactor, labelSize, - chartPointInfo[index].seriesRenderer!, + chartPointInfo[index].seriesRendererDetails!, i, previousWidth, width, @@ -1281,7 +1512,7 @@ class _TrackballPainter extends CustomPainter { ? 7 : 0) : 0; - _drawText( + drawText( canvas, colon + str1[j], Offset( @@ -1299,14 +1530,14 @@ class _TrackballPainter extends CustomPainter { } } - /// draw marker inside the trackball tooltip + /// Draw marker inside the trackball tooltip void _drawTooltipMarker( String labelValue, Canvas canvas, RRect tooltipRect, double animationFactor, Size tooltipMarkerResult, - CartesianSeriesRenderer? seriesRenderer, + SeriesRendererDetails? seriesRendererDetails, int i, double? previousWidth, double? width, @@ -1318,17 +1549,18 @@ class _TrackballPainter extends CustomPainter { Offset markerPoint; if (chart.trackballBehavior.tooltipSettings.canShowMarker) { if (!isGroupMode) { - if (seriesRenderer!._seriesType.contains('hilo') || - seriesRenderer._seriesType.contains('candle') || - seriesRenderer._seriesType.contains('boxandwhisker')) { + if (seriesRendererDetails!.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('boxandwhisker') == + true) { markerPoint = Offset( tooltipRect.left + tooltipRect.width / 2 - tooltipStringResult.width / 2 - markerSize, (eachTextHeight - tooltipStringResult.height / 2) + 0.0); - _renderMarker( - markerPoint, seriesRenderer, animationFactor, canvas, index); + _renderMarker(markerPoint, seriesRendererDetails, animationFactor, + canvas, index); } else { markerPoint = Offset( (tooltipRect.left + @@ -1340,22 +1572,24 @@ class _TrackballPainter extends CustomPainter { markerSize); } _renderMarker( - markerPoint, seriesRenderer, animationFactor, canvas, index); + markerPoint, seriesRendererDetails, animationFactor, canvas, index); } else { if (i > 0 && labelValue != '') { - seriesRenderer = stringValue[i].seriesRenderer!; + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stringValue[i].seriesRenderer!); // ignore: unnecessary_null_comparison - if (seriesRenderer != null && - seriesRenderer._series.name != null && - seriesRenderer._chart.trackballBehavior.tooltipSettings.format == + if (seriesRendererDetails != null && + seriesRendererDetails.series.name != null && + seriesRendererDetails + .chart.trackballBehavior.tooltipSettings.format == null) { if (previousWidth != null && width != null) { markerPoint = Offset( (tooltipRect.left + 10) + (previousWidth > width ? previousWidth : width), eachTextHeight - tooltipMarkerResult.height / 2); - _renderMarker( - markerPoint, seriesRenderer, animationFactor, canvas, index); + _renderMarker(markerPoint, seriesRendererDetails, animationFactor, + canvas, index); } else if (stringValue[i].needRender) { markerPoint = Offset( tooltipRect.left + 10, @@ -1368,8 +1602,8 @@ class _TrackballPainter extends CustomPainter { : lastMarkerResultHeight - headerSize.height)); lastMarkerResultHeight = tooltipMarkerResult.height; stringValue[i].needRender = false; - _renderMarker( - markerPoint, seriesRenderer, animationFactor, canvas, index); + _renderMarker(markerPoint, seriesRendererDetails, animationFactor, + canvas, index); } } else { markerPoint = Offset( @@ -1377,8 +1611,8 @@ class _TrackballPainter extends CustomPainter { tooltipMarkerResult.width / 2) - markerSize, eachTextHeight - tooltipMarkerResult.height / 2); - _renderMarker( - markerPoint, seriesRenderer, animationFactor, canvas, index); + _renderMarker(markerPoint, seriesRendererDetails, animationFactor, + canvas, index); } } } @@ -1388,84 +1622,82 @@ class _TrackballPainter extends CustomPainter { // To render marker for the chart tooltip void _renderMarker( Offset markerPoint, - CartesianSeriesRenderer _seriesRenderer, + SeriesRendererDetails _seriesRendererDetails, double animationFactor, Canvas canvas, int index) { - _seriesRenderer._isMarkerRenderEvent = true; + _seriesRendererDetails.isMarkerRenderEvent = true; final MarkerSettings markerSettings = chart.trackballBehavior.markerSettings == null - ? _seriesRenderer._series.markerSettings + ? _seriesRendererDetails.series.markerSettings : chart.trackballBehavior.markerSettings!; - final Path markerPath = _getMarkerShapesPath( + final Path markerPath = getMarkerShapesPath( markerSettings.shape, markerPoint, Size((2 * markerSize) * animationFactor, (2 * markerSize) * animationFactor), - _seriesRenderer); + _seriesRendererDetails); Color? _seriesColor; - if (_seriesRenderer._seriesType.contains('candle')) { - final CandleSeriesRenderer seriesRenderer = - _seriesRenderer as CandleSeriesRenderer; - _seriesColor = - (seriesRenderer._segments[chartPointInfo[index].dataPointIndex!] - as CandleSegment) - ._isBull - ? seriesRenderer._candleSeries.bullColor - : seriesRenderer._candleSeries.bearColor; - } else if (_seriesRenderer._seriesType.contains('hiloopenclose')) { - final HiloOpenCloseSeriesRenderer seriesRenderer = - _seriesRenderer as HiloOpenCloseSeriesRenderer; - _seriesColor = - (seriesRenderer._segments[chartPointInfo[index].dataPointIndex!] - as HiloOpenCloseSegment) - ._isBull - ? seriesRenderer._hiloOpenCloseSeries.bullColor - : seriesRenderer._hiloOpenCloseSeries.bearColor; + if (_seriesRendererDetails.seriesType.contains('candle') == true) { + _seriesColor = SegmentHelper.getSegmentProperties(_seriesRendererDetails + .segments[chartPointInfo[index].dataPointIndex!]) + .isBull == + true + ? _seriesRendererDetails.candleSeries.bullColor + : _seriesRendererDetails.candleSeries.bearColor; + } else if (_seriesRendererDetails.seriesType.contains('hiloopenclose') == + true) { + _seriesColor = SegmentHelper.getSegmentProperties(_seriesRendererDetails + .segments[chartPointInfo[index].dataPointIndex!]) + .isBull == + true + ? _seriesRendererDetails.hiloOpenCloseSeries.bullColor + : _seriesRendererDetails.hiloOpenCloseSeries.bearColor; } else { _seriesColor = (chartPointInfo[index].dataPointIndex! < - _seriesRenderer._dataPoints.length - ? _seriesRenderer - ._dataPoints[chartPointInfo[index].dataPointIndex!] + _seriesRendererDetails.dataPoints.length + ? _seriesRendererDetails + .dataPoints[chartPointInfo[index].dataPointIndex!] .pointColorMapper : null) ?? - _seriesRenderer._seriesColor; + _seriesRendererDetails.seriesColor; } Paint markerPaint = Paint(); markerPaint.color = markerSettings.color ?? _seriesColor ?? Colors.white; - if (_seriesRenderer._series.gradient != null) { - markerPaint = _getLinearGradientPaint( - _seriesRenderer._series.gradient!, - _getMarkerShapesPath( + if (_seriesRendererDetails.series.gradient != null) { + markerPaint = getLinearGradientPaint( + _seriesRendererDetails.series.gradient!, + getMarkerShapesPath( markerSettings.shape, Offset(markerPoint.dx, markerPoint.dy), Size((2 * markerSize) * animationFactor, (2 * markerSize) * animationFactor), - _seriesRenderer) + _seriesRendererDetails) .getBounds(), - _seriesRenderer._chartState!._requireInvertedAxis); + _seriesRendererDetails.stateProperties.requireInvertedAxis); } canvas.drawPath(markerPath, markerPaint); Paint markerBorderPaint = Paint(); markerBorderPaint.color = markerSettings.borderColor ?? _seriesColor ?? - _seriesRenderer._renderingDetails!.chartTheme.tooltipLabelColor; + _seriesRendererDetails + .stateProperties.renderingDetails.chartTheme.tooltipLabelColor; markerBorderPaint.strokeWidth = 1; markerBorderPaint.style = PaintingStyle.stroke; - if (_seriesRenderer._series.gradient != null) { - markerBorderPaint = _getLinearGradientPaint( - _seriesRenderer._series.gradient!, - _getMarkerShapesPath( + if (_seriesRendererDetails.series.gradient != null) { + markerBorderPaint = getLinearGradientPaint( + _seriesRendererDetails.series.gradient!, + getMarkerShapesPath( markerSettings.shape, Offset(markerPoint.dx, markerPoint.dy), Size((2 * markerSize) * animationFactor, (2 * markerSize) * animationFactor), - _seriesRenderer) + _seriesRendererDetails) .getBounds(), - _seriesRenderer._chartState!._requireInvertedAxis); + _seriesRendererDetails.stateProperties.requireInvertedAxis); } canvas.drawPath(markerPath, markerBorderPaint); } @@ -1476,36 +1708,3 @@ class _TrackballPainter extends CustomPainter { /// Return value as string String getFormattedValue(num value) => value.toString(); } - -/// Class to store the about the details of the closest points -class _ClosestPoints { - /// Creates the parameterized constructor for class _ClosestPoints - const _ClosestPoints( - {required this.closestPointX, required this.closestPointY}); - - final double closestPointX; - - final double closestPointY; -} - -/// 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); - - final List tooltipTop; - - 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); - - final String label; - - final CartesianSeriesRenderer? seriesRenderer; - - bool needRender = true; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart index 7346df606..8c86dc904 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/trackball_template.dart @@ -1,37 +1,67 @@ -part of charts; - -// Widget class which is used to display the trackball template -class _TrackballTemplate extends StatefulWidget { - const _TrackballTemplate( +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +import '../../common/rendering_details.dart'; +import '../../common/utils/enum.dart'; +import '../axis/axis.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/interactive_tooltip.dart'; +import '../common/trackball_marker_settings.dart'; +import '../utils/enum.dart'; +import 'trackball.dart'; +import 'trackball_marker_setting_renderer.dart'; +import 'trackball_painter.dart'; + +/// Widget class which is used to display the trackball template +class TrackballTemplate extends StatefulWidget { + /// Creates an instance of trackball template + const TrackballTemplate( {required Key key, - required this.chartState, + required this.stateProperties, required this.trackballBehavior}) : super(key: key); - final SfCartesianChartState chartState; + /// Specifies the value of cartesian state properties + final CartesianStateProperties stateProperties; + + /// Holds the value of trackball behavior final TrackballBehavior trackballBehavior; @override State createState() { - return _TrackballTemplateState(); + return TrackballTemplateState(); } } -class _TrackballTemplateState extends State<_TrackballTemplate> { +/// Represents the trackball template state +class TrackballTemplateState extends State { bool _isRender = false; //ignore: unused_field - late _TrackballTemplateState _state; - List<_ChartPointInfo>? _chartPointInfo; - List? _markerShapes; + late TrackballTemplateState _state; + + /// Holds the chart point info + List? chartPointInfo; + + /// Holds the list of marker shapes + List? markerShapes; + + /// Holds the value of trackball grouping mode info late TrackballGroupingModeInfo groupingModeInfo; late Widget _template; + + /// Specifies the trackball duration value //ignore: unused_field - late double _duration; - //ignore: unused_field - late bool _alwaysShow; - //ignore: unused_field - late Timer _trackballTimer; + late double duration; + + /// Specifies whether to show the trackball always + bool? alwaysShow; + bool _isRangeSeries = false, _isBoxSeries = false; @override @@ -41,7 +71,7 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { } @override - void didUpdateWidget(_TrackballTemplate oldWidget) { + void didUpdateWidget(TrackballTemplate oldWidget) { super.didUpdateWidget(oldWidget); } @@ -52,11 +82,11 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { _state = this; String seriesType; if (_isRender && - widget.chartState._animationCompleted && - _chartPointInfo != null && - _chartPointInfo!.isNotEmpty) { - for (int index = 0; index < _chartPointInfo!.length; index++) { - seriesType = _chartPointInfo![index].seriesRenderer!._seriesType; + widget.stateProperties.animationCompleted && + chartPointInfo != null && + chartPointInfo!.isNotEmpty) { + for (int index = 0; index < chartPointInfo!.length; index++) { + seriesType = chartPointInfo![index].seriesRendererDetails!.seriesType; _isRangeSeries = seriesType.contains('range') || seriesType.contains('hilo') || seriesType == 'candle'; @@ -68,13 +98,13 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { trackballWidget = _TrackballRenderObject( child: _template, template: _template, - chartState: widget.chartState, - xPos: _chartPointInfo![index].xPosition!, + stateProperties: widget.stateProperties, + xPos: chartPointInfo![index].xPosition!, yPos: (_isRangeSeries - ? _chartPointInfo![index].highYPosition + ? chartPointInfo![index].highYPosition : _isBoxSeries - ? _chartPointInfo![index].maxYPosition - : _chartPointInfo![index].yPosition)!, + ? chartPointInfo![index].maxYPosition + : chartPointInfo![index].yPosition)!, trackballBehavior: widget.trackballBehavior); trackballWidgets.add(trackballWidget); @@ -85,26 +115,26 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { _template = widget.trackballBehavior.builder!( context, TrackballDetails( - _chartPointInfo![index] - .seriesRenderer! - ._dataPoints[_chartPointInfo![index].dataPointIndex!], - _chartPointInfo![index].seriesRenderer!._series, - _chartPointInfo![index].dataPointIndex, - _chartPointInfo![index].seriesIndex, + chartPointInfo![index] + .seriesRendererDetails! + .dataPoints[chartPointInfo![index].dataPointIndex!], + chartPointInfo![index].seriesRendererDetails!.series, + chartPointInfo![index].dataPointIndex, + chartPointInfo![index].seriesIndex, null)); trackballWidget = _TrackballRenderObject( child: _template, template: _template, - chartState: widget.chartState, - xPos: _chartPointInfo![index].xPosition!, + stateProperties: widget.stateProperties, + xPos: chartPointInfo![index].xPosition!, yPos: (_isRangeSeries - ? _chartPointInfo![index].highYPosition + ? chartPointInfo![index].highYPosition : _isBoxSeries - ? _chartPointInfo![index].maxYPosition - : _chartPointInfo![index].yPosition)!, + ? chartPointInfo![index].maxYPosition + : chartPointInfo![index].yPosition)!, trackballBehavior: widget.trackballBehavior, - chartPointInfo: _chartPointInfo!, + chartPointInfo: chartPointInfo!, index: index); trackballWidgets.add(trackballWidget); @@ -113,8 +143,8 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { return Stack(children: [ Stack(children: trackballWidgets), CustomPaint( - painter: _TracklinePainter(widget.trackballBehavior, - widget.chartState, _chartPointInfo, _markerShapes)) + painter: TracklinePainter(widget.trackballBehavior, + widget.stateProperties, chartPointInfo, markerShapes)) ]); } else { trackballWidget = Container(); @@ -131,7 +161,7 @@ class _TrackballTemplateState extends State<_TrackballTemplate> { /// To hide tooltip templates void hideTrackballTemplate() { - if (mounted && !_alwaysShow) { + if (mounted && alwaysShow != null && !alwaysShow!) { setState(() { _isRender = false; }); @@ -145,7 +175,7 @@ class _TrackballRenderObject extends SingleChildRenderObjectWidget { {Key? key, required Widget child, required this.template, - required this.chartState, + required this.stateProperties, required this.xPos, required this.yPos, required this.trackballBehavior, @@ -155,21 +185,21 @@ class _TrackballRenderObject extends SingleChildRenderObjectWidget { final Widget template; final int? index; - final SfCartesianChartState chartState; - final List<_ChartPointInfo>? chartPointInfo; + final CartesianStateProperties stateProperties; + final List? chartPointInfo; final double xPos; final double yPos; final TrackballBehavior trackballBehavior; @override RenderObject createRenderObject(BuildContext context) { - return _TrackballTemplateRenderBox( - template, chartState, xPos, yPos, chartPointInfo, index); + return TrackballTemplateRenderBox( + template, stateProperties, xPos, yPos, chartPointInfo, index); } @override void updateRenderObject( - BuildContext context, covariant _TrackballTemplateRenderBox renderBox) { + BuildContext context, covariant TrackballTemplateRenderBox renderBox) { renderBox.template = template; renderBox.index = index; renderBox.xPos = xPos; @@ -179,29 +209,73 @@ class _TrackballRenderObject extends SingleChildRenderObjectWidget { } /// Render the annotation widget in the respective position. -class _TrackballTemplateRenderBox extends RenderShiftedBox { - _TrackballTemplateRenderBox( - this._template, this._chartState, this.xPos, this.yPos, +class TrackballTemplateRenderBox extends RenderShiftedBox { + /// Creates an instance of trackball template render box + TrackballTemplateRenderBox( + this._template, this.stateProperties, this.xPos, this.yPos, [this.chartPointInfo, this.index, RenderBox? child]) : super(child); Widget _template; - final SfCartesianChartState _chartState; + + /// Holds the value of cartesian state properties + final CartesianStateProperties stateProperties; + + /// Holds the value of x and y position double xPos, yPos; - List<_ChartPointInfo>? chartPointInfo; + + /// 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 late TrackballBehavior trackballBehavior; - bool isGroupAllPoints = false, isNearestPoint = false; + + /// 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; - bool isRight = false, isBottom = false; + + /// 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; - _TooltipPositions? tooltipPosition; + + /// Holds the tooltip position + TooltipPositions? tooltipPosition; + + /// Holds the value of box parent data late BoxParentData childParentData; + + /// Gets the trackball rendering details + TrackballRenderingDetails get trackballRenderingDetails => + TrackballHelper.getRenderingDetails( + stateProperties.trackballBehaviorRenderer); + + /// Sets the template value set template(Widget value) { if (_template != value) { _template = value; @@ -211,24 +285,22 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { @override void performLayout() { - trackballBehavior = _chartState._chart.trackballBehavior; + trackballBehavior = stateProperties.chart.trackballBehavior; isGroupAllPoints = trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.groupAllPoints; isNearestPoint = trackballBehavior.tooltipDisplayMode == TrackballDisplayMode.nearestPoint; - final TrackballBehaviorRenderer trackballBehaviorRenderer = - _chartState._trackballBehaviorRenderer; final List? tooltipTop = []; final List tooltipBottom = []; - final List<_ClosestPoints> visiblePoints = - trackballBehaviorRenderer._visiblePoints; + final List visiblePoints = + trackballRenderingDetails.visiblePoints; final List xAxesInfo = - trackballBehaviorRenderer._xAxesInfo; + trackballRenderingDetails.xAxesInfo; final List yAxesInfo = - trackballBehaviorRenderer._yAxesInfo; - boundaryRect = _chartState._chartAxis._axisClipRect; + trackballRenderingDetails.yAxesInfo; + boundaryRect = stateProperties.chartAxis.axisClipRect; final num totalWidth = boundaryRect.left + boundaryRect.width; - tooltipPosition = trackballBehaviorRenderer._tooltipPosition; + tooltipPosition = trackballRenderingDetails.tooltipPosition; final bool isTrackballMarkerEnabled = trackballBehavior.markerSettings != null; final BoxConstraints constraints = this.constraints; @@ -259,30 +331,34 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { } if (chartPointInfo != null) { for (int index = 0; index < chartPointInfo!.length; index++) { - tooltipTop!.add(_chartState._requireInvertedAxis + tooltipTop!.add(stateProperties.requireInvertedAxis ? visiblePoints[index].closestPointX - (size.width / 2) : visiblePoints[index].closestPointY - size.height / 2); - tooltipBottom.add(_chartState._requireInvertedAxis + tooltipBottom.add(stateProperties.requireInvertedAxis ? visiblePoints[index].closestPointX + (size.width / 2) : visiblePoints[index].closestPointY + size.height / 2); - xAxesInfo - .add(chartPointInfo![index].seriesRenderer!._xAxisRenderer!); - yAxesInfo - .add(chartPointInfo![index].seriesRenderer!._yAxisRenderer!); + xAxesInfo.add(chartPointInfo![index] + .seriesRendererDetails! + .xAxisDetails! + .axisRenderer); + yAxesInfo.add(chartPointInfo![index] + .seriesRendererDetails! + .yAxisDetails! + .axisRenderer); } } if (tooltipTop != null && tooltipTop.isNotEmpty) { - tooltipPosition = trackballBehaviorRenderer._smartTooltipPositions( + tooltipPosition = trackballRenderingDetails.smartTooltipPositions( tooltipTop, tooltipBottom, xAxesInfo, yAxesInfo, chartPointInfo!, - _chartState._requireInvertedAxis); + stateProperties.requireInvertedAxis); } if (!isGroupAllPoints) { - left = (_chartState._requireInvertedAxis + left = (stateProperties.requireInvertedAxis ? tooltipPosition!.tooltipTop[index!] : xPos + padding + @@ -290,7 +366,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { ? trackballBehavior.markerSettings!.width / 2 : 0)) .toDouble(); - top = (_chartState._requireInvertedAxis + top = (stateProperties.requireInvertedAxis ? yPos + pointerLength + (isTrackballMarkerEnabled @@ -300,14 +376,14 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { .toDouble(); if (isNearestPoint) { - left = _chartState._requireInvertedAxis + left = stateProperties.requireInvertedAxis ? xPos + size.width / 2 : xPos + padding + (isTrackballMarkerEnabled ? trackballBehavior.markerSettings!.width / 2 : 0); - top = _chartState._requireInvertedAxis + top = stateProperties.requireInvertedAxis ? yPos + padding + (isTrackballMarkerEnabled @@ -316,7 +392,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { : yPos - size.height / 2; } - if (!_chartState._requireInvertedAxis) { + if (!stateProperties.requireInvertedAxis) { if (left + size.width > totalWidth) { isRight = true; left = xPos - @@ -411,7 +487,7 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { @override void paint(PaintingContext context, Offset offset) { super.paint(context, offset); - final _RenderingDetails renderingDetails = _chartState._renderingDetails; + final RenderingDetails renderingDetails = stateProperties.renderingDetails; if (!isGroupAllPoints) { final Rect templateRect = Rect.fromLTWH( offset.dx + trackballTemplateRect!.left, @@ -421,20 +497,20 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { final Paint fillPaint = Paint() ..color = trackballBehavior.tooltipSettings.color ?? - (chartPointInfo![index!].seriesRenderer!._series.color ?? + (chartPointInfo![index!].seriesRendererDetails!.series.color ?? renderingDetails.chartTheme.crosshairBackgroundColor) ..isAntiAlias = false ..style = PaintingStyle.fill; final Paint strokePaint = Paint() ..color = trackballBehavior.tooltipSettings.borderColor ?? - (chartPointInfo![index!].seriesRenderer!._series.color ?? + (chartPointInfo![index!].seriesRendererDetails!.series.color ?? renderingDetails.chartTheme.crosshairBackgroundColor) ..strokeWidth = trackballBehavior.tooltipSettings.borderWidth ..strokeCap = StrokeCap.butt ..isAntiAlias = false ..style = PaintingStyle.stroke; final Path path = Path(); - if (!_chartState._requireInvertedAxis) { + if (!stateProperties.requireInvertedAxis) { if (!isRight) { path.moveTo(templateRect.left, templateRect.top + templateRect.height / 2 - pointerWidth); @@ -477,63 +553,42 @@ class _TrackballTemplateRenderBox extends RenderShiftedBox { } } -class _TracklinePainter extends CustomPainter { - _TracklinePainter(this.trackballBehavior, this.chartState, - this.chartPointInfo, this.markerShapes); +/// Class to store about the details of the closest points +class ClosestPoints { + /// Creates the parameterized constructor for class ClosestPoints + const ClosestPoints( + {required this.closestPointX, required this.closestPointY}); - TrackballBehavior trackballBehavior; - SfCartesianChartState chartState; - List<_ChartPointInfo>? chartPointInfo; - List? markerShapes; - bool isTrackLineDrawn = false; + /// Holds the closest x point value + final double closestPointX; - @override - void paint(Canvas canvas, Size size) { - final Path dashArrayPath = Path(); - final Paint trackballLinePaint = Paint(); - trackballLinePaint.color = trackballBehavior.lineColor ?? - chartState._renderingDetails.chartTheme.crosshairLineColor; - trackballLinePaint.strokeWidth = trackballBehavior.lineWidth; - trackballLinePaint.style = PaintingStyle.stroke; - trackballBehavior.lineWidth == 0 - ? trackballLinePaint.color = Colors.transparent - : trackballLinePaint.color = trackballLinePaint.color; - final Rect boundaryRect = chartState._chartAxis._axisClipRect; - - if (chartPointInfo != null && chartPointInfo!.isNotEmpty) { - for (int index = 0; index < chartPointInfo!.length; index++) { - if (index == 0) { - if (chartPointInfo![index].seriesRenderer!._seriesType.contains('bar') - ? chartState._requireInvertedAxis - : chartState._requireInvertedAxis) { - 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); - } - trackballBehavior.lineDashArray != null - ? _drawDashedLine(canvas, trackballBehavior.lineDashArray!, - trackballLinePaint, dashArrayPath) - : canvas.drawPath(dashArrayPath, trackballLinePaint); - } - if (markerShapes != null && - markerShapes!.isNotEmpty && - markerShapes!.length > index) { - chartState._trackballBehaviorRenderer._renderTrackballMarker( - chartPointInfo![index].seriesRenderer!, - canvas, - trackballBehavior, - index); - } - } - } - } + /// Holds the closest y point value + final double closestPointY; +} - @override - bool shouldRepaint(_TracklinePainter oldDelegate) => true; +/// 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 CartesianSeriesRenderer? seriesRenderer; + + /// Specifies whether to render the trackball element + bool needRender = true; } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart index 2226090f9..e37920c41 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_painter.dart @@ -1,24 +1,50 @@ -part of charts; +import 'dart:ui'; -///Below class is for drawing zoomRct -class _ZoomRectPainter extends CustomPainter { - _ZoomRectPainter( +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/user_interaction/zooming_panning.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../common/rendering_details.dart'; +import '../../common/utils/helper.dart'; +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/interactive_tooltip.dart'; +import '../utils/helper.dart'; + +///Class for drawing zooming rectangle +class ZoomRectPainter extends CustomPainter { + /// Creates an instance for zoom rect painter + ZoomRectPainter( {this.isRepaint = true, - required this.chartState, + required this.stateProperties, ValueNotifier? notifier}) - : chart = chartState._chart, + : chart = stateProperties.chart, super(repaint: notifier); + + /// Specifies whether to repaint the zoom rect final bool isRepaint; + + /// Holds the value of chart final SfCartesianChart chart; - SfCartesianChartState chartState; + + /// Specifies the cartesian state properties + CartesianStateProperties stateProperties; + + /// Specifies the value of stroke paint and fill paint late Paint strokePaint, fillPaint; - _RenderingDetails get renderingDetails => chartState._renderingDetails; + + /// Gets the value of rendering details + RenderingDetails get renderingDetails => stateProperties.renderingDetails; @override void paint(Canvas canvas, Size size) => - chartState._zoomPanBehaviorRenderer.onPaint(canvas); + stateProperties.zoomPanBehaviorRenderer.onPaint(canvas); - /// To draw Rect + /// To draw zooming rectangle void drawRect(Canvas canvas) { final Color? fillColor = chart.zoomPanBehavior.selectionRectColor; strokePaint = Paint() @@ -35,18 +61,20 @@ class _ZoomRectPainter extends CustomPainter { : renderingDetails.chartTheme.selectionRectColor ..style = PaintingStyle.fill; strokePaint.isAntiAlias = false; - if (chartState._zoomPanBehaviorRenderer._rectPath != null) { + final ZoomingBehaviorDetails zoomingBehaviorDetails = + ZoomPanBehaviorHelper.getRenderingDetails( + stateProperties.zoomPanBehaviorRenderer); + if (zoomingBehaviorDetails.rectPath != null) { canvas.drawPath( !kIsWeb - ? _dashPath( - chartState._zoomPanBehaviorRenderer._rectPath!, - dashArray: _CircularIntervalList([5, 5]), + ? dashPath( + zoomingBehaviorDetails.rectPath!, + dashArray: CircularIntervalList([5, 5]), )! - : chartState._zoomPanBehaviorRenderer._rectPath!, + : zoomingBehaviorDetails.rectPath!, strokePaint); - canvas.drawRect( - chartState._zoomPanBehaviorRenderer._zoomingRect, fillPaint); - final Rect zoomRect = chartState._zoomPanBehaviorRenderer._zoomingRect; + canvas.drawRect(zoomingBehaviorDetails.zoomingRect, fillPaint); + final Rect zoomRect = zoomingBehaviorDetails.zoomingRect; /// To show the interactive tooltip on selection zooming if (zoomRect.width != 0) { @@ -60,50 +88,52 @@ class _ZoomRectPainter extends CustomPainter { /// To draw connector line void _drawConnectorLine(Canvas canvas, Offset start, Offset end) { - _drawAxisTooltip(chartState._chartAxis._bottomAxisRenderers, canvas, start, - end, 'bottom'); - _drawAxisTooltip( - chartState._chartAxis._topAxisRenderers, canvas, start, end, 'top'); - _drawAxisTooltip( - chartState._chartAxis._leftAxisRenderers, canvas, start, end, 'left'); + _drawAxisTooltip(stateProperties.chartAxis.bottomAxisRenderers, canvas, + start, end, 'bottom'); _drawAxisTooltip( - chartState._chartAxis._rightAxisRenderers, canvas, start, end, 'right'); + stateProperties.chartAxis.topAxisRenderers, canvas, start, end, 'top'); + _drawAxisTooltip(stateProperties.chartAxis.leftAxisRenderers, canvas, start, + end, 'left'); + _drawAxisTooltip(stateProperties.chartAxis.rightAxisRenderers, canvas, + start, end, 'right'); } /// Draw axis tootip connector line void _drawAxisTooltip(List axisRenderers, Canvas canvas, Offset startPosition, Offset endPosition, String axisPosition) { for (int index = 0; index < axisRenderers.length; index++) { - final ChartAxisRenderer axisRenderer = axisRenderers[index]; - if (axisRenderer._axis.interactiveTooltip.enable && - axisRenderer._visibleLabels.isNotEmpty) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderers[index]); + if (axisDetails.axis.interactiveTooltip.enable && + axisDetails.visibleLabels.isNotEmpty) { _drawTooltipConnector( - axisRenderer, startPosition, endPosition, canvas, axisPosition); + axisDetails, startPosition, endPosition, canvas, axisPosition); } } } - /// It returns the tooltip label on zooming - String _getValue( - Offset position, ChartAxisRenderer axisRenderer, String axisPosition) { - final ChartAxis axis = axisRenderer._axis; + /// Returns the tooltip label on zooming + String _getValue(Offset position, + ChartAxisRendererDetails axisRendererDetails, String axisPosition) { + final ChartAxis axis = axisRendererDetails.axis; final bool isHorizontal = axisPosition == 'bottom' || axisPosition == 'top'; - final Rect axisClipRect = chartState._chartAxis._axisClipRect; + final Rect axisClipRect = stateProperties.chartAxis.axisClipRect; final num value = isHorizontal - ? _pointToXVal( + ? pointToXVal( chart, - axisRenderer, - axisRenderer._bounds, + axisRendererDetails.axisRenderer, + axisRendererDetails.bounds, position.dx - (axisClipRect.left + axis.plotOffset), position.dy - (axisClipRect.top + axis.plotOffset)) - : _pointToYVal( + : pointToYVal( chart, - axisRenderer, - axisRenderer._bounds, + axisRendererDetails.axisRenderer, + axisRendererDetails.bounds, position.dx - (axisClipRect.left + axis.plotOffset), position.dy - (axisClipRect.top + axis.plotOffset)); - dynamic result = _getInteractiveTooltipLabel(value, axisRenderer); + dynamic result = + getInteractiveTooltipLabel(value, axisRendererDetails.axisRenderer); if (axis.interactiveTooltip.format != null) { final String stringValue = axis.interactiveTooltip.format!.replaceAll('{value}', result); @@ -125,29 +155,29 @@ class _ZoomRectPainter extends CustomPainter { smallRect.bottom); /// Calculate the rect, based on the zoomed axis position - Rect _calculateRect(ChartAxisRenderer axisRenderer, Offset position, - Size labelSize, String axisPosition) { + Rect _calculateRect(ChartAxisRendererDetails axisRendererDetails, + Offset position, Size labelSize, String axisPosition) { Rect rect; const double paddingForRect = 10; final double arrowLength = - axisRenderer._axis.interactiveTooltip.arrowLength; + axisRendererDetails.axis.interactiveTooltip.arrowLength; if (axisPosition == 'bottom') { rect = Rect.fromLTWH( position.dx - (labelSize.width / 2 + paddingForRect / 2), - axisRenderer._bounds.top + arrowLength, + axisRendererDetails.bounds.top + arrowLength, labelSize.width + paddingForRect, labelSize.height + paddingForRect); } else if (axisPosition == 'top') { rect = Rect.fromLTWH( position.dx - (labelSize.width / 2 + paddingForRect / 2), - axisRenderer._bounds.top - + axisRendererDetails.bounds.top - (labelSize.height + paddingForRect) - arrowLength, labelSize.width + paddingForRect, labelSize.height + paddingForRect); } else if (axisPosition == 'left') { rect = Rect.fromLTWH( - axisRenderer._bounds.left - + axisRendererDetails.bounds.left - (labelSize.width + paddingForRect) - arrowLength, position.dy - (labelSize.height + paddingForRect) / 2, @@ -155,7 +185,7 @@ class _ZoomRectPainter extends CustomPainter { labelSize.height + paddingForRect); } else { rect = Rect.fromLTWH( - axisRenderer._bounds.left + arrowLength, + axisRendererDetails.bounds.left + arrowLength, position.dy - (labelSize.height / 2 + paddingForRect / 2), labelSize.width + paddingForRect, labelSize.height + paddingForRect); @@ -165,7 +195,7 @@ class _ZoomRectPainter extends CustomPainter { /// To draw tooltip connector void _drawTooltipConnector( - ChartAxisRenderer axisRenderer, + ChartAxisRendererDetails axisRendererDetails, Offset startPosition, Offset endPosition, Canvas canvas, @@ -174,7 +204,7 @@ class _ZoomRectPainter extends CustomPainter { String startValue, endValue; Size startLabelSize, endLabelSize; Rect startLabelRect, endLabelRect; - final ChartAxis axis = axisRenderer._axis; + final ChartAxis axis = axisRendererDetails.axis; final Paint labelFillPaint = Paint() ..color = renderingDetails.chartTheme.crosshairBackgroundColor ..strokeCap = StrokeCap.butt @@ -200,14 +230,14 @@ class _ZoomRectPainter extends CustomPainter { final Path startLabelPath = Path(); final Path endLabelPath = Path(); final bool isHorizontal = axisPosition == 'bottom' || axisPosition == 'top'; - startValue = _getValue(startPosition, axisRenderer, axisPosition); - endValue = _getValue(endPosition, axisRenderer, axisPosition); + startValue = _getValue(startPosition, axisRendererDetails, axisPosition); + endValue = _getValue(endPosition, axisRendererDetails, axisPosition); startLabelSize = measureText(startValue, axis.interactiveTooltip.textStyle); endLabelSize = measureText(endValue, axis.interactiveTooltip.textStyle); startLabelRect = _calculateRect( - axisRenderer, startPosition, startLabelSize, axisPosition); - endLabelRect = - _calculateRect(axisRenderer, endPosition, endLabelSize, axisPosition); + axisRendererDetails, startPosition, startLabelSize, axisPosition); + endLabelRect = _calculateRect( + axisRendererDetails, endPosition, endLabelSize, axisPosition); if (!isHorizontal && startLabelRect.width != endLabelRect.width) { (startLabelRect.width > endLabelRect.width) ? endLabelRect = @@ -278,8 +308,8 @@ class _ZoomRectPainter extends CustomPainter { tooltip.connectorLineDashArray != null ? canvas.drawPath( !kIsWeb - ? _dashPath(connectorPath, - dashArray: _CircularIntervalList( + ? dashPath(connectorPath, + dashArray: CircularIntervalList( tooltip.connectorLineDashArray!))! : connectorPath, connectorLinePaint) @@ -307,16 +337,16 @@ class _ZoomRectPainter extends CustomPainter { final bool isHorizontal = axisPosition == 'bottom' || axisPosition == 'top'; labelRect = - _validateRectBounds(labelRect, renderingDetails.chartContainerRect); + validateRectBounds(labelRect, renderingDetails.chartContainerRect); labelRect = isHorizontal - ? _validateRectXPosition(labelRect, chartState) - : _validateRectYPosition(labelRect, chartState); + ? validateRectXPosition(labelRect, stateProperties) + : validateRectYPosition(labelRect, stateProperties); path.reset(); - rect = _getRoundedCornerRect(labelRect, tooltip.borderRadius); + rect = getRoundedCornerRect(labelRect, tooltip.borderRadius); path.addRRect(rect); _calculateNeckPositions(canvas, fillPaint, strokePaint, path, axisPosition, position, rect, tooltip); - _drawText( + drawText( canvas, value, Offset((rect.left + rect.width / 2) - labelSize.width / 2, @@ -380,7 +410,7 @@ class _ZoomRectPainter extends CustomPainter { x4 = rect.left - tooltip.arrowLength; y4 = position.dy; } - _drawTooltipArrowhead( + drawTooltipArrowhead( canvas, path, fillPaint, strokePaint, x1, y1, x2, y2, x3, y3, x4, y4); } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart index 24efbe7b3..be5c5c83c 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/user_interaction/zooming_panning.dart @@ -1,15 +1,32 @@ -part of charts; +import 'dart:math' as math; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../axis/axis.dart'; +import '../base/chart_base.dart'; +import '../chart_behavior/zoom_behavior.dart'; +import '../common/cartesian_state_properties.dart'; +import '../common/common.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'zooming_painter.dart'; /// Customizes the zooming options. /// -/// Customize the various zooming actions such.toDouble()tapZooming, selectionZooming, zoomPinch. -/// In selection you can long press and drag to select a range on the chart to be zoomed in and also -/// zooming you can customize the selection rectangle using Borderwidth,color and RectColor. +/// Customize the various zooming actions such as tapZooming, selectionZooming, +/// zoomPinch. 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. /// -/// zoomPinching can be performed by moving two fingers over the chartDefault mode is [ZoomMode.xy]. -/// Zooming will be stopped after reaching [maximumZoomLevel]. +/// zoomPinching 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]. +///_Note:_ This is only applicable for `SfCartesianChart`. class ZoomPanBehavior { /// Creating an argument constructor of ZoomPanBehavior class. ZoomPanBehavior( @@ -19,7 +36,7 @@ class ZoomPanBehavior { this.enableSelectionZooming = false, this.enableMouseWheelZooming = false, this.zoomMode = ZoomMode.xy, - this.maximumZoomLevel, + this.maximumZoomLevel = 0.01, this.selectionRectBorderWidth = 1, this.selectionRectBorderColor, this.selectionRectColor}); @@ -44,7 +61,7 @@ class ZoomPanBehavior { ///Enables or disables the double tap zooming. /// ///Zooming will enable when you tap double time in plotarea. - ///After reaching the Maximum zoom level, zooming will be stopped. + ///After reaching the maximum zoom level, zooming will be stopped. /// ///Defaults to `false`. /// @@ -61,7 +78,6 @@ class ZoomPanBehavior { ///Enables or disables the panning. /// ///Panning can be performed on a zoomed axis. - ///you can able to panning the zoomed chart. /// ///Defaults to `false`. /// @@ -77,8 +93,7 @@ class ZoomPanBehavior { ///Enables or disables the selection zooming. /// - ///Selection zooming can be performed by dragging. - ///The drawn rectangular region will be zoomed on touch. + ///Selection zooming can be performed by long-press and then dragging. /// ///Defaults to `false`. /// @@ -94,8 +109,9 @@ class ZoomPanBehavior { ///Enables or disables the mouseWheelZooming. /// - ///Mouse wheel zooming for can be performed by rolling the mouse wheel up or down. - ///The place where the cursor is hovering gets zoomed io or out according to the mouse wheel rolling up or down. + ///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`. /// @@ -129,7 +145,7 @@ class ZoomPanBehavior { ///Maximum zoom level. /// - ///Zooming will be stopped after reached this value. + ///Zooming will be stopped after reached this value and ranges from 0 to 1. /// ///Defaults to `null`. /// @@ -137,11 +153,11 @@ class ZoomPanBehavior { ///Widget build(BuildContext context) { /// return Container( /// child: SfCartesianChart( - /// zoomPanBehavior: ZoomPanBehavior(enableSelectionZooming: true, maximumZoomLevel: 2), + /// zoomPanBehavior: ZoomPanBehavior(enableSelectionZooming: true, maximumZoomLevel: 0.8), /// )); ///} ///``` - final double? maximumZoomLevel; + final double maximumZoomLevel; ///Border width of the selection zooming rectangle. /// @@ -164,8 +180,6 @@ class ZoomPanBehavior { /// ///It used to change the stroke color of the selection rectangle. /// - ///Defaults to `Colors.grey`. - /// ///```dart ///Widget build(BuildContext context) { /// return Container( @@ -181,8 +195,6 @@ class ZoomPanBehavior { /// ///It used to change the background color of the selection rectangle. /// - ///Defaults to `Color.fromRGBO(89, 244, 66, 0.2)`. - /// ///```dart ///Widget build(BuildContext context) { /// return Container( @@ -235,90 +247,97 @@ class ZoomPanBehavior { return hashList(values); } - SfCartesianChartState? _chartState; + /// Holds the value of cartesian state properties + late CartesianStateProperties _stateProperties; /// Increases the magnification of the plot area. void zoomIn() { - _chartState!._canSetRangeController = true; - final SfCartesianChartState chartState = _chartState!; - final SfCartesianChart chart = chartState._chart; + _stateProperties.canSetRangeController = true; + final SfCartesianChart chart = _stateProperties.chart; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = - chartState._zoomPanBehaviorRenderer; - zoomPanBehaviorRenderer._isZoomIn = true; - zoomPanBehaviorRenderer._isZoomOut = false; - final double? zoomFactor = zoomPanBehaviorRenderer._zoomFactor; - chartState._zoomProgress = true; - ChartAxisRenderer axisRenderer; + _stateProperties.zoomPanBehaviorRenderer; + zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomIn = true; + zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomOut = false; + final double? zoomFactor = + zoomPanBehaviorRenderer._zoomingBehaviorDetails.zoomFactor; + _stateProperties.zoomProgress = true; + ChartAxisRendererDetails axisDetails; bool? needZoom; for (int index = 0; - index < chartState._chartAxis._axisRenderersCollection.length; + index < _stateProperties.chartAxis.axisRenderersCollection.length; index++) { - axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; - if (axisRenderer._zoomFactor <= 1.0 && axisRenderer._zoomFactor > 0.0) { - if (axisRenderer._zoomFactor - 0.1 < 0) { + axisDetails = AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[index]); + if (axisDetails.zoomFactor <= 1.0 && axisDetails.zoomFactor > 0.0) { + if (axisDetails.zoomFactor - 0.1 < 0) { needZoom = false; break; } else { - zoomPanBehaviorRenderer._setZoomFactorAndZoomPosition( - chartState, axisRenderer, zoomFactor); + zoomPanBehaviorRenderer._zoomingBehaviorDetails + .setZoomFactorAndZoomPosition( + _stateProperties.chartState, axisDetails, zoomFactor); needZoom = true; } } if (chart.onZooming != null) { - _bindZoomEvent(chart, axisRenderer, chart.onZooming!); + bindZoomEvent(chart, axisDetails, chart.onZooming!); } } if (needZoom == true) { - zoomPanBehaviorRenderer._createZoomState(); + zoomPanBehaviorRenderer._zoomingBehaviorDetails.createZoomState(); } } /// Decreases the magnification of the plot area. void zoomOut() { - _chartState!._canSetRangeController = true; - final SfCartesianChartState chartState = _chartState!; - final SfCartesianChart chart = chartState._chart; + _stateProperties.canSetRangeController = true; + final SfCartesianChart chart = _stateProperties.chart; final ZoomPanBehaviorRenderer zoomPanBehaviorRenderer = - chartState._zoomPanBehaviorRenderer; - zoomPanBehaviorRenderer._isZoomOut = true; - zoomPanBehaviorRenderer._isZoomIn = false; - final double? zoomFactor = zoomPanBehaviorRenderer._zoomFactor; - ChartAxisRenderer axisRenderer; + _stateProperties.zoomPanBehaviorRenderer; + zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomOut = true; + zoomPanBehaviorRenderer._zoomingBehaviorDetails.isZoomIn = false; + final double? zoomFactor = + zoomPanBehaviorRenderer._zoomingBehaviorDetails.zoomFactor; + ChartAxisRendererDetails axisDetails; for (int index = 0; - index < chartState._chartAxis._axisRenderersCollection.length; + index < _stateProperties.chartAxis.axisRenderersCollection.length; index++) { - axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; - if (axisRenderer._zoomFactor < 1.0 && axisRenderer._zoomFactor > 0.0) { - zoomPanBehaviorRenderer._setZoomFactorAndZoomPosition( - chartState, axisRenderer, zoomFactor); - axisRenderer._zoomFactor = axisRenderer._zoomFactor > 1.0 + axisDetails = AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[index]); + if (axisDetails.zoomFactor < 1.0 && axisDetails.zoomFactor > 0.0) { + zoomPanBehaviorRenderer._zoomingBehaviorDetails + .setZoomFactorAndZoomPosition( + _stateProperties.chartState, axisDetails, zoomFactor); + axisDetails.zoomFactor = axisDetails.zoomFactor > 1.0 ? 1.0 - : (axisRenderer._zoomFactor < 0.0 ? 0.0 : axisRenderer._zoomFactor); + : (axisDetails.zoomFactor < 0.0 ? 0.0 : axisDetails.zoomFactor); } if (chart.onZooming != null) { - _bindZoomEvent(chart, axisRenderer, chart.onZooming!); + bindZoomEvent(chart, axisDetails, chart.onZooming!); } } - zoomPanBehaviorRenderer._createZoomState(); + zoomPanBehaviorRenderer._zoomingBehaviorDetails.createZoomState(); } /// 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. + /// 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) { - _chartState!._canSetRangeController = true; - final SfCartesianChartState chartState = _chartState!; - final SfCartesianChart chart = chartState._chart; - ChartAxisRenderer axisRenderer; + _stateProperties.canSetRangeController = true; + final SfCartesianChart chart = _stateProperties.chart; + ChartAxisRendererDetails axisDetails; for (int index = 0; - index < chartState._chartAxis._axisRenderersCollection.length; + index < _stateProperties.chartAxis.axisRenderersCollection.length; index++) { - axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; - axisRenderer._zoomFactor = zoomFactor; + axisDetails = AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[index]); + axisDetails.zoomFactor = zoomFactor; if (chart.onZooming != null) { - _bindZoomEvent(chart, axisRenderer, chart.onZooming!); + bindZoomEvent(chart, axisDetails, chart.onZooming!); } - chartState._zoomPanBehaviorRenderer._createZoomState(); + _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails + .createZoomState(); } } @@ -327,9 +346,9 @@ class ZoomPanBehavior { /// 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) { - _chartState!._canSetRangeController = true; - final SfCartesianChartState chartState = _chartState!; - chartState._zoomPanBehaviorRenderer._doSelectionZooming(rect); + _stateProperties.canSetRangeController = true; + _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails + .doSelectionZooming(rect); } /// Change the zoom level of an appropriate axis. @@ -337,142 +356,221 @@ class ZoomPanBehavior { /// 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) { - _chartState!._canSetRangeController = true; - final SfCartesianChartState chartState = _chartState!; - final ChartAxisRenderer? axisRenderer = _findExistingAxisRenderer( - axis, chartState._chartAxis._axisRenderersCollection); + _stateProperties.canSetRangeController = true; + final ChartAxisRenderer? axisRenderer = findExistingAxisRenderer( + axis, _stateProperties.chartAxis.axisRenderersCollection); + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer!); if (axisRenderer != null) { - axisRenderer._zoomFactor = zoomFactor; - axisRenderer._zoomPosition = zoomPosition; - chartState._zoomPanBehaviorRenderer._createZoomState(); + axisDetails.zoomFactor = zoomFactor; + axisDetails.zoomPosition = zoomPosition; + _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails + .createZoomState(); } } /// 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. + /// To perform this action, the plot area needs to be in zoomed state. void panToDirection(String direction) { - _chartState!._canSetRangeController = true; - final SfCartesianChartState chartState = _chartState!; - final SfCartesianChart chart = chartState._chart; - ChartAxisRenderer axisRenderer; + _stateProperties.canSetRangeController = true; + final SfCartesianChart chart = _stateProperties.chart; + ChartAxisRendererDetails axisDetails; direction = direction.toLowerCase(); for (int axisIndex = 0; - axisIndex < chartState._chartAxis._axisRenderersCollection.length; + axisIndex < _stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - axisRenderer = chartState._chartAxis._axisRenderersCollection[axisIndex]; - if (axisRenderer._orientation == AxisOrientation.horizontal) { + axisDetails = AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[axisIndex]); + if (axisDetails.orientation == AxisOrientation.horizontal) { if (direction == 'left') { - axisRenderer._zoomPosition = (axisRenderer._zoomPosition > 0 && - axisRenderer._zoomPosition <= 0.9) - ? axisRenderer._zoomPosition - 0.1 - : axisRenderer._zoomPosition; - axisRenderer._zoomPosition = axisRenderer._zoomPosition < 0.0 - ? 0.0 - : axisRenderer._zoomPosition; + axisDetails.zoomPosition = + (axisDetails.zoomPosition > 0 && axisDetails.zoomPosition <= 0.9) + ? axisDetails.zoomPosition - 0.1 + : axisDetails.zoomPosition; + axisDetails.zoomPosition = + axisDetails.zoomPosition < 0.0 ? 0.0 : axisDetails.zoomPosition; } if (direction == 'right') { - axisRenderer._zoomPosition = (axisRenderer._zoomPosition >= 0 && - axisRenderer._zoomPosition < 1) - ? axisRenderer._zoomPosition + 0.1 - : axisRenderer._zoomPosition; - axisRenderer._zoomPosition = axisRenderer._zoomPosition > 1.0 - ? 1.0 - : axisRenderer._zoomPosition; + axisDetails.zoomPosition = + (axisDetails.zoomPosition >= 0 && axisDetails.zoomPosition < 1) + ? axisDetails.zoomPosition + 0.1 + : axisDetails.zoomPosition; + axisDetails.zoomPosition = + axisDetails.zoomPosition > 1.0 ? 1.0 : axisDetails.zoomPosition; } } else { if (direction == 'bottom') { - axisRenderer._zoomPosition = (axisRenderer._zoomPosition > 0 && - axisRenderer._zoomPosition <= 0.9) - ? axisRenderer._zoomPosition - 0.1 - : axisRenderer._zoomPosition; - axisRenderer._zoomPosition = axisRenderer._zoomPosition < 0.0 - ? 0.0 - : axisRenderer._zoomPosition; + axisDetails.zoomPosition = + (axisDetails.zoomPosition > 0 && axisDetails.zoomPosition <= 0.9) + ? axisDetails.zoomPosition - 0.1 + : axisDetails.zoomPosition; + axisDetails.zoomPosition = + axisDetails.zoomPosition < 0.0 ? 0.0 : axisDetails.zoomPosition; } if (direction == 'top') { - axisRenderer._zoomPosition = (axisRenderer._zoomPosition >= 0 && - axisRenderer._zoomPosition < 1) - ? axisRenderer._zoomPosition + 0.1 - : axisRenderer._zoomPosition; - axisRenderer._zoomPosition = axisRenderer._zoomPosition > 1.0 - ? 1.0 - : axisRenderer._zoomPosition; + axisDetails.zoomPosition = + (axisDetails.zoomPosition >= 0 && axisDetails.zoomPosition < 1) + ? axisDetails.zoomPosition + 0.1 + : axisDetails.zoomPosition; + axisDetails.zoomPosition = + axisDetails.zoomPosition > 1.0 ? 1.0 : axisDetails.zoomPosition; } } if (chart.onZooming != null) { - _bindZoomEvent(chart, axisRenderer, chart.onZooming!); + bindZoomEvent(chart, axisDetails, chart.onZooming!); } } - chartState._zoomPanBehaviorRenderer._createZoomState(); + _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails + .createZoomState(); } /// Returns the plot area back to its original position after zooming. void reset() { - _chartState!._canSetRangeController = true; - final SfCartesianChartState chartState = _chartState!; - final SfCartesianChart chart = chartState._chart; - ChartAxisRenderer axisRenderer; + _stateProperties.canSetRangeController = true; + final SfCartesianChart chart = _stateProperties.chart; + ChartAxisRendererDetails axisDetails; for (int index = 0; - index < chartState._chartAxis._axisRenderersCollection.length; + index < _stateProperties.chartAxis.axisRenderersCollection.length; index++) { - axisRenderer = chartState._chartAxis._axisRenderersCollection[index]; - axisRenderer._zoomFactor = 1.0; - axisRenderer._zoomPosition = 0.0; + axisDetails = AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[index]); + axisDetails.zoomFactor = 1.0; + axisDetails.zoomPosition = 0.0; if (chart.onZoomReset != null) { - _bindZoomEvent(chart, axisRenderer, chart.onZoomReset!); + bindZoomEvent(chart, axisDetails, chart.onZoomReset!); } } - chartState._zoomPanBehaviorRenderer._createZoomState(); + _stateProperties.zoomPanBehaviorRenderer._zoomingBehaviorDetails + .createZoomState(); } } /// Creates a renderer class for zoomPanBehavior class class ZoomPanBehaviorRenderer with ZoomBehavior { /// Creates an argument constructor for ZoomPanBehavior renderer class - ZoomPanBehaviorRenderer(this._chartState); + ZoomPanBehaviorRenderer(this._stateProperties) { + _zoomingBehaviorDetails = ZoomingBehaviorDetails(_stateProperties); + } + + /// Holds the value of zoom behavior details + late ZoomingBehaviorDetails _zoomingBehaviorDetails; + final CartesianStateProperties _stateProperties; + + /// Performs panning action. + @override + void onPan(double xPos, double yPos) => + _zoomingBehaviorDetails.doPan(xPos, yPos); + + /// Performs the double-tap action. + @override + void onDoubleTap(double xPos, double yPos, double? zoomFactor) => + _zoomingBehaviorDetails.doubleTapZooming(xPos, yPos, zoomFactor); + + /// Draws selection zoomRect + @override + void onPaint(Canvas canvas) => + _zoomingBehaviorDetails.painter.drawRect(canvas); + + /// Performs selection zooming. + @override + void onDrawSelectionZoomRect( + double currentX, double currentY, double startX, double startY) => + _zoomingBehaviorDetails.drawSelectionZoomRect( + currentX, currentY, startX, startY); + + /// Performs pinch start action. + @override + void onPinchStart(ChartAxis axis, double firstX, double firstY, + double secondX, double secondY, double scaleFactor) {} + + /// Performs pinch end action. + @override + void onPinchEnd(ChartAxis axis, double firstX, double firstY, double secondX, + double secondY, double scaleFactor) {} + + /// Performs pinch zooming. + @override + void onPinch(ChartAxisRendererDetails axisDetails, double position, + double scaleFactor) { + axisDetails.zoomFactor = scaleFactor; + axisDetails.zoomPosition = position; + } +} + +/// 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; +} + +/// Represents the zooming behavior details class +class ZoomingBehaviorDetails { + /// Creates an instance for zooming behavior details + ZoomingBehaviorDetails(this.stateProperties); ZoomPanBehavior get _zoomPanBehavior => _chart.zoomPanBehavior; - SfCartesianChart get _chart => _chartState._chart; - final SfCartesianChartState _chartState; - late _ZoomRectPainter _painter; - Offset? _previousMovedPosition; - bool? _isPanning, _isPinching; - //ignore: prefer_final_fields - bool _canPerformSelection = false; - Rect _zoomingRect = const Rect.fromLTWH(0, 0, 0, 0); - bool _delayRedraw = false; - double? _zoomFactor, _zoomPosition; - late bool _isZoomIn, _isZoomOut; - Path? _rectPath; - - /// Below method for Double tap Zooming - void _doubleTapZooming(double xPos, double yPos, double? zoomFactor) { - _chartState._canSetRangeController = true; - _chartState._zoomProgress = true; - ChartAxisRenderer axisRenderer; + SfCartesianChart get _chart => stateProperties.chart; + + /// Creates an instance of cartesian state properties + final CartesianStateProperties stateProperties; + + /// Holds the value of zoom rect painter + late ZoomRectPainter painter; + + /// Holds the previously moved position + Offset? previousMovedPosition; + + /// Specifies whether the panning or pinching is done + bool? isPanning, isPinching; + + /// Specifies whether to do perform selection + bool canPerformSelection = false; + + /// Holds the value of zooming rect + Rect zoomingRect = const Rect.fromLTWH(0, 0, 0, 0); + + /// Specifies whether to draw with delay + bool delayRedraw = false; + + /// Specifies the value of zoom factor and zoom position + double? zoomFactor, _zoomPosition; + + /// Specifies whether the zoom in or zoom out is performed + late bool isZoomIn, isZoomOut; + + /// Specifies the value of rect path + Path? rectPath; + + /// Below method for double tap zooming + void doubleTapZooming(double xPos, double yPos, double? zoomFactor) { + stateProperties.canSetRangeController = true; + stateProperties.zoomProgress = true; + ChartAxisRendererDetails axisDetails; double cumulative, origin, maxZoomFactor; for (int axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[axisIndex]); if (_chart.onZoomStart != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZoomStart!); + bindZoomEvent(_chart, axisDetails, _chart.onZoomStart!); } - axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; - axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; - if ((axisRenderer._orientation == AxisOrientation.vertical && + axisDetails.previousZoomFactor = axisDetails.zoomFactor; + axisDetails.previousZoomPosition = axisDetails.zoomPosition; + if ((axisDetails.orientation == AxisOrientation.vertical && _zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisRenderer._orientation == AxisOrientation.horizontal && + (axisDetails.orientation == AxisOrientation.horizontal && _zoomPanBehavior.zoomMode != ZoomMode.y)) { cumulative = math.max( - math.max(1 / _minMax(axisRenderer._zoomFactor, 0, 1), 1) + (0.25), - 1); + math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1) + (0.25), 1); if (cumulative >= 1) { - origin = axisRenderer._orientation == AxisOrientation.horizontal - ? xPos / _chartState._chartAxis._axisClipRect.width - : 1 - (yPos / _chartState._chartAxis._axisClipRect.height); + origin = axisDetails.orientation == AxisOrientation.horizontal + ? xPos / stateProperties.chartAxis.axisClipRect.width + : 1 - (yPos / stateProperties.chartAxis.axisClipRect.height); origin = origin > 1 ? 1 : origin < 0 @@ -482,91 +580,92 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { cumulative == 1 ? 1 : _minMax(1 / cumulative, 0, 1).toDouble(); _zoomPosition = (cumulative == 1) ? 0 - : axisRenderer._zoomPosition + - ((axisRenderer._zoomFactor - zoomFactor) * origin); - if (axisRenderer._zoomPosition != _zoomPosition || - axisRenderer._zoomFactor != zoomFactor) { + : axisDetails.zoomPosition + + ((axisDetails.zoomFactor - zoomFactor) * origin); + if (axisDetails.zoomPosition != _zoomPosition || + axisDetails.zoomFactor != zoomFactor) { zoomFactor = (_zoomPosition! + zoomFactor) > 1 ? (1 - _zoomPosition!) : zoomFactor; } - axisRenderer._zoomPosition = _zoomPosition!; - axisRenderer._zoomFactor = zoomFactor; - axisRenderer._bounds = const Rect.fromLTWH(0, 0, 0, 0); - axisRenderer._visibleLabels = []; + axisDetails.zoomPosition = _zoomPosition!; + axisDetails.zoomFactor = zoomFactor; + axisDetails.bounds = const Rect.fromLTWH(0, 0, 0, 0); + axisDetails.visibleLabels = []; } - maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + maxZoomFactor = _zoomPanBehavior.maximumZoomLevel; if (zoomFactor! < maxZoomFactor) { - axisRenderer._zoomFactor = maxZoomFactor; - axisRenderer._zoomPosition = 0.0; + axisDetails.zoomFactor = maxZoomFactor; + axisDetails.zoomPosition = 0.0; zoomFactor = maxZoomFactor; } } if (_chart.onZoomEnd != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZoomEnd!); + bindZoomEvent(_chart, axisDetails, _chart.onZoomEnd!); } } - _createZoomState(); + createZoomState(); } /// Below method is for panning the zoomed chart - void _doPan(double xPos, double yPos) { - _chartState._canSetRangeController = true; + void doPan(double xPos, double yPos) { + stateProperties.canSetRangeController = true; num currentScale, value; - ChartAxisRenderer axisRenderer; + ChartAxisRendererDetails axisDetails; double currentZoomPosition; for (int axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; - axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; - axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; - if ((axisRenderer._orientation == AxisOrientation.vertical && + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[axisIndex]); + axisDetails.previousZoomFactor = axisDetails.zoomFactor; + axisDetails.previousZoomPosition = axisDetails.zoomPosition; + if ((axisDetails.orientation == AxisOrientation.vertical && _zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisRenderer._orientation == AxisOrientation.horizontal && + (axisDetails.orientation == AxisOrientation.horizontal && _zoomPanBehavior.zoomMode != ZoomMode.y)) { - currentZoomPosition = axisRenderer._zoomPosition; - currentScale = math.max(1 / _minMax(axisRenderer._zoomFactor, 0, 1), 1); - if (axisRenderer._orientation == AxisOrientation.horizontal) { - value = (_previousMovedPosition!.dx - xPos) / - _chartState._chartAxis._axisClipRect.width / + currentZoomPosition = axisDetails.zoomPosition; + currentScale = math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1); + if (axisDetails.orientation == AxisOrientation.horizontal) { + value = (previousMovedPosition!.dx - xPos) / + stateProperties.chartAxis.axisClipRect.width / currentScale; currentZoomPosition = _minMax( - axisRenderer._axis.isInversed - ? axisRenderer._zoomPosition - value - : axisRenderer._zoomPosition + value, + axisDetails.axis.isInversed + ? axisDetails.zoomPosition - value + : axisDetails.zoomPosition + value, 0, - 1 - axisRenderer._zoomFactor) + 1 - axisDetails.zoomFactor) .toDouble(); - axisRenderer._zoomPosition = currentZoomPosition; + axisDetails.zoomPosition = currentZoomPosition; } else { - value = (_previousMovedPosition!.dy - yPos) / - _chartState._chartAxis._axisClipRect.height / + value = (previousMovedPosition!.dy - yPos) / + stateProperties.chartAxis.axisClipRect.height / currentScale; currentZoomPosition = _minMax( - axisRenderer._axis.isInversed - ? axisRenderer._zoomPosition + value - : axisRenderer._zoomPosition - value, + axisDetails.axis.isInversed + ? axisDetails.zoomPosition + value + : axisDetails.zoomPosition - value, 0, - 1 - axisRenderer._zoomFactor) + 1 - axisDetails.zoomFactor) .toDouble(); - axisRenderer._zoomPosition = currentZoomPosition; + axisDetails.zoomPosition = currentZoomPosition; } } if (_chart.onZooming != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZooming!); + bindZoomEvent(_chart, axisDetails, _chart.onZooming!); } } - _createZoomState(); + createZoomState(); } - ///Below method for drawing selection rectangle - void _drawSelectionZoomRect( + ///Below method for drawing selection rectangle + void drawSelectionZoomRect( double currentX, double currentY, double startX, double startY) { - _chartState._canSetRangeController = true; - final Rect clipRect = _chartState._chartAxis._axisClipRect; + stateProperties.canSetRangeController = true; + final Rect clipRect = stateProperties.chartAxis.axisClipRect; final Offset startPosition = Offset( (startX < clipRect.left) ? clipRect.left : startX, (startY < clipRect.top) ? clipRect.top : startY); @@ -577,87 +676,85 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { (currentY > clipRect.bottom) ? clipRect.bottom : ((currentY < clipRect.top) ? clipRect.top : currentY)); - _rectPath = Path(); + rectPath = Path(); if (_zoomPanBehavior.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(); + 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 (_zoomPanBehavior.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(); + 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(); + 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(); - _chartState._repaintNotifiers['zoom']!.value++; + zoomingRect = rectPath!.getBounds(); + stateProperties.repaintNotifiers['zoom']!.value++; } /// Below method for zooming selected portion - void _doSelectionZooming(Rect zoomRect) { - _chartState._canSetRangeController = true; - final Rect rect = _chartState._chartAxis._axisClipRect; - ChartAxisRenderer axisRenderer; + void doSelectionZooming(Rect zoomRect) { + stateProperties.canSetRangeController = true; + final Rect rect = stateProperties.chartAxis.axisClipRect; + ChartAxisRendererDetails axisDetails; for (int axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[axisIndex]); if (_chart.onZoomStart != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZoomStart!); + bindZoomEvent(_chart, axisDetails, _chart.onZoomStart!); } - axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; - axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; - if (axisRenderer._orientation == AxisOrientation.horizontal) { + axisDetails.previousZoomFactor = axisDetails.zoomFactor; + axisDetails.previousZoomPosition = axisDetails.zoomPosition; + if (axisDetails.orientation == AxisOrientation.horizontal) { if (_zoomPanBehavior.zoomMode != ZoomMode.y) { - axisRenderer._zoomPosition += + axisDetails.zoomPosition += ((zoomRect.left - rect.left) / (rect.width)).abs() * - axisRenderer._zoomFactor; - axisRenderer._zoomFactor *= zoomRect.width / rect.width; - if (_zoomPanBehavior.maximumZoomLevel != null) { - axisRenderer._zoomFactor = - axisRenderer._zoomFactor >= _zoomPanBehavior.maximumZoomLevel! - ? axisRenderer._zoomFactor - : _zoomPanBehavior.maximumZoomLevel!; - } + axisDetails.zoomFactor; + axisDetails.zoomFactor *= zoomRect.width / rect.width; + axisDetails.zoomFactor = + axisDetails.zoomFactor >= _zoomPanBehavior.maximumZoomLevel + ? axisDetails.zoomFactor + : _zoomPanBehavior.maximumZoomLevel; } } else { if (_zoomPanBehavior.zoomMode != ZoomMode.x) { - axisRenderer._zoomPosition += (1 - + axisDetails.zoomPosition += (1 - ((zoomRect.height + (zoomRect.top - rect.top)) / (rect.height)) .abs()) * - axisRenderer._zoomFactor; - axisRenderer._zoomFactor *= zoomRect.height / rect.height; - if (_zoomPanBehavior.maximumZoomLevel != null) { - axisRenderer._zoomFactor = - axisRenderer._zoomFactor >= _zoomPanBehavior.maximumZoomLevel! - ? axisRenderer._zoomFactor - : _zoomPanBehavior.maximumZoomLevel!; - } + axisDetails.zoomFactor; + axisDetails.zoomFactor *= zoomRect.height / rect.height; + + axisDetails.zoomFactor = + axisDetails.zoomFactor >= _zoomPanBehavior.maximumZoomLevel + ? axisDetails.zoomFactor + : _zoomPanBehavior.maximumZoomLevel; } } if (_chart.onZoomEnd != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZoomEnd!); + bindZoomEvent(_chart, axisDetails, _chart.onZoomEnd!); } } zoomRect = const Rect.fromLTRB(0, 0, 0, 0); - _rectPath = Path(); - _createZoomState(); + rectPath = Path(); + createZoomState(); } /// Below method is for pinch zooming - void _performPinchZooming( + void performPinchZooming( List touchStartList, List touchMoveList) { - _chartState._canSetRangeController = true; + stateProperties.canSetRangeController = true; num touch0StartX, touch0StartY, touch1StartX, @@ -666,12 +763,12 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { touch0EndY, touch1EndX, touch1EndY; - if (!(_zoomingRect.width > 0 && _zoomingRect.height > 0)) { + if (!(zoomingRect.width > 0 && zoomingRect.height > 0)) { _calculateZoomAxesRange(_chart); - _delayRedraw = true; - final Rect clipRect = _chartState._chartAxis._axisClipRect; + delayRedraw = true; + final Rect clipRect = stateProperties.chartAxis.axisClipRect; final Rect containerRect = - _chartState._renderingDetails.chartContainerRect; + stateProperties.renderingDetails.chartContainerRect; touch0StartX = touchStartList[0].position.dx - containerRect.left; touch0StartY = touchStartList[0].position.dy - containerRect.top; touch0EndX = touchMoveList[0].position.dx - containerRect.left; @@ -693,43 +790,44 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { pinchRect = Rect.fromLTWH( clipX, clipY, clipRect.width / scaleX, clipRect.height / scaleY); _calculatePinchZoomFactor(_chart, pinchRect); - _chartState._zoomProgress = true; - _createZoomState(); + stateProperties.zoomProgress = true; + createZoomState(); } } /// To create zoomed states - void _createZoomState() { - _chartState._rangeChangeBySlider = false; - _chartState._isRedrawByZoomPan = true; - _chartState._zoomedAxisRendererStates = []; - _chartState._zoomedAxisRendererStates - .addAll(_chartState._chartAxis._axisRenderersCollection); - _chartState._renderingDetails.isLegendToggled = false; - _chartState._redraw(); + void createZoomState() { + stateProperties.rangeChangeBySlider = false; + stateProperties.isRedrawByZoomPan = true; + stateProperties.zoomedAxisRendererStates = []; + stateProperties.zoomedAxisRendererStates + .addAll(stateProperties.chartAxis.axisRenderersCollection); + stateProperties.renderingDetails.isLegendToggled = false; + stateProperties.redraw(); } /// Below method is for pinch zooming void _calculatePinchZoomFactor(SfCartesianChart chart, Rect pinchRect) { - _chartState._canSetRangeController = true; + stateProperties.canSetRangeController = true; final ZoomMode mode = _zoomPanBehavior.zoomMode; num selectionMin, selectionMax, rangeMin, rangeMax, value, axisTrans; double currentZoomPosition, currentZoomFactor; double currentFactor, currentPosition, maxZoomFactor, minZoomFactor = 1.0; - final Rect clipRect = _chartState._chartAxis._axisClipRect; - final List<_ZoomAxisRange> _zoomAxes = _chartState._zoomAxes; - ChartAxisRenderer axisRenderer; + final Rect clipRect = stateProperties.chartAxis.axisClipRect; + final List _zoomAxes = stateProperties.zoomAxes; + ChartAxisRendererDetails axisDetails; for (int axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; - axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; - axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; - if ((axisRenderer._orientation == AxisOrientation.horizontal && + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[axisIndex]); + axisDetails.previousZoomFactor = axisDetails.zoomFactor; + axisDetails.previousZoomPosition = axisDetails.zoomPosition; + if ((axisDetails.orientation == AxisOrientation.horizontal && mode != ZoomMode.y) || - (axisRenderer._orientation == AxisOrientation.vertical && + (axisDetails.orientation == AxisOrientation.vertical && mode != ZoomMode.x)) { - if (axisRenderer._orientation == AxisOrientation.horizontal) { + if (axisDetails.orientation == AxisOrientation.horizontal) { value = pinchRect.left - clipRect.left; axisTrans = clipRect.width / _zoomAxes[axisIndex].delta!; rangeMin = value / axisTrans + _zoomAxes[axisIndex].min!; @@ -752,27 +850,27 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { (selectionMax - selectionMin) / _zoomAxes[axisIndex].actualDelta!; currentZoomPosition = currentPosition < 0 ? 0 : currentPosition; currentZoomFactor = currentFactor > 1 ? 1 : currentFactor; - maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; - if (axisRenderer._axis.autoScrollingDelta != null && - axisRenderer._scrollingDelta != null) { + maxZoomFactor = _zoomPanBehavior.maximumZoomLevel; + if (axisDetails.axis.autoScrollingDelta != null && + axisDetails.scrollingDelta != null) { //to find zoom factor for corresponding auto scroll delta minZoomFactor = - axisRenderer._scrollingDelta! / _zoomAxes[axisIndex].actualDelta!; + axisDetails.scrollingDelta! / _zoomAxes[axisIndex].actualDelta!; } if (currentZoomFactor < maxZoomFactor) { - axisRenderer._zoomFactor = maxZoomFactor; - axisRenderer._zoomPosition = 0.0; + axisDetails.zoomFactor = maxZoomFactor; currentZoomFactor = maxZoomFactor; } if (currentZoomFactor > minZoomFactor) { //to restrict zoom factor to corresponding auto scroll delta - axisRenderer._zoomFactor = minZoomFactor; + axisDetails.zoomFactor = minZoomFactor; currentZoomFactor = minZoomFactor; } - onPinch(axisRenderer, currentZoomPosition, currentZoomFactor); + stateProperties.zoomPanBehaviorRenderer + .onPinch(axisDetails, currentZoomPosition, currentZoomFactor); } if (chart.onZooming != null) { - _bindZoomEvent(chart, axisRenderer, chart.onZooming!); + bindZoomEvent(chart, axisDetails, chart.onZooming!); } } } @@ -782,63 +880,66 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { /// Below method is for storing calculated zoom range void _calculateZoomAxesRange(SfCartesianChart chart) { - ChartAxisRenderer axisRenderer; - _ZoomAxisRange range; - _VisibleRange axisRange; + ChartAxisRendererDetails axisDetails; + ZoomAxisRange range; + VisibleRange axisRange; for (int index = 0; - index < _chartState._chartAxis._axisRenderersCollection.length; + index < stateProperties.chartAxis.axisRenderersCollection.length; index++) { - axisRenderer = _chartState._chartAxis._axisRenderersCollection[index]; - range = _ZoomAxisRange(); - axisRange = axisRenderer._visibleRange!; - if (_chartState._zoomAxes.isNotEmpty && - index <= _chartState._zoomAxes.length - 1) { - if (!_delayRedraw) { - _chartState._zoomAxes[index].min = axisRange.minimum.toDouble(); - _chartState._zoomAxes[index].delta = axisRange.delta.toDouble(); + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[index]); + range = ZoomAxisRange(); + axisRange = axisDetails.visibleRange!; + if (stateProperties.zoomAxes.isNotEmpty && + index <= stateProperties.zoomAxes.length - 1) { + if (!delayRedraw) { + stateProperties.zoomAxes[index].min = axisRange.minimum.toDouble(); + stateProperties.zoomAxes[index].delta = axisRange.delta.toDouble(); } } else { - // _chartState._zoomAxes ??= <_ZoomAxisRange>[]; - range.actualMin = axisRenderer._actualRange!.minimum.toDouble(); - range.actualDelta = axisRenderer._actualRange!.delta.toDouble(); + // _stateProperties.zoomAxes ??= <_ZoomAxisRange>[]; + range.actualMin = axisDetails.actualRange!.minimum.toDouble(); + range.actualDelta = axisDetails.actualRange!.delta.toDouble(); range.min = axisRange.minimum.toDouble(); range.delta = axisRange.delta.toDouble(); - _chartState._zoomAxes.add(range); + stateProperties.zoomAxes.add(range); } } } - /// Below method is for mouseWheel Zooming - void _performMouseWheelZooming( + /// Below method is for mouse wheel Zooming + void performMouseWheelZooming( PointerScrollEvent event, double mouseX, double mouseY) { + stateProperties.canSetRangeController = true; final double direction = (event.scrollDelta.dy / 120) > 0 ? -1 : 1; double origin = 0.5; double cumulative, zoomFactor, zoomPosition, maxZoomFactor; - _chartState._zoomProgress = true; + stateProperties.zoomProgress = true; _calculateZoomAxesRange(_chart); - _isPanning = _chart.zoomPanBehavior.enablePanning; - ChartAxisRenderer axisRenderer; + isPanning = _chart.zoomPanBehavior.enablePanning; + ChartAxisRendererDetails axisDetails; for (int axisIndex = 0; - axisIndex < _chartState._chartAxis._axisRenderersCollection.length; + axisIndex < stateProperties.chartAxis.axisRenderersCollection.length; axisIndex++) { - axisRenderer = _chartState._chartAxis._axisRenderersCollection[axisIndex]; + axisDetails = AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[axisIndex]); if (_chart.onZoomStart != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZoomStart!); + bindZoomEvent(_chart, axisDetails, _chart.onZoomStart!); } - axisRenderer._previousZoomFactor = axisRenderer._zoomFactor; - axisRenderer._previousZoomPosition = axisRenderer._zoomPosition; - if ((axisRenderer._orientation == AxisOrientation.vertical && + axisDetails.previousZoomFactor = axisDetails.zoomFactor; + axisDetails.previousZoomPosition = axisDetails.zoomPosition; + if ((axisDetails.orientation == AxisOrientation.vertical && _zoomPanBehavior.zoomMode != ZoomMode.x) || - (axisRenderer._orientation == AxisOrientation.horizontal && + (axisDetails.orientation == AxisOrientation.horizontal && _zoomPanBehavior.zoomMode != ZoomMode.y)) { cumulative = math.max( - math.max(1 / _minMax(axisRenderer._zoomFactor, 0, 1), 1) + + math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1) + (0.25 * direction), 1); if (cumulative >= 1) { - origin = axisRenderer._orientation == AxisOrientation.horizontal - ? mouseX / _chartState._chartAxis._axisClipRect.width - : 1 - (mouseY / _chartState._chartAxis._axisClipRect.height); + origin = axisDetails.orientation == AxisOrientation.horizontal + ? mouseX / stateProperties.chartAxis.axisClipRect.width + : 1 - (mouseY / stateProperties.chartAxis.axisClipRect.height); origin = origin > 1 ? 1 : origin < 0 @@ -848,53 +949,52 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { .toDouble(); zoomPosition = (cumulative == 1) ? 0 - : axisRenderer._zoomPosition + - ((axisRenderer._zoomFactor - zoomFactor) * origin); - if (axisRenderer._zoomPosition != zoomPosition || - axisRenderer._zoomFactor != zoomFactor) { + : axisDetails.zoomPosition + + ((axisDetails.zoomFactor - zoomFactor) * origin); + if (axisDetails.zoomPosition != zoomPosition || + axisDetails.zoomFactor != zoomFactor) { zoomFactor = (zoomPosition + zoomFactor) > 1 ? (1 - zoomPosition) : zoomFactor; } - axisRenderer._zoomPosition = zoomPosition; - axisRenderer._zoomFactor = zoomFactor; - axisRenderer._bounds = const Rect.fromLTWH(0, 0, 0, 0); - axisRenderer._visibleLabels = []; - maxZoomFactor = _zoomPanBehavior.maximumZoomLevel ?? 0.1; + axisDetails.zoomPosition = zoomPosition; + axisDetails.zoomFactor = zoomFactor; + axisDetails.bounds = const Rect.fromLTWH(0, 0, 0, 0); + axisDetails.visibleLabels = []; + maxZoomFactor = _zoomPanBehavior.maximumZoomLevel; if (zoomFactor < maxZoomFactor) { - axisRenderer._zoomFactor = maxZoomFactor; - axisRenderer._zoomPosition = 0.0; + axisDetails.zoomFactor = maxZoomFactor; zoomFactor = maxZoomFactor; } if (_chart.onZoomEnd != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZoomEnd!); + bindZoomEvent(_chart, axisDetails, _chart.onZoomEnd!); } - if (axisRenderer._zoomFactor.toInt() == 1 && - axisRenderer._zoomPosition.toInt() == 0 && + if (axisDetails.zoomFactor.toInt() == 1 && + axisDetails.zoomPosition.toInt() == 0 && _chart.onZoomReset != null) { - _bindZoomEvent(_chart, axisRenderer, _chart.onZoomReset!); + bindZoomEvent(_chart, axisDetails, _chart.onZoomReset!); } } } } - _createZoomState(); + createZoomState(); } /// Below method is for zoomIn and zoomOut public methods - void _setZoomFactorAndZoomPosition(SfCartesianChartState chartState, - ChartAxisRenderer axisRenderer, double? zoomFactor) { - final Rect axisClipRect = chartState._chartAxis._axisClipRect; - final num direction = _isZoomIn + void setZoomFactorAndZoomPosition(SfCartesianChartState chartState, + ChartAxisRendererDetails axisDetails, double? zoomFactor) { + final Rect axisClipRect = stateProperties.chartAxis.axisClipRect; + final num direction = isZoomIn ? 1 - : _isZoomOut + : isZoomOut ? -1 : 1; final num cumulative = math.max( - math.max(1 / _minMax(axisRenderer._zoomFactor, 0, 1), 1) + + math.max(1 / _minMax(axisDetails.zoomFactor, 0, 1), 1) + (0.1 * direction), 1); if (cumulative >= 1) { - num origin = axisRenderer._orientation == AxisOrientation.horizontal + num origin = axisDetails.orientation == AxisOrientation.horizontal ? (axisClipRect.left + axisClipRect.width / 2) / axisClipRect.width : 1 - ((axisClipRect.top + axisClipRect.height / 2) / @@ -908,59 +1008,33 @@ class ZoomPanBehaviorRenderer with ZoomBehavior { ((cumulative == 1) ? 1 : _minMax(1 / cumulative, 0, 1)).toDouble(); _zoomPosition = (cumulative == 1) ? 0 - : axisRenderer._zoomPosition + - ((axisRenderer._zoomFactor - zoomFactor) * origin); - if (axisRenderer._zoomPosition != _zoomPosition || - axisRenderer._zoomFactor != zoomFactor) { + : axisDetails.zoomPosition + + ((axisDetails.zoomFactor - zoomFactor) * origin); + if (axisDetails.zoomPosition != _zoomPosition || + axisDetails.zoomFactor != zoomFactor) { zoomFactor = (_zoomPosition! + zoomFactor) > 1 ? (1 - _zoomPosition!) : zoomFactor; } - axisRenderer._zoomPosition = _zoomPosition!; - axisRenderer._zoomFactor = zoomFactor; + axisDetails.zoomPosition = _zoomPosition!; + axisDetails.zoomFactor = zoomFactor; } } +} - /// Performs panning action. - @override - void onPan(double xPos, double yPos) => _doPan(xPos, yPos); - - /// Performs the double-tap action. - @override - void onDoubleTap(double xPos, double yPos, double? zoomFactor) => - _doubleTapZooming(xPos, yPos, zoomFactor); - - /// Draws selection zoomRect - @override - void onPaint(Canvas canvas) => _painter.drawRect(canvas); - - /// Performs selection zooming. - @override - void onDrawSelectionZoomRect( - double currentX, double currentY, double startX, double startY) => - _drawSelectionZoomRect(currentX, currentY, startX, startY); - - /// Performs pinch start action. - @override - void onPinchStart(ChartAxis axis, double firstX, double firstY, - double secondX, double secondY, double scaleFactor) {} - - /// Performs pinch end action. - @override - void onPinchEnd(ChartAxis axis, double firstX, double firstY, double secondX, - double secondY, double scaleFactor) {} - - /// Performs pinch zooming. - @override - void onPinch( - ChartAxisRenderer axisRenderer, double position, double scaleFactor) { - axisRenderer._zoomFactor = scaleFactor; - axisRenderer._zoomPosition = position; +// ignore: avoid_classes_with_only_static_members +/// Helper class to get the zooming behavior rendering details instance from its renderer +class ZoomPanBehaviorHelper { + /// Gets the zooming behavior details + static ZoomingBehaviorDetails getRenderingDetails( + ZoomPanBehaviorRenderer renderer) { + return renderer._zoomingBehaviorDetails; } -} -class _ZoomAxisRange { - _ZoomAxisRange({this.actualMin, this.actualDelta, this.min, this.delta}); - double? actualMin, actualDelta, min, delta; + /// Method to set the cartesian state properties + static void setStateProperties(ZoomPanBehavior zoomPanBehavior, + CartesianStateProperties stateProperties) { + zoomPanBehavior._stateProperties = stateProperties; + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart index b6ca67f56..2cd8451cd 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/utils/enum.dart @@ -1,8 +1,6 @@ -part of charts; - /// Orientation of an axis. /// -/// The Axis Orientation can be changed either to vertical or horizontal +/// The axis orientation can be changed either to vertical or horizontal enum AxisOrientation { /// - AxisOrientation.vertical, renders the axis vertically. vertical, @@ -13,11 +11,11 @@ enum AxisOrientation { /// Padding for axis ranges. enum ChartRangePadding { - /// - ChartRangePadding.auto, will apply None as padding for horizontal numeric axis, while the - /// vertical numeric axis takes Normal as padding calculation. + /// - ChartRangePadding.auto, will apply `none` as padding for horizontal numeric axis, while the + /// vertical numeric axis takes `normal` as padding calculation. auto, - /// - ChartRangePadding.none, will not add any padding to the min and max values. + /// - ChartRangePadding.none, will not add any padding to the minimum and maximum values. none, /// - ChartRangePadding.normal, will apply padding to the axis based on the default range calculation. @@ -26,7 +24,7 @@ enum ChartRangePadding { /// - ChartRangePadding.additional, will add an interval to the minimum and maximum of the axis. additional, - /// - ChartRangePadding.round, will round the minimum and maximum values to the nearest possible value, + /// - ChartRangePadding.round, will round the minimum and maximum values to the nearest possible value. round } @@ -39,7 +37,7 @@ enum LabelPlacement { onTicks } -/// Action while the axis label intersects.Axis Label placements can be determined when the axis labels get intesected. +/// Action while the axis label intersects. Axis label placements can be determined when the axis labels get intersected. /// enum AxisLabelIntersectAction { /// - AxisLabelIntersectAction.none, will not perform any action on intersecting labels. @@ -169,7 +167,7 @@ enum TickPosition { /// Trendline type /// -/// On the basis of the trend line type, the trend line is rendered +/// On the basis of the trendline type, the trendline is rendered enum TrendlineType { /// - TrendlineType.linear, displays linear trendline type linear, @@ -194,16 +192,16 @@ enum TrendlineType { /// /// Based on the activation mode, the user interaction will be triggered. enum ActivationMode { - /// - ActivationMode.singleTap , activates the appropriate feature on single tap. + /// - ActivationMode.singleTap, activates the appropriate feature on single tap. singleTap, - /// - ActivationMode.doubleTap , activates on double tap. + /// - ActivationMode.doubleTap, activates on double tap. doubleTap, - /// - ActivationMode.longPress , activates on longPress. + /// - ActivationMode.longPress, activates on longPress. longPress, - /// - ActivationMode.none , does not activate any feature. + /// - ActivationMode.none, does not activate any feature. none } @@ -281,7 +279,10 @@ enum CoordinateUnit { point, /// - CoordinateUnit.logicalPixel, places the annotation concerning to the pixel value. - logicalPixel + logicalPixel, + + /// - CoordinateUnit.percentage, places the annotation concerning to the percentage value. + percentage } /// Annotation is a note by way of explanation or comment added to the chart. @@ -406,7 +407,7 @@ enum ChartSwipeDirection { /// the direction is `ChartSwipeDirection.start` start, - ///if the swipe happens from right to left direction, the + ///If the swipe happens from right to left direction, the /// direction is `ChartSwipeDirection.end`. end } @@ -419,9 +420,53 @@ enum ChartSwipeDirection { /// ///Defaults to `AutoScrollingMode.end`. enum AutoScrollingMode { - ///`AutoScrollingMode.start`, If the chart is scrolled from left to right direction + ///`AutoScrollingMode.start`, if the chart is scrolled from left to right direction start, - ///`AutoScrollingMode.end`, If the chart is scrolled from right to left direction + ///`AutoScrollingMode.end`, if the chart is scrolled from right to left direction end } + +/// Determines the type of the Error Bar. +enum ErrorBarType { + /// The horizontal and vertical error values should be fixed. + fixed, + + /// The horizontal and vertical error values changes into error percentages. + percentage, + + /// The horizontal and vertical error values changes depends on the deviation values. + standardDeviation, + + /// The horizontal and vertical error values changes depends on approximate + /// error values of all points. + standardError, + + /// It determines the positive and negative error values in both horizontal + /// and vertical direction. + custom +} + +/// Determines the error bar direction. +enum Direction { + /// Determines the error bar direction in positive side. + plus, + + /// Determines the error bar direction in negative side. + minus, + + /// Determines the error bar direction in both positive and negative sides. + both +} + +/// Determines mode of the error bar. +enum RenderingMode { + /// Determines the vertical side of the error bar. + vertical, + + /// Determines the horizontal side of the error bar. + horizontal, + + /// Determines both the vertical and horizontal sides of the error bar. + both +} diff --git a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart index 5cdc981fe..a1c45ec6a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/chart/utils/helper.dart @@ -1,7 +1,78 @@ -part of charts; +import 'dart:math' as math; +import 'dart:math' as math_lib; +import 'dart:math'; +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import './../../circular_chart/utils/helper.dart'; +import './../../common/event_args.dart'; +import './../../common/rendering_details.dart'; +import './../../common/user_interaction/selection_behavior.dart'; +import './../../common/utils/helper.dart'; +import './../../common/utils/typedef.dart'; +import './../annotation/annotation_settings.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/chart_base.dart'; +import './../chart_segment/chart_segment.dart'; +import './../chart_segment/scatter_segment.dart'; +import './../chart_series/bar_series.dart'; +import './../chart_series/box_and_whisker_series.dart'; +import './../chart_series/bubble_series.dart'; +import './../chart_series/candle_series.dart'; +import './../chart_series/column_series.dart'; +import './../chart_series/financial_series_base.dart'; +import './../chart_series/hilo_series.dart'; +import './../chart_series/hiloopenclose_series.dart'; +import './../chart_series/histogram_series.dart'; +import './../chart_series/range_area_series.dart'; +import './../chart_series/range_column_series.dart'; +import './../chart_series/series.dart'; +import './../chart_series/series_renderer_properties.dart'; +import './../chart_series/spline_area_series.dart'; +import './../chart_series/spline_range_area_series.dart'; +import './../chart_series/spline_series.dart'; +import './../chart_series/stacked_bar_series.dart'; +import './../chart_series/stacked_column_series.dart'; +import './../chart_series/stacked_series_base.dart'; +import './../chart_series/waterfall_series.dart'; +import './../chart_series/xy_data_series.dart'; +import './../common/cartesian_state_properties.dart'; +import './../common/common.dart'; +import './../common/marker.dart'; +import './../common/renderer.dart'; +import './../common/segment_properties.dart'; +import './../series_painter/bar_painter.dart'; +import './../series_painter/box_and_whisker_painter.dart'; +import './../series_painter/candle_painter.dart'; +import './../series_painter/column_painter.dart'; +import './../series_painter/hilo_painter.dart'; +import './../series_painter/hiloopenclose_painter.dart'; +import './../series_painter/histogram_painter.dart'; +import './../series_painter/range_column_painter.dart'; +import './../series_painter/scatter_painter.dart'; +import './../series_painter/spline_area_painter.dart'; +import './../series_painter/spline_painter.dart'; +import './../series_painter/spline_range_area_painter.dart'; +import './../series_painter/waterfall_painter.dart'; +import './../trendlines/trendlines.dart'; +import './../trendlines/trendlines_painter.dart'; +import './../user_interaction/trackball.dart'; +import '../../../charts.dart'; +import 'enum.dart'; /// Return percentage to value -num? _percentageToValue(String? value, num size) { +num? percentageToValue(String? value, num size) { if (value != null) { return value.contains('%') ? (size / 100) * num.tryParse(value.replaceAll(RegExp('%'), ''))! @@ -11,13 +82,13 @@ num? _percentageToValue(String? value, num size) { } /// Draw the text -void _drawText(Canvas canvas, String text, Offset point, TextStyle style, +void drawText(Canvas canvas, String text, Offset point, TextStyle style, [int? angle]) { final int maxLines = getMaxLinesContent(text); final TextSpan span = TextSpan(text: text, style: style); final TextPainter tp = TextPainter( text: span, - textDirection: TextDirection.ltr, + textDirection: dart_ui.TextDirection.ltr, textAlign: TextAlign.center, maxLines: maxLines); tp.layout(); @@ -33,7 +104,7 @@ void _drawText(Canvas canvas, String text, Offset point, TextStyle style, } /// Draw the path -void _drawDashedPath(Canvas canvas, _CustomPaintStyle style, Offset moveToPoint, +void drawDashedPath(Canvas canvas, CustomPaintStyle style, Offset moveToPoint, Offset lineToPoint, [List? dashArray]) { bool even = false; @@ -53,9 +124,9 @@ void _drawDashedPath(Canvas canvas, _CustomPaintStyle style, Offset moveToPoint, } if (even == false) { canvas.drawPath( - _dashPath( + dashPath( path, - dashArray: _CircularIntervalList(dashArray), + dashArray: CircularIntervalList(dashArray), )!, paint); } @@ -64,32 +135,33 @@ void _drawDashedPath(Canvas canvas, _CustomPaintStyle style, Offset moveToPoint, } } -/// find the position of point -num _valueToCoefficient(num? value, ChartAxisRenderer axisRenderer) { +/// Find the position of point +num valueToCoefficient( + num? value, ChartAxisRendererDetails axisRendererDetails) { num result = 0; - if (axisRenderer._visibleRange != null && value != null) { - final _VisibleRange range = axisRenderer._visibleRange!; + if (axisRendererDetails.visibleRange != null && value != null) { + final VisibleRange range = axisRendererDetails.visibleRange!; // ignore: unnecessary_null_comparison if (range != null) { result = (value - range.minimum) / (range.delta); - result = axisRenderer._axis.isInversed ? (1 - result) : result; + result = axisRendererDetails.axis.isInversed ? (1 - result) : result; } } return result; } -/// find logarithmic values -num _calculateLogBaseValue(num value, num base) => +/// Find logarithmic values +num calculateLogBaseValue(num value, num base) => math.log(value) / math.log(base); /// To check if value is within range -bool _withInRange(num value, _VisibleRange range) => +bool withInRange(num value, VisibleRange range) => // ignore: unnecessary_null_comparison value != null && (value <= range.maximum) && (value >= range.minimum); /// To find the proper series color of each point in waterfall chart, /// which includes intermediate sum, total sum and negative point. -Color? _getWaterfallSeriesColor(WaterfallSeries series, +Color? getWaterfallSeriesColor(WaterfallSeries series, CartesianChartPoint point, Color? seriesColor) => point.isIntermediateSum! ? series.intermediateSumColor ?? seriesColor @@ -100,60 +172,69 @@ Color? _getWaterfallSeriesColor(WaterfallSeries series, : seriesColor; /// Get the location of point -_ChartLocation _calculatePoint( +ChartLocation calculatePoint( num x, num? y, - ChartAxisRenderer xAxisRenderer, - ChartAxisRenderer yAxisRenderer, + ChartAxisRendererDetails xAxisRendererDetails, + ChartAxisRendererDetails yAxisRendererDetails, bool isInverted, CartesianSeries? series, Rect rect) { - final ChartAxis xAxis = xAxisRenderer._axis, yAxis = yAxisRenderer._axis; + final ChartAxis xAxis = xAxisRendererDetails.axis; + final ChartAxis yAxis = yAxisRendererDetails.axis; x = xAxis is LogarithmicAxis - ? _calculateLogBaseValue(x > 1 ? x : 1, xAxis.logBase) + ? calculateLogBaseValue(x > 1 ? x : 1, xAxis.logBase) : x; y = yAxis is LogarithmicAxis ? y != null - ? _calculateLogBaseValue(y > 1 ? y : 1, yAxis.logBase) + ? calculateLogBaseValue(y > 1 ? y : 1, yAxis.logBase) : 0 : y; - x = _valueToCoefficient(x, xAxisRenderer); - y = _valueToCoefficient(y, yAxisRenderer); + x = valueToCoefficient(x, xAxisRendererDetails); + y = valueToCoefficient(y, yAxisRendererDetails); 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); + return ChartLocation(locationX, locationY); } /// Calculate the minimum points delta -num _calculateMinPointsDelta( +num calculateMinPointsDelta( ChartAxisRenderer axisRenderer, List seriesRenderers, - SfCartesianChartState chartState) { + CartesianStateProperties stateProperties) { num minDelta = 1.7976931348623157e+308, minVal; num? seriesMin; dynamic xValues; for (final CartesianSeriesRenderer seriesRenderer in seriesRenderers) { - final CartesianSeries series = seriesRenderer._series; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final CartesianSeries series = + seriesRendererDetails.series; num value; xValues = []; - if (seriesRenderer._visible! && - ((axisRenderer._name == series.xAxisName) || - (axisRenderer._name == - (chartState._chart.primaryXAxis.name ?? 'primaryXAxis') && + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + if (seriesRendererDetails.visible! == true && + ((axisRendererDetails.name == series.xAxisName) || + (axisRendererDetails.name == + (stateProperties.chart.primaryXAxis.name ?? + 'primaryXAxis') && series.xAxisName == null) || - (axisRenderer._name == - chartState._chartAxis._primaryXAxisRenderer._name && + (axisRendererDetails.name == + AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.primaryXAxisRenderer!) + .name && series.xAxisName != null))) { xValues = axisRenderer is DateTimeAxisRenderer - ? seriesRenderer._overAllDataPoints + ? seriesRendererDetails.overAllDataPoints .map((CartesianChartPoint? point) { return point!.xValue; }).toList() - : seriesRenderer._dataPoints + : seriesRendererDetails.dataPoints .map((CartesianChartPoint point) { return point.xValue; }).toList(); @@ -163,16 +244,17 @@ num _calculateMinPointsDelta( num? minimumInSeconds; if (axisRenderer is DateTimeAxisRenderer) { minDate = DateTime.fromMillisecondsSinceEpoch( - seriesRenderer._minimumX! as int); + seriesRendererDetails.minimumX! as int); minDate = minDate.subtract(const Duration(days: 1)); minimumInSeconds = minDate.millisecondsSinceEpoch; } seriesMin = (axisRenderer is DateTimeAxisRenderer && - seriesRenderer._minimumX == seriesRenderer._maximumX) + seriesRendererDetails.minimumX == + seriesRendererDetails.maximumX) ? minimumInSeconds - : seriesRenderer._minimumX; - minVal = - xValues[0] - (seriesMin ?? axisRenderer._visibleRange!.minimum); + : seriesRendererDetails.minimumX; + minVal = xValues[0] - + (seriesMin ?? axisRendererDetails.visibleRange!.minimum); if (minVal != 0) { minDelta = math.min(minDelta, minVal); } @@ -197,8 +279,8 @@ num _calculateMinPointsDelta( return minDelta; } -///Draw Legend series type icon -PaintingStyle _calculateLegendShapes(Path path, Rect rect, String seriesType) { +///Draw legend series type icon +PaintingStyle calculateLegendShapes(Path path, Rect rect, String seriesType) { PaintingStyle style = PaintingStyle.fill; switch (seriesType) { case 'line': @@ -208,19 +290,19 @@ PaintingStyle _calculateLegendShapes(Path path, Rect rect, String seriesType) { style = PaintingStyle.stroke; break; case 'spline': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.splineSeries); style = PaintingStyle.stroke; break; case 'splinearea': case 'splinerangearea': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.splineAreaSeries); break; case 'bar': case 'stackedbar': case 'stackedbar100': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.barSeries); break; case 'column': @@ -228,58 +310,58 @@ PaintingStyle _calculateLegendShapes(Path path, Rect rect, String seriesType) { case 'stackedcolumn100': case 'rangecolumn': case 'histogram': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.columnSeries); break; case 'area': case 'stackedarea': case 'rangearea': case 'stackedarea100': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.areaSeries); break; case 'stepline': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.stepLineSeries); style = PaintingStyle.stroke; break; case 'bubble': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.bubbleSeries); break; case 'hilo': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.hiloSeries); style = PaintingStyle.stroke; break; case 'hiloopenclose': case 'candle': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.candleSeries); style = PaintingStyle.stroke; break; case 'waterfall': case 'boxandwhisker': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.waterfallSeries); break; case 'pie': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.pieSeries); break; case 'doughnut': case 'radialbar': break; case 'steparea': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.stepAreaSeries); break; case 'pyramid': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.pyramidSeries); break; case 'funnel': - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.funnelSeries); break; default: @@ -293,29 +375,167 @@ PaintingStyle _calculateLegendShapes(Path path, Rect rect, String seriesType) { return style; } -/// Calculate the rect bounds for column series and Bar series. -Rect _calculateRectangle(num x1, num? y1, num x2, num? y2, - CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState) { - final Rect rect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); - final bool isInverted = _chartState._requireInvertedAxis; - final _ChartLocation point1 = _calculatePoint( +///Calculate bar legend icon path +void calculateBarTypeIconPath( + Path path, double x, double y, double width, double height) { + const num padding = 10; + path.moveTo(x - (width / 2) - padding / 4, y - 3 * (height / 5)); + path.lineTo(x + 3 * (width / 10), y - 3 * (height / 5)); + path.lineTo(x + 3 * (width / 10), y - 3 * (height / 10)); + path.lineTo(x - (width / 2) - padding / 4, y - 3 * (height / 10)); + path.close(); + path.moveTo( + x - (width / 2) - (padding / 4), y - (height / 5) + (padding / 20)); + path.lineTo( + x + (width / 2) + (padding / 4), y - (height / 5) + (padding / 20)); + path.lineTo( + x + (width / 2) + (padding / 4), y + (height / 10) + (padding / 20)); + path.lineTo( + x - (width / 2) - (padding / 4), y + (height / 10) + (padding / 20)); + path.close(); + path.moveTo( + x - (width / 2) - (padding / 4), y + (height / 5) + (padding / 10)); + path.lineTo(x - width / 4, y + (height / 5) + (padding / 10)); + path.lineTo(x - width / 4, y + (height / 2) + (padding / 10)); + path.lineTo( + x - (width / 2) - (padding / 4), y + (height / 2) + (padding / 10)); + path.close(); +} + +///Calculate column legend icon path +void calculateColumnTypeIconPath( + Path path, double x, double y, double width, double height) { + const num padding = 10; + path.moveTo(x - 3 * (width / 5), y - (height / 5)); + path.lineTo(x + 3 * (-width / 10), y - (height / 5)); + path.lineTo(x + 3 * (-width / 10), y + (height / 2)); + path.lineTo(x - 3 * (width / 5), y + (height / 2)); + path.close(); + path.moveTo( + x - (width / 10) - (width / 20), y - (height / 4) - (padding / 2)); + path.lineTo( + x + (width / 10) + (width / 20), y - (height / 4) - (padding / 2)); + path.lineTo(x + (width / 10) + (width / 20), y + (height / 2)); + path.lineTo(x - (width / 10) - (width / 20), y + (height / 2)); + path.close(); + path.moveTo(x + 3 * (width / 10), y); + path.lineTo(x + 3 * (width / 5), y); + path.lineTo(x + 3 * (width / 5), y + (height / 2)); + path.lineTo(x + 3 * (width / 10), y + (height / 2)); + path.close(); +} + +///Calculate area type legend icon path +void calculateAreaTypeIconPath( + Path path, double x, double y, double width, double height) { + const num padding = 10; + path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); + path.lineTo(x - (width / 4) - (padding / 8), y - (height / 2)); + path.lineTo(x, y + (height / 4)); + path.lineTo(x + (width / 4) + (padding / 8), y - (height / 2) + (height / 4)); + path.lineTo(x + (height / 2) + (padding / 4), y + (height / 2)); + path.close(); +} + +///Calculate stepline legend icon path +void calculateSteplineIconPath( + Path path, double x, double y, double width, double height) { + const num padding = 10; + path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); + path.lineTo(x - (width / 2) + (width / 10), y + (height / 2)); + path.lineTo(x - (width / 2) + (width / 10), y); + path.lineTo(x - (width / 10), y); + path.lineTo(x - (width / 10), y + (height / 2)); + path.lineTo(x + (width / 5), y + (height / 2)); + path.lineTo(x + (width / 5), y - (height / 2)); + path.lineTo(x + (width / 2), y - (height / 2)); + path.lineTo(x + (width / 2), y + (height / 2)); + path.lineTo(x + (width / 2) + (padding / 4), y + (height / 2)); +} + +///Calculate steparea legend icon path +void calculateStepAreaIconPath( + Path path, double x, double y, double width, double height) { + const num padding = 10; + path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); + path.lineTo(x - (width / 2) - (padding / 4), y - (height / 4)); + path.lineTo(x - (width / 2) + (width / 10), y - (height / 4)); + path.lineTo(x - (width / 2) + (width / 10), y - (height / 2)); + path.lineTo(x - (width / 10), y - (height / 2)); + path.lineTo(x - (width / 10), y); + path.lineTo(x + (width / 5), y); + path.lineTo(x + (width / 5), y - (height / 3)); + path.lineTo(x + (width / 2), y - (height / 3)); + path.lineTo(x + (width / 2), y + (height / 2)); + path.close(); +} + +///Calculate pie legend icon path +void calculatePieIconPath( + Path path, double x, double y, double width, double height) { + final double r = math.min(height, width) / 2; + path.moveTo(x, y); + path.lineTo(x + r, y); + path.arcTo(Rect.fromCircle(center: Offset(x, y), radius: r), + degreesToRadians(0).toDouble(), degreesToRadians(270).toDouble(), false); + path.close(); + path.moveTo(x + width / 10, y - height / 10); + path.lineTo(x + r, y - height / 10); + path.arcTo(Rect.fromCircle(center: Offset(x + 2, y - 2), radius: r), + degreesToRadians(-5).toDouble(), degreesToRadians(-80).toDouble(), false); + path.close(); +} + +///Calculate pyramid legend icon path +void calculatePyramidIconPath( + Path path, double x, double y, double width, double height) { + path.moveTo(x - width / 2, y + height / 2); + path.lineTo(x + width / 2, y + height / 2); + path.lineTo(x, y - height / 2); + path.lineTo(x - width / 2, y + height / 2); + path.close(); +} + +///Calculate funnel legend icon path +void calculateFunnelIconPath( + Path path, double x, double y, double width, double height) { + path.moveTo(x + width / 2, y - height / 2); + path.lineTo(x, y + height / 2); + path.lineTo(x - width / 2, y - height / 2); + path.lineTo(x + width / 2, y - height / 2); + path.close(); +} + +/// Calculate the rect bounds for column series and bar series. +Rect calculateRectangle( + num x1, + num? y1, + num x2, + num? y2, + CartesianSeriesRenderer seriesRenderer, + CartesianStateProperties stateProperties) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final Rect rect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + final bool isInverted = stateProperties.requireInvertedAxis; + final ChartLocation point1 = calculatePoint( x1, y1, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, isInverted, - seriesRenderer._series, + seriesRendererDetails.series, rect); - final _ChartLocation point2 = _calculatePoint( + final ChartLocation point2 = calculatePoint( x2, y2, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, isInverted, - seriesRenderer._series, + seriesRendererDetails.series, rect); return Rect.fromLTWH( math.min(point1.x, point2.x), @@ -324,65 +544,69 @@ Rect _calculateRectangle(num x1, num? y1, num x2, num? y2, (point2.y - point1.y).abs()); } -///Calculate the tracker rect bounds for column series and Bar series. -Rect _calculateShadowRectangle( +///Calculate the tracker rect bounds for column series and bar series. +Rect calculateShadowRectangle( num x1, num y1, num x2, num y2, CartesianSeriesRenderer seriesRenderer, - SfCartesianChartState _chartState, + CartesianStateProperties stateProperties, Offset plotOffset) { - final Rect rect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer!._axis.plotOffset, - seriesRenderer._yAxisRenderer!._axis.plotOffset)); - final bool isInverted = _chartState._requireInvertedAxis; - final _ChartLocation point1 = _calculatePoint( + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final Rect rect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, + Offset(seriesRendererDetails.xAxisDetails!.axis.plotOffset, + seriesRendererDetails.yAxisDetails!.axis.plotOffset)); + final bool isInverted = stateProperties.requireInvertedAxis; + final ChartLocation point1 = calculatePoint( x1, y1, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, isInverted, - seriesRenderer._series, + seriesRendererDetails.series, rect); - final _ChartLocation point2 = _calculatePoint( + final ChartLocation point2 = calculatePoint( x2, y2, - seriesRenderer._xAxisRenderer!, - seriesRenderer._yAxisRenderer!, + seriesRendererDetails.xAxisDetails!, + seriesRendererDetails.yAxisDetails!, isInverted, - seriesRenderer._series, + seriesRendererDetails.series, rect); - final bool isColumn = seriesRenderer._seriesType == 'column'; - final bool isHistogram = seriesRenderer._seriesType == 'histogram'; - final bool isStackedColumn = seriesRenderer._seriesType == 'stackedcolumn'; - final bool isStackedBar = seriesRenderer._seriesType == 'stackedbar'; - final bool isRangeColumn = seriesRenderer._seriesType == 'rangecolumn'; + final bool isColumn = seriesRendererDetails.seriesType == 'column'; + final bool isHistogram = seriesRendererDetails.seriesType == 'histogram'; + final bool isStackedColumn = + seriesRendererDetails.seriesType == 'stackedcolumn'; + final bool isStackedBar = seriesRendererDetails.seriesType == 'stackedbar'; + final bool isRangeColumn = seriesRendererDetails.seriesType == 'rangecolumn'; ColumnSeries? columnSeries; BarSeries? barSeries; StackedColumnSeries? stackedColumnSeries; StackedBarSeries? stackedBarSeries; RangeColumnSeries? rangeColumnSeries; HistogramSeries? histogramSeries; - if (seriesRenderer._seriesType == 'column') { - columnSeries = seriesRenderer._series as ColumnSeries; - } else if (seriesRenderer._seriesType == 'bar') { - barSeries = seriesRenderer._series as BarSeries; - } else if (seriesRenderer._seriesType == 'stackedcolumn') { + if (seriesRendererDetails.seriesType == 'column') { + columnSeries = + seriesRendererDetails.series as ColumnSeries; + } else if (seriesRendererDetails.seriesType == 'bar') { + barSeries = seriesRendererDetails.series as BarSeries; + } else if (seriesRendererDetails.seriesType == 'stackedcolumn') { stackedColumnSeries = - seriesRenderer._series as StackedColumnSeries; - } else if (seriesRenderer._seriesType == 'stackedbar') { + seriesRendererDetails.series as StackedColumnSeries; + } else if (seriesRendererDetails.seriesType == 'stackedbar') { stackedBarSeries = - seriesRenderer._series as StackedBarSeries; - } else if (seriesRenderer._seriesType == 'rangecolumn') { + seriesRendererDetails.series as StackedBarSeries; + } else if (seriesRendererDetails.seriesType == 'rangecolumn') { rangeColumnSeries = - seriesRenderer._series as RangeColumnSeries; - } else if (seriesRenderer._seriesType == 'histogram') { + seriesRendererDetails.series as RangeColumnSeries; + } else if (seriesRendererDetails.seriesType == 'histogram') { histogramSeries = - seriesRenderer._series as HistogramSeries; + seriesRendererDetails.series as HistogramSeries; } - return !_chartState._chart.isTransposed + return !stateProperties.chart.isTransposed ? _getNormalShadowRect( rect, isColumn, @@ -390,7 +614,7 @@ Rect _calculateShadowRectangle( isRangeColumn, isStackedColumn, isStackedBar, - _chartState, + stateProperties, plotOffset, point1, point2, @@ -407,7 +631,7 @@ Rect _calculateShadowRectangle( isRangeColumn, isStackedColumn, isStackedBar, - _chartState, + stateProperties, plotOffset, point1, point2, @@ -419,7 +643,7 @@ Rect _calculateShadowRectangle( histogramSeries); } -/// calculate shadow rectangle for normal rect series +/// Calculate shadow rectangle for normal rect series Rect _getNormalShadowRect( Rect rect, bool isColumn, @@ -427,10 +651,10 @@ Rect _getNormalShadowRect( bool isRangeColumn, bool isStackedColumn, bool isStackedBar, - SfCartesianChartState _chartState, + CartesianStateProperties stateProperties, Offset plotOffset, - _ChartLocation point1, - _ChartLocation point2, + ChartLocation point1, + ChartLocation point2, ColumnSeries? columnSeries, BarSeries? barSeries, StackedColumnSeries? stackedColumnSeries, @@ -454,8 +678,8 @@ Rect _getNormalShadowRect( (-stackedColumnSeries!.trackBorderWidth - stackedColumnSeries.trackPadding) : isStackedBar - ? _chartState._chartAxis._axisClipRect.left - : _chartState._chartAxis._axisClipRect.left, + ? stateProperties.chartAxis.axisClipRect.left + : stateProperties.chartAxis.axisClipRect.left, isColumn || isHistogram || isRangeColumn ? rect.top : isStackedColumn @@ -484,12 +708,12 @@ Rect _getNormalShadowRect( (stackedColumnSeries!.trackBorderWidth * 2) + stackedColumnSeries.trackPadding * 2 : isStackedBar - ? _chartState._chartAxis._axisClipRect.width - : _chartState._chartAxis._axisClipRect.width, + ? stateProperties.chartAxis.axisClipRect.width + : stateProperties.chartAxis.axisClipRect.width, isColumn || isHistogram || isRangeColumn - ? (_chartState._chartAxis._axisClipRect.height - 2 * plotOffset.dy) + ? (stateProperties.chartAxis.axisClipRect.height - 2 * plotOffset.dy) : isStackedColumn - ? (_chartState._chartAxis._axisClipRect.height - + ? (stateProperties.chartAxis.axisClipRect.height - 2 * plotOffset.dy) : isStackedBar ? (point2.y - point1.y).abs() + @@ -500,7 +724,7 @@ Rect _getNormalShadowRect( barSeries.trackPadding * 2); } -/// calculate shadow rectangle for transposed rect series +/// Calculate shadow rectangle for transposed rect series Rect _getTransposedShadowRect( Rect rect, bool isColumn, @@ -508,10 +732,10 @@ Rect _getTransposedShadowRect( bool isRangeColumn, bool isStackedColumn, bool isStackedBar, - SfCartesianChartState _chartState, + CartesianStateProperties stateProperties, Offset plotOffset, - _ChartLocation point1, - _ChartLocation point2, + ChartLocation point1, + ChartLocation point2, ColumnSeries? columnSeries, BarSeries? barSeries, StackedColumnSeries? stackedColumnSeries, @@ -520,9 +744,9 @@ Rect _getTransposedShadowRect( HistogramSeries? histogramSeries) { return Rect.fromLTWH( isColumn || isRangeColumn || isHistogram - ? _chartState._chartAxis._axisClipRect.left + ? stateProperties.chartAxis.axisClipRect.left : isStackedColumn - ? _chartState._chartAxis._axisClipRect.left + ? stateProperties.chartAxis.axisClipRect.left : isStackedBar ? math.min(point1.x, point2.x) + (-stackedBarSeries!.trackBorderWidth - @@ -549,9 +773,9 @@ Rect _getTransposedShadowRect( ? rect.top : rect.top, isColumn || isRangeColumn || isHistogram - ? _chartState._chartAxis._axisClipRect.width + ? stateProperties.chartAxis.axisClipRect.width : isStackedColumn - ? _chartState._chartAxis._axisClipRect.width + ? stateProperties.chartAxis.axisClipRect.width : isStackedBar ? (point2.x - point1.x).abs() + (stackedBarSeries!.trackBorderWidth * 2) + @@ -576,95 +800,98 @@ Rect _getTransposedShadowRect( stackedColumnSeries!.trackBorderWidth * 2 + stackedColumnSeries.trackPadding * 2 : isStackedBar - ? (_chartState._chartAxis._axisClipRect.height - + ? (stateProperties.chartAxis.axisClipRect.height - 2 * plotOffset.dy) - : (_chartState._chartAxis._axisClipRect.height - + : (stateProperties.chartAxis.axisClipRect.height - 2 * plotOffset.dy)); } -/// Calculated the side by side range for Column and bar series -_VisibleRange _calculateSideBySideInfo( - CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState) { +/// Calculate the side by side range for column and bar series +VisibleRange calculateSideBySideInfo(CartesianSeriesRenderer seriesRenderer, + CartesianStateProperties stateProperties) { num? rectPosition; num? count; num seriesSpacing; num? pointSpacing; - final SfCartesianChart chart = _chartState._chart; - final CartesianSeries series = seriesRenderer._series; - if (seriesRenderer._seriesType == 'column' && + final SfCartesianChart chart = stateProperties.chart; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final CartesianSeries series = seriesRendererDetails.series; + if (seriesRendererDetails.seriesType == 'column' && chart.enableSideBySideSeriesPlacement) { final ColumnSeriesRenderer columnSeriesRenderer = seriesRenderer as ColumnSeriesRenderer; - _calculateSideBySidePositions(columnSeriesRenderer, _chartState); - rectPosition = columnSeriesRenderer._rectPosition; - count = columnSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'histogram' && + _calculateSideBySidePositions(columnSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'histogram' && chart.enableSideBySideSeriesPlacement) { final HistogramSeriesRenderer histogramSeriesRenderer = seriesRenderer as HistogramSeriesRenderer; - _calculateSideBySidePositions(histogramSeriesRenderer, _chartState); - rectPosition = histogramSeriesRenderer._rectPosition; - count = histogramSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'bar' && + _calculateSideBySidePositions(histogramSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'bar' && chart.enableSideBySideSeriesPlacement) { final BarSeriesRenderer barSeriesRenderer = seriesRenderer as BarSeriesRenderer; - _calculateSideBySidePositions(barSeriesRenderer, _chartState); - rectPosition = barSeriesRenderer._rectPosition; - count = barSeriesRenderer._rectCount; - } else if ((seriesRenderer._seriesType.contains('stackedcolumn') || - seriesRenderer._seriesType.contains('stackedbar')) && + _calculateSideBySidePositions(barSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if ((seriesRendererDetails.seriesType.contains('stackedcolumn') == + true || + seriesRendererDetails.seriesType.contains('stackedbar') == true) && chart.enableSideBySideSeriesPlacement) { - final _StackedSeriesRenderer stackedRectSeriesRenderer = - seriesRenderer as _StackedSeriesRenderer; - _calculateSideBySidePositions(stackedRectSeriesRenderer, _chartState); - rectPosition = stackedRectSeriesRenderer._rectPosition; - count = stackedRectSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'rangecolumn' && + final StackedSeriesRenderer stackedRectSeriesRenderer = + seriesRenderer as StackedSeriesRenderer; + _calculateSideBySidePositions(stackedRectSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'rangecolumn' && chart.enableSideBySideSeriesPlacement) { final RangeColumnSeriesRenderer rangeColumnSeriesRenderer = seriesRenderer as RangeColumnSeriesRenderer; - _calculateSideBySidePositions(rangeColumnSeriesRenderer, _chartState); - rectPosition = rangeColumnSeriesRenderer._rectPosition; - count = rangeColumnSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'hilo' && + _calculateSideBySidePositions(rangeColumnSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'hilo' && chart.enableSideBySideSeriesPlacement) { final HiloSeriesRenderer hiloSeriesRenderer = seriesRenderer as HiloSeriesRenderer; - _calculateSideBySidePositions(hiloSeriesRenderer, _chartState); - rectPosition = hiloSeriesRenderer._rectPosition; - count = hiloSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'hiloopenclose' && + _calculateSideBySidePositions(hiloSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'hiloopenclose' && chart.enableSideBySideSeriesPlacement) { final HiloOpenCloseSeriesRenderer hiloOpenCloseSeriesRenderer = seriesRenderer as HiloOpenCloseSeriesRenderer; - _calculateSideBySidePositions(hiloOpenCloseSeriesRenderer, _chartState); - rectPosition = hiloOpenCloseSeriesRenderer._rectPosition; - count = hiloOpenCloseSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'candle' && + _calculateSideBySidePositions(hiloOpenCloseSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'candle' && chart.enableSideBySideSeriesPlacement) { final CandleSeriesRenderer candleSeriesRenderer = seriesRenderer as CandleSeriesRenderer; - _calculateSideBySidePositions(candleSeriesRenderer, _chartState); - rectPosition = candleSeriesRenderer._rectPosition; - count = candleSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'boxandwhisker' && + _calculateSideBySidePositions(candleSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'boxandwhisker' && chart.enableSideBySideSeriesPlacement) { final BoxAndWhiskerSeriesRenderer boxAndWhiskerSeriesRenderer = seriesRenderer as BoxAndWhiskerSeriesRenderer; - _calculateSideBySidePositions(boxAndWhiskerSeriesRenderer, _chartState); - rectPosition = boxAndWhiskerSeriesRenderer._rectPosition; - count = boxAndWhiskerSeriesRenderer._rectCount; - } else if (seriesRenderer._seriesType == 'waterfall' && + _calculateSideBySidePositions(boxAndWhiskerSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; + } else if (seriesRendererDetails.seriesType == 'waterfall' && chart.enableSideBySideSeriesPlacement) { final WaterfallSeriesRenderer waterfallSeriesRenderer = seriesRenderer as WaterfallSeriesRenderer; - _calculateSideBySidePositions(waterfallSeriesRenderer, _chartState); - rectPosition = waterfallSeriesRenderer._rectPosition; - count = waterfallSeriesRenderer._rectCount; + _calculateSideBySidePositions(waterfallSeriesRenderer, stateProperties); + rectPosition = seriesRendererDetails.rectPosition; + count = seriesRendererDetails.rectCount; } - if (seriesRenderer._seriesType == 'column') { + if (seriesRendererDetails.seriesType == 'column') { final ColumnSeries columnSeries = series as ColumnSeries; seriesSpacing = @@ -672,7 +899,7 @@ _VisibleRange _calculateSideBySideInfo( assert(columnSeries.width == null || columnSeries.width! <= 1, 'The width of the column series must be less than or equal 1.'); pointSpacing = columnSeries.width!; - } else if (seriesRenderer._seriesType == 'histogram') { + } else if (seriesRendererDetails.seriesType == 'histogram') { final HistogramSeries histogramSeries = series as HistogramSeries; seriesSpacing = @@ -680,16 +907,16 @@ _VisibleRange _calculateSideBySideInfo( assert(histogramSeries.width! <= 1, 'The width of the histogram series must be less than or equal 1.'); pointSpacing = histogramSeries.width!; - } else if (seriesRenderer._seriesType == 'stackedcolumn' || - seriesRenderer._seriesType == 'stackedcolumn100' || - seriesRenderer._seriesType == 'stackedbar' || - seriesRenderer._seriesType == 'stackedbar100') { - final _StackedSeriesBase stackedRectSeries = - series as _StackedSeriesBase; + } else if (seriesRendererDetails.seriesType == 'stackedcolumn' || + seriesRendererDetails.seriesType == 'stackedcolumn100' || + seriesRendererDetails.seriesType == 'stackedbar' || + seriesRendererDetails.seriesType == 'stackedbar100') { + final StackedSeriesBase stackedRectSeries = + series as StackedSeriesBase; seriesSpacing = chart.enableSideBySideSeriesPlacement ? stackedRectSeries.spacing : 0; pointSpacing = stackedRectSeries.width!; - } else if (seriesRenderer._seriesType == 'rangecolumn') { + } else if (seriesRendererDetails.seriesType == 'rangecolumn') { final RangeColumnSeries rangeColumnSeries = series as RangeColumnSeries; seriesSpacing = @@ -697,25 +924,25 @@ _VisibleRange _calculateSideBySideInfo( assert(rangeColumnSeries.width == null || rangeColumnSeries.width! <= 1, 'The width of the range column series must be less than or equal 1.'); pointSpacing = rangeColumnSeries.width; - } else if (seriesRenderer._seriesType == 'hilo') { + } else if (seriesRendererDetails.seriesType == 'hilo') { final HiloSeries hiloSeries = series as HiloSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? hiloSeries.spacing : 0; pointSpacing = hiloSeries.width!; - } else if (seriesRenderer._seriesType == 'hiloopenclose') { + } else if (seriesRendererDetails.seriesType == 'hiloopenclose') { final HiloOpenCloseSeries hiloOpenCloseSeries = series as HiloOpenCloseSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? hiloOpenCloseSeries.spacing : 0; pointSpacing = hiloOpenCloseSeries.width!; - } else if (seriesRenderer._seriesType == 'candle') { + } else if (seriesRendererDetails.seriesType == 'candle') { final CandleSeries candleSeries = series as CandleSeries; seriesSpacing = chart.enableSideBySideSeriesPlacement ? candleSeries.spacing : 0; pointSpacing = candleSeries.width!; - } else if (seriesRenderer._seriesType == 'boxandwhisker') { + } else if (seriesRendererDetails.seriesType == 'boxandwhisker') { final BoxAndWhiskerSeries boxAndWhiskerSeries = series as BoxAndWhiskerSeries; seriesSpacing = @@ -723,7 +950,7 @@ _VisibleRange _calculateSideBySideInfo( assert(boxAndWhiskerSeries.width! <= 1, 'The width of the box plot series must be less than or equal to 1.'); pointSpacing = boxAndWhiskerSeries.width!; - } else if (seriesRenderer._seriesType == 'waterfall') { + } else if (seriesRendererDetails.seriesType == 'waterfall') { final WaterfallSeries waterfallSeries = series as WaterfallSeries; seriesSpacing = @@ -745,21 +972,20 @@ _VisibleRange _calculateSideBySideInfo( final num rectCount = !chart.enableSideBySideSeriesPlacement ? 1 : count!; /// Gets the minimum point delta in series - final num minPointsDelta = seriesRenderer._minDelta ?? - _calculateMinPointsDelta(seriesRenderer._xAxisRenderer!, - _chartState._seriesRenderers, _chartState); + final num minPointsDelta = seriesRendererDetails.minDelta ?? + calculateMinPointsDelta(seriesRendererDetails.xAxisDetails!.axisRenderer, + stateProperties.seriesRenderers, stateProperties); final num width = minPointsDelta * pointSpacing!; final num location = position / rectCount - 0.5; - _VisibleRange doubleRange = - _VisibleRange(location, location + (1 / rectCount)); + VisibleRange doubleRange = VisibleRange(location, location + (1 / rectCount)); /// Side by side range will be calculated based on calculated width. if ((doubleRange.minimum is num) && (doubleRange.maximum is num)) { doubleRange = - _VisibleRange(doubleRange.minimum * width, doubleRange.maximum * width); + VisibleRange(doubleRange.minimum * width, doubleRange.maximum * width); doubleRange.delta = doubleRange.maximum - doubleRange.minimum; final num radius = seriesSpacing * doubleRange.delta; - doubleRange = _VisibleRange( + doubleRange = VisibleRange( doubleRange.minimum + radius / 2, doubleRange.maximum - radius / 2); doubleRange.delta = doubleRange.maximum - doubleRange.minimum; } @@ -767,7 +993,7 @@ _VisibleRange _calculateSideBySideInfo( } /// The method returns rotated text location for the given angle -_ChartLocation _getRotatedTextLocation(double pointX, double pointY, +ChartLocation getRotatedTextLocation(double pointX, double pointY, String labelText, TextStyle textStyle, int angle, ChartAxis axis) { if (angle > 0) { final Size textSize = measureText(labelText, textStyle); @@ -791,7 +1017,7 @@ _ChartLocation _getRotatedTextLocation(double pointX, double pointY, (angle / 270) * textSize.height : 0; - /// label rotation 270 to 360 + /// Label rotation 270 to 360 pointX -= (angle > 270) ? (rotateTextSize.width - textSize.height).abs() : 0; pointY -= (angle > 270) @@ -817,24 +1043,26 @@ _ChartLocation _getRotatedTextLocation(double pointX, double pointY, } } } - return _ChartLocation(pointX, pointY); + return ChartLocation(pointX, pointY); } /// Checking whether new series and old series are similar -bool _isSameSeries(dynamic oldSeries, dynamic newSeries) { +bool isSameSeries(dynamic oldSeries, dynamic newSeries) { return oldSeries.runtimeType == newSeries.runtimeType && oldSeries.key == newSeries.key; } -/// Calculate the side by side position for Rect series -void _calculateSideBySidePositions( - CartesianSeriesRenderer seriesRenderer, SfCartesianChartState _chartState) { +/// Calculate the side by side position for rect series +void _calculateSideBySidePositions(CartesianSeriesRenderer seriesRenderer, + CartesianStateProperties stateProperties) { final List seriesCollection = - _findRectSeriesCollection(_chartState); + _findRectSeriesCollection(stateProperties); int rectCount = 0; num? position; final num seriesLength = seriesCollection.length; List<_StackingGroup>? stackingGroupPos; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); for (final dynamic seriesRenderer in seriesCollection) { if (seriesRenderer is ColumnSeriesRenderer || seriesRenderer is BarSeriesRenderer || @@ -845,34 +1073,40 @@ void _calculateSideBySidePositions( seriesRenderer is CandleSeriesRenderer || seriesRenderer is BoxAndWhiskerSeriesRenderer || seriesRenderer is WaterfallSeriesRenderer) { - seriesRenderer._rectPosition = rectCount++; - seriesRenderer._rectCount = seriesLength; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + seriesRendererDetails.rectPosition = rectCount++; + seriesRendererDetails.rectCount = seriesLength; } } - if (seriesRenderer is _StackedSeriesRenderer) { + if (seriesRenderer is StackedSeriesRenderer) { for (int i = 0; i < seriesCollection.length; i++) { - _StackedSeriesBase? series; - if (seriesCollection[i] is _StackedSeriesRenderer) { + StackedSeriesBase? series; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesCollection[i]); + if (seriesCollection[i] is StackedSeriesRenderer) { seriesRenderer = seriesCollection[i]; - series = seriesRenderer._series as _StackedSeriesBase; + series = + seriesRendererDetails.series as StackedSeriesBase; } // ignore: unnecessary_null_comparison - if (seriesRenderer != null && seriesRenderer is _StackedSeriesRenderer) { + if (seriesRenderer != null && seriesRenderer is StackedSeriesRenderer) { final String groupName = series!.groupName; // ignore: unnecessary_null_comparison if (groupName != null) { stackingGroupPos ??= <_StackingGroup>[]; if (stackingGroupPos.isEmpty) { - seriesRenderer._rectPosition = rectCount; + seriesRendererDetails.rectPosition = rectCount; stackingGroupPos.add(_StackingGroup(groupName, rectCount++)); } else if (stackingGroupPos.isNotEmpty) { for (int j = 0; j < stackingGroupPos.length; j++) { if (groupName == stackingGroupPos[j].groupName) { - seriesRenderer._rectPosition = stackingGroupPos[j].stackCount; + seriesRendererDetails.rectPosition = + stackingGroupPos[j].stackCount; break; } else if (groupName != stackingGroupPos[j].groupName && j == stackingGroupPos.length - 1) { - seriesRenderer._rectPosition = rectCount; + seriesRendererDetails.rectPosition = rectCount; stackingGroupPos.add(_StackingGroup(groupName, rectCount++)); break; } @@ -880,66 +1114,76 @@ void _calculateSideBySidePositions( } } else { if (position == null) { - seriesRenderer._rectPosition = rectCount; + seriesRendererDetails.rectPosition = rectCount; position = rectCount++; } else { - seriesRenderer._rectPosition = position; + seriesRendererDetails.rectPosition = position; } } } } } - if (seriesRenderer._seriesType.contains('stackedcolumn') || - seriesRenderer._seriesType.contains('stackedbar')) { + if (seriesRendererDetails.seriesType.contains('stackedcolumn') == true || + seriesRendererDetails.seriesType.contains('stackedbar') == true) { for (int i = 0; i < seriesCollection.length; i++) { - _StackedSeriesRenderer? seriesRenderer; - if (seriesCollection[i] is _StackedSeriesRenderer) { - seriesRenderer = seriesCollection[i] as _StackedSeriesRenderer; + StackedSeriesRenderer? seriesRenderer; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesCollection[i]); + if (seriesCollection[i] is StackedSeriesRenderer) { + seriesRenderer = seriesCollection[i] as StackedSeriesRenderer; } if (seriesRenderer != null) { - seriesRenderer._rectCount = rectCount; + seriesRendererDetails.rectCount = rectCount; } } } } /// Find the column and bar series collection in axes. -List _findSeriesCollection( - SfCartesianChartState _chartState, +List findSeriesCollection( + CartesianStateProperties stateProperties, //ignore: unused_element [bool? isRect]) { final List seriesRendererCollection = []; for (int xAxisIndex = 0; - xAxisIndex < _chartState._chartAxis._horizontalAxisRenderers.length; + xAxisIndex < stateProperties.chartAxis.horizontalAxisRenderers.length; xAxisIndex++) { final ChartAxisRenderer xAxisRenderer = - _chartState._chartAxis._horizontalAxisRenderers[xAxisIndex]; + stateProperties.chartAxis.horizontalAxisRenderers[xAxisIndex]; + final ChartAxisRendererDetails xAxisRendererDetails = + AxisHelper.getAxisRendererDetails(xAxisRenderer); for (int xSeriesIndex = 0; - xSeriesIndex < xAxisRenderer._seriesRenderers.length; + xSeriesIndex < xAxisRendererDetails.seriesRenderers.length; xSeriesIndex++) { final CartesianSeriesRenderer xAxisSeriesRenderer = - xAxisRenderer._seriesRenderers[xSeriesIndex]; + xAxisRendererDetails.seriesRenderers[xSeriesIndex]; for (int yAxisIndex = 0; - yAxisIndex < _chartState._chartAxis._verticalAxisRenderers.length; + yAxisIndex < stateProperties.chartAxis.verticalAxisRenderers.length; yAxisIndex++) { final ChartAxisRenderer yAxisRenderer = - _chartState._chartAxis._verticalAxisRenderers[yAxisIndex]; + stateProperties.chartAxis.verticalAxisRenderers[yAxisIndex]; + final ChartAxisRendererDetails yAxisRendererDetails = + AxisHelper.getAxisRendererDetails(yAxisRenderer); for (int ySeriesIndex = 0; - ySeriesIndex < yAxisRenderer._seriesRenderers.length; + ySeriesIndex < yAxisRendererDetails.seriesRenderers.length; ySeriesIndex++) { final CartesianSeriesRenderer yAxisSeriesRenderer = - yAxisRenderer._seriesRenderers[ySeriesIndex]; + yAxisRendererDetails.seriesRenderers[ySeriesIndex]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(xAxisSeriesRenderer); if (xAxisSeriesRenderer == yAxisSeriesRenderer && - (xAxisSeriesRenderer._seriesType.contains('column') || - xAxisSeriesRenderer._seriesType.contains('bar') || - xAxisSeriesRenderer._seriesType.contains('hilo') || - xAxisSeriesRenderer._seriesType.contains('candle') || - xAxisSeriesRenderer._seriesType.contains('stackedarea') || - xAxisSeriesRenderer._seriesType.contains('stackedline') || - xAxisSeriesRenderer._seriesType == 'histogram' || - xAxisSeriesRenderer._seriesType == 'boxandwhisker') && - xAxisSeriesRenderer._visible!) { + (seriesRendererDetails.seriesType.contains('column') == true || + seriesRendererDetails.seriesType.contains('bar') == true || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('stackedarea') == + true || + seriesRendererDetails.seriesType.contains('stackedline') == + true || + seriesRendererDetails.seriesType == 'histogram' || + seriesRendererDetails.seriesType == 'boxandwhisker') && + seriesRendererDetails.visible! == true) { if (!seriesRendererCollection.contains(yAxisSeriesRenderer)) { seriesRendererCollection.add(yAxisSeriesRenderer); } @@ -951,8 +1195,8 @@ List _findSeriesCollection( return seriesRendererCollection; } -/// convert normal rect to rounded rect by using border radius -RRect _getRRectFromRect(Rect rect, BorderRadius borderRadius) { +/// Convert normal rect to rounded rect by using border radius +RRect getRRectFromRect(Rect rect, BorderRadius borderRadius) { return RRect.fromRectAndCorners( rect, bottomLeft: borderRadius.bottomLeft, @@ -964,38 +1208,46 @@ RRect _getRRectFromRect(Rect rect, BorderRadius borderRadius) { /// Find the rect series collection in axes. List _findRectSeriesCollection( - SfCartesianChartState _chartState) { + CartesianStateProperties stateProperties) { final List seriesRenderCollection = []; for (int xAxisIndex = 0; - xAxisIndex < _chartState._chartAxis._horizontalAxisRenderers.length; + xAxisIndex < stateProperties.chartAxis.horizontalAxisRenderers.length; xAxisIndex++) { final ChartAxisRenderer xAxisRenderer = - _chartState._chartAxis._horizontalAxisRenderers[xAxisIndex]; + stateProperties.chartAxis.horizontalAxisRenderers[xAxisIndex]; + final ChartAxisRendererDetails xAxisRendererDetails = + AxisHelper.getAxisRendererDetails(xAxisRenderer); for (int xSeriesIndex = 0; - xSeriesIndex < xAxisRenderer._seriesRenderers.length; + xSeriesIndex < xAxisRendererDetails.seriesRenderers.length; xSeriesIndex++) { final CartesianSeriesRenderer xAxisSeriesRenderer = - xAxisRenderer._seriesRenderers[xSeriesIndex]; + xAxisRendererDetails.seriesRenderers[xSeriesIndex]; for (int yAxisIndex = 0; - yAxisIndex < _chartState._chartAxis._verticalAxisRenderers.length; + yAxisIndex < stateProperties.chartAxis.verticalAxisRenderers.length; yAxisIndex++) { final ChartAxisRenderer yAxisRenderer = - _chartState._chartAxis._verticalAxisRenderers[yAxisIndex]; + stateProperties.chartAxis.verticalAxisRenderers[yAxisIndex]; + final ChartAxisRendererDetails yAxisRendererDetails = + AxisHelper.getAxisRendererDetails(yAxisRenderer); for (int ySeriesIndex = 0; - ySeriesIndex < yAxisRenderer._seriesRenderers.length; + ySeriesIndex < yAxisRendererDetails.seriesRenderers.length; ySeriesIndex++) { final CartesianSeriesRenderer yAxisSeriesRenderer = - yAxisRenderer._seriesRenderers[ySeriesIndex]; + yAxisRendererDetails.seriesRenderers[ySeriesIndex]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(xAxisSeriesRenderer); if (xAxisSeriesRenderer == yAxisSeriesRenderer && - (xAxisSeriesRenderer._seriesType.contains('column') || - xAxisSeriesRenderer._seriesType.contains('waterfall') || - xAxisSeriesRenderer._seriesType.contains('bar') || - xAxisSeriesRenderer._seriesType.contains('hilo') || - xAxisSeriesRenderer._seriesType == 'candle' || - xAxisSeriesRenderer._seriesType == 'histogram' || - xAxisSeriesRenderer._seriesType == 'boxandwhisker') && - xAxisSeriesRenderer._visible!) { + (seriesRendererDetails.seriesType.contains('column') == true || + seriesRendererDetails.seriesType.contains('waterfall') == + true || + (seriesRendererDetails.seriesType.contains('bar') == true && + !seriesRendererDetails.seriesType.contains('errorbar')) || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType == 'histogram' || + seriesRendererDetails.seriesType == 'boxandwhisker') && + seriesRendererDetails.visible! == true) { if (!seriesRenderCollection.contains(yAxisSeriesRenderer)) { seriesRenderCollection.add(yAxisSeriesRenderer); } @@ -1008,15 +1260,14 @@ List _findRectSeriesCollection( } /// To calculate plot offset -Rect _calculatePlotOffset(Rect axisClipRect, Offset plotOffset) => - Rect.fromLTWH( - axisClipRect.left + plotOffset.dx, - axisClipRect.top + plotOffset.dy, - axisClipRect.width - 2 * plotOffset.dx, - axisClipRect.height - 2 * plotOffset.dy); +Rect calculatePlotOffset(Rect axisClipRect, Offset plotOffset) => Rect.fromLTWH( + axisClipRect.left + plotOffset.dx, + axisClipRect.top + plotOffset.dy, + axisClipRect.width - 2 * plotOffset.dx, + axisClipRect.height - 2 * plotOffset.dy); ///Get gradient fill colors -Paint _getLinearGradientPaint( +Paint getLinearGradientPaint( LinearGradient gradientFill, Rect region, bool isInvertedAxis) { Paint gradientPaint; gradientPaint = Paint() @@ -1025,7 +1276,8 @@ Paint _getLinearGradientPaint( return gradientPaint; } -Paint _getShaderPaint(Shader shader) { +/// Method to get the shader paint +Paint getShaderPaint(Shader shader) { Paint shaderPaint; shaderPaint = Paint() ..shader = shader @@ -1033,8 +1285,8 @@ Paint _getShaderPaint(Shader shader) { return shaderPaint; } -/// It returns the actual label value for tooltip and data label etc -String _getLabelValue(dynamic value, dynamic axis, [int? showDigits]) { +/// Gets the the actual label value for tooltip and data label etc +String getLabelValue(dynamic value, dynamic axis, [int? showDigits]) { if (value.toString().split('.').length > 1) { final String str = value.toString(); final List list = str.split('.'); @@ -1059,7 +1311,7 @@ String _getLabelValue(dynamic value, dynamic axis, [int? showDigits]) { } /// Calculate the X value from the current screen point -double _pointToXValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, +double pointToXValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) { // ignore: unnecessary_null_comparison if (axisRenderer != null) { @@ -1073,7 +1325,7 @@ double _pointToXValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, /// Calculate the Y value from the current screen point // ignore: unused_element -double _pointToYValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, +double pointToYValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) { // ignore: unnecessary_null_comparison if (axisRenderer != null) { @@ -1085,122 +1337,132 @@ double _pointToYValue(bool _requireInvertedAxis, ChartAxisRenderer axisRenderer, return double.nan; } -/// To return coefficient-based value +/// Returns coefficient-based value double _coefficientToValue(double coefficient, ChartAxisRenderer axisRenderer) { double result; - coefficient = axisRenderer._axis.isInversed ? 1 - coefficient : coefficient; - result = axisRenderer._visibleRange!.minimum + - (axisRenderer._visibleRange!.delta * coefficient); + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + coefficient = + axisRendererDetails.axis.isInversed ? 1 - coefficient : coefficient; + result = axisRendererDetails.visibleRange!.minimum + + (axisRendererDetails.visibleRange!.delta * coefficient); return result; } /// To repaint chart and axes -void _needsRepaintChart( - SfCartesianChartState _chartState, +void needsRepaintChart( + CartesianStateProperties stateProperties, List oldChartAxisRenderers, List oldChartSeriesRenderers) { - if (_chartState._chartSeries.visibleSeriesRenderers.length == + if (stateProperties.chartSeries.visibleSeriesRenderers.length == oldChartSeriesRenderers.length) { for (int seriesIndex = 0; - seriesIndex < _chartState._seriesRenderers.length; + seriesIndex < stateProperties.seriesRenderers.length; seriesIndex++) { - _canRepaintChartSeries(_chartState, oldChartSeriesRenderers, seriesIndex); + _canRepaintChartSeries( + stateProperties, oldChartSeriesRenderers, seriesIndex); } } else { // ignore: avoid_function_literals_in_foreach_calls - _chartState._seriesRenderers.forEach((ChartSeriesRenderer seriesRenderer) => - seriesRenderer._needsRepaint = true); + stateProperties.seriesRenderers.forEach( + (CartesianSeriesRenderer seriesRenderer) => + SeriesHelper.getSeriesRendererDetails(seriesRenderer).needsRepaint = + true); } - if (_chartState._chartAxis._axisRenderersCollection.length == + if (stateProperties.chartAxis.axisRenderersCollection.length == oldChartAxisRenderers.length) { for (int axisIndex = 0; axisIndex < oldChartAxisRenderers.length; axisIndex++) { - _canRepaintAxis(_chartState, oldChartAxisRenderers, axisIndex); - if (_chartState._chartAxis._needsRepaint) { + _canRepaintAxis(stateProperties, oldChartAxisRenderers, axisIndex); + if (stateProperties.chartAxis.needsRepaint) { break; } } } else { - _chartState._chartAxis._needsRepaint = true; + stateProperties.chartAxis.needsRepaint = true; } } /// To check series repaint -void _canRepaintChartSeries(SfCartesianChartState _chartState, +void _canRepaintChartSeries(CartesianStateProperties stateProperties, List oldChartSeriesRenderers, int seriesIndex) { final CartesianSeriesRenderer seriesRenderer = - _chartState._seriesRenderers[seriesIndex]; - final CartesianSeries series = seriesRenderer._series; + stateProperties.seriesRenderers[seriesIndex]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final CartesianSeries series = seriesRendererDetails.series; final CartesianSeriesRenderer oldWidgetSeriesRenderer = oldChartSeriesRenderers[seriesIndex]; + final SeriesRendererDetails oldSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(oldWidgetSeriesRenderer); final CartesianSeries oldWidgetSeries = - oldWidgetSeriesRenderer._series; + oldSeriesRendererDetails.series; if (series.animationDuration != oldWidgetSeries.animationDuration || - oldWidgetSeriesRenderer._chartState!._chartSeries != - _chartState._chartSeries || + oldSeriesRendererDetails.stateProperties.chartSeries != + stateProperties.chartSeries || series.color?.value != oldWidgetSeries.color?.value || series.width != oldWidgetSeries.width || series.isVisible != oldWidgetSeries.isVisible || series.enableTooltip != oldWidgetSeries.enableTooltip || series.name != oldWidgetSeries.name || series.gradient != oldWidgetSeries.gradient || - seriesRenderer._xAxisRenderer!._visibleRange?.delta != - oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.delta || - seriesRenderer._xAxisRenderer!._visibleRange?.maximum != - oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.maximum || - seriesRenderer._xAxisRenderer!._visibleRange?.minimum != - oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.minimum || - seriesRenderer._xAxisRenderer!._visibleRange?.interval != - oldWidgetSeriesRenderer._xAxisRenderer!._visibleRange?.interval || - seriesRenderer._xAxisRenderer!._axis.isVisible != - oldWidgetSeriesRenderer._xAxisRenderer!._axis.isVisible || - seriesRenderer._xAxisRenderer!._bounds != - oldWidgetSeriesRenderer._xAxisRenderer!._bounds || - seriesRenderer._xAxisRenderer!._axis.isInversed != - oldWidgetSeriesRenderer._xAxisRenderer!._axis.isInversed || - seriesRenderer._xAxisRenderer!._axis.desiredIntervals != - oldWidgetSeriesRenderer._xAxisRenderer!._axis.desiredIntervals || - seriesRenderer._xAxisRenderer!._axis.enableAutoIntervalOnZooming != - oldWidgetSeriesRenderer - ._xAxisRenderer!._axis.enableAutoIntervalOnZooming || - seriesRenderer._xAxisRenderer!._axis.opposedPosition != - oldWidgetSeriesRenderer._xAxisRenderer!._axis.opposedPosition || - seriesRenderer._xAxisRenderer!._orientation != - oldWidgetSeriesRenderer._xAxisRenderer!._orientation || - seriesRenderer._xAxisRenderer!._axis.plotOffset != - oldWidgetSeriesRenderer._xAxisRenderer!._axis.plotOffset || - seriesRenderer._xAxisRenderer!._axis.rangePadding != - oldWidgetSeriesRenderer._xAxisRenderer!._axis.rangePadding || - seriesRenderer._dataPoints.length != - oldWidgetSeriesRenderer._dataPoints.length || - seriesRenderer._yAxisRenderer!._visibleRange?.delta != - oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.delta || - seriesRenderer._yAxisRenderer!._visibleRange?.maximum != - oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.maximum || - seriesRenderer._yAxisRenderer!._visibleRange?.minimum != - oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.minimum || - seriesRenderer._yAxisRenderer!._visibleRange?.interval != - oldWidgetSeriesRenderer._yAxisRenderer!._visibleRange?.interval || - seriesRenderer._yAxisRenderer!._axis.isVisible != - oldWidgetSeriesRenderer._yAxisRenderer!._axis.isVisible || - seriesRenderer._yAxisRenderer!._bounds != - oldWidgetSeriesRenderer._yAxisRenderer!._bounds || - seriesRenderer._yAxisRenderer!._axis.isInversed != - oldWidgetSeriesRenderer._yAxisRenderer!._axis.isInversed || - seriesRenderer._yAxisRenderer!._axis.desiredIntervals != - oldWidgetSeriesRenderer._yAxisRenderer!._axis.desiredIntervals || - seriesRenderer._yAxisRenderer!._axis.enableAutoIntervalOnZooming != - oldWidgetSeriesRenderer - ._yAxisRenderer!._axis.enableAutoIntervalOnZooming || - seriesRenderer._yAxisRenderer!._axis.opposedPosition != - oldWidgetSeriesRenderer._yAxisRenderer!._axis.opposedPosition || - seriesRenderer._yAxisRenderer!._orientation != - oldWidgetSeriesRenderer._yAxisRenderer!._orientation || - seriesRenderer._yAxisRenderer!._axis.plotOffset != - oldWidgetSeriesRenderer._yAxisRenderer!._axis.plotOffset || - seriesRenderer._yAxisRenderer!._axis.rangePadding != - oldWidgetSeriesRenderer._yAxisRenderer!._axis.rangePadding || + seriesRendererDetails.xAxisDetails!.visibleRange?.delta != + oldSeriesRendererDetails.xAxisDetails!.visibleRange?.delta || + seriesRendererDetails.xAxisDetails!.visibleRange?.maximum != + oldSeriesRendererDetails.xAxisDetails!.visibleRange?.maximum || + seriesRendererDetails.xAxisDetails!.visibleRange?.minimum != + oldSeriesRendererDetails.xAxisDetails!.visibleRange?.minimum || + seriesRendererDetails.xAxisDetails!.visibleRange?.interval != + oldSeriesRendererDetails.xAxisDetails!.visibleRange?.interval || + seriesRendererDetails.xAxisDetails!.axis.isVisible != + oldSeriesRendererDetails.xAxisDetails!.axis.isVisible || + seriesRendererDetails.xAxisDetails!.bounds != + oldSeriesRendererDetails.xAxisDetails!.bounds || + seriesRendererDetails.xAxisDetails!.axis.isInversed != + oldSeriesRendererDetails.xAxisDetails!.axis.isInversed || + seriesRendererDetails.xAxisDetails!.axis.desiredIntervals != + oldSeriesRendererDetails.xAxisDetails!.axis.desiredIntervals || + seriesRendererDetails.xAxisDetails!.axis.enableAutoIntervalOnZooming != + oldSeriesRendererDetails + .xAxisDetails!.axis.enableAutoIntervalOnZooming || + seriesRendererDetails.xAxisDetails!.axis.opposedPosition != + oldSeriesRendererDetails.xAxisDetails!.axis.opposedPosition || + seriesRendererDetails.xAxisDetails!.orientation != + oldSeriesRendererDetails.xAxisDetails!.orientation || + seriesRendererDetails.xAxisDetails!.axis.plotOffset != + oldSeriesRendererDetails.xAxisDetails!.axis.plotOffset || + seriesRendererDetails.xAxisDetails!.axis.rangePadding != + oldSeriesRendererDetails.xAxisDetails!.axis.rangePadding || + seriesRendererDetails.dataPoints.length != + oldSeriesRendererDetails.dataPoints.length || + seriesRendererDetails.yAxisDetails!.visibleRange?.delta != + oldSeriesRendererDetails.yAxisDetails!.visibleRange?.delta || + seriesRendererDetails.yAxisDetails!.visibleRange?.maximum != + oldSeriesRendererDetails.yAxisDetails!.visibleRange?.maximum || + seriesRendererDetails.yAxisDetails!.visibleRange?.minimum != + oldSeriesRendererDetails.yAxisDetails!.visibleRange?.minimum || + seriesRendererDetails.yAxisDetails!.visibleRange?.interval != + oldSeriesRendererDetails.yAxisDetails!.visibleRange?.interval || + seriesRendererDetails.yAxisDetails!.axis.isVisible != + oldSeriesRendererDetails.yAxisDetails!.axis.isVisible || + seriesRendererDetails.yAxisDetails!.bounds != + oldSeriesRendererDetails.yAxisDetails!.bounds || + seriesRendererDetails.yAxisDetails!.axis.isInversed != + oldSeriesRendererDetails.yAxisDetails!.axis.isInversed || + seriesRendererDetails.yAxisDetails!.axis.desiredIntervals != + oldSeriesRendererDetails.yAxisDetails!.axis.desiredIntervals || + seriesRendererDetails.yAxisDetails!.axis.enableAutoIntervalOnZooming != + oldSeriesRendererDetails + .yAxisDetails!.axis.enableAutoIntervalOnZooming || + seriesRendererDetails.yAxisDetails!.axis.opposedPosition != + oldSeriesRendererDetails.yAxisDetails!.axis.opposedPosition || + seriesRendererDetails.yAxisDetails!.orientation != + oldSeriesRendererDetails.yAxisDetails!.orientation || + seriesRendererDetails.yAxisDetails!.axis.plotOffset != + oldSeriesRendererDetails.yAxisDetails!.axis.plotOffset || + seriesRendererDetails.yAxisDetails!.axis.rangePadding != + oldSeriesRendererDetails.yAxisDetails!.axis.rangePadding || series.animationDuration != oldWidgetSeries.animationDuration || series.borderColor != oldWidgetSeries.borderColor || series.borderWidth != oldWidgetSeries.borderWidth || @@ -1213,10 +1475,10 @@ void _canRepaintChartSeries(SfCartesianChartState _chartState, oldWidgetSeries.emptyPointSettings.color || series.emptyPointSettings.mode != oldWidgetSeries.emptyPointSettings.mode || - seriesRenderer._maximumX != oldWidgetSeriesRenderer._maximumX || - seriesRenderer._maximumY != oldWidgetSeriesRenderer._maximumY || - seriesRenderer._minimumX != oldWidgetSeriesRenderer._minimumX || - seriesRenderer._minimumY != oldWidgetSeriesRenderer._minimumY || + seriesRendererDetails.maximumX != oldSeriesRendererDetails.maximumX || + seriesRendererDetails.maximumY != oldSeriesRendererDetails.maximumY || + seriesRendererDetails.minimumX != oldSeriesRendererDetails.minimumX || + seriesRendererDetails.minimumY != oldSeriesRendererDetails.minimumY || series.dashArray.length != oldWidgetSeries.dashArray.length || series.dataSource.length != oldWidgetSeries.dataSource.length || series.markerSettings.width != oldWidgetSeries.markerSettings.width || @@ -1266,108 +1528,119 @@ void _canRepaintChartSeries(SfCartesianChartState _chartState, oldWidgetSeries.dataLabelSettings.margin.left || series.dataLabelSettings.borderRadius != oldWidgetSeries.dataLabelSettings.borderRadius) { - seriesRenderer._needsRepaint = true; + seriesRendererDetails.needsRepaint = true; } else { - seriesRenderer._needsRepaint = false; + seriesRendererDetails.needsRepaint = false; } } /// To check axis repaint -void _canRepaintAxis(SfCartesianChartState _chartState, +void _canRepaintAxis(CartesianStateProperties stateProperties, List oldChartAxisRenderers, int axisIndex) { // ignore: unnecessary_null_comparison - if (_chartState._chartAxis._axisRenderersCollection != null && - _chartState._chartAxis._axisRenderersCollection.isNotEmpty) { + if (stateProperties.chartAxis.axisRenderersCollection != null && + stateProperties.chartAxis.axisRenderersCollection.isNotEmpty) { final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[axisIndex]; + stateProperties.chartAxis.axisRenderersCollection[axisIndex]; final ChartAxisRenderer oldWidgetAxisRenderer = oldChartAxisRenderers[axisIndex]; - final ChartAxis axis = axisRenderer._axis, - oldWidgetAxis = oldWidgetAxisRenderer._axis; + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxisRendererDetails oldAxisRendererDetails = + AxisHelper.getAxisRendererDetails(oldWidgetAxisRenderer); + final ChartAxis axis = axisRendererDetails.axis; + final ChartAxis oldWidgetAxis = oldAxisRendererDetails.axis; if (axis.rangePadding.index != oldWidgetAxis.rangePadding.index || axis.plotOffset != oldWidgetAxis.plotOffset || - axisRenderer._orientation != oldWidgetAxisRenderer._orientation || + axisRendererDetails.orientation != oldAxisRendererDetails.orientation || axis.opposedPosition != oldWidgetAxis.opposedPosition || axis.minorTicksPerInterval != oldWidgetAxis.minorTicksPerInterval || axis.desiredIntervals != oldWidgetAxis.desiredIntervals || axis.isInversed != oldWidgetAxis.isInversed || - axisRenderer._bounds != oldWidgetAxisRenderer._bounds || + axisRendererDetails.bounds != oldAxisRendererDetails.bounds || axis.majorGridLines.dashArray?.length != oldWidgetAxis.majorGridLines.dashArray?.length || axis.majorGridLines.width != oldWidgetAxis.majorGridLines.width || axis.majorGridLines.color != oldWidgetAxis.majorGridLines.color || axis.title != oldWidgetAxis.title || - axisRenderer._visibleRange?.interval != - oldWidgetAxisRenderer._visibleRange?.interval || - axisRenderer._visibleRange?.minimum != - oldWidgetAxisRenderer._visibleRange?.minimum || - axisRenderer._visibleRange?.maximum != - oldWidgetAxisRenderer._visibleRange?.maximum || - axisRenderer._visibleRange?.delta != - oldWidgetAxisRenderer._visibleRange?.delta || - axisRenderer._isInsideTickPosition != - oldWidgetAxisRenderer._isInsideTickPosition || + axisRendererDetails.visibleRange?.interval != + oldAxisRendererDetails.visibleRange?.interval || + axisRendererDetails.visibleRange?.minimum != + oldAxisRendererDetails.visibleRange?.minimum || + axisRendererDetails.visibleRange?.maximum != + oldAxisRendererDetails.visibleRange?.maximum || + axisRendererDetails.visibleRange?.delta != + oldAxisRendererDetails.visibleRange?.delta || + axisRendererDetails.isInsideTickPosition != + oldAxisRendererDetails.isInsideTickPosition || axis.maximumLabels != oldWidgetAxis.maximumLabels || axis.minorGridLines.dashArray?.length != oldWidgetAxis.minorGridLines.dashArray?.length || axis.minorGridLines.width != oldWidgetAxis.minorGridLines.width || axis.minorGridLines.color != oldWidgetAxis.minorGridLines.color || axis.tickPosition.index != oldWidgetAxis.tickPosition.index) { - _chartState._chartAxis._needsRepaint = true; + stateProperties.chartAxis.needsRepaint = true; } else { - _chartState._chartAxis._needsRepaint = false; + stateProperties.chartAxis.needsRepaint = false; } } } -// To get interactive tooltip label -dynamic _getInteractiveTooltipLabel( +/// To get interactive tooltip label +dynamic getInteractiveTooltipLabel( dynamic value, ChartAxisRenderer axisRenderer) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final ChartAxis axis = axisRendererDetails.axis; if (axisRenderer is CategoryAxisRenderer) { + final CategoryAxisDetails categoryAxisRendererDetails = + axisRendererDetails as CategoryAxisDetails; value = value < 0 == true ? 0 : value; - value = axisRenderer._labels[ - (value.round() >= axisRenderer._labels.length == true - ? (value.round() > axisRenderer._labels.length == true - ? axisRenderer._labels.length - 1 + value = categoryAxisRendererDetails.labels[ + (value.round() >= categoryAxisRendererDetails.labels.length == true + ? (value.round() > categoryAxisRendererDetails.labels.length == + true + ? categoryAxisRendererDetails.labels.length - 1 : value - 1) : value) .round()]; } else if (axisRenderer is DateTimeCategoryAxisRenderer) { + final DateTimeCategoryAxisDetails dateTimeAxisDetails = + axisRendererDetails as DateTimeCategoryAxisDetails; value = value < 0 == true ? 0 : value; - value = axisRenderer._labels[ - (value.round() >= axisRenderer._labels.length == true - ? (value.round() > axisRenderer._labels.length == true - ? axisRenderer._labels.length - 1 + value = dateTimeAxisDetails.labels[ + (value.round() >= dateTimeAxisDetails.labels.length == true + ? (value.round() > dateTimeAxisDetails.labels.length == true + ? dateTimeAxisDetails.labels.length - 1 : value - 1) : value) .round()]; } else if (axisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis _dateTimeAxis = axisRenderer._axis as DateTimeAxis; + final DateTimeAxis _dateTimeAxis = axisRendererDetails.axis as DateTimeAxis; final DateFormat dateFormat = - _dateTimeAxis.dateFormat ?? _getDateTimeLabelFormat(axisRenderer); + _dateTimeAxis.dateFormat ?? getDateTimeLabelFormat(axisRenderer); value = dateFormat.format(DateTime.fromMillisecondsSinceEpoch(value.toInt())); } else { - value = _getLabelValue(value, axis, axis.interactiveTooltip.decimalPlaces); + value = getLabelValue(value, axis, axis.interactiveTooltip.decimalPlaces); } return value; } -/// It returns the path of marker shapes -Path _getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, - [CartesianSeriesRenderer? seriesRenderer, +/// Returns the path of marker shapes +Path getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, + [SeriesRendererDetails? seriesRendererDetails, int? index, TrackballBehavior? trackballBehavior, Animation? animationController, ChartSegment? segment]) { - if (seriesRenderer?._chart.onMarkerRender != null && - !seriesRenderer!._isMarkerRenderEvent) { - final MarkerRenderArgs event = _triggerMarkerRenderEvent( - seriesRenderer, + if (seriesRendererDetails!.chart.onMarkerRender != null && + seriesRendererDetails.isMarkerRenderEvent == false) { + final MarkerRenderArgs event = triggerMarkerRenderEvent( + seriesRendererDetails, size, markerType, - seriesRenderer._dataPoints[index!].visiblePointIndex!, + seriesRendererDetails.dataPoints[index!].visiblePointIndex!, animationController, segment)!; markerType = event.shape; @@ -1379,38 +1652,38 @@ Path _getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, switch (markerType) { case DataMarkerType.circle: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.circle); } break; case DataMarkerType.rectangle: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.rectangle); } break; case DataMarkerType.image: { - if (seriesRenderer?._series != null) { - _loadMarkerImage(seriesRenderer!, trackballBehavior); + if (seriesRendererDetails.series != null) { + _loadMarkerImage(seriesRendererDetails.renderer, trackballBehavior); } } break; case DataMarkerType.pentagon: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.pentagon); } break; case DataMarkerType.verticalLine: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.verticalLine); } break; case DataMarkerType.invertedTriangle: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.invertedTriangle); @@ -1418,19 +1691,19 @@ Path _getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, break; case DataMarkerType.horizontalLine: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.horizontalLine); } break; case DataMarkerType.diamond: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.diamond); } break; case DataMarkerType.triangle: { - ShapePainter.getShapesPath( + getShapesPath( path: path, rect: rect, shapeType: ShapeMarkerType.triangle); } break; @@ -1440,11 +1713,17 @@ Path _getMarkerShapesPath(DataMarkerType markerType, Offset position, Size size, return path; } -class _StackingInfo { - _StackingInfo(this.groupName, this._stackingValues); +/// Represents the stacking info class +class StackingInfo { + /// Creates an instance of stacking info class + StackingInfo(this.groupName, this.stackingValues); + + /// Holds the group name String groupName; + + /// Holds the list of stacking values // ignore: prefer_final_fields - List? _stackingValues; + List? stackingValues; } class _StackingGroup { @@ -1458,8 +1737,10 @@ class _StackingGroup { // ignore: avoid_void_async void _loadMarkerImage(CartesianSeriesRenderer seriesRenderer, TrackballBehavior? trackballBehavior) async { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; if ((trackballBehavior != null && trackballBehavior.markerSettings != null && trackballBehavior.markerSettings!.shape == DataMarkerType.image && @@ -1467,50 +1748,58 @@ void _loadMarkerImage(CartesianSeriesRenderer seriesRenderer, // ignore: unnecessary_null_comparison (series.markerSettings != null && (series.markerSettings.isVisible || - seriesRenderer._seriesType == 'scatter') && + seriesRendererDetails.seriesType == 'scatter') && series.markerSettings.shape == DataMarkerType.image && series.markerSettings.image != null)) { - _calculateImage( - seriesRenderer._chartState!, seriesRenderer, trackballBehavior); + calculateImage(seriesRendererDetails.stateProperties, seriesRendererDetails, + trackballBehavior); } } -/// It returns the chart location of the annotation -_ChartLocation _getAnnotationLocation( - CartesianChartAnnotation annotation, SfCartesianChartState _chartState) { +/// Gets the chart location of the annotation +ChartLocation getAnnotationLocation(CartesianChartAnnotation annotation, + CartesianStateProperties stateProperties) { final String? xAxisName = annotation.xAxisName; final String? yAxisName = annotation.yAxisName; ChartAxisRenderer? xAxisRenderer, yAxisRenderer; num? xValue; Rect axisClipRect; - _ChartLocation? location; + ChartLocation? location; if (annotation.coordinateUnit == CoordinateUnit.logicalPixel) { location = annotation.region == AnnotationRegion.chart - ? _ChartLocation(annotation.x.toDouble(), annotation.y.toDouble()) - : _ChartLocation( - _chartState._chartAxis._axisClipRect.left + annotation.x, - _chartState._chartAxis._axisClipRect.top + annotation.y); - } else { + ? ChartLocation(annotation.x.toDouble(), annotation.y.toDouble()) + : ChartLocation( + stateProperties.chartAxis.axisClipRect.left + annotation.x, + stateProperties.chartAxis.axisClipRect.top + annotation.y); + } else if (annotation.coordinateUnit == CoordinateUnit.point) { for (int i = 0; - i < _chartState._chartAxis._axisRenderersCollection.length; + i < stateProperties.chartAxis.axisRenderersCollection.length; i++) { final ChartAxisRenderer axisRenderer = - _chartState._chartAxis._axisRenderersCollection[i]; - if (xAxisName == axisRenderer._name || - (xAxisName == null && axisRenderer._name == 'primaryXAxis')) { + stateProperties.chartAxis.axisRenderersCollection[i]; + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + if (xAxisName == axisRendererDetails.name || + (xAxisName == null && axisRendererDetails.name == 'primaryXAxis')) { xAxisRenderer = axisRenderer; if (xAxisRenderer is CategoryAxisRenderer) { + final CategoryAxisDetails categoryAxisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) + as CategoryAxisDetails; if (annotation.x != null && num.tryParse(annotation.x.toString()) != null) { xValue = num.tryParse(annotation.x.toString())!; - } else if (xAxisRenderer._labels.isNotEmpty) { - xValue = xAxisRenderer._labels.indexOf(annotation.x); + } else if (annotation.x is String) { + xValue = categoryAxisDetails.labels.indexOf(annotation.x); } } else if (xAxisRenderer is DateTimeAxisRenderer) { xValue = annotation.x is DateTime ? (annotation.x).millisecondsSinceEpoch : annotation.x; } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) { + final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = + AxisHelper.getAxisRendererDetails(xAxisRenderer) + as DateTimeCategoryAxisDetails; if (annotation.x != null && num.tryParse(annotation.x.toString()) != null) { xValue = num.tryParse(annotation.x.toString())!; @@ -1518,33 +1807,80 @@ _ChartLocation _getAnnotationLocation( xValue = annotation.x is num ? annotation.x : (annotation.x is DateTime - ? xAxisRenderer._labels - .indexOf(xAxisRenderer._dateFormat.format(annotation.x)) - : xAxisRenderer._labels.indexOf(annotation.x)); + ? dateTimeCategoryAxisDetails.labels.indexOf( + dateTimeCategoryAxisDetails.dateFormat + .format(annotation.x)) + : dateTimeCategoryAxisDetails.labels.indexOf(annotation.x)); } } else { xValue = annotation.x; } - } else if (yAxisName == axisRenderer._name || - (yAxisName == null && axisRenderer._name == 'primaryYAxis')) { + } else if (yAxisName == axisRendererDetails.name || + (yAxisName == null && axisRendererDetails.name == 'primaryYAxis')) { yAxisRenderer = axisRenderer; } } if (xAxisRenderer != null && yAxisRenderer != null) { - axisClipRect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, + axisClipRect = calculatePlotOffset( + stateProperties.chartAxis.axisClipRect, Offset( - xAxisRenderer._axis.plotOffset, yAxisRenderer._axis.plotOffset)); - location = _calculatePoint(xValue!, annotation.y, xAxisRenderer, - yAxisRenderer, _chartState._requireInvertedAxis, null, axisClipRect); + AxisHelper.getAxisRendererDetails(xAxisRenderer).axis.plotOffset, + AxisHelper.getAxisRendererDetails(yAxisRenderer) + .axis + .plotOffset)); + location = calculatePoint( + xValue!, + annotation.y, + AxisHelper.getAxisRendererDetails(xAxisRenderer), + AxisHelper.getAxisRendererDetails(yAxisRenderer), + stateProperties.requireInvertedAxis, + null, + axisClipRect); + } + } else { + if (annotation.region == AnnotationRegion.chart) { + location = ChartLocation( + stateProperties.renderingDetails.chartContainerRect.left + + percentageToValue( + annotation.x.contains('%') == true + ? annotation.x + : annotation.x + '%', + stateProperties + .renderingDetails.chartContainerRect.right)! + .toDouble(), + stateProperties.renderingDetails.chartContainerRect.top + + percentageToValue( + annotation.y.contains('%') == true + ? annotation.y + : annotation.y + '%', + stateProperties + .renderingDetails.chartContainerRect.bottom)! + .toDouble()); + } else { + location = ChartLocation( + stateProperties.chartAxis.axisClipRect.left + + percentageToValue( + annotation.x.contains('%') == true + ? annotation.x + : annotation.x + '%', + stateProperties.chartAxis.axisClipRect.right - + stateProperties.chartAxis.axisClipRect.left)! + .toDouble(), + stateProperties.chartAxis.axisClipRect.top + + percentageToValue( + annotation.y.contains('%') == true + ? annotation.y + : annotation.y + '%', + stateProperties.chartAxis.axisClipRect.bottom)! + .toDouble()); } } return location!; } /// Draw tooltip arrow head -void _drawTooltipArrowhead( +void drawTooltipArrowhead( Canvas canvas, Path backgroundPath, Paint fillPaint, @@ -1568,7 +1904,7 @@ void _drawTooltipArrowhead( } /// Calculate rounded rect from rect and corner radius -RRect _getRoundedCornerRect(Rect rect, double cornerRadius) => +RRect getRoundedCornerRect(Rect rect, double cornerRadius) => RRect.fromRectAndCorners( rect, bottomLeft: Radius.circular(cornerRadius), @@ -1578,7 +1914,7 @@ RRect _getRoundedCornerRect(Rect rect, double cornerRadius) => ); /// Calculate the X value from the current screen point -double _pointToXVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, +double pointToXVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) { // ignore: unnecessary_null_comparison if (axisRenderer != null) { @@ -1588,7 +1924,7 @@ double _pointToXVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, } /// Calculate the Y value from the current screen point -double _pointToYVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, +double pointToYVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, Rect rect, double x, double y) { // ignore: unnecessary_null_comparison if (axisRenderer != null) { @@ -1597,50 +1933,52 @@ double _pointToYVal(SfCartesianChart chart, ChartAxisRenderer axisRenderer, return double.nan; } -/// It returns the x position of validated rect -Rect _validateRectXPosition(Rect labelRect, SfCartesianChartState _chartState) { +/// Gets the x position of validated rect +Rect validateRectXPosition( + Rect labelRect, CartesianStateProperties stateProperties) { Rect validatedRect = labelRect; - if (labelRect.right >= _chartState._chartAxis._axisClipRect.right) { + if (labelRect.right >= stateProperties.chartAxis.axisClipRect.right) { validatedRect = Rect.fromLTRB( labelRect.left - - (labelRect.right - _chartState._chartAxis._axisClipRect.right), + (labelRect.right - stateProperties.chartAxis.axisClipRect.right), labelRect.top, - _chartState._chartAxis._axisClipRect.right, + stateProperties.chartAxis.axisClipRect.right, labelRect.bottom); - } else if (labelRect.left <= _chartState._chartAxis._axisClipRect.left) { + } else if (labelRect.left <= stateProperties.chartAxis.axisClipRect.left) { validatedRect = Rect.fromLTRB( - _chartState._chartAxis._axisClipRect.left, + stateProperties.chartAxis.axisClipRect.left, labelRect.top, labelRect.right + - (_chartState._chartAxis._axisClipRect.left - labelRect.left), + (stateProperties.chartAxis.axisClipRect.left - labelRect.left), labelRect.bottom); } return validatedRect; } -/// It returns the y position of validated rect -Rect _validateRectYPosition(Rect labelRect, SfCartesianChartState _chartState) { +/// Gets the y position of validated rect +Rect validateRectYPosition( + Rect labelRect, CartesianStateProperties stateProperties) { Rect validatedRect = labelRect; - if (labelRect.bottom >= _chartState._chartAxis._axisClipRect.bottom) { + if (labelRect.bottom >= stateProperties.chartAxis.axisClipRect.bottom) { validatedRect = Rect.fromLTRB( labelRect.left, labelRect.top - - (labelRect.bottom - _chartState._chartAxis._axisClipRect.bottom), + (labelRect.bottom - stateProperties.chartAxis.axisClipRect.bottom), labelRect.right, - _chartState._chartAxis._axisClipRect.bottom); - } else if (labelRect.top <= _chartState._chartAxis._axisClipRect.top) { + stateProperties.chartAxis.axisClipRect.bottom); + } else if (labelRect.top <= stateProperties.chartAxis.axisClipRect.top) { validatedRect = Rect.fromLTRB( labelRect.left, - _chartState._chartAxis._axisClipRect.top, + stateProperties.chartAxis.axisClipRect.top, labelRect.right, labelRect.bottom + - (_chartState._chartAxis._axisClipRect.top - labelRect.top)); + (stateProperties.chartAxis.axisClipRect.top - labelRect.top)); } return validatedRect; } /// This method will validate whether the tooltip exceeds the screen or not -Rect _validateRectBounds(Rect tooltipRect, Rect boundary) { +Rect validateRectBounds(Rect tooltipRect, Rect boundary) { Rect validatedRect = tooltipRect; double difference = 0; @@ -1685,7 +2023,7 @@ Rect _validateRectBounds(Rect tooltipRect, Rect boundary) { } /// To render a rect for stacked series -void _renderStackingRectSeries( +void renderStackingRectSeries( Paint? fillPaint, Paint? strokePaint, Path path, @@ -1695,48 +2033,53 @@ void _renderStackingRectSeries( RRect segmentRect, CartesianChartPoint _currentPoint, int currentSegmentIndex) { - final _StackedSeriesBase series = - seriesRenderer._series as _StackedSeriesBase; - if (seriesRenderer._isSelectionEnable) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final StackedSeriesBase series = + seriesRendererDetails.series as StackedSeriesBase; + if (seriesRendererDetails.isSelectionEnable == true) { final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - seriesRenderer._segments[currentSegmentIndex], seriesRenderer._chart); + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[currentSegmentIndex], + seriesRendererDetails.chart); } if (fillPaint != null) { series.animationDuration > 0 - ? _animateStackedRectSeries( + ? animateStackedRectSeries( canvas, segmentRect, fillPaint, - seriesRenderer, + seriesRendererDetails, animationFactor, _currentPoint, - seriesRenderer._chartState!) + seriesRendererDetails.stateProperties) : canvas.drawRRect(segmentRect, fillPaint); } if (strokePaint != null) { if (series.dashArray[0] != 0 && series.dashArray[1] != 0) { final XyDataSeries _series = - seriesRenderer._series as XyDataSeries; - _drawDashedLine(canvas, _series.dashArray, strokePaint, path); + seriesRendererDetails.series as XyDataSeries; + drawDashedLine(canvas, _series.dashArray, strokePaint, path); } else { series.animationDuration > 0 - ? _animateStackedRectSeries( + ? animateStackedRectSeries( canvas, segmentRect, strokePaint, - seriesRenderer, + seriesRendererDetails, animationFactor, _currentPoint, - seriesRenderer._chartState!) + seriesRendererDetails.stateProperties) : canvas.drawRRect(segmentRect, strokePaint); } } } /// Draw stacked area path -void _drawStackedAreaPath( +void drawStackedAreaPath( Path _path, Path _strokePath, CartesianSeriesRenderer seriesRenderer, @@ -1746,31 +2089,37 @@ void _drawStackedAreaPath( Rect _pathRect; dynamic stackedAreaSegment; _pathRect = _path.getBounds(); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); final XyDataSeries _series = - seriesRenderer._series as XyDataSeries; - stackedAreaSegment = seriesRenderer._segments[0]; - stackedAreaSegment._pathRect = _pathRect; - if (seriesRenderer._isSelectionEnable) { + seriesRendererDetails.series as XyDataSeries; + stackedAreaSegment = seriesRendererDetails.segments[0]; + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(stackedAreaSegment); + segmentProperties.pathRect = _pathRect; + if (seriesRendererDetails.isSelectionEnable == true) { final SelectionBehaviorRenderer? selectionBehaviorRenderer = - seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer?._selectionRenderer?._checkWithSelectionState( - seriesRenderer._segments[0], seriesRenderer._chart); + seriesRendererDetails.selectionBehaviorRenderer; + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer!) + .selectionRenderer + ?.checkWithSelectionState( + seriesRendererDetails.segments[0], seriesRendererDetails.chart); } canvas.drawPath( _path, (_series.gradient == null) ? fillPaint - : seriesRenderer._segments[0].getFillPaint()); - strokePaint = seriesRenderer._segments[0].getStrokePaint(); + : seriesRendererDetails.segments[0].getFillPaint()); + strokePaint = seriesRendererDetails.segments[0].getStrokePaint(); if (strokePaint.color != Colors.transparent) { - _drawDashedLine(canvas, _series.dashArray, strokePaint, _strokePath); + drawDashedLine(canvas, _series.dashArray, strokePaint, _strokePath); } } /// Render stacked line series -void _renderStackedLineSeries( - _StackedSeriesBase series, +void renderStackedLineSeries( + StackedSeriesBase series, Canvas canvas, Paint strokePaint, double x1, @@ -1780,86 +2129,96 @@ void _renderStackedLineSeries( final Path path = Path(); path.moveTo(x1, y1); path.lineTo(x2, y2); - _drawDashedLine(canvas, series.dashArray, strokePaint, path); + drawDashedLine(canvas, series.dashArray, strokePaint, path); } /// Painter method for stacked area series -void _stackedAreaPainter( +void stackedAreaPainter( Canvas canvas, dynamic seriesRenderer, - SfCartesianChartState _chartState, + CartesianStateProperties _stateProperties, Animation? seriesAnimation, Animation? chartElementAnimation, - _PainterKey painterKey) { + PainterKey painterKey) { Rect clipRect, axisClipRect; final int seriesIndex = painterKey.index; - final SfCartesianChart chart = _chartState._chart; - final _RenderingDetails _renderingDetails = _chartState._renderingDetails; - seriesRenderer._storeSeriesProperties(_chartState, seriesIndex); + final SfCartesianChart chart = _stateProperties.chart; + final RenderingDetails _renderingDetails = _stateProperties.renderingDetails; + seriesRenderer.seriesRendererDetails + .storeSeriesProperties(_stateProperties.chartState, seriesIndex); double animationFactor; - final num? crossesAt = _getCrossesAtValue(seriesRenderer, _chartState); + final num? crossesAt = getCrossesAtValue(seriesRenderer, _stateProperties); final List> dataPoints = - seriesRenderer._dataPoints; + seriesRenderer.seriesRendererDetails.dataPoints; /// Clip rect will be added for series. - if (seriesRenderer._visible! == true) { - final dynamic series = seriesRenderer._series; + if (seriesRenderer.seriesRendererDetails.visible! == true) { + final dynamic series = seriesRenderer.seriesRendererDetails.series; canvas.save(); - axisClipRect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + axisClipRect = calculatePlotOffset( + _stateProperties.chartAxis.axisClipRect, + Offset( + seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, + seriesRenderer + .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); canvas.clipRect(axisClipRect); animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; - if (seriesRenderer._reAnimate == true || + if (seriesRenderer.seriesRendererDetails.reAnimate == true || ((!(_renderingDetails.widgetNeedUpdate || _renderingDetails.isLegendToggled) || - !_chartState._oldSeriesKeys.contains(series.key)) && + !_stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0 == true)) { - _performLinearAnimation(_chartState, seriesRenderer._xAxisRenderer!._axis, - canvas, animationFactor); + performLinearAnimation( + _stateProperties, + seriesRenderer.seriesRendererDetails.xAxisDetails!.axis, + canvas, + animationFactor); } final Path _path = Path(), _strokePath = Path(); - final Rect rect = _chartState._chartAxis._axisClipRect; - _ChartLocation point1, point2; - final ChartAxisRenderer xAxisRenderer = seriesRenderer._xAxisRenderer!, - yAxisRenderer = seriesRenderer._yAxisRenderer!; + final Rect rect = _stateProperties.chartAxis.axisClipRect; + ChartLocation point1, point2; + final ChartAxisRendererDetails xAxisDetails = + seriesRenderer.seriesRendererDetails.xAxisDetails!, + yAxisDetails = seriesRenderer.seriesRendererDetails.yAxisDetails!; CartesianChartPoint? point; - final dynamic _series = seriesRenderer._series; + final dynamic _series = seriesRenderer.seriesRendererDetails.series; final List _points = []; if (dataPoints.isNotEmpty) { int startPoint = 0; - final _StackedValues stackedValues = seriesRenderer._stackingValues[0]; + final StackedValues stackedValues = + seriesRenderer.seriesRendererDetails.stackingValues[0]; List seriesRendererCollection; CartesianSeriesRenderer previousSeriesRenderer; - seriesRendererCollection = _findSeriesCollection(_chartState); - point1 = _calculatePoint( + seriesRendererCollection = findSeriesCollection(_stateProperties); + point1 = calculatePoint( dataPoints[0].xValue, - math_lib.max(yAxisRenderer._visibleRange!.minimum, + math_lib.max(yAxisDetails.visibleRange!.minimum, crossesAt ?? stackedValues.startValues[0]), - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + _stateProperties.requireInvertedAxis, _series, rect); _path.moveTo(point1.x, point1.y); _strokePath.moveTo(point1.x, point1.y); - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty == true) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRenderer.seriesRendererDetails.visibleDataPoints == null || + seriesRenderer.seriesRendererDetails.visibleDataPoints!.isNotEmpty == + true) { + seriesRenderer.seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; pointIndex < dataPoints.length; pointIndex++) { point = dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - _chartState, seriesRenderer, seriesIndex, point, pointIndex); + seriesRenderer.seriesRendererDetails.calculateRegionData( + _stateProperties, seriesRenderer, seriesIndex, point, pointIndex); if (point.isVisible) { - point1 = _calculatePoint( + point1 = calculatePoint( dataPoints[pointIndex].xValue, stackedValues.endValues[pointIndex], - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + _stateProperties.requireInvertedAxis, _series, rect); _points.add(Offset(point1.x, point1.y)); @@ -1868,12 +2227,12 @@ void _stackedAreaPainter( } else { if (_series.emptyPointSettings.mode != EmptyPointMode.drop) { for (int j = pointIndex - 1; j >= startPoint; j--) { - point2 = _calculatePoint( + point2 = calculatePoint( dataPoints[j].xValue, crossesAt ?? stackedValues.startValues[j], - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + _stateProperties.requireInvertedAxis, _series, rect); _path.lineTo(point2.x, point2.y); @@ -1887,12 +2246,12 @@ void _stackedAreaPainter( // ignore: unnecessary_null_comparison dataPoints[pointIndex + 1] != null && dataPoints[pointIndex + 1].isVisible) { - point1 = _calculatePoint( + point1 = calculatePoint( dataPoints[pointIndex + 1].xValue, crossesAt ?? stackedValues.startValues[pointIndex + 1], - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + _stateProperties.requireInvertedAxis, _series, rect); _path.moveTo(point1.x, point1.y); @@ -1908,16 +2267,18 @@ void _stackedAreaPainter( } for (int j = dataPoints.length - 1; j >= startPoint; j--) { previousSeriesRenderer = - _getPreviousSeriesRenderer(seriesRendererCollection, seriesIndex); - if (previousSeriesRenderer._series.emptyPointSettings.mode != + getPreviousSeriesRenderer(seriesRendererCollection, seriesIndex); + final SeriesRendererDetails previousSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(previousSeriesRenderer); + if (previousSeriesRendererDetails.series.emptyPointSettings.mode != EmptyPointMode.drop || - previousSeriesRenderer._dataPoints[j].isVisible) { - point2 = _calculatePoint( + previousSeriesRendererDetails.dataPoints[j].isVisible == true) { + point2 = calculatePoint( dataPoints[j].xValue, crossesAt ?? stackedValues.startValues[j], - xAxisRenderer, - yAxisRenderer, - _chartState._requireInvertedAxis, + xAxisDetails, + yAxisDetails, + _stateProperties.requireInvertedAxis, _series, rect); _path.lineTo(point2.x, point2.y); @@ -1931,42 +2292,46 @@ void _stackedAreaPainter( } // ignore: unnecessary_null_comparison if (_path != null && - seriesRenderer._segments != null && - seriesRenderer._segments.isNotEmpty == true) { - final dynamic areaSegment = seriesRenderer._segments[0]; - seriesRenderer._drawSegment( + seriesRenderer.seriesRendererDetails.segments != null && + seriesRenderer.seriesRendererDetails.segments.isNotEmpty == true) { + final dynamic areaSegment = + seriesRenderer.seriesRendererDetails.segments[0]; + seriesRenderer.seriesRendererDetails.drawSegment( canvas, areaSegment .._path = _path .._strokePath = _strokePath); } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( rect.left - _series.markerSettings.width, rect.top - _series.markerSettings.height, rect.right + _series.markerSettings.width, rect.bottom + _series.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + Offset( + seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, + seriesRenderer + .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); canvas.restore(); if ((_series.animationDuration <= 0 == true || !_renderingDetails.initialRender! || - animationFactor >= _chartState._seriesDurationFactor) && + animationFactor >= _stateProperties.seriesDurationFactor) && (_series.markerSettings.isVisible == true || - _series.dataLabelSettings.isVisible == true)) { + _series.dataLabelSettings.isVisible == true || + _series.errorBarSettings.isVisible! == true)) { canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - chart, canvas, chartElementAnimation); + seriesRenderer.seriesRendererDetails + .renderSeriesElements(chart, canvas, chartElementAnimation); } if (animationFactor >= 1) { - _chartState._setPainterKey(painterKey.index, painterKey.name, true); + _stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } /// To get previous series renderer -CartesianSeriesRenderer _getPreviousSeriesRenderer( +CartesianSeriesRenderer getPreviousSeriesRenderer( List seriesRendererCollection, num seriesIndex) { for (int i = 0; i < seriesRendererCollection.length; i++) { if (seriesIndex == @@ -1979,117 +2344,137 @@ CartesianSeriesRenderer _getPreviousSeriesRenderer( } /// Rect painter for stacked series -void _stackedRectPainter(Canvas canvas, dynamic seriesRenderer, - SfCartesianChartState _chartState, _PainterKey painterKey) { - if (seriesRenderer._visible! == true) { +void stackedRectPainter(Canvas canvas, dynamic seriesRenderer, + CartesianStateProperties _stateProperties, PainterKey painterKey) { + if (seriesRenderer.seriesRendererDetails.visible! == true) { canvas.save(); Rect clipRect, axisClipRect; CartesianChartPoint point; - final XyDataSeries series = seriesRenderer._series; - final _RenderingDetails _renderingDetails = _chartState._renderingDetails; + final XyDataSeries series = + seriesRenderer.seriesRendererDetails.series; + final RenderingDetails _renderingDetails = + _stateProperties.renderingDetails; final int seriesIndex = painterKey.index; - final Animation seriesAnimation = seriesRenderer._seriesAnimation; + final Animation seriesAnimation = + seriesRenderer.seriesRendererDetails.seriesAnimation; final Animation chartElementAnimation = - seriesRenderer._seriesElementAnimation; - seriesRenderer._storeSeriesProperties(_chartState, seriesIndex); + seriesRenderer.seriesRendererDetails.seriesElementAnimation; + seriesRenderer.seriesRendererDetails + .storeSeriesProperties(_stateProperties.chartState, seriesIndex); double animationFactor; // ignore: unnecessary_null_comparison animationFactor = seriesAnimation != null && - (seriesRenderer._reAnimate == true || + (seriesRenderer.seriesRendererDetails.reAnimate == true || (!(_renderingDetails.widgetNeedUpdate || _renderingDetails.isLegendToggled))) ? seriesAnimation.value : 1; /// Clip rect will be added for series. - axisClipRect = _calculatePlotOffset( - _chartState._chartAxis._axisClipRect, - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + axisClipRect = calculatePlotOffset( + _stateProperties.chartAxis.axisClipRect, + Offset( + seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, + seriesRenderer + .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); canvas.clipRect(axisClipRect); int segmentIndex = -1; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty == true) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRenderer.seriesRendererDetails.visibleDataPoints == null || + seriesRenderer.seriesRendererDetails.visibleDataPoints!.isNotEmpty == + true) { + seriesRenderer.seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; - pointIndex < seriesRenderer._dataPoints.length; + pointIndex < seriesRenderer.seriesRendererDetails.dataPoints.length; pointIndex++) { - point = seriesRenderer._dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - _chartState, seriesRenderer, painterKey.index, point, pointIndex); + point = seriesRenderer.seriesRendererDetails.dataPoints[pointIndex]; + seriesRenderer.seriesRendererDetails.calculateRegionData(_stateProperties, + seriesRenderer, painterKey.index, point, pointIndex); if (point.isVisible && !point.isGap) { - seriesRenderer._drawSegment( + seriesRenderer.seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( point, segmentIndex += 1, painterKey.index, animationFactor)); } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( - _chartState._chartAxis._axisClipRect.left - + _stateProperties.chartAxis.axisClipRect.left - series.markerSettings.width, - _chartState._chartAxis._axisClipRect.top - + _stateProperties.chartAxis.axisClipRect.top - series.markerSettings.height, - _chartState._chartAxis._axisClipRect.right + + _stateProperties.chartAxis.axisClipRect.right + series.markerSettings.width, - _chartState._chartAxis._axisClipRect.bottom + + _stateProperties.chartAxis.axisClipRect.bottom + series.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + Offset( + seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, + seriesRenderer + .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || !_renderingDetails.initialRender! || - animationFactor >= _chartState._seriesDurationFactor) && + animationFactor >= _stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - _chartState._chart, canvas, chartElementAnimation); + seriesRenderer.seriesRendererDetails.renderSeriesElements( + _stateProperties.chart, canvas, chartElementAnimation); } if (animationFactor >= 1) { - _chartState._setPainterKey(painterKey.index, painterKey.name, true); + _stateProperties.setPainterKey(painterKey.index, painterKey.name, true); } } } /// Painter for stacked line series -void _stackedLinePainter( +void stackedLinePainter( Canvas canvas, dynamic seriesRenderer, Animation? seriesAnimation, - SfCartesianChartState _chartState, + CartesianStateProperties _stateProperties, Animation? chartElementAnimation, - _PainterKey painterKey) { + PainterKey painterKey) { Rect clipRect; double animationFactor; - final _RenderingDetails _renderingDetails = _chartState._renderingDetails; - if (seriesRenderer._visible! == true) { - final XyDataSeries series = seriesRenderer._series; + final RenderingDetails _renderingDetails = _stateProperties.renderingDetails; + if (seriesRenderer.seriesRendererDetails.visible! == true) { + final XyDataSeries series = + seriesRenderer.seriesRendererDetails.series; canvas.save(); animationFactor = seriesAnimation != null ? seriesAnimation.value : 1; final int seriesIndex = painterKey.index; - _StackedValues? stackedValues; - seriesRenderer._storeSeriesProperties(_chartState, seriesIndex); - if (seriesRenderer is _StackedSeriesRenderer && - seriesRenderer._stackingValues.isNotEmpty) { - stackedValues = seriesRenderer._stackingValues[0]; + StackedValues? stackedValues; + seriesRenderer.seriesRendererDetails + .storeSeriesProperties(_stateProperties.chartState, seriesIndex); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + if (seriesRenderer is StackedSeriesRenderer && + seriesRendererDetails.stackingValues.isNotEmpty == true) { + stackedValues = seriesRendererDetails.stackingValues[0]; } - final Rect rect = seriesRenderer._chartState!._chartAxis._axisClipRect; + final Rect rect = seriesRenderer + .seriesRendererDetails.stateProperties.chartAxis.axisClipRect; /// Clip rect will be added for series. - final Rect axisClipRect = _calculatePlotOffset( + final Rect axisClipRect = calculatePlotOffset( rect, - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + Offset( + seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, + seriesRenderer + .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); canvas.clipRect(axisClipRect); - if (seriesRenderer._reAnimate == true || + if (seriesRenderer.seriesRendererDetails.reAnimate == true || ((!(_renderingDetails.widgetNeedUpdate || _renderingDetails.isLegendToggled) || - !_chartState._oldSeriesKeys.contains(series.key)) && + !_stateProperties.oldSeriesKeys.contains(series.key)) && series.animationDuration > 0)) { - _performLinearAnimation(_chartState, seriesRenderer._xAxisRenderer!._axis, - canvas, animationFactor); + performLinearAnimation( + _stateProperties, + seriesRenderer.seriesRendererDetails.xAxisDetails!.axis, + canvas, + animationFactor); } int segmentIndex = -1; @@ -2098,24 +2483,29 @@ void _stackedLinePainter( endPoint, currentPoint, _nextPoint; - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints!.isNotEmpty == true) { - seriesRenderer._visibleDataPoints = >[]; + if (seriesRenderer.seriesRendererDetails.visibleDataPoints == null || + seriesRenderer.seriesRendererDetails.visibleDataPoints!.isNotEmpty == + true) { + seriesRenderer.seriesRendererDetails.visibleDataPoints = + >[]; } for (int pointIndex = 0; - pointIndex < seriesRenderer._dataPoints.length; + pointIndex < seriesRenderer.seriesRendererDetails.dataPoints.length; pointIndex++) { - currentPoint = seriesRenderer._dataPoints[pointIndex]; - seriesRenderer._calculateRegionData( - _chartState, seriesRenderer, seriesIndex, currentPoint, pointIndex); + currentPoint = + seriesRenderer.seriesRendererDetails.dataPoints[pointIndex]; + seriesRenderer.seriesRendererDetails.calculateRegionData(_stateProperties, + seriesRenderer, seriesIndex, currentPoint, pointIndex); if ((currentPoint!.isVisible && !currentPoint.isGap) && startPoint == null && stackedValues != null) { startPoint = currentPoint; currentCummulativePos = stackedValues.endValues[pointIndex]; } - if (pointIndex + 1 < seriesRenderer._dataPoints.length) { - _nextPoint = seriesRenderer._dataPoints[pointIndex + 1]; + if (pointIndex + 1 < + seriesRenderer.seriesRendererDetails.dataPoints.length) { + _nextPoint = + seriesRenderer.seriesRendererDetails.dataPoints[pointIndex + 1]; if (startPoint != null && _nextPoint!.isGap) { startPoint = null; } else if (_nextPoint!.isVisible && @@ -2127,7 +2517,7 @@ void _stackedLinePainter( } if (startPoint != null && endPoint != null) { - seriesRenderer._drawSegment( + seriesRenderer.seriesRendererDetails.drawSegment( canvas, seriesRenderer._createSegments( startPoint, @@ -2140,26 +2530,28 @@ void _stackedLinePainter( endPoint = startPoint = null; } } - clipRect = _calculatePlotOffset( + clipRect = calculatePlotOffset( Rect.fromLTRB( rect.left - series.markerSettings.width, rect.top - series.markerSettings.height, rect.right + series.markerSettings.width, rect.bottom + series.markerSettings.height), - Offset(seriesRenderer._xAxisRenderer?._axis?.plotOffset, - seriesRenderer._yAxisRenderer?._axis?.plotOffset)); + Offset( + seriesRenderer.seriesRendererDetails.xAxisDetails?.axis?.plotOffset, + seriesRenderer + .seriesRendererDetails.yAxisDetails?.axis?.plotOffset)); canvas.restore(); if ((series.animationDuration <= 0 || !_renderingDetails.initialRender! || - animationFactor >= _chartState._seriesDurationFactor) && + animationFactor >= _stateProperties.seriesDurationFactor) && (series.markerSettings.isVisible || series.dataLabelSettings.isVisible)) { canvas.clipRect(clipRect); - seriesRenderer._renderSeriesElements( - _chartState._chart, canvas, chartElementAnimation); + seriesRenderer.seriesRendererDetails.renderSeriesElements( + _stateProperties.chart, canvas, chartElementAnimation); } if (animationFactor >= 1) { - _chartState._setPainterKey(seriesIndex, painterKey.name, true); + _stateProperties.setPainterKey(seriesIndex, painterKey.name, true); } } } @@ -2250,7 +2642,7 @@ List _getCardinalSpline(List xValues, List yValues, } /// To find NaturalSpline -List _naturalSpline(List xValues, List yValues, +List naturalSpline(List xValues, List yValues, List yCoeff, int dataCount, SplineType? splineType) { const double a = 6; final int count = dataCount; @@ -2362,7 +2754,8 @@ List _calculateCardinalControlPoints( List controlPoints) { double yCoefficient = coefficientY; double y1Coefficient = coefficientY1; - if (seriesRenderer._xAxisRenderer is DateTimeAxisRenderer) { + if (SeriesHelper.getSeriesRendererDetails(seriesRenderer).xAxisDetails + is DateTimeAxisRenderer) { yCoefficient = coefficientY / dateTimeInterval(seriesRenderer); y1Coefficient = coefficientY1 / dateTimeInterval(seriesRenderer); } @@ -2376,10 +2769,11 @@ List _calculateCardinalControlPoints( return controlPoints; } -/// calculate the dateTime intervals for cardinal spline type +/// Calculate the dateTime intervals for cardinal spline type num dateTimeInterval(CartesianSeriesRenderer seriesRenderer) { final DateTimeAxis xAxis = - seriesRenderer._xAxisRenderer!._axis as DateTimeAxis; + SeriesHelper.getSeriesRendererDetails(seriesRenderer).xAxisDetails!.axis + as DateTimeAxis; final DateTimeIntervalType _actualIntervalType = xAxis.intervalType; num intervalInMilliseconds; if (_actualIntervalType == DateTimeIntervalType.years) { @@ -2400,50 +2794,54 @@ num dateTimeInterval(CartesianSeriesRenderer seriesRenderer) { return intervalInMilliseconds; } -/// to trigger Marker event -MarkerRenderArgs? _triggerMarkerRenderEvent( - CartesianSeriesRenderer seriesRenderer, +/// Triggers marker event +MarkerRenderArgs? triggerMarkerRenderEvent( + SeriesRendererDetails seriesRendererDetails, Size size, DataMarkerType markerType, int pointIndex, Animation? animationController, [ChartSegment? segment]) { MarkerRenderArgs? markerargs; - final int seriesIndex = seriesRenderer - ._chartState!._chartSeries.visibleSeriesRenderers - .indexOf(seriesRenderer); + final int seriesIndex = seriesRendererDetails + .stateProperties.chartSeries.visibleSeriesRenderers + .indexOf(seriesRendererDetails.renderer); final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; final MarkerSettingsRenderer markerSettingsRenderer = - seriesRenderer._markerSettingsRenderer!; - markerSettingsRenderer._color = series.markerSettings.color; + seriesRendererDetails.markerSettingsRenderer!; + markerSettingsRenderer.color = series.markerSettings.color; final bool isScatter = segment != null && segment is ScatterSegment; - if (seriesRenderer._chart.onMarkerRender != null) { - markerargs = MarkerRenderArgs(pointIndex, seriesIndex, - seriesRenderer._visibleDataPoints![pointIndex].overallDataPointIndex!); + if (seriesRendererDetails.chart.onMarkerRender != null) { + markerargs = MarkerRenderArgs( + pointIndex, + seriesIndex, + seriesRendererDetails + .visibleDataPoints![pointIndex].overallDataPointIndex!); markerargs.markerHeight = size.height; markerargs.markerWidth = size.width; markerargs.shape = markerType; markerargs.color = - isScatter ? segment.fillPaint!.color : markerSettingsRenderer._color; + isScatter ? segment.fillPaint!.color : markerSettingsRenderer.color; markerargs.borderColor = isScatter ? segment.strokePaint!.color - : markerSettingsRenderer._borderColor; + : markerSettingsRenderer.borderColor; markerargs.borderWidth = isScatter ? segment.strokePaint!.strokeWidth - : markerSettingsRenderer._borderWidth; + : markerSettingsRenderer.borderWidth; if (animationController == null || isScatter || ((animationController.value == 1.0 && animationController.status == AnimationStatus.completed) || - seriesRenderer._animationController.duration!.inMilliseconds == + seriesRendererDetails + .animationController.duration!.inMilliseconds == 0)) { - seriesRenderer._chart.onMarkerRender!(markerargs); + seriesRendererDetails.chart.onMarkerRender!(markerargs); size = Size(markerargs.markerWidth, markerargs.markerHeight); markerType = markerargs.shape; - markerSettingsRenderer._color = markerargs.color; - markerSettingsRenderer._borderColor = markerargs.borderColor; - markerSettingsRenderer._borderWidth = markerargs.borderWidth; + markerSettingsRenderer.color = markerargs.color; + markerSettingsRenderer.borderColor = markerargs.borderColor; + markerSettingsRenderer.borderWidth = markerargs.borderWidth; } if (isScatter) { @@ -2466,7 +2864,7 @@ MarkerRenderArgs? _triggerMarkerRenderEvent( } /// To find Natural ControlPoints -List _calculateControlPoints(List xValues, List yValues, +List calculateControlPoints(List xValues, List yValues, double yCoef, double nextyCoef, int i, List controlPoints) { final List values = List.filled(4, null); final double x = xValues[i].toDouble(); @@ -2494,8 +2892,10 @@ List _calculateControlPoints(List xValues, List yValues, } /// To calculate spline area control points -void _calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { - final dynamic series = seriesRenderer._series; +void calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final dynamic series = seriesRendererDetails.series; List? yCoef = []; List? lowCoef = []; List? highCoef = []; @@ -2505,14 +2905,14 @@ void _calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { final List lowValues = []; final SplineType? splineType = series.splineType; int i; - for (i = 0; i < seriesRenderer._dataPoints.length; i++) { - xValues.add(seriesRenderer._dataPoints[i].xValue); + for (i = 0; i < seriesRendererDetails.dataPoints.length; i++) { + xValues.add(seriesRendererDetails.dataPoints[i].xValue); if (seriesRenderer is SplineAreaSeriesRenderer || seriesRenderer is SplineSeriesRenderer) { - yValues.add(seriesRenderer._dataPoints[i].yValue); + yValues.add(seriesRendererDetails.dataPoints[i].yValue); } else if (seriesRenderer is SplineRangeAreaSeriesRenderer) { - highValues.add(seriesRenderer._dataPoints[i].high); - lowValues.add(seriesRenderer._dataPoints[i].low); + highValues.add(seriesRendererDetails.dataPoints[i].high); + lowValues.add(seriesRendererDetails.dataPoints[i].low); } } @@ -2545,11 +2945,11 @@ void _calculateSplineAreaControlPoints(CartesianSeriesRenderer seriesRenderer) { if (series is SplineAreaSeries || seriesRenderer is SplineSeriesRenderer) { yCoef = - _naturalSpline(xValues, yValues, yCoef, xValues.length, splineType); + naturalSpline(xValues, yValues, yCoef, xValues.length, splineType); } else { - lowCoef = _naturalSpline( + lowCoef = naturalSpline( xValues, lowValues, lowCoef, xValues.length, splineType); - highCoef = _naturalSpline( + highCoef = naturalSpline( xValues, highValues, highCoef, xValues.length, splineType); } } @@ -2579,6 +2979,8 @@ void _updateSplineAreaControlPoints( List? dx) { double x, y, nextX, nextY; List controlPoints; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); for (int pointIndex = 0; pointIndex < xValues.length - 1; pointIndex++) { controlPoints = []; // ignore: unnecessary_null_comparison @@ -2603,7 +3005,7 @@ void _updateSplineAreaControlPoints( yCoef[pointIndex + 1]!.toDouble(), dx![pointIndex]!.toDouble(), controlPoints); - seriesRenderer._drawControlPoints.add(controlPoints); + seriesRendererDetails.drawControlPoints.add(controlPoints); } else if (splineType == SplineType.cardinal) { controlPoints = _calculateCardinalControlPoints( x, @@ -2614,22 +3016,22 @@ void _updateSplineAreaControlPoints( yCoef[pointIndex + 1]!.toDouble(), seriesRenderer, controlPoints); - seriesRenderer._drawControlPoints.add(controlPoints); + seriesRendererDetails.drawControlPoints.add(controlPoints); } else { - controlPoints = _calculateControlPoints( + controlPoints = calculateControlPoints( xValues, yValues, yCoef![pointIndex]!.toDouble(), yCoef[pointIndex + 1]!.toDouble(), pointIndex, controlPoints); - seriesRenderer._drawControlPoints.add(controlPoints); + seriesRendererDetails.drawControlPoints.add(controlPoints); } } } } -/// calculate splinerangearea control point +/// Calculate spline range area control point void _findSplineRangeAreaControlPoint( SplineRangeAreaSeriesRenderer seriesRenderer, SplineType? splineType, @@ -2645,20 +3047,23 @@ void _findSplineRangeAreaControlPoint( for (pointIndex = 0; pointIndex < xValues.length - 1; pointIndex++) { controlPointslow = []; controlPointshigh = []; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); // ignore: unnecessary_null_comparison if (xValues[pointIndex] != null && - seriesRenderer._dataPoints[pointIndex].low != null && - seriesRenderer._dataPoints[pointIndex].high != null && + seriesRendererDetails.dataPoints[pointIndex].low != null && + seriesRendererDetails.dataPoints[pointIndex].high != null && // ignore: unnecessary_null_comparison xValues[pointIndex + 1] != null && - seriesRenderer._dataPoints[pointIndex + 1].low != null && - seriesRenderer._dataPoints[pointIndex + 1].high != null) { + seriesRendererDetails.dataPoints[pointIndex + 1].low != null && + seriesRendererDetails.dataPoints[pointIndex + 1].high != null) { x = xValues[pointIndex].toDouble(); - low = seriesRenderer._dataPoints[pointIndex].low.toDouble(); - high = seriesRenderer._dataPoints[pointIndex].high.toDouble(); + low = seriesRendererDetails.dataPoints[pointIndex].low.toDouble(); + high = seriesRendererDetails.dataPoints[pointIndex].high.toDouble(); nextX = xValues[pointIndex + 1].toDouble(); - nextlow = seriesRenderer._dataPoints[pointIndex + 1].low.toDouble(); - nexthigh = seriesRenderer._dataPoints[pointIndex + 1].high.toDouble(); + nextlow = seriesRendererDetails.dataPoints[pointIndex + 1].low.toDouble(); + nexthigh = + seriesRendererDetails.dataPoints[pointIndex + 1].high.toDouble(); if (splineType == SplineType.monotonic) { controlPointslow = _calculateMonotonicControlPoints( x, @@ -2669,7 +3074,7 @@ void _findSplineRangeAreaControlPoint( lowCoef[pointIndex + 1]!.toDouble(), dx![pointIndex]!.toDouble(), controlPointslow); - seriesRenderer._drawLowControlPoints.add(controlPointslow); + seriesRendererDetails.drawLowControlPoints.add(controlPointslow); controlPointshigh = _calculateMonotonicControlPoints( x, high, @@ -2679,7 +3084,7 @@ void _findSplineRangeAreaControlPoint( highCoef[pointIndex + 1]!.toDouble(), dx[pointIndex]!.toDouble(), controlPointshigh); - seriesRenderer._drawHighControlPoints.add(controlPointshigh); + seriesRendererDetails.drawHighControlPoints.add(controlPointshigh); } else if (splineType == SplineType.cardinal) { controlPointslow = _calculateCardinalControlPoints( x, @@ -2690,7 +3095,7 @@ void _findSplineRangeAreaControlPoint( lowCoef[pointIndex + 1]!.toDouble(), seriesRenderer, controlPointslow); - seriesRenderer._drawLowControlPoints.add(controlPointslow); + seriesRendererDetails.drawLowControlPoints.add(controlPointslow); controlPointshigh = _calculateCardinalControlPoints( x, high, @@ -2700,34 +3105,35 @@ void _findSplineRangeAreaControlPoint( highCoef[pointIndex + 1]!.toDouble(), seriesRenderer, controlPointshigh); - seriesRenderer._drawHighControlPoints.add(controlPointshigh); + seriesRendererDetails.drawHighControlPoints.add(controlPointshigh); } else { - controlPointslow = _calculateControlPoints( + controlPointslow = calculateControlPoints( xValues, lowValues, lowCoef![pointIndex]!.toDouble(), lowCoef[pointIndex + 1]!.toDouble(), pointIndex, controlPointslow); - seriesRenderer._drawLowControlPoints.add(controlPointslow); - controlPointshigh = _calculateControlPoints( + seriesRendererDetails.drawLowControlPoints.add(controlPointslow); + controlPointshigh = calculateControlPoints( xValues, highValues, highCoef![pointIndex]!.toDouble(), highCoef[pointIndex + 1]!.toDouble(), pointIndex, controlPointshigh); - seriesRenderer._drawHighControlPoints.add(controlPointshigh); + seriesRendererDetails.drawHighControlPoints.add(controlPointshigh); } } } } -///get the old axis (for stock chart animation) -ChartAxisRenderer? _getOldAxisRenderer(ChartAxisRenderer axisRenderer, +///Get the old axis (for stock chart animation) +ChartAxisRenderer? getOldAxisRenderer(ChartAxisRenderer axisRenderer, List oldAxisRendererList) { for (int i = 0; i < oldAxisRendererList.length; i++) { - if (oldAxisRendererList[i]._name == axisRenderer._name) { + if (AxisHelper.getAxisRendererDetails(oldAxisRendererList[i]).name == + AxisHelper.getAxisRendererDetails(axisRenderer).name) { return oldAxisRendererList[i]; } } @@ -2735,7 +3141,7 @@ ChartAxisRenderer? _getOldAxisRenderer(ChartAxisRenderer axisRenderer, } /// To get chart point -CartesianChartPoint? _getChartPoint( +CartesianChartPoint? getChartPoint( CartesianSeriesRenderer seriesRenderer, dynamic data, int pointIndex) { dynamic xVal, yVal, @@ -2753,7 +3159,9 @@ CartesianChartPoint? _getChartPoint( maximumVal; bool? isIntermediateSum, isTotalSum; CartesianChartPoint? currentPoint; - final dynamic series = seriesRenderer._series; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + final dynamic series = seriesRendererDetails.series; final ChartIndexedValueMapper? _xMap = series.xValueMapper; final ChartIndexedValueMapper? _yMap = series.yValueMapper; final ChartIndexedValueMapper? _highMap = series.highValueMapper; @@ -2770,15 +3178,16 @@ CartesianChartPoint? _getChartPoint( final ChartIndexedValueMapper? _pointTextMap = series.dataLabelMapper; if (seriesRenderer is HistogramSeriesRenderer) { - minVal = seriesRenderer._histogramValues.minValue; - yVal = seriesRenderer._histogramValues.yValues! + minVal = seriesRendererDetails.histogramValues.minValue; + yVal = seriesRendererDetails.histogramValues.yValues! .where((dynamic x) => x >= minVal == true && - x < (minVal + seriesRenderer._histogramValues.binWidth) == true) + x < (minVal + seriesRendererDetails.histogramValues.binWidth) == + true) .length; - xVal = minVal + seriesRenderer._histogramValues.binWidth! / 2; - minVal += seriesRenderer._histogramValues.binWidth; - seriesRenderer._histogramValues.minValue = minVal; + xVal = minVal + seriesRendererDetails.histogramValues.binWidth! / 2; + minVal += seriesRendererDetails.histogramValues.binWidth; + seriesRendererDetails.histogramValues.minValue = minVal; } else { if (_xMap != null) { xVal = _xMap(pointIndex); @@ -2825,9 +3234,9 @@ CartesianChartPoint? _getChartPoint( lowVal = _lowMap(pointIndex); } - if (series is _FinancialSeriesBase) { - final _FinancialSeriesBase financialSeries = - seriesRenderer._series as _FinancialSeriesBase; + if (series is FinancialSeriesBase) { + final FinancialSeriesBase financialSeries = + seriesRendererDetails.series as FinancialSeriesBase; final ChartIndexedValueMapper? _openMap = financialSeries.openValueMapper; final ChartIndexedValueMapper? _closeMap = @@ -2897,24 +3306,24 @@ CartesianChartPoint? _getChartPoint( return currentPoint; } -/// This method used to find the min value. -num _findMinValue(num axisValue, num pointValue) => +/// Gets the minimum value. +num findMinValue(num axisValue, num pointValue) => math.min(axisValue, pointValue); -/// This method used to find the max value. -num _findMaxValue(num axisValue, num pointValue) => +/// Gets the maximum value. +num findMaxValue(num axisValue, num pointValue) => math.max(axisValue, pointValue); -//This method finds whether the given point has been updated/changed and returns a boolean value. -bool _findChangesInPoint( +/// This method finds whether the given point has been updated/changed and returns a boolean value. +bool findChangesInPoint( CartesianChartPoint point, CartesianChartPoint oldPoint, - CartesianSeriesRenderer seriesRenderer) { - if (seriesRenderer._series.sortingOrder == - seriesRenderer._oldSeries?.sortingOrder) { - if (seriesRenderer is CandleSeriesRenderer || - seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('hilo')) { + SeriesRendererDetails seriesRendererDetails) { + if (seriesRendererDetails.series.sortingOrder == + seriesRendererDetails.oldSeries?.sortingOrder) { + if (seriesRendererDetails.renderer is CandleSeriesRenderer || + seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType.contains('hilo') == true) { return point.x != oldPoint.x || point.high != oldPoint.high || point.low != oldPoint.low || @@ -2922,13 +3331,13 @@ bool _findChangesInPoint( point.close != oldPoint.close || point.volume != oldPoint.volume || point.sortValue != oldPoint.sortValue; - } else if (seriesRenderer._seriesType == 'waterfall') { + } else if (seriesRendererDetails.seriesType == 'waterfall') { return point.x != oldPoint.x || (point.y != null && (point.y != oldPoint.y)) || point.sortValue != oldPoint.sortValue || point.isIntermediateSum != oldPoint.isIntermediateSum || point.isTotalSum != oldPoint.isTotalSum; - } else if (seriesRenderer._seriesType == 'boxandwhisker') { + } else if (seriesRendererDetails.seriesType == 'boxandwhisker') { if (point.y.length != oldPoint.y.length || point.x != oldPoint.x || point.sortValue != oldPoint.sortValue) { @@ -2954,27 +3363,29 @@ bool _findChangesInPoint( } /// To calculate range Y on zoom mode X -_VisibleRange _calculateYRangeOnZoomX( - _VisibleRange _actualRange, dynamic axisRenderer) { +VisibleRange calculateYRangeOnZoomX( + VisibleRange _actualRange, dynamic axisRenderer) { num? _mini, _maxi; - final dynamic axis = axisRenderer._axis; + final dynamic axis = axisRenderer.axis; final List _seriesRenderers = - axisRenderer._seriesRenderers; + axisRenderer.seriesRenderers; final num? minimum = axis.minimum, maximum = axis.maximum; for (int i = 0; i < _seriesRenderers.length && _seriesRenderers.isNotEmpty; i++) { - final dynamic xAxisRenderer = _seriesRenderers[i]._xAxisRenderer; - xAxisRenderer._calculateRangeAndInterval( - axisRenderer._chartState, 'AnchoringRange'); - final _VisibleRange xRange = xAxisRenderer._visibleRange; - if (_seriesRenderers[i]._yAxisRenderer == axisRenderer && + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(_seriesRenderers[i]); + final dynamic xAxisRenderer = seriesRendererDetails.xAxisDetails; + xAxisRenderer.calculateRangeAndInterval( + axisRenderer.stateProperties, 'AnchoringRange'); + final VisibleRange xRange = xAxisRenderer.visibleRange; + if (seriesRendererDetails.yAxisDetails == axisRenderer && // ignore: unnecessary_null_comparison xRange != null && - _seriesRenderers[i]._visible!) { - for (int j = 0; j < _seriesRenderers[i]._dataPoints.length; j++) { + seriesRendererDetails.visible! == true) { + for (int j = 0; j < seriesRendererDetails.dataPoints.length; j++) { final CartesianChartPoint point = - _seriesRenderers[i]._dataPoints[j]; + seriesRendererDetails.dataPoints[j]; if (point.xValue >= xRange.minimum == true && point.xValue <= xRange.maximum == true) { if (point.yValue != null) { @@ -2988,72 +3399,85 @@ _VisibleRange _calculateYRangeOnZoomX( } } } - return _VisibleRange(minimum ?? (_mini ?? _actualRange.minimum), + return VisibleRange(minimum ?? (_mini ?? _actualRange.minimum), maximum ?? (_maxi ?? _actualRange.maximum)); } -/// Bool to calculate need for Y range -bool _needCalculateYrange(num? minimum, num? maximum, - SfCartesianChartState _chartState, AxisOrientation _orientation) { - final SfCartesianChart chart = _chartState._chart; - return (_chartState._zoomedState == true || - _chartState._zoomProgress || - _chartState._rangeChangeBySlider) && - !(minimum != null && maximum != null) && - (!_chartState._requireInvertedAxis - ? (_orientation == AxisOrientation.vertical && - chart.zoomPanBehavior.zoomMode == ZoomMode.x) - : (_orientation == AxisOrientation.horizontal && - chart.zoomPanBehavior.zoomMode == ZoomMode.y)); +/// Bool to calculate for Y range +bool needCalculateYrange(num? minimum, num? maximum, + CartesianStateProperties stateProperties, AxisOrientation _orientation) { + final SfCartesianChart chart = stateProperties.chart; + return !(minimum != null && maximum != null) && + (stateProperties.rangeChangeBySlider || + ((stateProperties.zoomedState == true || + stateProperties.zoomProgress) && + (!stateProperties.requireInvertedAxis + ? (_orientation == AxisOrientation.vertical && + chart.zoomPanBehavior.zoomMode == ZoomMode.x) + : (_orientation == AxisOrientation.horizontal && + chart.zoomPanBehavior.zoomMode == ZoomMode.y)))); } -//this method returns the axisRenderer for the given axis from given collection, if not found returns null. -ChartAxisRenderer? _findExistingAxisRenderer( +/// This method returns the axisRenderer for the given axis from given collection, if not found returns null. +ChartAxisRenderer? findExistingAxisRenderer( ChartAxis axis, List axisRenderers) { for (final ChartAxisRenderer axisRenderer in axisRenderers) { - if (identical(axis, axisRenderer._axis)) { + if (identical(axis, AxisHelper.getAxisRendererDetails(axisRenderer).axis)) { return axisRenderer; } } return null; } -int _getOldSegmentIndex(ChartSegment segment) { - if (segment._oldSeriesRenderer != null) { - for (final ChartSegment oldSegment - in segment._oldSeriesRenderer!._segments) { +/// Method to get the old segment index value +int getOldSegmentIndex(ChartSegment segment) { + final SegmentProperties segmentProperties = + SegmentHelper.getSegmentProperties(segment); + if (segmentProperties.oldSeriesRenderer != null) { + for (final ChartSegment oldSegment in SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!) + .segments) { + final SegmentProperties oldSegmentProperties = + SegmentHelper.getSegmentProperties(oldSegment); if (segment.runtimeType == oldSegment.runtimeType && - (segment._seriesRenderer._xAxisRenderer is CategoryAxisRenderer - ? oldSegment._currentPoint!.x == segment._currentPoint!.x - : oldSegment._currentPoint!.xValue == - segment._currentPoint!.xValue)) { - return segment._oldSeriesRenderer!._segments.indexOf(oldSegment); + (SeriesHelper.getSeriesRendererDetails( + segmentProperties.seriesRenderer) + .xAxisDetails is CategoryAxisRenderer + ? oldSegmentProperties.currentPoint!.x == + segmentProperties.currentPoint!.x + : oldSegmentProperties.currentPoint!.xValue == + segmentProperties.currentPoint!.xValue)) { + final SeriesRendererDetails rendererDetails = + SeriesHelper.getSeriesRendererDetails( + segmentProperties.oldSeriesRenderer!); + return rendererDetails.segments.indexOf(oldSegment); } } } return -1; } -//this method determines the whether all the series animations has been completed and renders the datalabel -void _setAnimationStatus(dynamic chartState) { - if (chartState._totalAnimatingSeries == chartState._animationCompleteCount) { - chartState._renderingDetails.animateCompleted = true; - chartState._animationCompleteCount = 0; +/// This method determines whether all the series animations have been completed and renders the datalabel +void setAnimationStatus(CartesianStateProperties stateProperties) { + if (stateProperties.totalAnimatingSeries == + stateProperties.animationCompleteCount) { + stateProperties.renderingDetails.animateCompleted = true; + stateProperties.animationCompleteCount = 0; } else { - chartState._renderingDetails.animateCompleted = false; + stateProperties.renderingDetails.animateCompleted = false; } - if (chartState._renderDataLabel != null) { - if (chartState._renderDataLabel.state.mounted == true) { - chartState._renderDataLabel.state?.render(); + if (stateProperties.renderDataLabel != null) { + if (stateProperties.renderDataLabel!.state!.mounted == true) { + stateProperties.renderDataLabel!.state?.render(); } } } /// Calculate date time nice interval -int _calculateDateTimeNiceInterval( - ChartAxisRenderer axisRenderer, Size size, _VisibleRange range, +int calculateDateTimeNiceInterval( + ChartAxisRenderer axisRenderer, Size size, VisibleRange range, [DateTime? startDate, DateTime? endDate]) { - final ChartAxis axis = axisRenderer._axis; + final ChartAxis axis = AxisHelper.getAxisRendererDetails(axisRenderer).axis; DateTime? visibleMinimum, visibleMaximum; DateTimeIntervalType? actualIntervalType; if (axis is DateTimeAxis) { @@ -3076,54 +3500,59 @@ int _calculateDateTimeNiceInterval( ((startDate.millisecondsSinceEpoch - endDate.millisecondsSinceEpoch) / perDay) .abs(); + dynamic axisRendererDetails; if (axis is DateTimeAxis) { + axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails; actualIntervalType = axis.intervalType; } else if (axis is DateTimeCategoryAxis) { + axisRendererDetails = AxisHelper.getAxisRendererDetails(axisRenderer) + as DateTimeCategoryAxisDetails; actualIntervalType = axis.intervalType; } switch (actualIntervalType) { case DateTimeIntervalType.years: - interval = axisRenderer._calculateNumericNiceInterval( + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays / 365, size); break; case DateTimeIntervalType.months: - interval = axisRenderer._calculateNumericNiceInterval( + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays / 30, size); break; case DateTimeIntervalType.days: - interval = axisRenderer._calculateNumericNiceInterval( + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays, size); break; case DateTimeIntervalType.hours: - interval = axisRenderer._calculateNumericNiceInterval( + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays * hours, size); break; case DateTimeIntervalType.minutes: - interval = axisRenderer._calculateNumericNiceInterval( + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays * hours * minutes, size); break; case DateTimeIntervalType.seconds: - interval = axisRenderer._calculateNumericNiceInterval( + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays * hours * minutes * seconds, size); break; case DateTimeIntervalType.milliseconds: - interval = axisRenderer._calculateNumericNiceInterval(axisRenderer, + interval = axisRendererDetails.calculateNumericNiceInterval(axisRenderer, totalDays * hours * minutes * seconds * milliseconds, size); break; case DateTimeIntervalType.auto: - /// for years - interval = axisRenderer._calculateNumericNiceInterval( + /// For years + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays / 365, size); - if (interval >= 1) { + if (interval! >= 1) { _setActualIntervalType(axisRenderer, DateTimeIntervalType.years); return interval.floor(); } - /// for months - interval = axisRenderer._calculateNumericNiceInterval( + /// For months + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays / 30, size); - if (interval >= 1) { + if (interval! >= 1) { _setActualIntervalType( axisRenderer, notDoubleInterval @@ -3132,10 +3561,10 @@ int _calculateDateTimeNiceInterval( return interval.floor(); } - /// for days - interval = axisRenderer._calculateNumericNiceInterval( + /// For days + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays, size); - if (interval >= 1) { + if (interval! >= 1) { _setActualIntervalType( axisRenderer, notDoubleInterval @@ -3144,10 +3573,10 @@ int _calculateDateTimeNiceInterval( return interval.floor(); } - /// for hours - interval = axisRenderer._calculateNumericNiceInterval( + /// For hours + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays * 24, size); - if (interval >= 1) { + if (interval! >= 1) { _setActualIntervalType( axisRenderer, notDoubleInterval @@ -3156,10 +3585,10 @@ int _calculateDateTimeNiceInterval( return interval.floor(); } - /// for minutes - interval = axisRenderer._calculateNumericNiceInterval( + /// For minutes + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays * 24 * 60, size); - if (interval >= 1) { + if (interval! >= 1) { _setActualIntervalType( axisRenderer, notDoubleInterval @@ -3168,10 +3597,10 @@ int _calculateDateTimeNiceInterval( return interval.floor(); } - /// for seconds - interval = axisRenderer._calculateNumericNiceInterval( + /// For seconds + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays * 24 * 60 * 60, size); - if (interval >= 1) { + if (interval! >= 1) { _setActualIntervalType( axisRenderer, notDoubleInterval @@ -3180,15 +3609,15 @@ int _calculateDateTimeNiceInterval( return interval.floor(); } - /// for milliseconds - interval = axisRenderer._calculateNumericNiceInterval( + /// For milliseconds + interval = axisRendererDetails.calculateNumericNiceInterval( axisRenderer, totalDays * 24 * 60 * 60 * 1000, size); _setActualIntervalType( axisRenderer, notDoubleInterval ? DateTimeIntervalType.milliseconds : DateTimeIntervalType.seconds); - return interval < 1 ? interval.ceil() : interval.floor(); + return interval! < 1 ? interval.ceil() : interval.floor(); default: break; } @@ -3199,23 +3628,35 @@ int _calculateDateTimeNiceInterval( void _setActualIntervalType( ChartAxisRenderer axisRenderer, DateTimeIntervalType intervalType) { if (axisRenderer is DateTimeAxisRenderer) { - axisRenderer._actualIntervalType = intervalType; + final DateTimeAxisDetails dateTimeAxisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails; + dateTimeAxisDetails.actualIntervalType = intervalType; } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - axisRenderer._actualIntervalType = intervalType; + final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) + as DateTimeCategoryAxisDetails; + dateTimeCategoryAxisDetails.actualIntervalType = intervalType; } } /// To get the label format of the date-time axis -DateFormat _getDateTimeLabelFormat(ChartAxisRenderer axisRenderer) { +DateFormat getDateTimeLabelFormat(ChartAxisRenderer axisRenderer) { DateFormat? format; - final bool notDoubleInterval = (axisRenderer._axis.interval != null && - axisRenderer._axis.interval! % 1 == 0) || - axisRenderer._axis.interval == null; + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final bool notDoubleInterval = (axisRendererDetails.axis.interval != null && + axisRendererDetails.axis.interval! % 1 == 0) || + axisRendererDetails.axis.interval == null; DateTimeIntervalType? actualIntervalType; if (axisRenderer is DateTimeAxisRenderer) { - actualIntervalType = axisRenderer._actualIntervalType; + final DateTimeAxisDetails dateTimeAxisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) as DateTimeAxisDetails; + actualIntervalType = dateTimeAxisDetails.actualIntervalType; } else if (axisRenderer is DateTimeCategoryAxisRenderer) { - actualIntervalType = axisRenderer._actualIntervalType; + final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) + as DateTimeCategoryAxisDetails; + actualIntervalType = dateTimeCategoryAxisDetails.actualIntervalType; } switch (actualIntervalType) { case DateTimeIntervalType.years: @@ -3248,20 +3689,23 @@ DateFormat _getDateTimeLabelFormat(ChartAxisRenderer axisRenderer) { return format!; } -void _setCategoryMinMaxValues( +/// Method to set the minimum and maximum value of category axis +void setCategoryMinMaxValues( ChartAxisRenderer axisRenderer, bool isXVisibleRange, bool isYVisibleRange, CartesianChartPoint point, int pointIndex, int dataLength, - CartesianSeriesRenderer seriesRenderer) { - final String seriesType = seriesRenderer._seriesType; + SeriesRendererDetails seriesRendererDetails) { + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final String seriesType = seriesRendererDetails.seriesType; final bool anchorRangeToVisiblePoints = - seriesRenderer._yAxisRenderer!._axis.anchorRangeToVisiblePoints; + seriesRendererDetails.yAxisDetails!.axis.anchorRangeToVisiblePoints; if (isYVisibleRange) { - seriesRenderer._minimumX ??= point.xValue; - seriesRenderer._maximumX ??= point.xValue; + seriesRendererDetails.minimumX ??= point.xValue; + seriesRendererDetails.maximumX ??= point.xValue; } if ((isXVisibleRange || !anchorRangeToVisiblePoints) && !seriesType.contains('range') && @@ -3269,14 +3713,14 @@ void _setCategoryMinMaxValues( !seriesType.contains('candle') && seriesType != 'boxandwhisker' && seriesType != 'waterfall') { - seriesRenderer._minimumY ??= point.yValue; - seriesRenderer._maximumY ??= point.yValue; + seriesRendererDetails.minimumY ??= point.yValue; + seriesRendererDetails.maximumY ??= point.yValue; } if (isYVisibleRange && point.xValue != null) { - seriesRenderer._minimumX = - math.min(seriesRenderer._minimumX!, point.xValue); - seriesRenderer._maximumX = - math.max(seriesRenderer._maximumX!, point.xValue); + seriesRendererDetails.minimumX = + math.min(seriesRendererDetails.minimumX!, point.xValue); + seriesRendererDetails.maximumX = + math.max(seriesRendererDetails.maximumX!, point.xValue); } if (isXVisibleRange || !anchorRangeToVisiblePoints) { if (point.yValue != null && @@ -3285,42 +3729,45 @@ void _setCategoryMinMaxValues( !seriesType.contains('candle') && seriesType != 'boxandwhisker' && seriesType != 'waterfall')) { - seriesRenderer._minimumY = - math.min(seriesRenderer._minimumY!, point.yValue); - seriesRenderer._maximumY = - math.max(seriesRenderer._maximumY!, point.yValue); + seriesRendererDetails.minimumY = + math.min(seriesRendererDetails.minimumY!, point.yValue); + seriesRendererDetails.maximumY = + math.max(seriesRendererDetails.maximumY!, point.yValue); } + if (point.high != null) { - axisRenderer._highMin = - _findMinValue(axisRenderer._highMin ?? point.high, point.high); - axisRenderer._highMax = - _findMaxValue(axisRenderer._highMax ?? point.high, point.high); + axisRendererDetails.highMin = + findMinValue(axisRendererDetails.highMin ?? point.high, point.high); + axisRendererDetails.highMax = + findMaxValue(axisRendererDetails.highMax ?? point.high, point.high); } if (point.low != null) { - axisRenderer._lowMin = - _findMinValue(axisRenderer._lowMin ?? point.low, point.low); - axisRenderer._lowMax = - _findMaxValue(axisRenderer._lowMax ?? point.low, point.low); + axisRendererDetails.lowMin = + findMinValue(axisRendererDetails.lowMin ?? point.low, point.low); + axisRendererDetails.lowMax = + findMaxValue(axisRendererDetails.lowMax ?? point.low, point.low); } if (point.maximum != null) { - axisRenderer._highMin = _findMinValue( - axisRenderer._highMin ?? point.maximum!, point.maximum!); - axisRenderer._highMax = _findMaxValue( - axisRenderer._highMax ?? point.minimum!, point.maximum!); + axisRendererDetails.highMin = findMinValue( + axisRendererDetails.highMin ?? point.maximum!, point.maximum!); + axisRendererDetails.highMax = findMaxValue( + axisRendererDetails.highMax ?? point.minimum!, point.maximum!); } if (point.minimum != null) { - axisRenderer._lowMin = - _findMinValue(axisRenderer._lowMin ?? point.minimum!, point.minimum!); - axisRenderer._lowMax = - _findMaxValue(axisRenderer._lowMax ?? point.minimum!, point.minimum!); + axisRendererDetails.lowMin = findMinValue( + axisRendererDetails.lowMin ?? point.minimum!, point.minimum!); + axisRendererDetails.lowMax = findMaxValue( + axisRendererDetails.lowMax ?? point.minimum!, point.minimum!); } if (seriesType == 'waterfall') { /// Empty point is not applicable for Waterfall series. point.yValue ??= 0; - seriesRenderer._minimumY = - _findMinValue(seriesRenderer._minimumY ?? point.yValue, point.yValue); - seriesRenderer._maximumY = _findMaxValue( - seriesRenderer._maximumY ?? point.maxYValue, point.maxYValue); + seriesRendererDetails.minimumY = findMinValue( + seriesRendererDetails.minimumY ?? point.yValue, point.yValue); + seriesRendererDetails.maximumY = findMaxValue( + seriesRendererDetails.maximumY ?? point.maxYValue, point.maxYValue); + } else if (seriesType == 'errorbar') { + updateErrorBarAxisRange(seriesRendererDetails, point); } } @@ -3329,205 +3776,216 @@ void _setCategoryMinMaxValues( seriesType.contains('hilo') || seriesType.contains('candle') || seriesType == 'boxandwhisker') { - axisRenderer._lowMin ??= 0; - axisRenderer._lowMax ??= 5; - axisRenderer._highMin ??= 0; - axisRenderer._highMax ??= 5; - seriesRenderer._minimumY = - math.min(axisRenderer._lowMin!, axisRenderer._highMin!); - seriesRenderer._maximumY = - math.max(axisRenderer._lowMax!, axisRenderer._highMax!); + axisRendererDetails.lowMin ??= 0; + axisRendererDetails.lowMax ??= 5; + axisRendererDetails.highMin ??= 0; + axisRendererDetails.highMax ??= 5; + seriesRendererDetails.minimumY = + math.min(axisRendererDetails.lowMin!, axisRendererDetails.highMin!); + seriesRendererDetails.maximumY = + math.max(axisRendererDetails.lowMax!, axisRendererDetails.highMax!); } - seriesRenderer._minimumY ??= 0; - seriesRenderer._maximumY ??= 5; + seriesRendererDetails.minimumY ??= 0; + seriesRendererDetails.maximumY ??= 5; } } -void _calculateDateTimeVisibleRange( +/// Method to calculate the date time visible range +void calculateDateTimeVisibleRange( Size availableSize, ChartAxisRenderer axisRenderer) { - final _VisibleRange actualRange = axisRenderer._actualRange!; - final SfCartesianChartState chartState = axisRenderer._chartState; - axisRenderer._setOldRangeFromRangeController(); - axisRenderer._visibleRange = chartState._rangeChangeBySlider && - axisRenderer._rangeMinimum != null && - axisRenderer._rangeMaximum != null - ? _VisibleRange(axisRenderer._rangeMinimum, axisRenderer._rangeMaximum) - : _VisibleRange(actualRange.minimum, actualRange.maximum); - final _VisibleRange visibleRange = axisRenderer._visibleRange!; + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + final VisibleRange actualRange = axisRendererDetails.actualRange!; + final CartesianStateProperties stateProperties = + axisRendererDetails.stateProperties; + axisRendererDetails.setOldRangeFromRangeController(); + axisRendererDetails.visibleRange = stateProperties.rangeChangeBySlider && + axisRendererDetails.rangeMinimum != null && + axisRendererDetails.rangeMaximum != null + ? VisibleRange( + axisRendererDetails.rangeMinimum, axisRendererDetails.rangeMaximum) + : VisibleRange(actualRange.minimum, actualRange.maximum); + final VisibleRange visibleRange = axisRendererDetails.visibleRange!; visibleRange.delta = actualRange.delta; visibleRange.interval = actualRange.interval; bool canAutoScroll = false; - if (axisRenderer._axis.autoScrollingDelta != null && - axisRenderer._axis.autoScrollingDelta! > 0 && - !chartState._isRedrawByZoomPan) { + if (axisRendererDetails.axis.autoScrollingDelta != null && + axisRendererDetails.axis.autoScrollingDelta! > 0 && + !stateProperties.isRedrawByZoomPan) { canAutoScroll = true; if (axisRenderer is DateTimeAxisRenderer) { - axisRenderer._updateScrollingDelta(); + final DateTimeAxisDetails dateTimeAxisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) + as DateTimeAxisDetails; + dateTimeAxisDetails.updateScrollingDelta(); } else { - axisRenderer._updateAutoScrollingDelta( - axisRenderer._axis.autoScrollingDelta!, axisRenderer); - } - } - if ((!canAutoScroll || chartState._zoomedState == true) && - !(chartState._rangeChangeBySlider && - !chartState._canSetRangeController)) { - axisRenderer._setZoomFactorAndPosition( - axisRenderer, chartState._zoomedAxisRendererStates); - } - if (axisRenderer._zoomFactor < 1 || - axisRenderer._zoomPosition > 0 || - (axisRenderer._axis.rangeController != null && - !chartState._renderingDetails.initialRender!)) { - chartState._zoomProgress = true; - axisRenderer._calculateZoomRange(axisRenderer, availableSize); - visibleRange.interval = axisRenderer._axis.enableAutoIntervalOnZooming && - chartState._zoomProgress && - !canAutoScroll && - axisRenderer is DateTimeAxisRenderer - ? axisRenderer.calculateInterval(visibleRange, availableSize) - : visibleRange.interval; + final DateTimeCategoryAxisDetails dateTimeCategoryAxisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer) + as DateTimeCategoryAxisDetails; + dateTimeCategoryAxisDetails.updateAutoScrollingDelta( + dateTimeCategoryAxisDetails.axis.autoScrollingDelta!, axisRenderer); + } + } + if ((!canAutoScroll || stateProperties.zoomedState == true) && + !(stateProperties.rangeChangeBySlider && + !stateProperties.canSetRangeController)) { + axisRendererDetails.setZoomFactorAndPosition( + axisRenderer, stateProperties.zoomedAxisRendererStates); + } + if (axisRendererDetails.zoomFactor < 1 || + axisRendererDetails.zoomPosition > 0 || + (axisRendererDetails.axis.rangeController != null && + !stateProperties.renderingDetails.initialRender!)) { + stateProperties.zoomProgress = true; + axisRendererDetails.calculateZoomRange(axisRenderer, availableSize); + visibleRange.interval = + axisRendererDetails.axis.enableAutoIntervalOnZooming && + stateProperties.zoomProgress && + !canAutoScroll && + axisRenderer is DateTimeAxisRenderer + ? axisRenderer.calculateInterval(visibleRange, availableSize) + : visibleRange.interval; if (axisRenderer is DateTimeAxisRenderer) { visibleRange.minimum = (visibleRange.minimum).floor(); visibleRange.maximum = (visibleRange.maximum).floor(); } - if (axisRenderer._axis.rangeController != null && - chartState._isRedrawByZoomPan && - chartState._canSetRangeController && - chartState._zoomProgress) { - chartState._rangeChangedByChart = true; - axisRenderer._setRangeControllerValues(axisRenderer); + if (axisRendererDetails.axis.rangeController != null && + stateProperties.isRedrawByZoomPan && + stateProperties.canSetRangeController && + stateProperties.zoomProgress) { + stateProperties.rangeChangedByChart = true; + axisRendererDetails.setRangeControllerValues(axisRenderer); } } - axisRenderer._setZoomValuesFromRangeController(); + axisRendererDetails.setZoomValuesFromRangeController(); } -// This method used to return the cross value of the axis. -num? _getCrossesAtValue( - CartesianSeriesRenderer seriesRenderer, SfCartesianChartState chart) { +/// This method used to return the cross value of the axis. +num? getCrossesAtValue(CartesianSeriesRenderer seriesRenderer, + CartesianStateProperties stateProperties) { num? crossesAt; - final int seriesIndex = - chart._chartSeries.visibleSeriesRenderers.indexOf(seriesRenderer); - final List axisCollection = chart._requireInvertedAxis - ? chart._chartAxis._verticalAxisRenderers - : chart._chartAxis._horizontalAxisRenderers; + final int seriesIndex = stateProperties.chartSeries.visibleSeriesRenderers + .indexOf(seriesRenderer); + final List axisCollection = + stateProperties.requireInvertedAxis + ? stateProperties.chartAxis.verticalAxisRenderers + : stateProperties.chartAxis.horizontalAxisRenderers; for (int i = 0; i < axisCollection.length; i++) { - if (chart._chartSeries.visibleSeriesRenderers[seriesIndex]._xAxisRenderer! - ._name == - axisCollection[i]._name) { - crossesAt = axisCollection[i]._crossValue; + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails(axisCollection[i]); + if (SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]) + .xAxisDetails! + .name == + axisRendererDetails.name) { + crossesAt = axisRendererDetails.crossValue; break; } } return crossesAt; } -List _getTooltipPaddingData(CartesianSeriesRenderer seriesRenderer, +/// Method to get the tooltip padding data +List getTooltipPaddingData(SeriesRendererDetails seriesRendererDetails, bool isTrendLine, Rect region, Rect paddedRegion, Offset? tooltipPosition) { Offset? padding, position; - if (seriesRenderer._seriesType == 'bubble' && !isTrendLine) { + if (seriesRendererDetails.seriesType == 'bubble' && !isTrendLine) { padding = Offset(region.center.dx - region.centerLeft.dx, 2 * (region.center.dy - region.topCenter.dy)); position = Offset(tooltipPosition!.dx, paddedRegion.top); if (region.top < 0) { padding = Offset(padding.dx, padding.dy + region.top); } - } else if (seriesRenderer._seriesType == 'scatter') { - padding = Offset(seriesRenderer._series.markerSettings.width, - seriesRenderer._series.markerSettings.height / 2); + } else if (seriesRendererDetails.seriesType == 'scatter') { + padding = Offset(seriesRendererDetails.series.markerSettings.width, + seriesRendererDetails.series.markerSettings.height / 2); position = Offset(tooltipPosition!.dx, tooltipPosition.dy); - } else if (seriesRenderer._seriesType.contains('rangearea')) { - padding = Offset(seriesRenderer._series.markerSettings.width, - seriesRenderer._series.markerSettings.height / 2); + } else if (seriesRendererDetails.seriesType.contains('rangearea') == true) { + padding = Offset(seriesRendererDetails.series.markerSettings.width, + seriesRendererDetails.series.markerSettings.height / 2); position = Offset(tooltipPosition!.dx, tooltipPosition.dy); } else { - padding = (seriesRenderer._series.markerSettings.isVisible) + padding = (seriesRendererDetails.series.markerSettings.isVisible == true) ? Offset( - seriesRenderer._series.markerSettings.width / 2, - seriesRenderer._series.markerSettings.height / 2 + - seriesRenderer._series.markerSettings.borderWidth / 2) + seriesRendererDetails.series.markerSettings.width / 2, + seriesRendererDetails.series.markerSettings.height / 2 + + seriesRendererDetails.series.markerSettings.borderWidth / 2) : const Offset(2, 2); } return [padding, position ?? tooltipPosition]; } -//Returns the old series renderer instance for the given series renderer -CartesianSeriesRenderer? _getOldSeriesRenderer( - SfCartesianChartState chartState, - CartesianSeriesRenderer seriesRenderer, +/// Returns the old series renderer instance for the given series renderer +CartesianSeriesRenderer? getOldSeriesRenderer( + CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails, int seriesIndex, List oldSeriesRenderers) { - if (chartState._renderingDetails.widgetNeedUpdate && - seriesRenderer._xAxisRenderer!._zoomFactor == 1 && - seriesRenderer._yAxisRenderer!._zoomFactor == 1 && + if (stateProperties.renderingDetails.widgetNeedUpdate && + seriesRendererDetails.xAxisDetails!.zoomFactor == 1 && + seriesRendererDetails.yAxisDetails!.zoomFactor == 1 && // ignore: unnecessary_null_comparison oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderers.length - 1 >= seriesIndex && - oldSeriesRenderers[seriesIndex]._seriesName == - seriesRenderer._seriesName) { + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderers[seriesIndex]) + .seriesName == + seriesRendererDetails.seriesName) { return oldSeriesRenderers[seriesIndex]; } else { return null; } } -//Returns the old chart point for the given point and series index if present. -CartesianChartPoint? _getOldChartPoint( - SfCartesianChartState chartState, - CartesianSeriesRenderer seriesRenderer, +/// Returns the old chart point for the given point and series index if present. +CartesianChartPoint? getOldChartPoint( + CartesianStateProperties stateProperties, + SeriesRendererDetails seriesRendererDetails, Type segmentType, int seriesIndex, int pointIndex, CartesianSeriesRenderer? oldSeriesRenderer, List oldSeriesRenderers) { - final _RenderingDetails _renderingDetails = chartState._renderingDetails; - return !seriesRenderer._reAnimate && - (seriesRenderer._series.animationDuration > 0 && + final RenderingDetails _renderingDetails = stateProperties.renderingDetails; + + return seriesRendererDetails.reAnimate == false && + (seriesRendererDetails.series.animationDuration > 0 && _renderingDetails.widgetNeedUpdate && !_renderingDetails.isLegendToggled && // ignore: unnecessary_null_comparison oldSeriesRenderers != null && oldSeriesRenderers.isNotEmpty && oldSeriesRenderer != null && - oldSeriesRenderer._segments.isNotEmpty && - oldSeriesRenderer._segments[0].runtimeType == segmentType && + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) + .segments + .isNotEmpty == + true && + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) + .segments[0] + .runtimeType == + segmentType && oldSeriesRenderers.length - 1 >= seriesIndex && - oldSeriesRenderer._dataPoints.length - 1 >= pointIndex) - ? oldSeriesRenderer._dataPoints[pointIndex] + SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) + .dataPoints + .length - + 1 >= + pointIndex) + ? SeriesHelper.getSeriesRendererDetails(oldSeriesRenderer) + .dataPoints[pointIndex] : null; } -/// To trim the specific label text -String _trimAxisLabelsText(String text, num labelsExtent, TextStyle labelStyle, - ChartAxisRenderer axisRenderer) { - String label = text; - num size = measureText( - text, axisRenderer._axis.labelStyle, axisRenderer._labelRotation) - .width; - if (size > labelsExtent) { - final int textLength = text.length; - for (int i = textLength - 1; i >= 0; --i) { - label = text.substring(0, i) + '...'; - size = measureText(label, labelStyle, axisRenderer._labelRotation).width; - if (size <= labelsExtent) { - return label == '...' ? '' : label; - } - } - } - return label == '...' ? '' : label; -} - /// Boolean to check whether it is necessary to render the axis tooltip. -bool _shouldShowAxisTooltip(SfCartesianChartState chartState) { +bool shouldShowAxisTooltip(CartesianStateProperties stateProperties) { bool requireAxisTooltip = false; for (int i = 0; - i < chartState._chartAxis._axisRenderersCollection.length; + i < stateProperties.chartAxis.axisRenderersCollection.length; i++) { - requireAxisTooltip = chartState._chartAxis._axisRenderersCollection[i]._axis - .maximumLabelWidth != - null || - chartState._chartAxis._axisRenderersCollection[i]._axis.labelsExtent != - null; + final ChartAxisRendererDetails axisRendererDetails = + AxisHelper.getAxisRendererDetails( + stateProperties.chartAxis.axisRenderersCollection[i]); + requireAxisTooltip = axisRendererDetails.axis.maximumLabelWidth != null || + axisRendererDetails.axis.labelsExtent != null; if (requireAxisTooltip) { break; } @@ -3535,32 +3993,319 @@ bool _shouldShowAxisTooltip(SfCartesianChartState chartState) { return requireAxisTooltip; } -int? _getVisibleDataPointIndex( - int? pointIndex, CartesianSeriesRenderer seriesRender) { +/// Method to get the visbile data point index +int? getVisibleDataPointIndex( + int? pointIndex, SeriesRendererDetails seriesRendererDetails) { int? index; if (pointIndex != null) { - if (pointIndex < seriesRender._dataPoints[0].overallDataPointIndex! || + if (pointIndex < + seriesRendererDetails.dataPoints[0].overallDataPointIndex! || pointIndex > - seriesRender._dataPoints[seriesRender._dataPoints.length - 1] + seriesRendererDetails + .dataPoints[seriesRendererDetails.dataPoints.length - 1] .overallDataPointIndex!) { index = null; - } else if (pointIndex > seriesRender._dataPoints.length - 1) { - for (int i = 0; i < seriesRender._dataPoints.length; i++) { - if (pointIndex == seriesRender._dataPoints[i].overallDataPointIndex) { - index = seriesRender._dataPoints[i].visiblePointIndex; + } else if (pointIndex > seriesRendererDetails.dataPoints.length - 1) { + for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { + if (pointIndex == + seriesRendererDetails.dataPoints[i].overallDataPointIndex) { + index = seriesRendererDetails.dataPoints[i].visiblePointIndex; } } } else { - index = seriesRender._dataPoints[pointIndex].visiblePointIndex; + index = seriesRendererDetails.dataPoints[pointIndex].visiblePointIndex; } } return index; } -bool _isLineTypeSeries(String seriesType) { +/// Method to check whether the series is line series type +bool isLineTypeSeries(String seriesType) { return seriesType == 'line' || seriesType == 'spline' || seriesType == 'stepline' || seriesType == 'stackedline' || seriesType == 'stackedline100'; } + +/// A circular array for dash offsets and lengths. +class CircularIntervalList { + /// Creates an instance of circular interval list + CircularIntervalList(this._values); + final List _values; + int _index = 0; + + /// Returns the next value + T get next { + if (_index >= _values.length) { + _index = 0; + } + return _values[_index++]; + } +} + +/// Gets the R-squared value +double? getRSquaredValue(CartesianSeriesRenderer series, Trendline trendline, + List? slope, double? intercept) { + double rSquare = 0.0; + const int power = 2; + final List xValue = []; + final List yValue = []; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(series); + double yMean = 0; + const int startXValue = 1; + final DateTime excelDate = DateTime(1900, 01, 01); + for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { + xValue.add( + seriesRendererDetails.xAxisDetails?.axisRenderer is DateTimeAxisRenderer + ? seriesRendererDetails.dataPoints[i].x.difference(excelDate).inDays + : i + startXValue); + yValue.add(seriesRendererDetails.dataPoints[i].y.toDouble()); + yMean += seriesRendererDetails.dataPoints[i].y.toDouble(); + } + yMean = yMean / yValue.length; + // Total sum of square (ssTot) + double ssTot = 0.0; + for (int j = 0; j < yValue.length; j++) { + ssTot += math.pow(yValue[j] - yMean, power); + } + // Sum of squares due to regression (ssReg) + double ssReg = 0.0; + if (trendline.type == TrendlineType.linear) { + for (int k = 0; k < yValue.length; k++) { + ssReg += math.pow(((slope![0] * xValue[k]) + intercept!) - yMean, power); + } + } + if (trendline.type == TrendlineType.exponential) { + for (int k = 0; k < yValue.length; k++) { + ssReg += math.pow( + (intercept! * math.exp(slope![0] * xValue[k])) - yMean, power); + } + } + if (trendline.type == TrendlineType.logarithmic) { + for (int k = 0; k < yValue.length; k++) { + ssReg += math.pow( + ((slope![0] * math.log(xValue[k])) + intercept!) - yMean, power); + } + } + if (trendline.type == TrendlineType.power) { + for (int k = 0; k < yValue.length; k++) { + ssReg += math.pow( + (intercept! * math.pow(xValue[k], slope![0])) - yMean, power); + } + } + if (trendline.type == TrendlineType.polynomial) { + for (int k = 0; k < yValue.length; k++) { + double yCap = 0.0; + for (int i = 0; i < slope!.length; i++) { + yCap += slope[i] * math.pow(xValue[k], i); + } + ssReg += math.pow(yCap - yMean, power); + } + } + rSquare = ssReg / ssTot; + return rSquare.isNaN ? 0 : rSquare; +} + +/// To calculate and return the bubble size +double calculateBubbleRadius( + SeriesRendererDetails seriesRendererDetails, + CartesianSeries series, + CartesianChartPoint currentPoint) { + final BubbleSeries bubbleSeries = + series as BubbleSeries; + num bubbleRadius, sizeRange, radiusRange, maxSize, minSize; + const num defaultBubbleSize = 4; + maxSize = seriesRendererDetails.maxSize!; + minSize = seriesRendererDetails.minSize!; + sizeRange = maxSize - minSize; + final num minRadius = bubbleSeries.minimumRadius, + maxRadius = bubbleSeries.maximumRadius; + final double bubbleSize = + ((currentPoint.bubbleSize) ?? defaultBubbleSize).toDouble(); + assert(minRadius >= 0 && bubbleSeries.maximumRadius >= 0, + 'The min radius and max radius of the bubble should be greater than or equal to 0.'); + if (bubbleSeries.sizeValueMapper == null) { + // ignore: unnecessary_null_comparison + minRadius != null ? bubbleRadius = minRadius : bubbleRadius = maxRadius; + } else { + if (sizeRange == 0) { + bubbleRadius = bubbleSize == 0 ? minRadius : maxRadius; + } else { + radiusRange = maxRadius - minRadius; + bubbleRadius = + minRadius + radiusRange * ((bubbleSize.abs() / maxSize).abs()); + } + } + return bubbleRadius.toDouble(); +} + +/// Cartesian point to pixel +Offset calculatePointToPixel( + CartesianChartPoint point, dynamic seriesRenderer) { + final num x = point.x; + final num y = point.y; + final SeriesRendererDetails seriesRendererDetails; + if (seriesRenderer is SeriesRendererDetails == false) { + seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + } else { + seriesRendererDetails = seriesRenderer; + } + final ChartAxisRendererDetails xAxisDetails = + seriesRendererDetails.xAxisDetails!; + final ChartAxisRendererDetails yAxisDetails = + seriesRendererDetails.yAxisDetails!; + + final bool isInverted = + seriesRendererDetails.stateProperties.requireInvertedAxis; + + final CartesianSeries series = seriesRendererDetails.series; + final ChartLocation location = calculatePoint( + x, + y, + xAxisDetails, + yAxisDetails, + isInverted, + series, + seriesRendererDetails.stateProperties.chartAxis.axisClipRect); + + return Offset(location.x, location.y); +} + +/// Cartesian pixel to point +CartesianChartPoint calculatePixelToPoint( + Offset position, dynamic seriesRenderer) { + SeriesRendererDetails seriesRendererDetails; + if (seriesRenderer is SeriesRendererDetails) { + seriesRendererDetails = seriesRenderer; + } else { + seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(seriesRenderer); + } + if (seriesRendererDetails.xAxisDetails == null) { + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + seriesRendererDetails.stateProperties.oldSeriesRenderers[0]); + } + ChartAxisRendererDetails xAxisDetails = seriesRendererDetails.xAxisDetails!; + ChartAxisRendererDetails yAxisDetails = seriesRendererDetails.yAxisDetails!; + + final ChartAxis xAxis = xAxisDetails.axis; + final ChartAxis yAxis = yAxisDetails.axis; + + final CartesianSeries series = seriesRendererDetails.series; + + final Rect rect = + seriesRendererDetails.stateProperties.chartAxis.axisClipRect; + + if (series.xAxisName != null || series.yAxisName != null) { + for (final ChartAxisRenderer axisRenderer in seriesRendererDetails + .stateProperties.chartAxis.axisRenderersCollection) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + if (axisDetails.name == series.xAxisName) { + xAxisDetails = axisDetails; + } else if (axisDetails.name == series.yAxisName) { + yAxisDetails = axisDetails; + } + } + } else { + xAxisDetails = xAxisDetails; + yAxisDetails = yAxisDetails; + } + + num xValue = pointToXValue( + seriesRendererDetails.stateProperties.requireInvertedAxis, + xAxisDetails.axisRenderer, + rect, + position.dx - (rect.left + xAxis.plotOffset), + position.dy - (rect.top + yAxis.plotOffset)); + num yValue = pointToYValue( + seriesRendererDetails.stateProperties.requireInvertedAxis, + yAxisDetails.axisRenderer, + rect, + position.dx - (rect.left + xAxis.plotOffset), + position.dy - (rect.top + yAxis.plotOffset)); + + if (xAxisDetails is LogarithmicAxisDetails) { + final LogarithmicAxis axis = xAxis as LogarithmicAxis; + xValue = math.pow(xValue, calculateLogBaseValue(xValue, axis.logBase)); + } else { + xValue = xValue; + } + if (yAxisDetails is LogarithmicAxisDetails) { + final LogarithmicAxis axis = yAxis as LogarithmicAxis; + yValue = math.pow(yValue, calculateLogBaseValue(yValue, axis.logBase)); + } else { + yValue = yValue; + } + return CartesianChartPoint(xValue, yValue); +} + +/// To get seriesType of the cartesian series renderer +String getSeriesType(CartesianSeriesRenderer seriesRenderer) { + String seriesType = ''; + + if (seriesRenderer is AreaSeriesRenderer) { + seriesType = 'area'; + } else if (seriesRenderer is BarSeriesRenderer) { + seriesType = 'bar'; + } else if (seriesRenderer is BubbleSeriesRenderer) { + seriesType = 'bubble'; + } else if (seriesRenderer is ColumnSeriesRenderer) { + seriesType = 'column'; + } else if (seriesRenderer is FastLineSeriesRenderer) { + seriesType = 'fastline'; + } else if (seriesRenderer is LineSeriesRenderer) { + seriesType = 'line'; + } else if (seriesRenderer is ScatterSeriesRenderer) { + seriesType = 'scatter'; + } else if (seriesRenderer is SplineSeriesRenderer) { + seriesType = 'spline'; + } else if (seriesRenderer is StepLineSeriesRenderer) { + seriesType = 'stepline'; + } else if (seriesRenderer is StackedColumnSeriesRenderer) { + seriesType = 'stackedcolumn'; + } else if (seriesRenderer is StackedBarSeriesRenderer) { + seriesType = 'stackedbar'; + } else if (seriesRenderer is StackedAreaSeriesRenderer) { + seriesType = 'stackedarea'; + } else if (seriesRenderer is StackedArea100SeriesRenderer) { + seriesType = 'stackedarea100'; + } else if (seriesRenderer is StackedLineSeriesRenderer) { + seriesType = 'stackedline'; + } else if (seriesRenderer is StackedLine100SeriesRenderer) { + seriesType = 'stackedline100'; + } else if (seriesRenderer is RangeColumnSeriesRenderer) { + seriesType = 'rangecolumn'; + } else if (seriesRenderer is RangeAreaSeriesRenderer) { + seriesType = 'rangearea'; + } else if (seriesRenderer is StackedColumn100SeriesRenderer) { + seriesType = 'stackedcolumn100'; + } else if (seriesRenderer is StackedBar100SeriesRenderer) { + seriesType = 'stackedbar100'; + } else if (seriesRenderer is SplineAreaSeriesRenderer) { + seriesType = 'splinearea'; + } else if (seriesRenderer is StepAreaSeriesRenderer) { + seriesType = 'steparea'; + } else if (seriesRenderer is HiloSeriesRenderer) { + seriesType = 'hilo'; + } else if (seriesRenderer is HiloOpenCloseSeriesRenderer) { + seriesType = 'hiloopenclose'; + } else if (seriesRenderer is CandleSeriesRenderer) { + seriesType = 'candle'; + } else if (seriesRenderer is HistogramSeriesRenderer) { + seriesType = 'histogram'; + } else if (seriesRenderer is SplineRangeAreaSeriesRenderer) { + seriesType = 'splinerangearea'; + } else if (seriesRenderer is BoxAndWhiskerSeriesRenderer) { + seriesType = 'boxandwhisker'; + } else if (seriesRenderer is WaterfallSeriesRenderer) { + seriesType = 'waterfall'; + } else if (seriesRenderer is ErrorBarSeriesRenderer) { + seriesType = 'errorbar'; + } + + return seriesType; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_area.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_area.dart new file mode 100644 index 000000000..0723263f1 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_area.dart @@ -0,0 +1,774 @@ +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/tooltip_internal.dart'; +import '../../chart/user_interaction/selection_renderer.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/event_args.dart'; +import '../../common/template/rendering.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../renderer/chart_point.dart'; +import '../renderer/circular_chart_annotation.dart'; +import '../renderer/circular_series.dart'; +import '../renderer/common.dart'; +import '../renderer/data_label_renderer.dart'; +import '../renderer/renderer_extension.dart'; +import '../series_painter/doughnut_series_painter.dart'; +import '../series_painter/pie_chart_painter.dart'; +import '../series_painter/radial_bar_painter.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'circular_base.dart'; +import 'circular_state_properties.dart'; + +/// Represents the circular chart area +/// +// ignore: must_be_immutable +class CircularArea extends StatelessWidget { + /// Creates an instance for circular area + // ignore: prefer_const_constructors_in_immutables + CircularArea({required this.stateProperties}); + + /// Here, we are using get keyword in order to get the proper & updated instance of chart widget + //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. + SfCircularChart get chart => stateProperties.chart; + + /// Specifies the chart state + final CircularStateProperties stateProperties; + + /// Gets or sets the circular series + CircularSeries? series; + + /// Holds the render box of the circular chart + late RenderBox renderBox; + + /// Specifies the point region + Region? pointRegion; + + /// Holds the tap down details + late TapDownDetails tapDownDetails; + + /// Holds the double tap position + Offset? doubleTapPosition; + + /// Specifies whether the mouse is hovered + final bool _enableMouseHover = kIsWeb; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + child: MouseRegion( + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerEvent event) => + _enableMouseHover ? _onHover(event) : null, + onExit: (PointerEvent event) { + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; + }, + child: Listener( + onPointerUp: (PointerUpEvent event) => _onTapUp(event), + onPointerDown: (PointerDownEvent event) => _onTapDown(event), + onPointerMove: (PointerMoveEvent event) => + _performPointerMove(event), + child: GestureDetector( + onLongPress: _onLongPress, + onTapUp: (TapUpDetails details) { + if (chart.onPointTapped != null && pointRegion != null) { + calculatePointSeriesIndex( + chart, stateProperties, null, pointRegion); + } + if (chart.series[0].onPointTap != null && + pointRegion != null) { + calculatePointSeriesIndex(chart, stateProperties, null, + pointRegion, ActivationMode.singleTap); + } + }, + onDoubleTap: _onDoubleTap, + child: Container( + height: constraints.maxHeight, + width: constraints.maxWidth, + child: _initializeChart(constraints, context), + decoration: const BoxDecoration(color: Colors.transparent), + )), + )), + ); + }); + } + + /// To perform the pointer down event + void _onTapDown(PointerDownEvent event) { + ChartTouchInteractionArgs touchArgs; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.isHovering = false; + stateProperties.renderingDetails.currentActive = null; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + pointRegion = getCircularPointRegion( + chart, + stateProperties.renderingDetails.tapPosition, + stateProperties.chartSeries.visibleSeriesRenderers[0]); + doubleTapPosition = stateProperties.renderingDetails.tapPosition; + if (stateProperties.renderingDetails.tapPosition != null && + pointRegion != null) { + stateProperties.renderingDetails.currentActive = ChartInteraction( + pointRegion?.seriesIndex, + pointRegion?.pointIndex, + stateProperties.chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex].series, + stateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex] + .renderPoints![pointRegion!.pointIndex], + pointRegion); + } else { + //hides the tooltip if the point of interaction is outside circular region of the chart + tooltipRenderingDetails.show = false; + tooltipRenderingDetails.hideTooltipTemplate(); + } + if (chart.onChartTouchInteractionDown != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionDown!(touchArgs); + } + } + + /// To perform the pointer move event + void _performPointerMove(PointerMoveEvent event) { + ChartTouchInteractionArgs touchArgs; + final Offset position = renderBox.globalToLocal(event.position); + if (chart.onChartTouchInteractionMove != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = position; + chart.onChartTouchInteractionMove!(touchArgs); + } + } + + /// To perform double tap touch interactions + void _onDoubleTap() { + if (doubleTapPosition != null && pointRegion != null) { + if (chart.series[0].onPointDoubleTap != null && pointRegion != null) { + calculatePointSeriesIndex(chart, stateProperties, null, pointRegion, + ActivationMode.doubleTap); + } + stateProperties.renderingDetails.currentActive = ChartInteraction( + pointRegion?.seriesIndex, + pointRegion?.pointIndex, + stateProperties.chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex].series, + stateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex] + .renderPoints![pointRegion!.pointIndex], + pointRegion); + if (stateProperties.renderingDetails.currentActive != null) { + if (stateProperties + .renderingDetails.currentActive?.series.explodeGesture == + ActivationMode.doubleTap) { + stateProperties.chartSeries.seriesPointExplosion( + stateProperties.renderingDetails.currentActive?.region); + } + } + stateProperties.chartSeries + .seriesPointSelection(pointRegion, ActivationMode.doubleTap); + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.doubleTap && + doubleTapPosition != null) { + stateProperties.requireDataLabelTooltip = null; + if (chart.tooltipBehavior.builder != null) { + showCircularTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer.onDoubleTap( + doubleTapPosition!.dx.toDouble(), + doubleTapPosition!.dy.toDouble()); + } + } + } + } + + /// To perform long press touch interactions + void _onLongPress() { + if (stateProperties.renderingDetails.tapPosition != null && + pointRegion != null) { + if (chart.series[0].onPointLongPress != null && pointRegion != null) { + calculatePointSeriesIndex(chart, stateProperties, null, pointRegion, + ActivationMode.longPress); + } + stateProperties.renderingDetails.currentActive = ChartInteraction( + pointRegion?.seriesIndex, + pointRegion?.pointIndex, + stateProperties.chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex].series, + stateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex] + .renderPoints![pointRegion!.pointIndex], + pointRegion); + stateProperties.chartSeries + .seriesPointSelection(pointRegion, ActivationMode.longPress); + if (stateProperties.renderingDetails.currentActive != null) { + if (stateProperties + .renderingDetails.currentActive?.series.explodeGesture == + ActivationMode.longPress) { + stateProperties.chartSeries.seriesPointExplosion( + stateProperties.renderingDetails.currentActive?.region); + } + } + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.longPress && + stateProperties.renderingDetails.tapPosition != null) { + stateProperties.requireDataLabelTooltip = null; + if (chart.tooltipBehavior.builder != null) { + showCircularTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer.onLongPress( + stateProperties.renderingDetails.tapPosition!.dx.toDouble(), + stateProperties.renderingDetails.tapPosition!.dy.toDouble()); + } + } + } + } + + /// To perform the pointer up event + void _onTapUp(PointerUpEvent event) { + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; + ChartTouchInteractionArgs touchArgs; + final CircularSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + final Offset position = renderBox.globalToLocal(event.position); + if (stateProperties.animationCompleted) { + _showTrimmedDataLabelTooltip(position, stateProperties, seriesRenderer); + } + if (chart.onDataLabelTapped != null) { + triggerCircularDataLabelEvent(chart, seriesRenderer, stateProperties, + stateProperties.renderingDetails.tapPosition); + } + if (stateProperties.renderingDetails.tapPosition != null) { + if (stateProperties.renderingDetails.currentActive != null && + stateProperties.renderingDetails.currentActive!.series != null && + stateProperties + .renderingDetails.currentActive!.series.explodeGesture == + ActivationMode.singleTap) { + stateProperties.chartSeries.seriesPointExplosion( + stateProperties.renderingDetails.currentActive!.region); + } + + if (stateProperties.renderingDetails.tapPosition != null && + stateProperties.renderingDetails.currentActive != null) { + stateProperties.chartSeries.seriesPointSelection( + stateProperties.renderingDetails.currentActive!.region, + ActivationMode.singleTap); + } + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.singleTap && + stateProperties.renderingDetails.currentActive != null && + stateProperties.renderingDetails.currentActive!.series != null) { + stateProperties.requireDataLabelTooltip = null; + if (chart.tooltipBehavior.builder != null) { + showCircularTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer + .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); + } + } + if (chart.onChartTouchInteractionUp != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionUp!(touchArgs); + } + } + stateProperties.renderingDetails.tapPosition = null; + } + + /// To perform hover event + void _onHover(PointerEvent event) { + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + stateProperties.renderingDetails.currentActive = null; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + pointRegion = getCircularPointRegion( + chart, + stateProperties.renderingDetails.tapPosition, + stateProperties.chartSeries.visibleSeriesRenderers[0]); + final CircularSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + final Offset position = renderBox.globalToLocal(event.position); + if (stateProperties.animationCompleted) { + _showTrimmedDataLabelTooltip(position, stateProperties, seriesRenderer); + } + if (chart.onDataLabelTapped != null) { + triggerCircularDataLabelEvent(chart, seriesRenderer, stateProperties, + stateProperties.renderingDetails.tapPosition); + } + if (stateProperties.renderingDetails.tapPosition != null && + pointRegion != null) { + stateProperties.renderingDetails.currentActive = ChartInteraction( + pointRegion!.seriesIndex, + pointRegion!.pointIndex, + stateProperties.chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex].series, + stateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion!.seriesIndex] + .renderPoints![pointRegion!.pointIndex], + pointRegion); + } else { + //hides the tooltip when the mouse is hovering out of the circular region + tooltipRenderingDetails.hide(); + } + if (stateProperties.renderingDetails.tapPosition != null) { + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.currentActive != null && + stateProperties.renderingDetails.currentActive!.series != null) { + tooltipRenderingDetails.isHovering = true; + stateProperties.requireDataLabelTooltip = null; + if (chart.tooltipBehavior.builder != null) { + showCircularTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer + .onEnter(position.dx.toDouble(), position.dy.toDouble()); + } + } else { + tooltipRenderingDetails.prevTooltipValue = null; + tooltipRenderingDetails.currentTooltipValue = null; + } + } + stateProperties.renderingDetails.tapPosition = null; + } + + /// This method gets executed for showing tooltip when builder is provided in behavior + ///the optional parameters will take values once thee public method gets called + void showCircularTooltipTemplate([int? seriesIndex, int? pointIndex]) { + stateProperties.isTooltipHidden = false; + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + if (!tooltipRenderingDetails.isHovering) { + //assigning null for the previous and current tooltip values in case of touch interaction + tooltipRenderingDetails.prevTooltipValue = null; + tooltipRenderingDetails.currentTooltipValue = null; + } + final CircularSeries chartSeries = + stateProperties.renderingDetails.currentActive?.series ?? + chart.series[seriesIndex!]; + final ChartPoint point = pointIndex == null + ? stateProperties.renderingDetails.currentActive?.point + : stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; + if (point.isVisible) { + final Offset? location = degreeToPoint(point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); + if (location != null && (chartSeries.enableTooltip)) { + tooltipRenderingDetails.showLocation = location; + tooltipRenderingDetails.chartTooltipState!.boundaryRect = + tooltipRenderingDetails.tooltipBounds = + stateProperties.renderingDetails.chartContainerRect; + tooltipRenderingDetails.tooltipTemplate = chart + .tooltipBehavior.builder!( + chartSeries.dataSource![pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!], + point, + chartSeries, + seriesIndex ?? + stateProperties.renderingDetails.currentActive!.seriesIndex!, + pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!); + if (tooltipRenderingDetails.isHovering) { + // assigning values for previous and current tooltip values when the mouse is hovering + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue; + tooltipRenderingDetails.currentTooltipValue = TooltipValue( + seriesIndex ?? + stateProperties.renderingDetails.currentActive!.seriesIndex!, + pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!); + } + if (!tooltipRenderingDetails.isHovering) { + tooltipRenderingDetails.hideTooltipTemplate(); + } + tooltipRenderingDetails.show = true; + tooltipRenderingDetails.performTooltip(); + } + } + } + + /// To initialize chart widgets + Widget _initializeChart(BoxConstraints constraints, BuildContext context) { + _calculateContainerSize(constraints); + if (chart.series.isNotEmpty) { + stateProperties.chartSeries.calculateAngleAndCenterPositions( + stateProperties.chartSeries.visibleSeriesRenderers[0]); + } + return Container( + decoration: const BoxDecoration(color: Colors.transparent), + child: _renderWidgets(constraints, context)); + } + + /// To calculate chart rect area size + void _calculateContainerSize(BoxConstraints constraints) { + final num width = constraints.maxWidth; + final num height = constraints.maxHeight; + stateProperties.renderingDetails.chartContainerRect = + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); + final EdgeInsets margin = chart.margin; + stateProperties.renderingDetails.chartAreaRect = Rect.fromLTWH( + margin.left, + margin.top, + width - margin.right - margin.left, + height - margin.top - margin.bottom); + } + + /// To render chart widgets + Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { + _bindSeriesWidgets(context); + _findTemplates(); + _renderTemplates(); + _bindTooltipWidgets(constraints); + stateProperties.circularArea = this; + renderBox = context.findRenderObject() as RenderBox; + return Container( + child: Stack( + textDirection: TextDirection.ltr, + children: stateProperties.renderingDetails.chartWidgets!)); + } + + /// To add chart templates + void _findTemplates() { + Offset labelLocation; + const num lineLength = 10; + ChartPoint point; + Widget labelWidget; + stateProperties.renderingDetails.templates = []; + stateProperties.renderingDetails.dataLabelTemplateRegions = []; + stateProperties.annotationRegions = []; + CircularSeriesRendererExtension seriesRenderer; + CircularSeries series; + ConnectorLineSettings connector; + ChartAlignment labelAlign; + num connectorLength; + for (int k = 0; + k < stateProperties.chartSeries.visibleSeriesRenderers.length; + k++) { + seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[k]; + series = seriesRenderer.series; + connector = series.dataLabelSettings.connectorLineSettings; + if (series.dataLabelSettings.isVisible && + series.dataLabelSettings.builder != null) { + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + point = seriesRenderer.renderPoints![i]; + if (point.isVisible) { + labelWidget = series.dataLabelSettings.builder!( + series.dataSource![i], point, series, i, k); + if (series.dataLabelSettings.labelPosition == + ChartDataLabelPosition.inside) { + labelLocation = degreeToPoint(point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); + labelLocation = Offset(labelLocation.dx, labelLocation.dy); + labelAlign = ChartAlignment.center; + } else { + connectorLength = percentToValue( + connector.length ?? '10%', point.outerRadius!)!; + labelLocation = degreeToPoint(point.midAngle!, + point.outerRadius! + connectorLength, point.center!); + labelLocation = Offset( + point.dataLabelPosition == Position.right + ? labelLocation.dx + lineLength + 5 + : labelLocation.dx - lineLength - 5, + labelLocation.dy); + labelAlign = point.dataLabelPosition == Position.left + ? ChartAlignment.far + : ChartAlignment.near; + } + stateProperties.renderingDetails.templates.add(ChartTemplateInfo( + key: GlobalKey(), + templateType: 'DataLabel', + pointIndex: i, + seriesIndex: k, + needMeasure: true, + clipRect: stateProperties.renderingDetails.chartAreaRect, + animationDuration: 500, + widget: labelWidget, + horizontalAlignment: labelAlign, + verticalAlignment: ChartAlignment.center, + location: labelLocation)); + } + } + } + } + + _setTemplateInfo(); + } + + /// Method to set the template info + void _setTemplateInfo() { + CircularChartAnnotation annotation; + double radius, annotationHeight, annotationWidth; + ChartTemplateInfo templateInfo; + Offset point; + if (chart.annotations != null && chart.annotations!.isNotEmpty) { + for (int i = 0; i < chart.annotations!.length; i++) { + annotation = chart.annotations![i]; + if (annotation.widget != null) { + radius = percentToValue( + annotation.radius, stateProperties.chartSeries.size / 2)! + .toDouble(); + point = degreeToPoint( + annotation.angle, radius, stateProperties.centerLocation); + annotationHeight = percentToValue( + annotation.height, stateProperties.chartSeries.size / 2)! + .toDouble(); + annotationWidth = percentToValue( + annotation.width, stateProperties.chartSeries.size / 2)! + .toDouble(); + templateInfo = ChartTemplateInfo( + key: GlobalKey(), + templateType: 'Annotation', + needMeasure: true, + horizontalAlignment: annotation.horizontalAlignment, + verticalAlignment: annotation.verticalAlignment, + clipRect: stateProperties.renderingDetails.chartContainerRect, + widget: annotationHeight > 0 && annotationWidth > 0 + ? Container( + height: annotationHeight, + width: annotationWidth, + child: annotation.widget) + : annotation.widget!, + pointIndex: i, + animationDuration: 500, + location: point); + stateProperties.renderingDetails.templates.add(templateInfo); + } + } + } + } + + /// To render chart templates + void _renderTemplates() { + if (stateProperties.renderingDetails.templates.isNotEmpty) { + for (int i = 0; + i < stateProperties.renderingDetails.templates.length; + i++) { + stateProperties.renderingDetails.templates[i].animationDuration = + !stateProperties.renderingDetails.initialRender! + ? 0 + : stateProperties + .renderingDetails.templates[i].animationDuration; + } + stateProperties.renderingDetails.chartTemplate = ChartTemplate( + templates: stateProperties.renderingDetails.templates, + render: stateProperties.renderingDetails.animateCompleted, + stateProperties: stateProperties); + stateProperties.renderingDetails.chartWidgets! + .add(stateProperties.renderingDetails.chartTemplate!); + } + } + + /// To add tooltip widgets to chart + void _bindTooltipWidgets(BoxConstraints constraints) { + TooltipHelper.setStateProperties(chart.tooltipBehavior, stateProperties); + final SfChartThemeData _chartTheme = + stateProperties.renderingDetails.chartTheme; + if (chart.tooltipBehavior.enable || + stateProperties.chartSeries.visibleSeriesRenderers[0].series + .dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) { + final TooltipBehavior tooltip = chart.tooltipBehavior; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue = null; + tooltipRenderingDetails.chartTooltip = SfTooltip( + color: tooltip.color ?? _chartTheme.tooltipColor, + key: GlobalKey(), + textStyle: tooltip.textStyle, + animationDuration: tooltip.animationDuration, + animationCurve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), + enable: tooltip.enable, + opacity: tooltip.opacity, + borderColor: tooltip.borderColor, + borderWidth: tooltip.borderWidth, + duration: tooltip.duration.toInt(), + shouldAlwaysShow: tooltip.shouldAlwaysShow, + elevation: tooltip.elevation, + canShowMarker: tooltip.canShowMarker, + textAlignment: tooltip.textAlignment, + decimalPlaces: tooltip.decimalPlaces, + labelColor: tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, + header: tooltip.header, + format: tooltip.format, + shadowColor: tooltip.shadowColor, + onTooltipRender: chart.onTooltipRender != null + ? tooltipRenderingDetails.tooltipRenderingEvent + : null); + final Widget uiWidget = IgnorePointer( + ignoring: true, + child: + Stack(children: [tooltipRenderingDetails.chartTooltip!])); + stateProperties.renderingDetails.chartWidgets!.add(uiWidget); + } + } + + /// To add series widgets in chart + void _bindSeriesWidgets(BuildContext context) { + late CustomPainter seriesPainter; + Animation? seriesAnimation; + stateProperties.renderingDetails.animateCompleted = false; + stateProperties.renderingDetails.chartWidgets ??= []; + CircularSeries series; + CircularSeriesRendererExtension seriesRenderer; + dynamic selectionBehavior; + SelectionBehaviorRenderer selectionBehaviorRenderer; + for (int i = 0; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; + i++) { + seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[i]; + series = seriesRenderer.series; + selectionBehavior = + seriesRenderer.selectionBehavior = series.selectionBehavior; + selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer = + SelectionBehaviorRenderer(selectionBehavior, chart, stateProperties); + SelectionHelper.setSelectionBehaviorRenderer( + series.selectionBehavior, selectionBehaviorRenderer); + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); + selectionDetails.selectionRenderer ??= SelectionRenderer(); + selectionDetails.selectionRenderer!.chart = chart; + selectionDetails.selectionRenderer!.stateProperties = stateProperties; + selectionDetails.selectionRenderer!.seriesRendererDetails = + seriesRenderer; + if (series.initialSelectedDataIndexes.isNotEmpty) { + for (int index = 0; + index < series.initialSelectedDataIndexes.length; + index++) { + stateProperties.renderingDetails.selectionData + .add(series.initialSelectedDataIndexes[index]); + } + } + stateProperties.renderingDetails.animateCompleted = false; + if (series.animationDuration > 0 && + !stateProperties.renderingDetails.didSizeChange && + (stateProperties.renderingDetails.oldDeviceOrientation == + stateProperties.renderingDetails.deviceOrientation) && + (stateProperties.renderingDetails.initialRender! || + (stateProperties.renderingDetails.widgetNeedUpdate && + seriesRenderer.needsAnimation) || + stateProperties.renderingDetails.isLegendToggled)) { + final int totalAnimationDuration = + series.animationDuration.toInt() + series.animationDelay.toInt(); + stateProperties.renderingDetails.animationController.duration = + Duration(milliseconds: totalAnimationDuration); + const double maxSeriesInterval = 0.8; + double minSeriesInterval = 0.1; + minSeriesInterval = series.animationDelay.toInt() / + totalAnimationDuration * + (maxSeriesInterval - minSeriesInterval) + + minSeriesInterval; + seriesAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: stateProperties.renderingDetails.animationController, + curve: Interval(minSeriesInterval, maxSeriesInterval, + curve: Curves.linear), + )..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + stateProperties.renderingDetails.animateCompleted = true; + if (stateProperties.renderDataLabel != null) { + stateProperties.renderDataLabel!.state.render(); + } + if (stateProperties.renderingDetails.chartTemplate != null) { + stateProperties.renderingDetails.chartTemplate!.state + .templateRender(); + } + } + })); + stateProperties.renderingDetails.chartElementAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: stateProperties.renderingDetails.animationController, + curve: const Interval(0.85, 1.0, curve: Curves.decelerate), + )); + stateProperties.renderingDetails.animationController.forward(from: 0.0); + } else { + stateProperties.renderingDetails.animateCompleted = true; + } + seriesRenderer.repaintNotifier = + stateProperties.renderingDetails.seriesRepaintNotifier; + if (seriesRenderer.seriesType == 'pie') { + seriesPainter = PieChartPainter( + stateProperties: stateProperties, + index: i, + isRepaint: seriesRenderer.needsRepaint, + animationController: + stateProperties.renderingDetails.animationController, + seriesAnimation: seriesAnimation, + notifier: stateProperties.renderingDetails.seriesRepaintNotifier); + } else if (seriesRenderer.seriesType == 'doughnut') { + seriesPainter = DoughnutChartPainter( + stateProperties: stateProperties, + index: i, + isRepaint: seriesRenderer.needsRepaint, + animationController: + stateProperties.renderingDetails.animationController, + seriesAnimation: seriesAnimation, + notifier: stateProperties.renderingDetails.seriesRepaintNotifier); + } else if (seriesRenderer.seriesType == 'radialbar') { + seriesPainter = RadialBarPainter( + stateProperties: stateProperties, + index: i, + isRepaint: seriesRenderer.needsRepaint, + animationController: + stateProperties.renderingDetails.animationController, + seriesAnimation: seriesAnimation, + notifier: stateProperties.renderingDetails.seriesRepaintNotifier); + } + stateProperties.renderingDetails.chartWidgets! + .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); + stateProperties.renderDataLabel = CircularDataLabelRenderer( + stateProperties: stateProperties, + show: stateProperties.renderingDetails.animateCompleted); + stateProperties.renderingDetails.chartWidgets! + .add(stateProperties.renderDataLabel!); + } + } +} + +/// Show tooltip for trimmed data label. +void _showTrimmedDataLabelTooltip( + Offset position, + CircularStateProperties stateProperties, + CircularSeriesRendererExtension seriesRenderer) { + stateProperties.requireDataLabelTooltip = + (stateProperties.requireDataLabelTooltip == null && + !stateProperties.isTooltipHidden) + ? stateProperties.requireDataLabelTooltip + : false; + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + if (seriesRenderer.series.dataLabelSettings.isVisible && + seriesRenderer.renderPoints![i].labelRect + .contains(Offset(position.dx, position.dy)) && + seriesRenderer.renderPoints![i].trimmedText != null && + seriesRenderer.renderPoints![i].trimmedText!.contains('...')) { + stateProperties.requireDataLabelTooltip = true; + stateProperties.renderingDetails.tooltipBehaviorRenderer + .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart index c74c8f95e..1c84ceee0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_base.dart @@ -1,4 +1,36 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../common/common.dart'; +import '../../common/legend/legend.dart'; +import '../../common/legend/renderer.dart'; +import '../../common/rendering_details.dart'; +import '../../common/template/rendering.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../../common/utils/typedef.dart'; +import '../renderer/chart_point.dart'; +import '../renderer/circular_chart_annotation.dart'; +import '../renderer/circular_series.dart'; +import '../renderer/circular_series_controller.dart'; +import '../renderer/common.dart'; +import '../renderer/renderer_base.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/helper.dart'; +import 'circular_area.dart'; +import 'circular_state_properties.dart'; +import 'series_base.dart'; ///Renders the circular chart /// @@ -502,48 +534,8 @@ class SfCircularChart extends StatefulWidget { /// class SfCircularChartState extends State with TickerProviderStateMixin { - /// Specifies the center location - late Offset _centerLocation; - - /// Specifies the annoatation region - late List _annotationRegions; - - /// Specifies the data label renderer - _CircularDataLabelRenderer? _renderDataLabel; - - /// Specifies the previous series renderer - CircularSeriesRenderer? _prevSeriesRenderer; - - /// Specifies the previous chart points - List?>? _oldPoints; - - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfCircularChart get _chart => widget; - - /// Holds the information of SeriesBase class - late _CircularSeries _chartSeries; - - /// Specifies the circular chart area - late _CircularArea _circularArea; - - /// Specifies whether move the label from center - late bool _needToMoveFromCenter; - - /// Specifies whether to explode the segments - late bool _needExplodeAll; - - /// Gets or sets the value for is toggled - late bool _isToggled; - /// Specifies the chart rendering details - late _RenderingDetails _renderingDetails; - - // ignore: unused_element - bool get _animationCompleted { - return _renderingDetails.animationController.status != - AnimationStatus.forward; - } + late CircularStateProperties _stateProperties; /// Called when this object is inserted into the tree. /// @@ -558,9 +550,10 @@ class SfCircularChartState extends State @override void initState() { - _renderingDetails = _RenderingDetails(); - _renderingDetails.didSizeChange = false; - _isToggled = false; + _stateProperties = CircularStateProperties( + renderingDetails: RenderingDetails(), chartState: this); + + _stateProperties.isToggled = false; _initializeDefaultValues(); // Create the series renderer while initial rendering // _createAndUpdateSeriesRenderer(); @@ -578,7 +571,7 @@ class SfCircularChartState extends State @override void didChangeDependencies() { - _renderingDetails.chartTheme = SfChartTheme.of(context); + _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -600,20 +593,27 @@ class SfCircularChartState extends State //Update and maintain the series state, when we update the series in the series collection // _createAndUpdateSeriesRenderer(oldWidget); - _needsRepaintCircularChart(_chartSeries.visibleSeriesRenderers, - [_prevSeriesRenderer]); + needsRepaintCircularChart( + _stateProperties.chartSeries.visibleSeriesRenderers, + [ + _stateProperties.prevSeriesRenderer + ]); - _needExplodeAll = widget.series.isNotEmpty && + _stateProperties.needExplodeAll = widget.series.isNotEmpty && (widget.series[0].explodeAll && widget.series[0].explode && oldWidget.series[0].explodeAll != widget.series[0].explodeAll); - _renderingDetails.isLegendToggled = false; - _renderingDetails.widgetNeedUpdate = true; - if (_renderingDetails.legendWidgetContext.isNotEmpty) { - _renderingDetails.legendWidgetContext.clear(); + _stateProperties.renderingDetails.isLegendToggled = false; + _stateProperties.renderingDetails.widgetNeedUpdate = true; + if (_stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { + _stateProperties.renderingDetails.legendWidgetContext.clear(); } - if (_renderingDetails.tooltipBehaviorRenderer._chartTooltipState != null) { - _renderingDetails.tooltipBehaviorRenderer._show = false; + + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + if (tooltipRenderingDetails.chartTooltipState != null) { + tooltipRenderingDetails.show = false; } super.didUpdateWidget(oldWidget); } @@ -631,22 +631,26 @@ class SfCircularChartState extends State @override Widget build(BuildContext context) { - _renderingDetails.initialRender = (_renderingDetails.widgetNeedUpdate && - !_renderingDetails.isLegendToggled) - ? _needExplodeAll - : (_renderingDetails.initialRender == null); - _renderingDetails.oldDeviceOrientation = - _renderingDetails.oldDeviceOrientation == null + _stateProperties.renderingDetails.initialRender = + (_stateProperties.renderingDetails.widgetNeedUpdate && + !_stateProperties.renderingDetails.isLegendToggled) + ? _stateProperties.needExplodeAll + : (_stateProperties.renderingDetails.initialRender == null); + _stateProperties.renderingDetails.oldDeviceOrientation = + _stateProperties.renderingDetails.oldDeviceOrientation == null ? MediaQuery.of(context).orientation - : _renderingDetails.deviceOrientation; - _renderingDetails.deviceOrientation = MediaQuery.of(context).orientation; - return RepaintBoundary( - child: _ChartContainer( + : _stateProperties.renderingDetails.deviceOrientation; + _stateProperties.renderingDetails.deviceOrientation = + MediaQuery.of(context).orientation; + _stateProperties.isTooltipOrientationChanged = false; + + final Widget container = ChartContainer( child: GestureDetector( child: Container( decoration: BoxDecoration( color: widget.backgroundColor ?? - _renderingDetails.chartTheme.plotAreaBackgroundColor, + _stateProperties + .renderingDetails.chartTheme.plotAreaBackgroundColor, image: widget.backgroundImage != null ? DecorationImage( image: widget.backgroundImage!, fit: BoxFit.fill) @@ -654,19 +658,23 @@ class SfCircularChartState extends State border: Border.all( color: widget.borderColor, width: widget.borderWidth)), child: Column( - children: [_renderChartTitle(this), _renderChartElements()], + children: [ + renderChartTitle(_stateProperties), + _renderChartElements() + ], ), )), - )); + ); + return RepaintBoundary(child: container); } /// Called when this object is removed from the tree permanently. /// /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this - /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// point. This stage of the life cycle is terminal: there is no way to remount a [State] object that has been disposed. /// - /// Subclasses should override this method to release any resources retained by this object. + /// Sub classes should override this method to release any resources retained by this object. /// /// * In [dispose], unsubscribe from the object. /// @@ -674,8 +682,9 @@ class SfCircularChartState extends State @override void dispose() { - _disposeAnimationController( - _renderingDetails.animationController, _repaintChartElements); + disposeAnimationController( + _stateProperties.renderingDetails.animationController, + _repaintChartElements); super.dispose(); } @@ -745,104 +754,106 @@ class SfCircularChartState extends State return image; } - /// To intialize default values of circular chart + /// To initialize default values of circular chart void _initializeDefaultValues() { - _chartSeries = _CircularSeries(this); - _circularArea = _CircularArea(chartState: this); - _renderingDetails.chartLegend = _ChartLegend(this); - _needToMoveFromCenter = true; - _renderingDetails.animateCompleted = false; - _renderingDetails.annotationController = AnimationController(vsync: this); - _renderingDetails.seriesRepaintNotifier = ValueNotifier(0); - _renderingDetails.legendWidgetContext = <_MeasureWidgetContext>[]; - _renderingDetails.explodedPoints = []; - _renderingDetails.templates = <_ChartTemplateInfo>[]; - _renderingDetails.legendToggleStates = <_LegendRenderContext>[]; - _renderingDetails.legendToggleTemplateStates = <_MeasureWidgetContext>[]; - _renderingDetails.dataLabelTemplateRegions = []; - _annotationRegions = []; - _renderingDetails.widgetNeedUpdate = false; - _renderingDetails.isLegendToggled = false; - _renderingDetails.selectionData = []; - _renderingDetails.animationController = AnimationController(vsync: this) - ..addListener(_repaintChartElements); - _renderingDetails.tooltipBehaviorRenderer = TooltipBehaviorRenderer(this); - _renderingDetails.legendRenderer = LegendRenderer(widget.legend); + _stateProperties.chartSeries = CircularSeriesBase(_stateProperties); + _stateProperties.circularArea = + CircularArea(stateProperties: _stateProperties); + _stateProperties.renderingDetails.chartLegend = + ChartLegend(_stateProperties); + _stateProperties.needToMoveFromCenter = true; + _stateProperties.renderingDetails.animateCompleted = false; + _stateProperties.renderingDetails.annotationController = + AnimationController(vsync: this); + _stateProperties.renderingDetails.seriesRepaintNotifier = + ValueNotifier(0); + _stateProperties.renderingDetails.legendWidgetContext = + []; + _stateProperties.renderingDetails.explodedPoints = []; + _stateProperties.renderingDetails.templates = []; + _stateProperties.renderingDetails.legendToggleStates = + []; + _stateProperties.renderingDetails.legendToggleTemplateStates = + []; + _stateProperties.renderingDetails.dataLabelTemplateRegions = []; + _stateProperties.annotationRegions = []; + _stateProperties.renderingDetails.widgetNeedUpdate = false; + _stateProperties.renderingDetails.isLegendToggled = false; + _stateProperties.renderingDetails.selectionData = []; + _stateProperties.renderingDetails.animationController = + AnimationController(vsync: this)..addListener(_repaintChartElements); + _stateProperties.renderingDetails.tooltipBehaviorRenderer = + TooltipBehaviorRenderer(_stateProperties); + _stateProperties.renderingDetails.legendRenderer = + LegendRenderer(widget.legend); } - // In this method, create and update the series renderer for each series // + /// In this method, create and update the series renderer for each series // void _createAndUpdateSeriesRenderer([SfCircularChart? oldWidget]) { if (widget.series.isNotEmpty) { - final CircularSeriesRenderer? oldSeriesRenderer = + final CircularSeriesRendererExtension? oldSeriesRenderer = oldWidget != null && oldWidget.series.isNotEmpty - ? _chartSeries.visibleSeriesRenderers[0] + ? _stateProperties.chartSeries.visibleSeriesRenderers[0] : null; dynamic series; series = widget.series[0]; - CircularSeriesRenderer seriesRenderer; + CircularSeriesRendererExtension? seriesRenderer; - if (_prevSeriesRenderer != null && - !_prevSeriesRenderer!._chartState._isToggled && - _isSameSeries(_prevSeriesRenderer!._series, series)) { - seriesRenderer = _prevSeriesRenderer!; + if (_stateProperties.prevSeriesRenderer != null && + !_stateProperties.prevSeriesRenderer!.stateProperties.isToggled && + isSameSeries(_stateProperties.prevSeriesRenderer!.series, series)) { + seriesRenderer = _stateProperties.prevSeriesRenderer!; } else { - seriesRenderer = series.createRenderer(series); - if (seriesRenderer._controller == null && + final CircularSeriesRenderer renderer = series.createRenderer(series); + if (renderer is CircularSeriesRendererExtension) { + seriesRenderer = renderer; + } else { + if (renderer is PieSeriesRenderer) { + seriesRenderer = PieSeriesRendererExtension(); + } else if (renderer is DoughnutSeriesRenderer) { + seriesRenderer = DoughnutSeriesRendererExtension(); + } else if (renderer is RadialBarSeriesRenderer) { + seriesRenderer = RadialBarSeriesRendererExtension(); + } + } + seriesRenderer!.renderer = ChartSeriesRender(); + if (seriesRenderer.controller == null && series.onRendererCreated != null) { - seriesRenderer._controller = CircularSeriesController(seriesRenderer); - series.onRendererCreated!(seriesRenderer._controller); + seriesRenderer.controller = CircularSeriesController(seriesRenderer); + series.onRendererCreated!(seriesRenderer.controller); } } if (oldWidget != null && oldWidget.series.isNotEmpty) { - _prevSeriesRenderer = oldSeriesRenderer; - _prevSeriesRenderer!._series = oldWidget.series[0]; - _prevSeriesRenderer!._oldRenderPoints = >[] - //ignore: prefer_spread_collections - ..addAll( - _prevSeriesRenderer!._renderPoints ?? >[]); - _prevSeriesRenderer!._renderPoints = >[]; + _stateProperties.prevSeriesRenderer = oldSeriesRenderer; + _stateProperties.prevSeriesRenderer!.series = oldWidget.series[0]; + _stateProperties.prevSeriesRenderer!.oldRenderPoints = + >[] + //ignore: prefer_spread_collections + ..addAll(_stateProperties.prevSeriesRenderer!.renderPoints ?? + >[]); + _stateProperties.prevSeriesRenderer!.renderPoints = + >[]; } - seriesRenderer._series = series; - seriesRenderer._isSelectionEnable = + seriesRenderer.series = series; + seriesRenderer.isSelectionEnable = series.selectionBehavior.enable == true; - seriesRenderer._chartState = this; - _chartSeries.visibleSeriesRenderers + seriesRenderer.stateProperties = _stateProperties; + _stateProperties.chartSeries.visibleSeriesRenderers ..clear() ..add(seriesRenderer); } } void _repaintChartElements() { - _renderingDetails.seriesRepaintNotifier.value++; - } - - /// To redraw chart elements - // ignore:unused_element - void _redraw() { - _renderingDetails.initialRender = false; - if (_renderingDetails.isLegendToggled) { - _isToggled = true; - _prevSeriesRenderer = _chartSeries.visibleSeriesRenderers[0]; - _oldPoints = List?>.filled( - _prevSeriesRenderer!._renderPoints!.length, null); - for (int i = 0; i < _prevSeriesRenderer!._renderPoints!.length; i++) { - _oldPoints![i] = _prevSeriesRenderer!._renderPoints![i]; - } - } - if (_renderingDetails.tooltipBehaviorRenderer._chartTooltipState != null) { - _renderingDetails.tooltipBehaviorRenderer._show = false; - } - setState(() { - /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. - }); + _stateProperties.renderingDetails.seriesRepaintNotifier.value++; } void _refresh() { - final List<_MeasureWidgetContext> legendContexts = - _renderingDetails.legendWidgetContext; + final List legendContexts = + _stateProperties.renderingDetails.legendWidgetContext; if (legendContexts.isNotEmpty) { - _MeasureWidgetContext templateContext; + MeasureWidgetContext templateContext; RenderBox renderBox; for (int i = 0; i < legendContexts.length; i++) { templateContext = legendContexts[i]; @@ -861,27 +872,33 @@ class SfCircularChartState extends State child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { Widget element; - _renderingDetails.prevSize = - _renderingDetails.prevSize ?? constraints.biggest; - _renderingDetails.didSizeChange = - _renderingDetails.prevSize != constraints.biggest; - _renderingDetails.prevSize = constraints.biggest; _initialize(constraints); - _chartSeries._findVisibleSeries(); - if (_chartSeries.visibleSeriesRenderers.isNotEmpty) { - _chartSeries - ._processDataPoints(_chartSeries.visibleSeriesRenderers[0]); + _stateProperties.renderingDetails.prevSize = + _stateProperties.renderingDetails.prevSize ?? constraints.biggest; + _stateProperties.renderingDetails.didSizeChange = + _stateProperties.renderingDetails.prevSize != constraints.biggest; + _stateProperties.renderingDetails.prevSize = constraints.biggest; + final ChartPoint tooltipPoint = + _getChartPoints(_stateProperties); + SchedulerBinding.instance!.addPostFrameCallback((_) { + _validateStateMaintenance(_stateProperties, tooltipPoint); + }); + _stateProperties.chartSeries.findVisibleSeries(); + if (_stateProperties.chartSeries.visibleSeriesRenderers.isNotEmpty) { + _stateProperties.chartSeries.processDataPoints( + _stateProperties.chartSeries.visibleSeriesRenderers[0]); } - final List legendTemplates = _bindLegendTemplateWidgets(this); + final List legendTemplates = + bindLegendTemplateWidgets(_stateProperties); if (legendTemplates.isNotEmpty && - _renderingDetails.legendWidgetContext.isEmpty) { + _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); } else { - _renderingDetails.chartLegend - ._calculateLegendBounds(_renderingDetails.chartLegend.chartSize); - element = - _getElements(this, _CircularArea(chartState: this), constraints)!; + _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( + _stateProperties.renderingDetails.chartLegend.chartSize); + element = getElements(_stateProperties, + CircularArea(stateProperties: _stateProperties), constraints)!; } return element; }), @@ -893,694 +910,89 @@ class SfCircularChartState extends State final num width = constraints.maxWidth; final num height = constraints.maxHeight; final EdgeInsets margin = widget.margin; - _renderingDetails.legendRenderer._legendPosition = + final bool isMobilePlatform = + defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS; + _stateProperties.renderingDetails.legendRenderer.legendPosition = (widget.legend.position == LegendPosition.auto) - ? (height > width ? LegendPosition.bottom : LegendPosition.right) + ? (height > width + ? isMobilePlatform + ? LegendPosition.top + : LegendPosition.bottom + : LegendPosition.right) : widget.legend.position; - _renderingDetails.chartLegend.chartSize = Size( + _stateProperties.renderingDetails.chartLegend.chartSize = Size( width - margin.left - margin.right, height - margin.top - margin.bottom); } -} - -// ignore: must_be_immutable -class _CircularArea extends StatelessWidget { - // ignore: prefer_const_constructors_in_immutables - _CircularArea({required this.chartState}); - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfCircularChart get chart => chartState._chart; - - /// Specifies the chart state - final SfCircularChartState chartState; - - /// Gets or sets the circular series - CircularSeries? series; - - /// Holds the render box of the circular chart - late RenderBox renderBox; - - /// Specifies the point region - _Region? pointRegion; - - /// Holds the tap down details - late TapDownDetails tapDownDetails; - - /// Holds the double tap position - Offset? doubleTapPosition; - - /// Specifies whether the mouse is hovered - final bool _enableMouseHover = kIsWeb; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Container( - child: MouseRegion( - // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. - // Issue: https://github.com/flutter/flutter/issues/68690 - onHover: (PointerEvent event) => - _enableMouseHover ? _onHover(event) : null, - onExit: (PointerEvent event) { - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = - false; - }, - child: Listener( - onPointerUp: (PointerUpEvent event) => _onTapUp(event), - onPointerDown: (PointerDownEvent event) => _onTapDown(event), - onPointerMove: (PointerMoveEvent event) => - _performPointerMove(event), - child: GestureDetector( - onLongPress: _onLongPress, - onTapUp: (TapUpDetails details) { - if (chart.onPointTapped != null && pointRegion != null) { - _calculatePointSeriesIndex( - chart, chartState, null, pointRegion); - } - if (chart.series[0].onPointTap != null && - pointRegion != null) { - _calculatePointSeriesIndex(chart, chartState, null, - pointRegion, ActivationMode.singleTap); - } - }, - onDoubleTap: _onDoubleTap, - child: Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: _initializeChart(constraints, context), - decoration: const BoxDecoration(color: Colors.transparent), - )), - )), - ); - }); - } - - /// To perform the pointer down event - void _onTapDown(PointerDownEvent event) { - ChartTouchInteractionArgs touchArgs; - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = false; - chartState._renderingDetails.currentActive = null; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - pointRegion = _getCircularPointRegion( - chart, - chartState._renderingDetails.tapPosition, - chartState._chartSeries.visibleSeriesRenderers[0]); - doubleTapPosition = chartState._renderingDetails.tapPosition; - if (chartState._renderingDetails.tapPosition != null && - pointRegion != null) { - chartState._renderingDetails.currentActive = _ChartInteraction( - pointRegion?.seriesIndex, - pointRegion?.pointIndex, - chartState._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, - chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - ._renderPoints![pointRegion!.pointIndex], - pointRegion); - } else { - //hides the tooltip if the point of interaction is outside circular region of the chart - chartState._renderingDetails.tooltipBehaviorRenderer._show = false; - chartState._renderingDetails.tooltipBehaviorRenderer - ._hideTooltipTemplate(); - } - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown!(touchArgs); - } - } - - /// To perform the pointer move event - void _performPointerMove(PointerMoveEvent event) { - ChartTouchInteractionArgs touchArgs; - final Offset position = renderBox.globalToLocal(event.position); - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = position; - chart.onChartTouchInteractionMove!(touchArgs); - } - } - /// To perform double tap touch interactions - void _onDoubleTap() { - if (doubleTapPosition != null && pointRegion != null) { - if (chart.series[0].onPointDoubleTap != null && pointRegion != null) { - _calculatePointSeriesIndex( - chart, chartState, null, pointRegion, ActivationMode.doubleTap); - } - chartState._renderingDetails.currentActive = _ChartInteraction( - pointRegion?.seriesIndex, - pointRegion?.pointIndex, - chartState._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, - chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - ._renderPoints![pointRegion!.pointIndex], - pointRegion); - if (chartState._renderingDetails.currentActive != null) { - if (chartState._renderingDetails.currentActive?.series.explodeGesture == - ActivationMode.doubleTap) { - chartState._chartSeries._seriesPointExplosion( - chartState._renderingDetails.currentActive?.region); - } - } - chartState._chartSeries - ._seriesPointSelection(pointRegion, ActivationMode.doubleTap); - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap && - doubleTapPosition != null) { - if (chart.tooltipBehavior.builder != null) { - _showCircularTooltipTemplate(); - } else { - chartState._renderingDetails.tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition!.dx.toDouble(), - doubleTapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform long press touch interactions - void _onLongPress() { - if (chartState._renderingDetails.tapPosition != null && - pointRegion != null) { - if (chart.series[0].onPointLongPress != null && pointRegion != null) { - _calculatePointSeriesIndex( - chart, chartState, null, pointRegion, ActivationMode.longPress); - } - chartState._renderingDetails.currentActive = _ChartInteraction( - pointRegion?.seriesIndex, - pointRegion?.pointIndex, - chartState._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, - chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - ._renderPoints![pointRegion!.pointIndex], - pointRegion); - chartState._chartSeries - ._seriesPointSelection(pointRegion, ActivationMode.longPress); - if (chartState._renderingDetails.currentActive != null) { - if (chartState._renderingDetails.currentActive?.series.explodeGesture == - ActivationMode.longPress) { - chartState._chartSeries._seriesPointExplosion( - chartState._renderingDetails.currentActive?.region); - } - } - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.longPress && - chartState._renderingDetails.tapPosition != null) { - if (chart.tooltipBehavior.builder != null) { - _showCircularTooltipTemplate(); - } else { - chartState._renderingDetails.tooltipBehaviorRenderer.onLongPress( - chartState._renderingDetails.tapPosition!.dx.toDouble(), - chartState._renderingDetails.tapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform the pointer up event - void _onTapUp(PointerUpEvent event) { - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = false; - ChartTouchInteractionArgs touchArgs; - final CircularSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[0]; - if (chart.onDataLabelTapped != null) { - _triggerCircularDataLabelEvent(chart, seriesRenderer, chartState, - chartState._renderingDetails.tapPosition); - } - if (chartState._renderingDetails.tapPosition != null) { - if (chartState._renderingDetails.currentActive != null && - chartState._renderingDetails.currentActive!.series != null && - chartState._renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.singleTap) { - chartState._chartSeries._seriesPointExplosion( - chartState._renderingDetails.currentActive!.region); - } - - if (chartState._renderingDetails.tapPosition != null && - chartState._renderingDetails.currentActive != null) { - chartState._chartSeries._seriesPointSelection( - chartState._renderingDetails.currentActive!.region, - ActivationMode.singleTap); - } - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap && - chartState._renderingDetails.currentActive != null && - chartState._renderingDetails.currentActive!.series != null) { - if (chart.tooltipBehavior.builder != null) { - _showCircularTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - chartState._renderingDetails.tooltipBehaviorRenderer - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); - } - } - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp!(touchArgs); - } - } - chartState._renderingDetails.tapPosition = null; - } - - /// To perform hover event - void _onHover(PointerEvent event) { - chartState._renderingDetails.currentActive = null; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - pointRegion = _getCircularPointRegion( - chart, - chartState._renderingDetails.tapPosition, - chartState._chartSeries.visibleSeriesRenderers[0]); - final CircularSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[0]; - if (chart.onDataLabelTapped != null) { - _triggerCircularDataLabelEvent(chart, seriesRenderer, chartState, - chartState._renderingDetails.tapPosition); - } - if (chartState._renderingDetails.tapPosition != null && - pointRegion != null) { - chartState._renderingDetails.currentActive = _ChartInteraction( - pointRegion!.seriesIndex, - pointRegion!.pointIndex, - chartState._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex]._series, - chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion!.seriesIndex] - ._renderPoints![pointRegion!.pointIndex], - pointRegion); - } else { - //hides the tooltip when the mouse is hovering out of the circular region - chartState._renderingDetails.tooltipBehaviorRenderer._hide(); - } - if (chartState._renderingDetails.tapPosition != null) { - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.currentActive != null && - chartState._renderingDetails.currentActive!.series != null) { - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = true; - if (chart.tooltipBehavior.builder != null) { - _showCircularTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - chartState._renderingDetails.tooltipBehaviorRenderer - .onEnter(position.dx.toDouble(), position.dy.toDouble()); - } - } else { - chartState._renderingDetails.tooltipBehaviorRenderer._prevTooltipValue = - null; - chartState._renderingDetails.tooltipBehaviorRenderer - ._currentTooltipValue = null; - } - } - chartState._renderingDetails.tapPosition = null; - } - - /// This method gets executed for showing tooltip when builder is provided in behavior - ///the optional parameters will take values once thee public method gets called - void _showCircularTooltipTemplate([int? seriesIndex, int? pointIndex]) { + /// This will return tooltip chart point + ChartPoint _getChartPoints( + CircularStateProperties _stateProperties) { final TooltipBehaviorRenderer tooltipBehaviorRenderer = - chartState._renderingDetails.tooltipBehaviorRenderer; - if (!tooltipBehaviorRenderer._isHovering) { - //assingning null for the previous and current tooltip values in case of touch interaction - tooltipBehaviorRenderer._prevTooltipValue = null; - tooltipBehaviorRenderer._currentTooltipValue = null; - } - final CircularSeries chartSeries = - chartState._renderingDetails.currentActive?.series ?? - chart.series[seriesIndex!]; - final ChartPoint point = pointIndex == null - ? chartState._renderingDetails.currentActive?.point - : chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; - if (point.isVisible) { - final Offset? location = _degreeToPoint(point.midAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - if (location != null && (chartSeries.enableTooltip)) { - tooltipBehaviorRenderer._showLocation = location; - tooltipBehaviorRenderer._chartTooltipState!.boundaryRect = - tooltipBehaviorRenderer._tooltipBounds = - chartState._renderingDetails.chartContainerRect; - tooltipBehaviorRenderer._tooltipTemplate = - chart.tooltipBehavior.builder!( - chartSeries.dataSource![pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!], - point, - chartSeries, - seriesIndex ?? - chartState._renderingDetails.currentActive!.seriesIndex!, - pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!); - if (tooltipBehaviorRenderer._isHovering) { - // assigning values for previous and current tooltip values when the mouse is hovering - tooltipBehaviorRenderer._prevTooltipValue = - tooltipBehaviorRenderer._currentTooltipValue; - tooltipBehaviorRenderer._currentTooltipValue = TooltipValue( - seriesIndex ?? - chartState._renderingDetails.currentActive!.seriesIndex!, - pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!); - } - if (!tooltipBehaviorRenderer._isHovering) { - tooltipBehaviorRenderer._hideTooltipTemplate(); - } - tooltipBehaviorRenderer._show = true; - tooltipBehaviorRenderer._performTooltip(); + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + + ChartPoint tooltipChartPoint = ChartPoint(null, null); + + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable == true && + _stateProperties.isTooltipHidden == false && + _stateProperties.requireDataLabelTooltip == null) { + tooltipChartPoint = circularPixelToPoint( + tooltipRenderingDetails.showLocation!, _stateProperties); } } + return tooltipChartPoint; } - /// To initialize chart widgets - Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - _calculateContainerSize(constraints); - if (chart.series.isNotEmpty) { - chartState._chartSeries._calculateAngleAndCenterPositions( - chartState._chartSeries.visibleSeriesRenderers[0]); - } - return Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: _renderWidgets(constraints, context)); - } - - /// To calculate chart rect area size - void _calculateContainerSize(BoxConstraints constraints) { - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - chartState._renderingDetails.chartContainerRect = - Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); - final EdgeInsets margin = chart.margin; - chartState._renderingDetails.chartAreaRect = Rect.fromLTWH( - margin.left, - margin.top, - width - margin.right - margin.left, - height - margin.top - margin.bottom); - } - - /// To render chart widgets - Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _bindSeriesWidgets(context); - _findTemplates(); - _renderTemplates(); - _bindTooltipWidgets(constraints); - chartState._circularArea = this; - renderBox = context.findRenderObject() as RenderBox; - return Container( - child: Stack( - textDirection: TextDirection.ltr, - children: chartState._renderingDetails.chartWidgets!)); - } - - /// To add chart templates - void _findTemplates() { - Offset labelLocation; - const num lineLength = 10; - ChartPoint point; - Widget labelWidget; - chartState._renderingDetails.templates = <_ChartTemplateInfo>[]; - chartState._renderingDetails.dataLabelTemplateRegions = []; - chartState._annotationRegions = []; - CircularSeriesRenderer seriesRenderer; - CircularSeries series; - ConnectorLineSettings connector; - ChartAlignment labelAlign; - num connectorLength; - for (int k = 0; - k < chartState._chartSeries.visibleSeriesRenderers.length; - k++) { - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[k]; - series = seriesRenderer._series; - connector = series.dataLabelSettings.connectorLineSettings; - if (series.dataLabelSettings.isVisible && - series.dataLabelSettings.builder != null) { - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - point = seriesRenderer._renderPoints![i]; - if (point.isVisible) { - labelWidget = series.dataLabelSettings.builder!( - series.dataSource![i], point, series, i, k); - if (series.dataLabelSettings.labelPosition == - ChartDataLabelPosition.inside) { - labelLocation = _degreeToPoint(point.midAngle!, - (point.innerRadius! + point.outerRadius!) / 2, point.center!); - labelLocation = Offset(labelLocation.dx, labelLocation.dy); - labelAlign = ChartAlignment.center; - } else { - connectorLength = _percentToValue( - connector.length ?? '10%', point.outerRadius!)!; - labelLocation = _degreeToPoint(point.midAngle!, - point.outerRadius! + connectorLength, point.center!); - labelLocation = Offset( - point.dataLabelPosition == Position.right - ? labelLocation.dx + lineLength + 5 - : labelLocation.dx - lineLength - 5, - labelLocation.dy); - labelAlign = point.dataLabelPosition == Position.left - ? ChartAlignment.far - : ChartAlignment.near; - } - chartState._renderingDetails.templates.add(_ChartTemplateInfo( - key: GlobalKey(), - templateType: 'DataLabel', - pointIndex: i, - seriesIndex: k, - needMeasure: true, - clipRect: chartState._renderingDetails.chartAreaRect, - animationDuration: 500, - widget: labelWidget, - horizontalAlignment: labelAlign, - verticalAlignment: ChartAlignment.center, - location: labelLocation)); + /// Here for orientation change/browser resize, the logic in this method will get executed + void _validateStateMaintenance(CircularStateProperties _stateProperties, + ChartPoint tooltipChartPoint) { + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable && + !_stateProperties.isTooltipHidden && + _stateProperties.requireDataLabelTooltip == null) { + _stateProperties.isTooltipOrientationChanged = true; + ChartPoint? point; + for (int i = 0; + i < + _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints.length; + i++) { + if (_stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i].x == + tooltipChartPoint.x && + _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i].y == + tooltipChartPoint.y) { + point = _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i]; } } - } - } - - _setTemplateInfo(); - } - - /// Method to set the tempalte info - void _setTemplateInfo() { - CircularChartAnnotation annotation; - double radius, annotationHeight, annotationWidth; - _ChartTemplateInfo templateInfo; - Offset point; - if (chart.annotations != null && chart.annotations!.isNotEmpty) { - for (int i = 0; i < chart.annotations!.length; i++) { - annotation = chart.annotations![i]; - if (annotation.widget != null) { - radius = _percentToValue( - annotation.radius, chartState._chartSeries.size / 2)! - .toDouble(); - point = _degreeToPoint( - annotation.angle, radius, chartState._centerLocation); - annotationHeight = _percentToValue( - annotation.height, chartState._chartSeries.size / 2)! - .toDouble(); - annotationWidth = _percentToValue( - annotation.width, chartState._chartSeries.size / 2)! - .toDouble(); - templateInfo = _ChartTemplateInfo( - key: GlobalKey(), - templateType: 'Annotation', - needMeasure: true, - horizontalAlignment: annotation.horizontalAlignment, - verticalAlignment: annotation.verticalAlignment, - clipRect: chartState._renderingDetails.chartContainerRect, - widget: annotationHeight > 0 && annotationWidth > 0 - ? Container( - height: annotationHeight, - width: annotationWidth, - child: annotation.widget) - : annotation.widget!, - pointIndex: i, - animationDuration: 500, - location: point); - chartState._renderingDetails.templates.add(templateInfo); - } - } - } - } - - /// To render chart templates - void _renderTemplates() { - if (chartState._renderingDetails.templates.isNotEmpty) { - for (int i = 0; i < chartState._renderingDetails.templates.length; i++) { - chartState._renderingDetails.templates[i].animationDuration = - !chartState._renderingDetails.initialRender! - ? 0 - : chartState._renderingDetails.templates[i].animationDuration; - } - chartState._renderingDetails.chartTemplate = _ChartTemplate( - templates: chartState._renderingDetails.templates, - render: chartState._renderingDetails.animateCompleted, - chartState: chartState); - chartState._renderingDetails.chartWidgets! - .add(chartState._renderingDetails.chartTemplate!); - } - } - - /// To add tooltip widgets to chart - void _bindTooltipWidgets(BoxConstraints constraints) { - chart.tooltipBehavior._chartState = chartState; - final SfChartThemeData _chartTheme = - chartState._renderingDetails.chartTheme; - if (chart.tooltipBehavior.enable) { - final TooltipBehavior tooltip = chart.tooltipBehavior; - chartState._renderingDetails.tooltipBehaviorRenderer._prevTooltipValue = - chartState._renderingDetails.tooltipBehaviorRenderer - ._currentTooltipValue = null; - chartState._renderingDetails.tooltipBehaviorRenderer._chartTooltip = - SfTooltip( - color: tooltip.color ?? _chartTheme.tooltipColor, - key: GlobalKey(), - textStyle: tooltip.textStyle, - animationDuration: tooltip.animationDuration, - animationCurve: - const Interval(0.1, 0.8, curve: Curves.easeOutBack), - enable: tooltip.enable, - opacity: tooltip.opacity, - borderColor: tooltip.borderColor, - borderWidth: tooltip.borderWidth, - duration: tooltip.duration.toInt(), - shouldAlwaysShow: tooltip.shouldAlwaysShow, - elevation: tooltip.elevation, - canShowMarker: tooltip.canShowMarker, - textAlignment: tooltip.textAlignment, - decimalPlaces: tooltip.decimalPlaces, - labelColor: - tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, - header: tooltip.header, - format: tooltip.format, - shadowColor: tooltip.shadowColor, - onTooltipRender: chart.onTooltipRender != null - ? chartState._renderingDetails.tooltipBehaviorRenderer - ._tooltipRenderingEvent - : null); - final Widget uiWidget = IgnorePointer( - ignoring: true, - child: Stack(children: [ - chartState._renderingDetails.tooltipBehaviorRenderer._chartTooltip! - ])); - chartState._renderingDetails.chartWidgets!.add(uiWidget); - } - } - - /// To add series widgets in chart - void _bindSeriesWidgets(BuildContext context) { - late CustomPainter seriesPainter; - Animation? seriesAnimation; - chartState._renderingDetails.animateCompleted = false; - chartState._renderingDetails.chartWidgets ??= []; - CircularSeries series; - CircularSeriesRenderer seriesRenderer; - dynamic selectionBehavior; - SelectionBehaviorRenderer selectionBehaviorRenderer; - for (int i = 0; - i < chartState._chartSeries.visibleSeriesRenderers.length; - i++) { - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[i]; - series = seriesRenderer._series; - series.selectionBehavior._chartState = chartState; - selectionBehavior = - seriesRenderer._selectionBehavior = series.selectionBehavior; - selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer = - SelectionBehaviorRenderer(selectionBehavior, chart, chartState); - selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer!.chart = chart; - selectionBehaviorRenderer._selectionRenderer!._chartState = chartState; - selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = - seriesRenderer; - if (series.initialSelectedDataIndexes.isNotEmpty) { - for (int index = 0; - index < series.initialSelectedDataIndexes.length; - index++) { - chartState._renderingDetails.selectionData - .add(series.initialSelectedDataIndexes[index]); + if (point != null) { + final Offset tooltipPosition = + circularPointToPixel(point, _stateProperties); + if (_stateProperties.chart.tooltipBehavior.builder != null) { + _stateProperties.circularArea + .showCircularTooltipTemplate(0, point.index); + } else { + tooltipRenderingDetails.internalShowByPixel( + tooltipPosition.dx, tooltipPosition.dy); + } } } - chartState._renderingDetails.animateCompleted = false; - if (series.animationDuration > 0 && - !chartState._renderingDetails.didSizeChange && - (chartState._renderingDetails.oldDeviceOrientation == - chartState._renderingDetails.deviceOrientation) && - (chartState._renderingDetails.initialRender! || - (chartState._renderingDetails.widgetNeedUpdate && - seriesRenderer._needsAnimation) || - chartState._renderingDetails.isLegendToggled)) { - chartState._renderingDetails.animationController.duration = - Duration(milliseconds: series.animationDuration.toInt()); - seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: chartState._renderingDetails.animationController, - curve: const Interval(0.1, 0.8, curve: Curves.linear), - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - chartState._renderingDetails.animateCompleted = true; - if (chartState._renderDataLabel != null) { - chartState._renderDataLabel!.state.render(); - } - if (chartState._renderingDetails.chartTemplate != null) { - chartState._renderingDetails.chartTemplate!.state - .templateRender(); - } - } - })); - chartState._renderingDetails.chartElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: chartState._renderingDetails.animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - chartState._renderingDetails.animationController.forward(from: 0.0); - } else { - chartState._renderingDetails.animateCompleted = true; - } - seriesRenderer._repaintNotifier = - chartState._renderingDetails.seriesRepaintNotifier; - if (seriesRenderer._seriesType == 'pie') { - seriesPainter = _PieChartPainter( - chartState: chartState, - index: i, - isRepaint: seriesRenderer._needsRepaint, - animationController: - chartState._renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: chartState._renderingDetails.seriesRepaintNotifier); - } else if (seriesRenderer._seriesType == 'doughnut') { - seriesPainter = _DoughnutChartPainter( - chartState: chartState, - index: i, - isRepaint: seriesRenderer._needsRepaint, - animationController: - chartState._renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: chartState._renderingDetails.seriesRepaintNotifier); - } else if (seriesRenderer._seriesType == 'radialbar') { - seriesPainter = _RadialBarPainter( - chartState: chartState, - index: i, - isRepaint: seriesRenderer._needsRepaint, - animationController: - chartState._renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: chartState._renderingDetails.seriesRepaintNotifier); - } - chartState._renderingDetails.chartWidgets! - .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); - chartState._renderDataLabel = _CircularDataLabelRenderer( - circularChartState: chartState, - show: chartState._renderingDetails.animateCompleted); - chartState._renderingDetails.chartWidgets! - .add(chartState._renderDataLabel!); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_state_properties.dart new file mode 100644 index 000000000..2ab494db3 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/circular_state_properties.dart @@ -0,0 +1,105 @@ +import 'dart:async'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import '../../common/rendering_details.dart'; +import '../../common/state_properties.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../renderer/chart_point.dart'; +import '../renderer/data_label_renderer.dart'; +import '../renderer/renderer_extension.dart'; +import 'circular_area.dart'; +import 'circular_base.dart'; +import 'series_base.dart'; + +/// Specifies the circular state properties +class CircularStateProperties extends StateProperties { + /// Creates an instance of circular chart properties + CircularStateProperties( + {required RenderingDetails renderingDetails, required this.chartState}) + : super(renderingDetails, chartState) { + renderingDetails.didSizeChange = false; + } + + /// Specifies the circular chart + @override + SfCircularChart get chart => chartState.widget; + + /// Specifies the circular chart state + @override + final SfCircularChartState chartState; + + /// Specifies the center location + late Offset centerLocation; + + /// Specifies the annotation region + late List annotationRegions; + + /// Specifies the data label renderer + CircularDataLabelRenderer? renderDataLabel; + + /// Specifies the previous series renderer + CircularSeriesRendererExtension? prevSeriesRenderer; + + /// Specifies the previous chart points + List?>? oldPoints; + + /// Holds the information of SeriesBase class + late CircularSeriesBase chartSeries; + + /// Specifies the circular chart area + late CircularArea circularArea; + + /// Specifies whether move the label from center + late bool needToMoveFromCenter; + + /// Specifies whether to explode the segments + late bool needExplodeAll; + + /// Gets or sets the value for is toggled + late bool isToggled; + + /// Specifies whether the tooltip needs to render for data label or not + bool? requireDataLabelTooltip; + + /// To redraw chart elements + void redraw() { + renderingDetails.initialRender = false; + if (renderingDetails.isLegendToggled) { + isToggled = true; + prevSeriesRenderer = chartSeries.visibleSeriesRenderers[0]; + oldPoints = List?>.filled( + prevSeriesRenderer!.renderPoints!.length, null); + for (int i = 0; i < prevSeriesRenderer!.renderPoints!.length; i++) { + oldPoints![i] = prevSeriesRenderer!.renderPoints![i]; + } + } + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + renderingDetails.tooltipBehaviorRenderer); + if (tooltipRenderingDetails.chartTooltipState != null) { + tooltipRenderingDetails.show = false; + } + // ignore: invalid_use_of_protected_member + chartState.setState(() { + /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. + }); + } + + /// Method when called, once animation completed + bool get animationCompleted { + return renderingDetails.animationController.status != + AnimationStatus.forward; + } + + /// Tooltip timer + Timer? tooltipTimer; + + /// To check the tooltip orientation changes. + bool isTooltipOrientationChanged = false; + + /// To check if tooltip has been hidden or not. + bool isTooltipHidden = false; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart index b158e1eae..b4be83f29 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/base/series_base.dart @@ -1,60 +1,88 @@ -part of charts; - -class _CircularSeries { - _CircularSeries(this._chartState); - - final SfCircularChartState _chartState; - - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this.. - SfCircularChart get chart => _chartState._chart; - +import 'dart:math'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/event_args.dart'; +import '../../common/legend/renderer.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/typedef.dart'; + +import '../renderer/chart_point.dart'; +import '../renderer/circular_series.dart'; +import '../renderer/common.dart'; +import '../renderer/data_label_renderer.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'circular_base.dart'; +import 'circular_state_properties.dart'; + +/// Represents the circular series base +/// +class CircularSeriesBase { + /// Creates the instance for the circular series base + CircularSeriesBase(this.stateProperties); + + /// Represents the circular chart state + final CircularStateProperties stateProperties; + + /// Gets the chart widget from the state properties + SfCircularChart get chart => stateProperties.chart; + + /// Specifies the current circular series late CircularSeries currentSeries; + /// Specifies the value of size late num size; + /// Specifies the value of sum of group late num sumOfGroup; - late _Region explodedRegion; + /// Specifies the value of exploded region + late Region explodedRegion; - late _Region selectRegion; + /// Specifies the value of select region + late Region selectRegion; - List visibleSeriesRenderers = - []; + /// Specifies the list that holds the visible series renderers + List visibleSeriesRenderers = + []; /// To find the visible series - void _findVisibleSeries() { + void findVisibleSeries() { CircularSeries series; List>? _oldPoints; CircularSeries? oldSeries; int oldPointIndex = 0; ChartPoint currentPoint; - for (final CircularSeriesRenderer seriesRenderer - in _chartState._chartSeries.visibleSeriesRenderers) { + for (final CircularSeriesRendererExtension seriesRenderer + in stateProperties.chartSeries.visibleSeriesRenderers) { _setSeriesType(seriesRenderer); - series = seriesRenderer._series = chart.series[0]; - seriesRenderer._dataPoints = >[]; - seriesRenderer._needsAnimation = false; - _oldPoints = _chartState._prevSeriesRenderer?._oldRenderPoints; - oldSeries = _chartState._prevSeriesRenderer?._series; + series = seriesRenderer.series = chart.series[0]; + seriesRenderer.dataPoints = >[]; + seriesRenderer.needsAnimation = false; + _oldPoints = stateProperties.prevSeriesRenderer?.oldRenderPoints; + oldSeries = stateProperties.prevSeriesRenderer?.series; oldPointIndex = 0; if (series.dataSource != null) { for (int pointIndex = 0; pointIndex < series.dataSource!.length; pointIndex++) { - currentPoint = _getCircularPoint(seriesRenderer, pointIndex); + currentPoint = getCircularPoint(seriesRenderer, pointIndex); if (currentPoint.x != null) { - seriesRenderer._dataPoints.add(currentPoint); - if (!seriesRenderer._needsAnimation) { + seriesRenderer.dataPoints.add(currentPoint); + if (!seriesRenderer.needsAnimation) { if (oldSeries != null) { - seriesRenderer._needsAnimation = + seriesRenderer.needsAnimation = (oldSeries.startAngle != series.startAngle) || (oldSeries.endAngle != series.endAngle); } - seriesRenderer._needsAnimation = !(_oldPoints != null && + seriesRenderer.needsAnimation = !(_oldPoints != null && _oldPoints.isNotEmpty && - !seriesRenderer._needsAnimation && + !seriesRenderer.needsAnimation && oldPointIndex < _oldPoints.length) || isDataUpdated(currentPoint, _oldPoints[oldPointIndex++]); } @@ -72,6 +100,7 @@ class _CircularSeries { } } + /// Method to check whether the data is updated bool isDataUpdated(ChartPoint point, ChartPoint oldPoint) { return point.x != oldPoint.x || point.y != oldPoint.y || @@ -79,29 +108,30 @@ class _CircularSeries { } /// To calculate circular empty points in chart - void _calculateCircularEmptyPoints(CircularSeriesRenderer seriesRenderer) { - final List> points = seriesRenderer._dataPoints; + void _calculateCircularEmptyPoints( + CircularSeriesRendererExtension seriesRenderer) { + final List> points = seriesRenderer.dataPoints; for (int i = 0; i < points.length; i++) { if (points[i].y == null) { - seriesRenderer._series + seriesRenderer.series .calculateEmptyPointValue(i, points[i], seriesRenderer); } } } /// To process data points from series - void _processDataPoints(CircularSeriesRenderer seriesRenderer) { - currentSeries = seriesRenderer._series; + void processDataPoints(CircularSeriesRendererExtension seriesRenderer) { + currentSeries = seriesRenderer.series; _calculateCircularEmptyPoints(seriesRenderer); _findingGroupPoints(); } /// Sort the dataSource - void _sortDataSource(CircularSeriesRenderer seriesRenderer) { - seriesRenderer._dataPoints.sort( + void _sortDataSource(CircularSeriesRendererExtension seriesRenderer) { + seriesRenderer.dataPoints.sort( // ignore: missing_return (ChartPoint firstPoint, ChartPoint secondPoint) { - if (seriesRenderer._series.sortingOrder == SortingOrder.ascending) { + if (seriesRenderer.series.sortingOrder == SortingOrder.ascending) { return (firstPoint.sortValue == null) ? -1 : (secondPoint.sortValue == null @@ -112,7 +142,7 @@ class _CircularSeries { .compareTo(secondPoint.sortValue.toLowerCase()) : firstPoint.sortValue .compareTo(secondPoint.sortValue))) as int; - } else if (seriesRenderer._series.sortingOrder == + } else if (seriesRenderer.series.sortingOrder == SortingOrder.descending) { return (firstPoint.sortValue == null) ? 1 @@ -132,9 +162,9 @@ class _CircularSeries { /// To group points based on group mode void _findingGroupPoints() { - final List seriesRenderers = - _chartState._chartSeries.visibleSeriesRenderers; - final CircularSeriesRenderer seriesRenderer = seriesRenderers[0]; + final List seriesRenderers = + stateProperties.chartSeries.visibleSeriesRenderers; + final CircularSeriesRendererExtension seriesRenderer = seriesRenderers[0]; final double? groupValue = currentSeries.groupTo; final CircularChartGroupMode? mode = currentSeries.groupMode; late bool isYText; @@ -142,9 +172,9 @@ class _CircularSeries { currentSeries.dataLabelMapper; ChartPoint point; sumOfGroup = 0; - seriesRenderer._renderPoints = >[]; - for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { - point = seriesRenderer._dataPoints[i]; + seriesRenderer.renderPoints = >[]; + for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { + point = seriesRenderer.dataPoints[i]; //ignore: prefer_if_null_operators point.text = point.text == null ? textMapping != null @@ -160,30 +190,30 @@ class _CircularSeries { point.y! <= groupValue))) { sumOfGroup += point.y!.abs(); } else { - seriesRenderer._renderPoints!.add(point); + seriesRenderer.renderPoints!.add(point); } } } if (sumOfGroup > 0) { - seriesRenderer._renderPoints! + seriesRenderer.renderPoints! .add(ChartPoint('Others', sumOfGroup)); seriesRenderer - ._renderPoints![seriesRenderer._renderPoints!.length - 1].text = + .renderPoints![seriesRenderer.renderPoints!.length - 1].text = isYText == true ? 'Others : ' + sumOfGroup.toString() : 'Others'; } _setPointStyle(seriesRenderer); } /// To set point properties - void _setPointStyle(CircularSeriesRenderer seriesRenderer) { + void _setPointStyle(CircularSeriesRendererExtension seriesRenderer) { final EmptyPointSettings empty = currentSeries.emptyPointSettings; final List palette = chart.palette; int i = 0; - List<_MeasureWidgetContext> legendToggles; - _MeasureWidgetContext item; - _LegendRenderContext legendRenderContext; - for (final ChartPoint point in seriesRenderer._renderPoints!) { + List legendToggles; + MeasureWidgetContext item; + LegendRenderContext legendRenderContext; + for (final ChartPoint point in seriesRenderer.renderPoints!) { // ignore: unnecessary_null_comparison point.fill = point.isEmpty && empty.color != null ? empty.color @@ -202,7 +232,7 @@ class _CircularSeries { if (chart.legend.legendItemBuilder != null) { legendToggles = - _chartState._renderingDetails.legendToggleTemplateStates; + stateProperties.renderingDetails.legendToggleTemplateStates; if (legendToggles.isNotEmpty) { for (int j = 0; j < legendToggles.length; j++) { item = legendToggles[j]; @@ -213,12 +243,12 @@ class _CircularSeries { } } } else { - if (_chartState._renderingDetails.legendToggleStates.isNotEmpty) { + if (stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { for (int j = 0; - j < _chartState._renderingDetails.legendToggleStates.length; + j < stateProperties.renderingDetails.legendToggleStates.length; j++) { legendRenderContext = - _chartState._renderingDetails.legendToggleStates[j]; + stateProperties.renderingDetails.legendToggleStates[j]; if (i == legendRenderContext.seriesIndex) { point.isVisible = false; break; @@ -231,9 +261,9 @@ class _CircularSeries { } /// To calculate angle, radius and center positions of circular charts - void _calculateAngleAndCenterPositions( - CircularSeriesRenderer seriesRenderer) { - currentSeries = seriesRenderer._series; + void calculateAngleAndCenterPositions( + CircularSeriesRendererExtension seriesRenderer) { + currentSeries = seriesRenderer.series; _findSumOfPoints(seriesRenderer); _calculateAngle(seriesRenderer); _calculateRadius(seriesRenderer); @@ -243,80 +273,82 @@ class _CircularSeries { } /// To calculate circular rect position for rendering chart - void _calculateCenterPosition(CircularSeriesRenderer seriesRenderer) { - if (_chartState._needToMoveFromCenter && + void _calculateCenterPosition( + CircularSeriesRendererExtension seriesRenderer) { + if (stateProperties.needToMoveFromCenter && currentSeries.pointRadiusMapper == null && - (seriesRenderer._seriesType == 'pie' || - seriesRenderer._seriesType == 'doughnut')) { - final Rect areaRect = _chartState._renderingDetails.chartAreaRect; + (seriesRenderer.seriesType == 'pie' || + seriesRenderer.seriesType == 'doughnut')) { + final Rect areaRect = stateProperties.renderingDetails.chartAreaRect; bool needExecute = true; double radius = - seriesRenderer._segmentRenderingValues['currentRadius']!.toDouble(); + seriesRenderer.segmentRenderingValues['currentRadius']!.toDouble(); while (needExecute) { radius += 1; - final Rect circularRect = _getArcPath( + final Rect circularRect = getArcPath( 0.0, radius, - seriesRenderer._center!, - seriesRenderer._segmentRenderingValues['start'], - seriesRenderer._segmentRenderingValues['end'], - seriesRenderer._segmentRenderingValues['totalAngle'], + seriesRenderer.center!, + seriesRenderer.segmentRenderingValues['start'], + seriesRenderer.segmentRenderingValues['end'], + seriesRenderer.segmentRenderingValues['totalAngle'], chart, true) .getBounds(); if (circularRect.width > areaRect.width || circularRect.height > areaRect.height) { needExecute = false; - seriesRenderer._rect = circularRect; + seriesRenderer.rect = circularRect; break; } } - seriesRenderer._rect = _getArcPath( + seriesRenderer.rect = getArcPath( 0.0, - seriesRenderer._segmentRenderingValues['currentRadius']!, - seriesRenderer._center!, - seriesRenderer._segmentRenderingValues['start'], - seriesRenderer._segmentRenderingValues['end'], - seriesRenderer._segmentRenderingValues['totalAngle'], + seriesRenderer.segmentRenderingValues['currentRadius']!, + seriesRenderer.center!, + seriesRenderer.segmentRenderingValues['start'], + seriesRenderer.segmentRenderingValues['end'], + seriesRenderer.segmentRenderingValues['totalAngle'], chart, true) .getBounds(); - for (final ChartPoint point in seriesRenderer._renderPoints!) { + for (final ChartPoint point in seriesRenderer.renderPoints!) { point.outerRadius = - seriesRenderer._segmentRenderingValues['currentRadius']; + seriesRenderer.segmentRenderingValues['currentRadius']; } } } /// To calculate start and end angle of circular charts - void _calculateStartAndEndAngle(CircularSeriesRenderer seriesRenderer) { + void _calculateStartAndEndAngle( + CircularSeriesRendererExtension seriesRenderer) { int pointIndex = 0; num pointEndAngle; - num pointStartAngle = seriesRenderer._segmentRenderingValues['start']!; + num pointStartAngle = seriesRenderer.segmentRenderingValues['start']!; final num innerRadius = - seriesRenderer._segmentRenderingValues['currentInnerRadius']!; - for (final ChartPoint point in seriesRenderer._renderPoints!) { + seriesRenderer.segmentRenderingValues['currentInnerRadius']!; + for (final ChartPoint point in seriesRenderer.renderPoints!) { if (point.isVisible) { point.innerRadius = - (seriesRenderer._seriesType == 'doughnut') ? innerRadius : 0.0; + (seriesRenderer.seriesType == 'doughnut') ? innerRadius : 0.0; point.degree = (point.y!.abs() / - (seriesRenderer._segmentRenderingValues['sumOfPoints']! != 0 - ? seriesRenderer._segmentRenderingValues['sumOfPoints']! + (seriesRenderer.segmentRenderingValues['sumOfPoints']! != 0 + ? seriesRenderer.segmentRenderingValues['sumOfPoints']! : 1)) * - seriesRenderer._segmentRenderingValues['totalAngle']!; + seriesRenderer.segmentRenderingValues['totalAngle']!; pointEndAngle = pointStartAngle + point.degree!; point.startAngle = pointStartAngle; point.endAngle = pointEndAngle; point.midAngle = (pointStartAngle + pointEndAngle) / 2; point.outerRadius = _calculatePointRadius(point.radius, point, - seriesRenderer._segmentRenderingValues['currentRadius']!); + seriesRenderer.segmentRenderingValues['currentRadius']!); point.center = _needExplode(pointIndex, currentSeries) ? _findExplodeCenter( point.midAngle!, seriesRenderer, point.outerRadius!) - : seriesRenderer._center; + : seriesRenderer.center; // ignore: unnecessary_null_comparison if (currentSeries.dataLabelSettings != null) { - _findDataLabelPosition(point); + findDataLabelPosition(point); } pointStartAngle = pointEndAngle; } @@ -327,178 +359,177 @@ class _CircularSeries { /// To check need for explode bool _needExplode(int pointIndex, CircularSeries series) { bool isNeedExplode = false; - final SfCircularChartState chartState = _chartState; if (series.explode) { - if (chartState._renderingDetails.initialRender!) { + if (stateProperties.renderingDetails.initialRender!) { if (pointIndex == series.explodeIndex || series.explodeAll) { - chartState._renderingDetails.explodedPoints.add(pointIndex); + stateProperties.renderingDetails.explodedPoints.add(pointIndex); isNeedExplode = true; } - } else if (!chartState._renderingDetails.initialRender!) { - if (!chartState._renderingDetails.explodedPoints.contains(pointIndex) && - !chartState._renderingDetails.isLegendToggled) { + } else if (!stateProperties.renderingDetails.initialRender!) { + if (!stateProperties.renderingDetails.explodedPoints + .contains(pointIndex) && + !stateProperties.renderingDetails.isLegendToggled) { if (pointIndex == series.explodeIndex || series.explodeAll) { - chartState._renderingDetails.explodedPoints.add(pointIndex); + stateProperties.renderingDetails.explodedPoints.add(pointIndex); isNeedExplode = true; } else if (!series.explodeAll && - chartState._renderingDetails.explodedPoints.isNotEmpty && + stateProperties.renderingDetails.explodedPoints.isNotEmpty && pointIndex <= - chartState._renderingDetails.explodedPoints.length - 1) { - chartState._renderingDetails.explodedPoints.removeAt(pointIndex); + stateProperties.renderingDetails.explodedPoints.length - 1) { + stateProperties.renderingDetails.explodedPoints + .removeAt(pointIndex); } } - isNeedExplode = - chartState._renderingDetails.explodedPoints.contains(pointIndex); + isNeedExplode = stateProperties.renderingDetails.explodedPoints + .contains(pointIndex); } } return isNeedExplode; } /// To find sum of points in the series - void _findSumOfPoints(CircularSeriesRenderer seriesRenderer) { - seriesRenderer._segmentRenderingValues['sumOfPoints'] = 0; - for (final ChartPoint point in seriesRenderer._renderPoints!) { + void _findSumOfPoints(CircularSeriesRendererExtension seriesRenderer) { + seriesRenderer.segmentRenderingValues['sumOfPoints'] = 0; + for (final ChartPoint point in seriesRenderer.renderPoints!) { if (point.isVisible) { - seriesRenderer._segmentRenderingValues['sumOfPoints'] = - seriesRenderer._segmentRenderingValues['sumOfPoints']! + + seriesRenderer.segmentRenderingValues['sumOfPoints'] = + seriesRenderer.segmentRenderingValues['sumOfPoints']! + point.y!.abs(); } } } /// To calculate angle of series - void _calculateAngle(CircularSeriesRenderer seriesRenderer) { - seriesRenderer._segmentRenderingValues['start'] = + void _calculateAngle(CircularSeriesRendererExtension seriesRenderer) { + seriesRenderer.segmentRenderingValues['start'] = currentSeries.startAngle < 0 ? currentSeries.startAngle < -360 ? (currentSeries.startAngle % 360) + 360 : currentSeries.startAngle + 360 : currentSeries.startAngle; - seriesRenderer._segmentRenderingValues['end'] = currentSeries.endAngle < 0 + seriesRenderer.segmentRenderingValues['end'] = currentSeries.endAngle < 0 ? currentSeries.endAngle < -360 ? (currentSeries.endAngle % 360) + 360 : currentSeries.endAngle + 360 : currentSeries.endAngle; - seriesRenderer._segmentRenderingValues['start'] = - seriesRenderer._segmentRenderingValues['start']! > 360 == true - ? seriesRenderer._segmentRenderingValues['start']! % 360 - : seriesRenderer._segmentRenderingValues['start']!; - seriesRenderer._segmentRenderingValues['end'] = - seriesRenderer._segmentRenderingValues['end']! > 360 == true - ? seriesRenderer._segmentRenderingValues['end']! % 360 - : seriesRenderer._segmentRenderingValues['end']!; - seriesRenderer._segmentRenderingValues['start'] = - seriesRenderer._segmentRenderingValues['start']! - 90; - seriesRenderer._segmentRenderingValues['end'] = - seriesRenderer._segmentRenderingValues['end']! - 90; - seriesRenderer._segmentRenderingValues['end'] = - seriesRenderer._segmentRenderingValues['start']! == - seriesRenderer._segmentRenderingValues['end']! - ? seriesRenderer._segmentRenderingValues['start']! + 360 - : seriesRenderer._segmentRenderingValues['end']!; - seriesRenderer._segmentRenderingValues['totalAngle'] = - seriesRenderer._segmentRenderingValues['start']! > - seriesRenderer._segmentRenderingValues['end']! == + seriesRenderer.segmentRenderingValues['start'] = + seriesRenderer.segmentRenderingValues['start']! > 360 == true + ? seriesRenderer.segmentRenderingValues['start']! % 360 + : seriesRenderer.segmentRenderingValues['start']!; + seriesRenderer.segmentRenderingValues['end'] = + seriesRenderer.segmentRenderingValues['end']! > 360 == true + ? seriesRenderer.segmentRenderingValues['end']! % 360 + : seriesRenderer.segmentRenderingValues['end']!; + seriesRenderer.segmentRenderingValues['start'] = + seriesRenderer.segmentRenderingValues['start']! - 90; + seriesRenderer.segmentRenderingValues['end'] = + seriesRenderer.segmentRenderingValues['end']! - 90; + seriesRenderer.segmentRenderingValues['end'] = + seriesRenderer.segmentRenderingValues['start']! == + seriesRenderer.segmentRenderingValues['end']! + ? seriesRenderer.segmentRenderingValues['start']! + 360 + : seriesRenderer.segmentRenderingValues['end']!; + seriesRenderer.segmentRenderingValues['totalAngle'] = + seriesRenderer.segmentRenderingValues['start']! > + seriesRenderer.segmentRenderingValues['end']! == true - ? (seriesRenderer._segmentRenderingValues['start']! - 360).abs() + - seriesRenderer._segmentRenderingValues['end']! - : (seriesRenderer._segmentRenderingValues['start']! - - seriesRenderer._segmentRenderingValues['end']!) + ? (seriesRenderer.segmentRenderingValues['start']! - 360).abs() + + seriesRenderer.segmentRenderingValues['end']! + : (seriesRenderer.segmentRenderingValues['start']! - + seriesRenderer.segmentRenderingValues['end']!) .abs(); } /// To calculate radius of circular chart - void _calculateRadius(CircularSeriesRenderer seriesRenderer) { - final SfCircularChartState chartState = _chartState; - final Rect chartAreaRect = chartState._renderingDetails.chartAreaRect; + void _calculateRadius(CircularSeriesRendererExtension seriesRenderer) { + final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; size = min(chartAreaRect.width, chartAreaRect.height); - seriesRenderer._segmentRenderingValues['currentRadius'] = - _percentToValue(currentSeries.radius, size / 2)!.toDouble(); - seriesRenderer._segmentRenderingValues['currentInnerRadius'] = - _percentToValue(currentSeries.innerRadius, - seriesRenderer._segmentRenderingValues['currentRadius']!)!; + seriesRenderer.segmentRenderingValues['currentRadius'] = + percentToValue(currentSeries.radius, size / 2)!.toDouble(); + seriesRenderer.segmentRenderingValues['currentInnerRadius'] = + percentToValue(currentSeries.innerRadius, + seriesRenderer.segmentRenderingValues['currentRadius']!)!; } /// To calculate center location of chart - void _calculateOrigin(CircularSeriesRenderer seriesRenderer) { - final SfCircularChartState chartState = _chartState; - final Rect chartAreaRect = chartState._renderingDetails.chartAreaRect; + void _calculateOrigin(CircularSeriesRendererExtension seriesRenderer) { + final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; final Rect chartContainerRect = - chartState._renderingDetails.chartContainerRect; - seriesRenderer._center = Offset( - _percentToValue(chart.centerX, chartAreaRect.width)!.toDouble(), - _percentToValue(chart.centerY, chartAreaRect.height)!.toDouble()); - seriesRenderer._center = Offset( - seriesRenderer._center!.dx + + stateProperties.renderingDetails.chartContainerRect; + seriesRenderer.center = Offset( + percentToValue(chart.centerX, chartAreaRect.width)!.toDouble(), + percentToValue(chart.centerY, chartAreaRect.height)!.toDouble()); + seriesRenderer.center = Offset( + seriesRenderer.center!.dx + (chartContainerRect.width - chartAreaRect.width).abs() / 2, - seriesRenderer._center!.dy + + seriesRenderer.center!.dy + (chartContainerRect.height - chartAreaRect.height).abs() / 2); - chartState._centerLocation = seriesRenderer._center!; + stateProperties.centerLocation = seriesRenderer.center!; } /// To find explode center position - Offset _findExplodeCenter( - num midAngle, CircularSeriesRenderer seriesRenderer, num currentRadius) { + Offset _findExplodeCenter(num midAngle, + CircularSeriesRendererExtension seriesRenderer, num currentRadius) { final num explodeCenter = - _percentToValue(seriesRenderer._series.explodeOffset, currentRadius)!; - return _degreeToPoint(midAngle, explodeCenter, seriesRenderer._center!); + percentToValue(seriesRenderer.series.explodeOffset, currentRadius)!; + return degreeToPoint(midAngle, explodeCenter, seriesRenderer.center!); } /// To calculate and return point radius num _calculatePointRadius( dynamic value, ChartPoint point, num radius) { - radius = value != null ? _percentToValue(value, size / 2)! : radius; + radius = value != null ? percentToValue(value, size / 2)! : radius; return radius; } /// To add selection points to selection list - void _seriesPointSelection(_Region? pointRegion, ActivationMode mode, + void seriesPointSelection(Region? pointRegion, ActivationMode mode, [int? pointIndex, int? seriesIndex]) { bool isPointAlreadySelected = false; pointIndex = pointRegion != null ? pointRegion.pointIndex : pointIndex; seriesIndex = pointRegion != null ? pointRegion.seriesIndex : seriesIndex; - final SfCircularChartState chartState = _chartState; - final CircularSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex!]; + final CircularSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex!]; int? currentSelectedIndex; - if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { - if (chartState._renderingDetails.selectionData.isNotEmpty) { + if (seriesRenderer.isSelectionEnable && mode == chart.selectionGesture) { + if (stateProperties.renderingDetails.selectionData.isNotEmpty) { if (!chart.enableMultiSelection && - chartState._renderingDetails.selectionData.isNotEmpty && - chartState._renderingDetails.selectionData.length > 1) { - if (chartState._renderingDetails.selectionData.contains(pointIndex)) { + stateProperties.renderingDetails.selectionData.isNotEmpty && + stateProperties.renderingDetails.selectionData.length > 1) { + if (stateProperties.renderingDetails.selectionData + .contains(pointIndex)) { currentSelectedIndex = pointIndex!; } - chartState._renderingDetails.selectionData.clear(); + stateProperties.renderingDetails.selectionData.clear(); if (currentSelectedIndex != null) { - chartState._renderingDetails.selectionData.add(pointIndex!); + stateProperties.renderingDetails.selectionData.add(pointIndex!); } } int selectionIndex; for (int i = 0; - i < chartState._renderingDetails.selectionData.length; + i < stateProperties.renderingDetails.selectionData.length; i++) { - selectionIndex = chartState._renderingDetails.selectionData[i]; + selectionIndex = stateProperties.renderingDetails.selectionData[i]; if (!chart.enableMultiSelection) { isPointAlreadySelected = - chartState._renderingDetails.selectionData.length == 1 && + stateProperties.renderingDetails.selectionData.length == 1 && pointIndex == selectionIndex; - if (seriesRenderer._selectionBehavior.toggleSelection == true || + if (seriesRenderer.selectionBehavior.toggleSelection == true || !isPointAlreadySelected) { - chartState._renderingDetails.selectionData.removeAt(i); + stateProperties.renderingDetails.selectionData.removeAt(i); } - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); } } else if (pointIndex == selectionIndex) { - if (seriesRenderer._selectionBehavior.toggleSelection == true) { - chartState._renderingDetails.selectionData.removeAt(i); + if (seriesRenderer.selectionBehavior.toggleSelection == true) { + stateProperties.renderingDetails.selectionData.removeAt(i); } isPointAlreadySelected = true; - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); @@ -508,8 +539,8 @@ class _CircularSeries { } } if (!isPointAlreadySelected) { - chartState._renderingDetails.selectionData.add(pointIndex!); - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.selectionData.add(pointIndex!); + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!( _getSelectionEventArgs(seriesRenderer, seriesIndex, pointIndex)); @@ -518,12 +549,12 @@ class _CircularSeries { } } - /// To perform slection event and return Selection Args + /// To perform selection event and return Selection Args SelectionArgs _getSelectionEventArgs( dynamic seriesRenderer, int seriesIndex, int pointIndex) { if (seriesRenderer != null) { final SelectionBehavior selectionBehavior = - seriesRenderer._selectionBehavior; + seriesRenderer.selectionBehavior; final SelectionArgs args = SelectionArgs( seriesRenderer: seriesRenderer, seriesIndex: seriesIndex, @@ -535,78 +566,83 @@ class _CircularSeries { args.unselectedBorderColor = selectionBehavior.unselectedBorderColor; args.unselectedBorderWidth = selectionBehavior.unselectedBorderWidth; args.unselectedColor = selectionBehavior.unselectedColor; - seriesRenderer._selectionArgs = args; + seriesRenderer.selectionArgs = args; } - return seriesRenderer._selectionArgs as SelectionArgs; + return seriesRenderer.selectionArgs as SelectionArgs; } - void _seriesPointExplosion(_Region? pointRegion) { + /// Method to explode the series point + void seriesPointExplosion(Region? pointRegion) { bool existExplodedRegion = false; - final SfCircularChartState chartState = _chartState; - final CircularSeriesRenderer seriesRenderer = _chartState - ._chartSeries.visibleSeriesRenderers[pointRegion!.seriesIndex]; + final CircularSeriesRendererExtension seriesRenderer = stateProperties + .chartSeries.visibleSeriesRenderers[pointRegion!.seriesIndex]; final ChartPoint point = - seriesRenderer._renderPoints![pointRegion.pointIndex]; + seriesRenderer.renderPoints![pointRegion.pointIndex]; int explodeIndex; - if (seriesRenderer._series.explode) { - if (chartState._renderingDetails.explodedPoints.isNotEmpty) { - if (chartState._renderingDetails.explodedPoints.length == 1 && - chartState._renderingDetails.explodedPoints + if (seriesRenderer.series.explode) { + if (stateProperties.renderingDetails.explodedPoints.isNotEmpty) { + if (stateProperties.renderingDetails.explodedPoints.length == 1 && + stateProperties.renderingDetails.explodedPoints .contains(pointRegion.pointIndex)) { existExplodedRegion = true; - point.center = seriesRenderer._center; - final int index = chartState._renderingDetails.explodedPoints + point.center = seriesRenderer.center; + final int index = stateProperties.renderingDetails.explodedPoints .indexOf(pointRegion.pointIndex); - chartState._renderingDetails.explodedPoints.removeAt(index); - chartState._renderingDetails.seriesRepaintNotifier.value++; - chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; - } else if (seriesRenderer._series.explodeAll && - chartState._renderingDetails.explodedPoints.length > 1 && - chartState._renderingDetails.explodedPoints + stateProperties.renderingDetails.explodedPoints.removeAt(index); + stateProperties.renderingDetails.seriesRepaintNotifier.value++; + stateProperties + .renderDataLabel!.state.dataLabelRepaintNotifier.value++; + } else if (seriesRenderer.series.explodeAll && + stateProperties.renderingDetails.explodedPoints.length > 1 && + stateProperties.renderingDetails.explodedPoints .contains(pointRegion.pointIndex)) { for (int i = 0; - i < chartState._renderingDetails.explodedPoints.length; + i < stateProperties.renderingDetails.explodedPoints.length; i++) { - explodeIndex = chartState._renderingDetails.explodedPoints[i]; - seriesRenderer._renderPoints![explodeIndex].center = - seriesRenderer._center; - chartState._renderingDetails.explodedPoints.removeAt(i); + explodeIndex = stateProperties.renderingDetails.explodedPoints[i]; + seriesRenderer.renderPoints![explodeIndex].center = + seriesRenderer.center; + stateProperties.renderingDetails.explodedPoints.removeAt(i); i--; } existExplodedRegion = true; - chartState._renderingDetails.seriesRepaintNotifier.value++; - chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; - } else if (chartState._renderingDetails.explodedPoints.length == 1) { + stateProperties.renderingDetails.seriesRepaintNotifier.value++; + stateProperties + .renderDataLabel!.state.dataLabelRepaintNotifier.value++; + } else if (stateProperties.renderingDetails.explodedPoints.length == + 1) { for (int i = 0; - i < chartState._renderingDetails.explodedPoints.length; + i < stateProperties.renderingDetails.explodedPoints.length; i++) { - explodeIndex = chartState._renderingDetails.explodedPoints[i]; - seriesRenderer._renderPoints![explodeIndex].center = - seriesRenderer._center; - chartState._renderingDetails.explodedPoints.removeAt(i); - chartState._renderingDetails.seriesRepaintNotifier.value++; - chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; + explodeIndex = stateProperties.renderingDetails.explodedPoints[i]; + seriesRenderer.renderPoints![explodeIndex].center = + seriesRenderer.center; + stateProperties.renderingDetails.explodedPoints.removeAt(i); + stateProperties.renderingDetails.seriesRepaintNotifier.value++; + stateProperties + .renderDataLabel!.state.dataLabelRepaintNotifier.value++; } } } if (!existExplodedRegion) { point.center = _findExplodeCenter( point.midAngle!, seriesRenderer, point.outerRadius!); - chartState._renderingDetails.explodedPoints.add(pointRegion.pointIndex); - chartState._renderingDetails.seriesRepaintNotifier.value++; - chartState._renderDataLabel!.state.dataLabelRepaintNotifier.value++; + stateProperties.renderingDetails.explodedPoints + .add(pointRegion.pointIndex); + stateProperties.renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderDataLabel!.state.dataLabelRepaintNotifier.value++; } } } /// Setting series type - void _setSeriesType(CircularSeriesRenderer seriesRenderer) { - if (seriesRenderer is PieSeriesRenderer) { - seriesRenderer._seriesType = 'pie'; - } else if (seriesRenderer is DoughnutSeriesRenderer) { - seriesRenderer._seriesType = 'doughnut'; - } else if (seriesRenderer is RadialBarSeriesRenderer) { - seriesRenderer._seriesType = 'radialbar'; + void _setSeriesType(CircularSeriesRendererExtension seriesRenderer) { + if (seriesRenderer is PieSeriesRendererExtension) { + seriesRenderer.seriesType = 'pie'; + } else if (seriesRenderer is DoughnutSeriesRendererExtension) { + seriesRenderer.seriesType = 'doughnut'; + } else if (seriesRenderer is RadialBarSeriesRendererExtension) { + seriesRenderer.seriesType = 'radialbar'; } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/chart_point.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/chart_point.dart new file mode 100644 index 000000000..8bac06748 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/chart_point.dart @@ -0,0 +1,235 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../utils/enum.dart'; +import 'circular_series.dart'; +import 'renderer_extension.dart'; + +/// It is the data type for the circular chart and it has the properties is used to assign at the value +/// declaration of the circular chart. +/// +/// It provides the options for color, stroke color, fill color, radius, angle to customize the circular chart. +/// +class ChartPoint { + /// Creating an argument constructor of ChartPoint class. + ChartPoint([this.x, this.y, this.radius, this.pointColor, this.sortValue]); + + /// X value of chart point + dynamic x; + + /// Y value of chart point + num? y; + + /// Degree of chart point + num? degree; + + /// Start angle of chart point + num? startAngle; + + /// End angle of chart point + num? endAngle; + + /// Middle angle of chart point + num? midAngle; + + /// Center position of chart point + Offset? center; + + /// Text value of chart point + String? text; + + /// Fill color of the chart point + late Color fill; + + /// Color of chart point + late Color color; + + /// Stroke color of chart point + late Color strokeColor; + + /// Sort value of chart point + D? sortValue; + + /// Stroke width of chart point + late num strokeWidth; + + /// Inner radius of chart point + num? innerRadius; + + /// Outer radius of chart point + num? outerRadius; + + /// To set the explode value of chart point + bool? isExplode; + + /// To set the shadow value of chart point + bool? isShadow; + + /// to set the empty value of chart point + bool isEmpty = false; + + /// To set the visibility of chart point + bool isVisible = true; + + /// To set the selected or unselected of chart point + bool isSelected = false; + + /// Data label position of chart point + late Position dataLabelPosition; + + /// Render position of chart point + ChartDataLabelPosition? renderPosition; + + /// Label rect of chart point. + late Rect labelRect; + + /// Size of the Data label of chart point + Size dataLabelSize = const Size(0, 0); + + /// Saturation region value of chart point + bool saturationRegionOutside = false; + + /// Y ratio of chart point + late num yRatio; + + /// Height Ratio of chart point + late num heightRatio; + + /// Radius of the chart point + String? radius; + + /// Color property of the chart point + Color? pointColor; + + /// To store the trimmed text, if the label renders outside the container area. + String? trimmedText; + + /// To execute onTooltipRender event or not. + // ignore: prefer_final_fields + bool isTooltipRenderEvent = false; + + /// To execute OnDataLabelRender event or not. + // ignore: prefer_final_fields + bool labelRenderEvent = false; + + /// Current point index. + late int index; + + // Data type + dynamic _data; + + /// PointShader Mapper + ChartShaderMapper? _pointShaderMapper; + + /// Holds the value either 0 or 1 to denote whether the current label is positioned smartly. + num? _isLabelUpdated; + + /// Holds the shifted angle of chart point. + num? _newAngle; + + /// Shader of chart point + Shader? get shader => + _pointShaderMapper != null && center != null && outerRadius != null + ? _pointShaderMapper!( + _data, + index, + fill, + Rect.fromCircle( + center: center!, + radius: outerRadius!.toDouble(), + ), + ) + : null; + + /// Path of circular Series + Rect? _pathRect; +} + +// ignore: avoid_classes_with_only_static_members +/// Helper class for handling private fields +class PointHelper { + /// returns the value of pathRect for given point + static Rect? getPathRect(ChartPoint point) => point._pathRect; + + /// Returns the value that data label is updated or not + static num? getLabelUpdated(ChartPoint point) => + point._isLabelUpdated; + + /// Stores the value of pathRect for a given point + static void setPathRect(ChartPoint point, Rect? pathRect) { + point._pathRect = pathRect; + } + + /// Stores the value 1 or 0 to indicate the label is updated or not + static void setLabelUpdated(ChartPoint point, num? isLabelUpdated) { + point._isLabelUpdated = isLabelUpdated; + } + + /// returns the value of _pointShaderMapper for given point + static ChartShaderMapper? getPointShaderMapper( + ChartPoint point) => + point._pointShaderMapper; + + /// Return the value of the shifted data label angle + static num? getNewAngle(ChartPoint point) => point._newAngle; + + /// Stores the shifted data label angle + static void setNewAngle(ChartPoint point, num? newAngle) { + point._newAngle = newAngle; + } +} + +/// To get current point +ChartPoint getCircularPoint( + CircularSeriesRendererExtension seriesRenderer, int pointIndex) { + final CircularSeries series = seriesRenderer.series; + late ChartPoint currentPoint; + final ChartIndexedValueMapper? xMap = series.xValueMapper; + final ChartIndexedValueMapper? yMap = series.yValueMapper; + final ChartIndexedValueMapper? sortFieldMap = + series.sortFieldValueMapper; + final ChartIndexedValueMapper? radiusMap = series.pointRadiusMapper; + final ChartIndexedValueMapper? colorMap = series.pointColorMapper; + final ChartShaderMapper? shadeMap = series.pointShaderMapper; + + /// Can be either a string or num value + dynamic xVal; + num? yVal; + String? radiusVal; + Color? colorVal; + String? sortVal; + if (xMap != null) { + xVal = xMap(pointIndex); + } + + if (xVal != null) { + if (yMap != null) { + yVal = yMap(pointIndex); + } + + if (radiusMap != null) { + radiusVal = radiusMap(pointIndex); + } + + if (colorMap != null) { + colorVal = colorMap(pointIndex); + } + if (sortFieldMap != null) { + sortVal = sortFieldMap(pointIndex); + } + + currentPoint = + ChartPoint(xVal, yVal, radiusVal, colorVal, sortVal); + } + currentPoint.index = pointIndex; + if (shadeMap != null) { + currentPoint._pointShaderMapper = shadeMap; + } else { + currentPoint._pointShaderMapper = null; + } + currentPoint.center = seriesRenderer.center; + + return currentPoint; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_chart_annotation.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_chart_annotation.dart new file mode 100644 index 000000000..fc4ffd0a2 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_chart_annotation.dart @@ -0,0 +1,328 @@ +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../common/utils/enum.dart'; +import '../utils/enum.dart'; + +/// Customizes the annotation of the circular chart. +/// +///Circular chart allows you to mark the specific area of interest in the chart area. +/// You can add the custom widgets using this annotation feature, It has the properties for customizing the appearance. +/// +/// The angle, orientation, height, and width of the inserted annotation can all be customized. +/// +/// It provides options for angle, height, width, vertical and horizontal alignment to customize the appearance. +/// +class CircularChartAnnotation { + /// Creating an argument constructor of CircularChartAnnotation class. + CircularChartAnnotation( + {int? angle, + String? radius, + this.widget, + String? height, + String? width, + ChartAlignment? horizontalAlignment, + ChartAlignment? verticalAlignment}) + : angle = angle ?? 0, + radius = radius ?? '0%', + height = height ?? '0%', + width = width ?? '0%', + verticalAlignment = verticalAlignment ?? ChartAlignment.center, + horizontalAlignment = horizontalAlignment ?? ChartAlignment.center; + + ///Angle to rotate the annotation. + /// + ///Defaults to `0` + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// angle: 40, + /// child: Container( + /// child: const Text('Empty data')), + /// ), + /// ], + /// )); + ///} + ///``` + final int angle; + + ///Radius for placing the annotation. + /// + ///The value ranges from 0% to 100%. + /// + ///Defaults to `0%` + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// radius: '10%' + /// child: Container( + /// child: const Text('Empty data'), + /// ), + /// ), + /// ], + /// )); + ///} + ///``` + final String radius; + + ///Considers any widget as annotation. + /// + ///Defaults to `null` + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// child: Container( + /// child:Text('Annotation')), + /// ), + /// ], + /// )); + ///} + ///``` + final Widget? widget; + + ///Height of the annotation. + /// + ///Defaults to `0%`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// height: '10%', + /// child: Container( + /// child: const Text('Empty data'), + /// ), + /// ), + /// ], + /// )); + ///} + ///``` + final String height; + + ///Width of the annotation. + /// + ///Defaults to `0%`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// width: '10%', + /// child: Container( + /// child: const Text('Empty data'), + /// ), + /// ), + /// ], + /// )); + ///} + ///``` + final String width; + + ///Aligns the annotation horizontally. + /// + ///Alignment can be set to near, far, or center. + /// + ///Defaults to `ChartAlignment.center` + /// + ///Also refer [ChartAlignment] + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// horizontalAlignment: ChartAlignment.near + /// child: Container( + /// child: const Text('Empty data'), + /// ), + /// ), + /// ], + /// )); + ///} + ///``` + final ChartAlignment horizontalAlignment; + + ///Aligns the annotation vertically. + /// + ///Alignment can be set to near, far, or center. + /// + ///Defaults to `ChartAlignment.center` + /// + ///Also refer [ChartAlignment] + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// annotations: [ + /// CircularChartAnnotation( + /// verticalAlignment: ChartAlignment.near + /// child: Container( + /// child: const Text('Empty data'), + /// ), + /// ), + /// ], + /// )); + ///} + ///``` + final ChartAlignment verticalAlignment; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is CircularChartAnnotation && + other.angle == angle && + other.radius == radius && + other.height == height && + other.horizontalAlignment == horizontalAlignment && + other.verticalAlignment == verticalAlignment && + other.widget == widget && + other.width == width; + } + + @override + int get hashCode { + final List values = [ + angle, + radius, + height, + horizontalAlignment, + verticalAlignment, + widget, + width + ]; + return hashList(values); + } +} + +/// This class holds the properties of the connector line. +/// +/// ConnectorLineSetting is the Argument type of [DataLabelSettings], It is used to customize the data label connected lines while the data label +/// position is outside the chart. It is enabled by setting the data label visibility. +/// +/// It provides the options for length, width, color, and enum type [ConnectorType] to customize the appearance. +/// +class ConnectorLineSettings { + /// Creating an argument constructor of ConnectorLineSettings class. + const ConnectorLineSettings( + {this.length, double? width, ConnectorType? type, this.color}) + : width = width ?? 1.0, + type = type ?? ConnectorType.line; + + ///Length of the connector line. + /// + ///Defaults to `null` + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// length: '8% + /// ) + /// ) + /// )); + ///} + ///``` + final String? length; + + ///Width of the connector line. + /// + ///Defaults to `1.0` + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// width: 2 + /// ) + /// ) + /// )); + ///} + ///``` + final double width; + + ///Color of the connector line. + /// + ///Defaults to `null` + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// color: Colors.red, + /// ) + /// ) + /// )); + ///} + ///``` + final Color? color; + + ///Type of the connector line. + /// + ///Defaults to `ConnectorType.line` + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// dataLabelSettings: DataLabelSettings( + /// connectorLineSettings: ConnectorLineSettings( + /// type: ConnectorType.curve + /// ) + /// ) + /// )); + ///} + ///``` + final ConnectorType type; + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + + return other is ConnectorLineSettings && + other.length == length && + other.width == width && + other.color == color && + other.type == type; + } + + @override + int get hashCode { + final List values = [length, width, color, type]; + return hashList(values); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart index 0274c7c6b..6b10e47f0 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series.dart @@ -1,8 +1,22 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/circular_base.dart'; +import '../renderer/renderer_base.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/enum.dart'; +import 'chart_point.dart'; /// This class holds the property of circular series. /// -/// To render the Circlular chart, create an instance of [PieSeries] or [DoughnutSeries] or [RadialBarSeries], and add it to the +/// To render the Circular chart, create an instance of [PieSeries] or [DoughnutSeries] or [RadialBarSeries], and add it to the /// series collection property of [SfCircularChart]. You can use the radius property to change the diameter of the circular chart for the plot area. /// Also, explode the circular chart segment by enabling the explode property. /// @@ -46,9 +60,11 @@ class CircularSeries extends ChartSeries double? borderWidth, DataLabelSettings? dataLabelSettings, bool? enableTooltip, - bool? enableSmartLabels, + @Deprecated('Use LabelIntersectAction.shift in dataLabelSettings.labelIntersectAction instead') + bool? enableSmartLabels, this.name, double? animationDuration, + double? animationDelay, SelectionBehavior? selectionBehavior, SortingOrder? sortingOrder, LegendIconType? legendIconType, @@ -56,6 +72,7 @@ class CircularSeries extends ChartSeries List? initialSelectedDataIndexes}) : startAngle = startAngle ?? 0, animationDuration = animationDuration ?? 1500, + animationDelay = animationDelay ?? 0, endAngle = endAngle ?? 360, radius = radius ?? '80%', innerRadius = innerRadius ?? '50%', @@ -76,9 +93,7 @@ class CircularSeries extends ChartSeries legendIconType = legendIconType ?? LegendIconType.seriesType, enableSmartLabels = enableSmartLabels ?? true, initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], - super(name: name) { - _renderer = _ChartSeriesRender(); - } + super(name: name); ///Opacity of the series. The value ranges from 0 to 1. /// @@ -130,8 +145,6 @@ class CircularSeries extends ChartSeries @override final DataLabelSettings dataLabelSettings; - _ChartSeriesRender? _renderer; - ///A collection of data required for rendering the series. /// /// If no data source is specified, @@ -860,6 +873,28 @@ class CircularSeries extends ChartSeries @override final double animationDuration; + /// Delay duration of the series animation.It takes a millisecond value as input. + /// By default, the series will get animated for the specified duration. + /// If animationDelay is specified, then the series will begin to animate + /// after the specified duration. + /// + /// Defaults to 0. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// animationDelay: 500; + /// ), + /// ], + /// )); + ///} + ///``` + @override + final double animationDelay; + /// List of data indexes initially selected /// /// Defaults to `null`. @@ -943,7 +978,7 @@ class CircularSeries extends ChartSeries ///Called when tapped on the chart data point. /// - ///The user can fetch the series index, point index, viewport point index and + ///The user can fetch the series index, point index, view port point index and /// data of the tapped data point. ///```dart ///Widget build(BuildContext context) { @@ -964,7 +999,7 @@ class CircularSeries extends ChartSeries ///Called when double tapped on the chart data point. /// - ///The user can fetch the series index, point index, viewport point index and + ///The user can fetch the series index, point index, view port point index and /// data of the double-tapped data point. ///```dart ///Widget build(BuildContext context) { @@ -985,7 +1020,7 @@ class CircularSeries extends ChartSeries ///Called when long pressed on the chart data point. /// - ///The user can fetch the series index, point index, viewport point index and + ///The user can fetch the series index, point index, view port point index and /// data of the long-pressed data point. ///```dart ///Widget build(BuildContext context) { @@ -1037,7 +1072,8 @@ class CircularSeries extends ChartSeries int pointIndex, ChartPoint currentPoint, [CircularSeriesRenderer? seriesRenderer]) { final EmptyPointSettings empty = emptyPointSettings; - final List>? dataPoints = seriesRenderer?._dataPoints; + final List>? dataPoints = + (seriesRenderer as CircularSeriesRendererExtension?)?.dataPoints; final int pointLength = dataPoints!.length; final ChartPoint point = dataPoints[pointIndex]; if (point.y == null) { @@ -1068,7 +1104,7 @@ class CircularSeries extends ChartSeries } /// To get visible point index -int? _getVisiblePointIndex( +int? getVisiblePointIndex( List?> points, String loc, int index) { if (loc == 'before') { for (int i = index; i >= 0; i--) { @@ -1085,830 +1121,3 @@ int? _getVisiblePointIndex( } return null; } - -abstract class _CircularChartSegment { - /// To get point color of current point - Color? getPointColor( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color color, - double opacity); - - /// To get opacity of current point - double getOpacity( - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - double opacity); - - /// To get Stroke color of current point - Color getPointStrokeColor( - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - Color strokeColor); - - /// To get Stroke width of current point - num getPointStrokeWidth( - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - num strokeWidth); -} - -abstract class _LabelSegment { - /// To get label text content - String getLabelContent( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - String content); - - /// To get textstyle of current point - TextStyle getDataLabelStyle( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - TextStyle style, - SfCircularChartState _chartState); - - /// To get data label color - Color? getDataLabelColor(CircularSeriesRenderer seriesRenderer, - ChartPoint point, int pointIndex, int seriesIndex, Color? color); - - /// To get the data label stroke color - Color getDataLabelStrokeColor( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color strokeColor); - - /// To get label stroke width - double getDataLabelStrokeWidth( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - double strokeWidth); -} - -class _ChartSeriesRender with _CircularChartSegment, _LabelSegment { - _ChartSeriesRender(); - - /// To get point color - @override - Color? getPointColor( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color color, - double opacity) => - color.withOpacity(opacity); - - /// To get point stroke color - @override - Color getPointStrokeColor( - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - Color strokeColor) => - strokeColor; - - /// To get point stroke width - @override - num getPointStrokeWidth( - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - num strokeWidth) => - strokeWidth; - - /// To return label text - @override - String getLabelContent( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - String content) => - content; - - /// To return textstyle of label - @override - TextStyle getDataLabelStyle( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - TextStyle style, - SfCircularChartState _chartState) { - final DataLabelSettings dataLabel = - seriesRenderer._series.dataLabelSettings; - final Color fontColor = dataLabel.textStyle.color ?? - _getCircularDataLabelColor(point, seriesRenderer, _chartState); - final TextStyle textStyle = TextStyle( - color: fontColor, - fontSize: dataLabel.textStyle.fontSize, - fontFamily: dataLabel.textStyle.fontFamily, - fontStyle: dataLabel.textStyle.fontStyle, - fontWeight: dataLabel.textStyle.fontWeight); - return textStyle; - } - - /// To return label color - @override - Color? getDataLabelColor( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color? color) => - color; - - /// To return label stroke color - @override - Color getDataLabelStrokeColor( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - Color? strokeColor) => - strokeColor ?? point.fill; - - /// To return label stroke width - @override - double getDataLabelStrokeWidth( - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - int pointIndex, - int seriesIndex, - double strokeWidth) => - strokeWidth; - - /// To return opacity of currrent point - @override - double getOpacity( - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - int pointIndex, - int seriesIndex, - double opacity) => - opacity; -} - -/// Creates a series renderer for Circular series -class CircularSeriesRenderer extends ChartSeriesRenderer { - /// Specifies the circular series - late CircularSeries _series; - - /// Specifies the series type - late String _seriesType; - - /// Specifies the list of data points - late List> _dataPoints; - - /// Specifies the list of rendering points - List>? _renderPoints; - - /// Specifies the list of old render points - List>? _oldRenderPoints; - - /// Specifies the map collection that holds all the values for rendering - /// the segment - final Map _segmentRenderingValues = {}; - - /// Specifies the value of center - Offset? _center; - - /// Specifies the value of point region - late List<_Region> _pointRegions; - - // ignore:unused_field - late Rect _rect; - - // Path saved for radial bar series - final List _renderPaths = []; - - /// Specifies the value of render list - final List _renderList = []; - - /// Specifies the value of inner radial radius - num? _innerRadialradius; - - /// Specifies the value of selection args - SelectionArgs? _selectionArgs; - - //Determines whether there is a need for animation - late bool _needsAnimation; - - ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods - ///in this before we must get the ChartSeriesController onRendererCreated event. - CircularSeriesController? _controller; - - /// Specifies the circular chart state - late SfCircularChartState _chartState; - - /// Repaint notifier for series - late ValueNotifier _repaintNotifier; - - /// Specifies the data label setting renderer - late DataLabelSettingsRenderer _dataLabelSettingsRenderer; - - /// specifeis the selection behavior renderer - late SelectionBehaviorRenderer _selectionBehaviorRenderer; - - /// Specifies the selection behavior - dynamic _selectionBehavior; - - // ignore: prefer_final_fields - bool _isSelectionEnable = false; - - /// To set style properties for selected points - _StyleOptions? _selectPoint( - int currentPointIndex, - CircularSeriesRenderer seriesRenderer, - SfCircularChart chart, - ChartPoint? point) { - _StyleOptions? pointStyle; - final dynamic selection = _series.selectionBehavior; - if (selection.enable == true) { - if (_chartState._renderingDetails.selectionData.isNotEmpty) { - int selectionIndex; - for (int i = 0; - i < _chartState._renderingDetails.selectionData.length; - i++) { - selectionIndex = _chartState._renderingDetails.selectionData[i]; - if (currentPointIndex == selectionIndex) { - pointStyle = _StyleOptions( - fill: seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs!.selectedColor - : selection.selectedColor, - strokeWidth: seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs!.selectedBorderWidth - : selection.selectedBorderWidth, - strokeColor: seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs!.selectedBorderColor - : selection.selectedBorderColor, - opacity: selection.selectedOpacity); - break; - } else if (i == - _chartState._renderingDetails.selectionData.length - 1) { - pointStyle = _StyleOptions( - fill: seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs!.unselectedColor - : selection.unselectedColor, - strokeWidth: seriesRenderer._selectionArgs != null - ? _selectionArgs!.unselectedBorderWidth - : selection.unselectedBorderWidth, - strokeColor: seriesRenderer._selectionArgs != null - ? seriesRenderer._selectionArgs!.unselectedBorderColor - : selection.unselectedBorderColor, - opacity: selection.unselectedOpacity); - } - } - } - } - return pointStyle; - } - - /// To calculate point start and end angle - num? _circularRenderPoint( - SfCircularChart chart, - CircularSeriesRenderer seriesRenderer, - ChartPoint point, - num? pointStartAngle, - num? innerRadius, - num? outerRadius, - Canvas canvas, - int seriesIndex, - int pointIndex, - num animationDegreeValue, - num animationRadiusValue, - bool isAnyPointSelect, - ChartPoint? oldPoint, - List?>? oldPointList) { - final bool isDynamicUpdate = oldPoint != null; - final num? oldStartAngle = oldPoint?.startAngle; - final num? oldEndAngle = oldPoint?.endAngle; - num? degree, pointEndAngle; - - /// below lines for dynamic dataSource changes - if (isDynamicUpdate) { - if (!oldPoint.isVisible && point.isVisible) { - final num val = point.startAngle == - seriesRenderer._segmentRenderingValues['start']! - ? seriesRenderer._segmentRenderingValues['start']! - : oldPointList![ - _getVisiblePointIndex(oldPointList, 'before', pointIndex)!]! - .endAngle!; - pointStartAngle = - val - (val - point.startAngle!) * animationDegreeValue; - pointEndAngle = val + (point.endAngle! - val) * animationDegreeValue; - degree = pointEndAngle - pointStartAngle; - } else if (oldPoint.isVisible && !point.isVisible) { - if (oldPoint.startAngle!.round() == - seriesRenderer._segmentRenderingValues['start'] && - (oldPoint.endAngle!.round() == - seriesRenderer._segmentRenderingValues['end'] || - oldPoint.endAngle!.round() == - 360 + seriesRenderer._segmentRenderingValues['end']!)) { - pointStartAngle = oldPoint.startAngle!; - pointEndAngle = oldPoint.endAngle! - - (oldPoint.endAngle! - oldPoint.startAngle!) * - animationDegreeValue; - } else if (oldPoint.startAngle == oldPoint.endAngle) { - pointStartAngle = pointEndAngle = oldPoint.startAngle!; - } else { - pointStartAngle = oldPoint.startAngle! - - (oldPoint.startAngle! - - (oldPoint.startAngle == - seriesRenderer._segmentRenderingValues['start']! - ? seriesRenderer._segmentRenderingValues['start']! - : seriesRenderer - ._renderPoints![_getVisiblePointIndex( - seriesRenderer._renderPoints!, - 'before', - pointIndex)!] - .endAngle!)) * - animationDegreeValue; - pointEndAngle = oldPoint.endAngle! - - (oldPoint.endAngle! - - ((oldPoint.endAngle!.round() == - seriesRenderer - ._segmentRenderingValues['end'] || - oldPoint.endAngle!.round() == - 360 + - seriesRenderer - ._segmentRenderingValues['end']!) - ? oldPoint.endAngle! - : seriesRenderer - ._renderPoints![_getVisiblePointIndex( - seriesRenderer._renderPoints!, - 'after', - pointIndex)!] - .startAngle!)) * - animationDegreeValue; - } - degree = pointEndAngle - pointStartAngle; - } else if (point.isVisible && oldPoint.isVisible) { - pointStartAngle = (point.startAngle! > oldStartAngle!) - ? oldStartAngle + - ((point.startAngle! - oldStartAngle) * animationDegreeValue) - : oldStartAngle - - ((oldStartAngle - point.startAngle!) * animationDegreeValue); - pointEndAngle = (point.endAngle! > oldEndAngle!) - ? oldEndAngle + - ((point.endAngle! - oldEndAngle) * animationDegreeValue) - : oldEndAngle - - ((oldEndAngle - point.endAngle!) * animationDegreeValue); - degree = pointEndAngle - pointStartAngle; - } - } else if (point.isVisible) { - degree = animationDegreeValue * point.degree!; - pointEndAngle = pointStartAngle! + degree; - } - outerRadius = _chartState._renderingDetails.initialRender! - ? animationRadiusValue * outerRadius! - : outerRadius; - _calculatePath( - pointIndex, - seriesIndex, - chart, - seriesRenderer, - point, - oldPoint, - canvas, - degree, - innerRadius, - outerRadius, - pointStartAngle, - pointEndAngle, - isDynamicUpdate); - return pointEndAngle; - } - - /// calculating the data point path - void _calculatePath( - int pointIndex, - int seriesIndex, - SfCircularChart chart, - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - ChartPoint? oldPoint, - Canvas canvas, - num? degree, - num? innerRadius, - num? outerRadius, - num? pointStartAngle, - num? pointEndAngle, - bool isDynamicUpdate) { - Path? renderPath; - final CornerStyle cornerStyle = _series.cornerStyle; - late num actualStartAngle, actualEndAngle; - if (!isDynamicUpdate || - (isDynamicUpdate && - ((oldPoint!.isVisible && point!.isVisible) || - (oldPoint.isVisible && !point!.isVisible) || - (!oldPoint.isVisible && point!.isVisible)))) { - innerRadius = innerRadius ?? oldPoint!.innerRadius; - outerRadius = outerRadius ?? oldPoint!.outerRadius; - if (cornerStyle != CornerStyle.bothFlat) { - final num angleDeviation = - _findAngleDeviation(innerRadius!, outerRadius!, 360); - actualStartAngle = (cornerStyle == CornerStyle.startCurve || - cornerStyle == CornerStyle.bothCurve) - ? (pointStartAngle! + angleDeviation) - : pointStartAngle!; - actualEndAngle = (cornerStyle == CornerStyle.endCurve || - cornerStyle == CornerStyle.bothCurve) - ? (pointEndAngle! - angleDeviation) - : pointEndAngle!; - } - renderPath = Path(); - renderPath = (cornerStyle == CornerStyle.startCurve || - cornerStyle == CornerStyle.endCurve || - cornerStyle == CornerStyle.bothCurve) - ? _getRoundedCornerArcPath( - innerRadius!, - outerRadius!, - point!.center ?? oldPoint!.center, - actualStartAngle, - actualEndAngle, - degree, - cornerStyle, - point) - : _getArcPath( - innerRadius!, - outerRadius!, - point!.center ?? oldPoint!.center!, - pointStartAngle, - pointEndAngle, - degree, - chart, - _chartState._renderingDetails.animateCompleted); - } - _drawDataPoints(pointIndex, seriesIndex, chart, seriesRenderer, point, - canvas, renderPath, degree, innerRadius); - } - - ///draw slice path - void _drawDataPoints( - int pointIndex, - int seriesIndex, - SfCircularChart chart, - CircularSeriesRenderer seriesRenderer, - ChartPoint? point, - Canvas canvas, - Path? renderPath, - num? degree, - num? innerRadius) { - if (point != null && point.isVisible) { - final _Region pointRegion = _Region( - _degreesToRadians(point.startAngle!), - _degreesToRadians(point.endAngle!), - point.startAngle!, - point.endAngle!, - seriesIndex, - pointIndex, - point.center, - innerRadius, - point.outerRadius!); - seriesRenderer._pointRegions.add(pointRegion); - } - final _StyleOptions? style = - _selectPoint(pointIndex, seriesRenderer, chart, point); - - final Color? fillColor = style != null && style.fill != null - ? style.fill - : (point != null && point.fill != Colors.transparent - ? seriesRenderer._series._renderer?.getPointColor( - seriesRenderer, - point, - pointIndex, - seriesIndex, - point.fill, - seriesRenderer._series.opacity) - : point!.fill); - - final Color? strokeColor = style != null && style.strokeColor != null - ? style.strokeColor - : seriesRenderer._series._renderer?.getPointStrokeColor( - seriesRenderer, point, pointIndex, seriesIndex, point!.strokeColor); - - final num? strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth - : seriesRenderer._series._renderer?.getPointStrokeWidth( - seriesRenderer, point, pointIndex, seriesIndex, point!.strokeWidth); - - assert(seriesRenderer._series.opacity >= 0, - 'The opacity value will not accept negative numbers.'); - assert(seriesRenderer._series.opacity <= 1, - 'The opacity value must be less than 1.'); - final double? opacity = style != null && style.opacity != null - ? style.opacity - : _series._renderer?.getOpacity(seriesRenderer, point, pointIndex, - seriesIndex, seriesRenderer._series.opacity); - - Shader? _renderModeShader; - - if (chart.series[0].pointRenderMode == PointRenderMode.gradient && - point?.shader == null) { - final List colorsList = []; - final List stopsList = []; - num initStops = 0; - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - point = seriesRenderer._renderPoints![i]; - if (point.isVisible) { - colorsList.add(point.fill); - if (stopsList.isEmpty) { - initStops = - (point.y! / _segmentRenderingValues['sumOfPoints']!) / 4; - stopsList.add( - point.y! / _segmentRenderingValues['sumOfPoints']! - initStops); - } else { - if (stopsList.length == 1) { - stopsList.add( - (point.y! / _segmentRenderingValues['sumOfPoints']! + - stopsList.last) + - initStops / 1.5); - } else { - stopsList.add(point.y! / _segmentRenderingValues['sumOfPoints']! + - stopsList.last); - } - } - } - } - - _renderModeShader = dart_ui.Gradient.sweep( - _center!, - colorsList, - stopsList, - TileMode.clamp, - degreeToRadian(chart.series[0].startAngle), - degreeToRadian(chart.series[0].endAngle), - _resolveTransform( - Rect.fromCircle( - center: _center!, - radius: point!.outerRadius!.toDouble(), - ), - TextDirection.ltr)); - } - - if (renderPath != null && degree! > 0) { - if (seriesRenderer is DoughnutSeriesRenderer) { - seriesRenderer._innerRadialradius = - !point!.isVisible || (seriesRenderer._innerRadialradius == null) - ? innerRadius - : seriesRenderer._innerRadialradius; - } - if (point != null && point.isVisible) { - point._pathRect = Rect.fromCircle( - center: _center!, - radius: point.outerRadius!.toDouble(), - ); - } - seriesRenderer._renderPaths.add(renderPath); - if (chart.onCreateShader != null && - point != null && - point.isVisible && - point.shader == null) { - Rect? innerRect; - if (seriesRenderer is DoughnutSeriesRenderer && - seriesRenderer._innerRadialradius != null) { - innerRect = Rect.fromCircle( - center: _center!, - radius: seriesRenderer._innerRadialradius!.toDouble(), - ); - } else { - innerRect = null; - } - if (point.isVisible) { - _renderList.clear(); - seriesRenderer._renderList.add(_StyleOptions( - fill: fillColor!, - strokeWidth: _chartState._renderingDetails.animateCompleted - ? strokeWidth! - : 0, - strokeColor: strokeColor!, - opacity: opacity)); - seriesRenderer._renderList.add(point._pathRect); - seriesRenderer._renderList.add(innerRect); - } - } else { - _drawPath( - canvas, - _StyleOptions( - fill: fillColor!, - strokeWidth: _chartState._renderingDetails.animateCompleted - ? strokeWidth! - : 0, - strokeColor: strokeColor!, - opacity: opacity), - renderPath, - point!._pathRect, - point.shader ?? _renderModeShader); - // ignore: unnecessary_null_comparison - if (point != null && - (_renderModeShader != null || point.shader != null)) { - // ignore: unnecessary_null_comparison - if (strokeColor != null && - strokeWidth != null && - strokeWidth > 0 && - _chartState._renderingDetails.animateCompleted) { - final Paint paint = Paint(); - paint.color = strokeColor; - paint.strokeWidth = strokeWidth.toDouble(); - paint.style = PaintingStyle.stroke; - canvas.drawPath(renderPath, paint); - } - } - } - } - } -} - -///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods -///in this before we must get the ChartSeriesController onRendererCreated event. -class CircularSeriesController { - /// Creating an argument constructor of CircularSeriesController class. - CircularSeriesController(this.seriesRenderer); - - ///Used to access the series properties. - /// - ///Defaults to `null` - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCircularChart( - /// series: PieSeries( - /// onRendererCreated: (CircularSeriesController controller) { - /// _chartSeriesController = controller; - /// // prints series yAxisName - /// print(_chartSeriesController.seriesRenderer._series.yAxisName); - /// }, - /// ), - /// )); - ///} - ///``` - late final CircularSeriesRenderer seriesRenderer; - - ///Used to process only the newly added, updated and removed data points in a series, - /// instead of processing all the data points. - /// - ///To re-render the chart with modified data points, setState() will be called. - /// This will render the process and render the chart from scratch. - /// Thus, the app’s performance will be degraded on continuous update. - /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. - /// Chart will process only that point and skip various steps like bounds calculation, - /// old data points processing, etc. Thus, this will improve the app’s performance. - /// - ///The following are the arguments of this method. - /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. - /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. - /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. - /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. - /// * removedDataIndex – `int` type – Index of removed data point in the existing series. - /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. - /// - ///Returns `void`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// CircularSeriesController seriesController; - /// return Column( - /// children: [ - /// Container( - /// child: SfCircularChart( - /// series: >[ - /// PieSeries( - /// dataSource: chartData, - /// onRendererCreated: (CircularSeriesController controller) { - /// seriesController = controller; - /// }, - /// ), - /// ], - /// )), - /// Container( - /// child: RaisedButton( - /// onPressed: () { - /// chartData.removeAt(0); - /// chartData.add(ChartData(3,23)); - /// seriesController.updateDataSource( - /// addedDataIndexes: [chartData.length -1], - /// removedDataIndexes: [0], - /// ); - /// }) - /// )] - /// ); - /// } - ///``` - void updateDataSource( - {List? addedDataIndexes, - List? removedDataIndexes, - List? updatedDataIndexes, - int? addedDataIndex, - int? removedDataIndex, - int? updatedDataIndex}) { - if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { - _removeDataPointsList(removedDataIndexes); - } else if (removedDataIndex != null) { - _removeDataPoint(removedDataIndex); - } - if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(addedDataIndexes, false); - } else if (addedDataIndex != null) { - _addOrUpdateDataPoint(addedDataIndex, false); - } - if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(updatedDataIndexes, true); - } else if (updatedDataIndex != null) { - _addOrUpdateDataPoint(updatedDataIndex, true); - } - _updateCircularSeries(); - } - - /// Add or update the data points on dynamic series update - void _addOrUpdateDataPoints(List indexes, bool needUpdate) { - int dataIndex; - for (int i = 0; i < indexes.length; i++) { - dataIndex = indexes[i]; - _addOrUpdateDataPoint(dataIndex, needUpdate); - } - } - - /// add or update a data point in the given index - void _addOrUpdateDataPoint(int index, bool needUpdate) { - final CircularSeries series = seriesRenderer._series; - if (index >= 0 && - series.dataSource!.length > index && - series.dataSource![index] != null) { - final ChartPoint _currentPoint = - _getCircularPoint(seriesRenderer, index); - if (_currentPoint.x != null) { - if (needUpdate) { - if (seriesRenderer._dataPoints.length > index) { - seriesRenderer._dataPoints[index] = _currentPoint; - } - } else { - if (seriesRenderer._dataPoints.length == index) { - seriesRenderer._dataPoints.add(_currentPoint); - } else if (seriesRenderer._dataPoints.length > index && index >= 0) { - seriesRenderer._dataPoints.insert(index, _currentPoint); - } - } - } - } - } - - ///Remove list of points - void _removeDataPointsList(List removedDataIndexes) { - ///Remove the redudant index from the list - final List indexList = removedDataIndexes.toSet().toList(); - indexList.sort((int b, int a) => a.compareTo(b)); - int dataIndex; - for (int i = 0; i < indexList.length; i++) { - dataIndex = indexList[i]; - _removeDataPoint(dataIndex); - } - } - - /// remove a data point in the given index - void _removeDataPoint(int index) { - if (seriesRenderer._dataPoints.isNotEmpty && - index >= 0 && - index < seriesRenderer._dataPoints.length) { - seriesRenderer._dataPoints.removeAt(index); - } - } - - /// After add/remove/update datapoints, recalculate the chart angle and positions - void _updateCircularSeries() { - final SfCircularChartState _chartState = seriesRenderer._chartState; - _chartState._chartSeries._processDataPoints(seriesRenderer); - _chartState._chartSeries._calculateAngleAndCenterPositions(seriesRenderer); - seriesRenderer._repaintNotifier.value++; - if (seriesRenderer._series.dataLabelSettings.isVisible && - _chartState._renderDataLabel != null) { - _chartState._renderDataLabel!.state.render(); - } - if (seriesRenderer._series.dataLabelSettings.isVisible && - _chartState._renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - _chartState._renderingDetails.chartTemplate!.state != null) { - _chartState._renderingDetails.chartTemplate!.state.templateRender(); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series_controller.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series_controller.dart new file mode 100644 index 000000000..6f6df1b1a --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/circular_series_controller.dart @@ -0,0 +1,239 @@ +import 'dart:ui'; +import '../base/circular_state_properties.dart'; +import '../utils/helper.dart'; +import 'chart_point.dart'; +import 'circular_series.dart'; +import 'renderer_base.dart'; +import 'renderer_extension.dart'; + +///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods +///in this before we must get the ChartSeriesController onRendererCreated event. +class CircularSeriesController { + /// Creating an argument constructor of CircularSeriesController class. + CircularSeriesController(this.seriesRenderer); + + ///Used to access the series properties. + /// + ///Defaults to `null` + /// + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCircularChart( + /// series: PieSeries( + /// onRendererCreated: (CircularSeriesController controller) { + /// _chartSeriesController = controller; + /// // prints series yAxisName + /// print(_chartSeriesController.seriesRenderer.seriesRendererDetails.series.yAxisName); + /// }, + /// ), + /// )); + ///} + ///``` + late final CircularSeriesRenderer seriesRenderer; + + ///Used to process only the newly added, updated and removed data points in a series, + /// instead of processing all the data points. + /// + ///To re-render the chart with modified data points, setState() will be called. + /// This will render the process and render the chart from scratch. + /// Thus, the app’s performance will be degraded on continuous update. + /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. + /// Chart will process only that point and skip various steps like bounds calculation, + /// old data points processing, etc. Thus, this will improve the app’s performance. + /// + ///The following are the arguments of this method. + /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. + /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. + /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. + /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. + /// * removedDataIndex – `int` type – Index of removed data point in the existing series. + /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. + /// + ///Returns `void`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// CircularSeriesController seriesController; + /// return Column( + /// children: [ + /// Container( + /// child: SfCircularChart( + /// series: >[ + /// PieSeries( + /// dataSource: chartData, + /// onRendererCreated: (CircularSeriesController controller) { + /// seriesController = controller; + /// }, + /// ), + /// ], + /// )), + /// Container( + /// child: RaisedButton( + /// onPressed: () { + /// chartData.removeAt(0); + /// chartData.add(ChartData(3,23)); + /// seriesController.updateDataSource( + /// addedDataIndexes: [chartData.length -1], + /// removedDataIndexes: [0], + /// ); + /// }) + /// )] + /// ); + /// } + ///``` + void updateDataSource( + {List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int? addedDataIndex, + int? removedDataIndex, + int? updatedDataIndex}) { + if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { + _removeDataPointsList(removedDataIndexes); + } else if (removedDataIndex != null) { + _removeDataPoint(removedDataIndex); + } + if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { + _addOrUpdateDataPoints(addedDataIndexes, false); + } else if (addedDataIndex != null) { + _addOrUpdateDataPoint(addedDataIndex, false); + } + if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { + _addOrUpdateDataPoints(updatedDataIndexes, true); + } else if (updatedDataIndex != null) { + _addOrUpdateDataPoint(updatedDataIndex, true); + } + _updateCircularSeries(); + } + + /// Add or update the data points on dynamic series update + void _addOrUpdateDataPoints(List indexes, bool needUpdate) { + int dataIndex; + for (int i = 0; i < indexes.length; i++) { + dataIndex = indexes[i]; + _addOrUpdateDataPoint(dataIndex, needUpdate); + } + } + + /// add or update a data point in the given index + void _addOrUpdateDataPoint(int index, bool needUpdate) { + final CircularSeriesRendererExtension renderer = + seriesRenderer as CircularSeriesRendererExtension; + final CircularSeries series = renderer.series; + if (index >= 0 && + series.dataSource!.length > index && + series.dataSource![index] != null) { + final ChartPoint _currentPoint = + getCircularPoint(renderer, index); + if (_currentPoint.x != null) { + if (needUpdate) { + if (renderer.dataPoints.length > index) { + renderer.dataPoints[index] = _currentPoint; + } + } else { + if (renderer.dataPoints.length == index) { + renderer.dataPoints.add(_currentPoint); + } else if (renderer.dataPoints.length > index && index >= 0) { + renderer.dataPoints.insert(index, _currentPoint); + } + } + } + } + } + + ///Remove list of points + void _removeDataPointsList(List removedDataIndexes) { + //Remove the redundant index from the list + final List indexList = removedDataIndexes.toSet().toList(); + indexList.sort((int b, int a) => a.compareTo(b)); + int dataIndex; + for (int i = 0; i < indexList.length; i++) { + dataIndex = indexList[i]; + _removeDataPoint(dataIndex); + } + } + + /// remove a data point in the given index + void _removeDataPoint(int index) { + final CircularSeriesRendererExtension renderer = + seriesRenderer as CircularSeriesRendererExtension; + if (renderer.dataPoints.isNotEmpty && + index >= 0 && + index < renderer.dataPoints.length) { + renderer.dataPoints.removeAt(index); + } + } + + /// After add/remove/update data points, recalculate the chart angle and positions + void _updateCircularSeries() { + final CircularSeriesRendererExtension renderer = + seriesRenderer as CircularSeriesRendererExtension; + final CircularStateProperties stateProperties = renderer.stateProperties; + stateProperties.chartSeries.processDataPoints(renderer); + stateProperties.chartSeries.calculateAngleAndCenterPositions(renderer); + renderer.repaintNotifier.value++; + if (renderer.series.dataLabelSettings.isVisible && + stateProperties.renderDataLabel != null) { + stateProperties.renderDataLabel!.state.render(); + } + if (renderer.series.dataLabelSettings.isVisible && + stateProperties.renderingDetails.chartTemplate != null && + // ignore: unnecessary_null_comparison + stateProperties.renderingDetails.chartTemplate!.state != null) { + stateProperties.renderingDetails.chartTemplate!.state.templateRender(); + } + } + + /// Converts chart data point value to logical pixel value. + /// + /// The [pointToPixel] method takes chart data point value as input and returns logical pixel value. + /// + /// _Note_: It returns the data point's center location value. + /// + /// late CircularSeriesController seriesController; + /// SfCircularChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// ChartPoint chartPoint1 = seriesController.pixelToPoint(value); + /// }, + /// series: >[ + /// PieSeries( + /// onRendererCreated: (CircularSeriesController controller) { + /// seriesController = controller; + /// } + /// ) + /// ] + /// ) + // ignore: unused_element + Offset _pointToPixel(ChartPoint point) { + return circularPointToPixel(point, + (seriesRenderer as CircularSeriesRendererExtension).stateProperties); + } + + /// Converts logical pixel value to the data point value. + /// + /// The [pixelToPoint] method takes logical pixel value as input and returns a chart data point. + /// + /// late CircularSeriesController seriesController; + /// SfCircularChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// }, + /// series: >[ + /// PieSeries( + /// onRendererCreated: (CircularSeriesController controller) { + /// seriesController = controller; + /// } + /// ) + /// ] + /// ) + ChartPoint pixelToPoint(Offset position) { + final CircularSeriesRendererExtension renderer = + seriesRenderer as CircularSeriesRendererExtension; + return circularPixelToPoint(position, renderer.stateProperties); + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart index 77be463df..c85181127 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/common.dart @@ -1,520 +1,111 @@ -part of charts; - -/// It is the data type for the circular chart and it has the properties is used to assign at the value -/// declaration of the circular chart. -/// -/// It provides the options for color, stroke color, fill color, radius, angle to customize the circular chart. +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../base/circular_base.dart'; +import '../base/circular_state_properties.dart'; +import 'chart_point.dart'; +import 'circular_series.dart'; +import 'radial_bar_series.dart'; +import 'renderer_extension.dart'; + +/// Represents the region /// -class ChartPoint { - /// Creating an argument constructor of ChartPoint class. - ChartPoint([this.x, this.y, this.radius, this.pointColor, this.sortValue]); +class Region { + /// Creates the instance for region + /// + Region(this.start, this.end, this.startAngle, this.endAngle, this.seriesIndex, + this.pointIndex, this.center, this.innerRadius, this.outerRadius); - /// X value of chart point - dynamic x; + /// Specifies the series index + int seriesIndex; - /// Y value of chart point - num? y; + /// Specifies the point index + int pointIndex; - /// Degree of chart point - num? degree; + /// Specifies the start angle + num startAngle; - /// Start angle of chart point - num? startAngle; + /// Specifies the start value + num start; - /// End angle of chart point - num? endAngle; + /// Specifies the end value + num end; - /// Middle angle of chart point - num? midAngle; + /// Specifies the end angle + num endAngle; - /// Center position of chart point + ///Specifies the center value Offset? center; - /// Text value of chart point - String? text; - - /// Fill color of the chart point - late Color fill; - - /// Color of chart point - late Color color; - - /// Stroke color of chart point - late Color strokeColor; - - /// Sort value of chart point - D? sortValue; - - /// Stroke width of chart point - late num strokeWidth; - - /// Inner radius of chart point + /// Specifies the value of inner radius num? innerRadius; - /// Outer radius of chart point - num? outerRadius; - - /// To set the explode value of chart point - bool? isExplode; - - /// To set the shadow value of chart point - bool? isShadow; - - /// to set the empty value of chart point - bool isEmpty = false; - - /// To set the visibility of chart point - bool isVisible = true; - - /// To set the selected or unselected of chart point - bool isSelected = false; - - /// Data label positin of chart point - late Position dataLabelPosition; - - /// Render position of chart point - ChartDataLabelPosition? renderPosition; - - /// Label rect of chart point. - late Rect labelRect; - - /// Size of the Data label of chart point - Size dataLabelSize = const Size(0, 0); - - /// Saturation region value of chart point - bool saturationRegionOutside = false; - - /// Y ratio of chart point - late num yRatio; - - /// Height Ratio of chart point - late num heightRatio; - - /// Radius of the chart point - String? radius; - - /// Color property of the chart point - Color? pointColor; - - /// To execute onTooltipRender event or not. - // ignore: prefer_final_fields - bool isTooltipRenderEvent = false; - - /// To execute OnDataLabelRender event or not. - // ignore: prefer_final_fields - bool labelRenderEvent = false; - - /// Current point index. - late int index; - - // Data type - dynamic _data; - - /// PointShader Mapper - ChartShaderMapper? _pointShaderMapper; - - /// Shader of chart point - Shader? get shader => - _pointShaderMapper != null && center != null && outerRadius != null - ? _pointShaderMapper!( - _data, - index, - fill, - Rect.fromCircle( - center: center!, - radius: outerRadius!.toDouble(), - ), - ) - : null; - - /// Path of circular Series - Rect? _pathRect; - - /// Stores the tooltip label text. - late String _tooltipLabelText; - - /// Stores the tooltip header text. - late String _tooltipHeaderText; -} - -class _Region { - _Region( - this.start, - this.end, - this.startAngle, - this.endAngle, - this.seriesIndex, - this.pointIndex, - this.center, - this.innerRadius, - this.outerRadius); - int seriesIndex; - int pointIndex; - num startAngle; - num start; - num end; - num endAngle; - Offset? center; - num? innerRadius; + /// Specifies the value of outer radius num outerRadius; } -class _StyleOptions { - _StyleOptions({this.fill, this.strokeWidth, this.strokeColor, this.opacity}); +/// Represents the style options +/// +class StyleOptions { + /// Creates the instance of style options + /// + StyleOptions({this.fill, this.strokeWidth, this.strokeColor, this.opacity}); + + /// Specifies the value of fill Color? fill; + + /// Specifies the value of stroke color Color? strokeColor; + + /// Specifies the value of opacity double? opacity; + + /// Specifies the value of stroke width num? strokeWidth; } -/// This class holds the properties of the connector line. -/// -/// ConnectorLineSetting is the Argument type of [DataLabelSettings], It is used to customize the data label connected lines while the data label -/// position is outside the chart. It is enabled by setting the data label visibility. +/// Represents the circular chart interaction /// -/// It provides the options for length, width, color, and enum type [ConnectorType] to customize the appearance. -/// -class ConnectorLineSettings { - /// Creating an argument constructor of ConnectorLineSettings class. - const ConnectorLineSettings( - {this.length, double? width, ConnectorType? type, this.color}) - : width = width ?? 1.0, - type = type ?? ConnectorType.line; - - ///Length of the connector line. - /// - ///Defaults to `null` +class ChartInteraction { + /// Creates the instance for circular chart interaction /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// length: '8% - /// ) - /// ) - /// )); - ///} - ///``` - final String? length; - - ///Width of the connector line. - /// - ///Defaults to `1.0` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// width: 2 - /// ) - /// ) - /// )); - ///} - ///``` - final double width; - - ///Color of the connector line. - /// - ///Defaults to `null` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// color: Colors.red, - /// ) - /// ) - /// )); - ///} - ///``` - final Color? color; - - ///Type of the connector line. - /// - ///Defaults to `ConnectorType.line` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// dataLabelSettings: DataLabelSettings( - /// connectorLineSettings: ConnectorLineSettings( - /// type: ConnectorType.curve - /// ) - /// ) - /// )); - ///} - ///``` - final ConnectorType type; - - @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 ConnectorLineSettings && - other.length == length && - other.width == width && - other.color == color && - other.type == type; - } - - @override - // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode { - final List values = [length, width, color, type]; - return hashList(values); - } -} - -class _ChartInteraction { - _ChartInteraction(this.seriesIndex, this.pointIndex, this.series, this.point, + ChartInteraction(this.seriesIndex, this.pointIndex, this.series, this.point, [this.region]); + + /// Specifies the value of series index int? seriesIndex; - int? pointIndex; - dynamic series; - dynamic point; - _Region? region; -} -/// Customizes the annotation of the circular chart. -/// -///Circular chart allows you to mark the specific area of interest in the chart area. -/// You can add the custom widgets using this annotation feature, It has the properties for customizing the appearance. -/// -/// The angle, orientation, height, and width of the inserted annotation can all be customized. -/// -/// It provides options for angle, height, width, vertical and horizontal alignment to customize the appearance. -/// -@immutable -class CircularChartAnnotation { - /// Creating an argument constructor of CircularChartAnnotation class. - const CircularChartAnnotation( - {int? angle, - String? radius, - this.widget, - String? height, - String? width, - ChartAlignment? horizontalAlignment, - ChartAlignment? verticalAlignment}) - : angle = angle ?? 0, - radius = radius ?? '0%', - height = height ?? '0%', - width = width ?? '0%', - verticalAlignment = verticalAlignment ?? ChartAlignment.center, - horizontalAlignment = horizontalAlignment ?? ChartAlignment.center; - - ///Angle to rotate the annotation. - /// - ///Defaults to `0` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// angle: 40, - /// child: Container( - /// child: const Text('Empty data')), - /// ), - /// ], - /// )); - ///} - ///``` - final int angle; - - ///Radius for placing the annotation. - /// - ///The value ranges from 0% to 100%. - /// - ///Defaults to `0%` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// radius: '10%' - /// child: Container( - /// child: const Text('Empty data'), - /// ), - /// ), - /// ], - /// )); - ///} - ///``` - final String radius; - - ///Considers any widget as annotation. - /// - ///Defaults to `null` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// child: Container( - /// child:Text('Annotation')), - /// ), - /// ], - /// )); - ///} - ///``` - final Widget? widget; - - ///Height of the annotation. - /// - ///Defaults to `0%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// height: '10%', - /// child: Container( - /// child: const Text('Empty data'), - /// ), - /// ), - /// ], - /// )); - ///} - ///``` - final String height; - - ///Width of the annotation. - /// - ///Defaults to `0%`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// width: '10%', - /// child: Container( - /// child: const Text('Empty data'), - /// ), - /// ), - /// ], - /// )); - ///} - ///``` - final String width; - - ///Aligns the annotation horizontally. - /// - ///Alignment can be set to near, far, or center. - /// - ///Defaults to `ChartAlignment.center` - /// - ///Also refer [ChartAlignment] - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// horizontalAlignment: ChartAlignment.near - /// child: Container( - /// child: const Text('Empty data'), - /// ), - /// ), - /// ], - /// )); - ///} - ///``` - final ChartAlignment horizontalAlignment; - - ///Aligns the annotation vertically. - /// - ///Alignment can be set to near, far, or center. - /// - ///Defaults to `ChartAlignment.center` - /// - ///Also refer [ChartAlignment] - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCircularChart( - /// annotations: [ - /// CircularChartAnnotation( - /// verticalAlignment: ChartAlignment.near - /// child: Container( - /// child: const Text('Empty data'), - /// ), - /// ), - /// ], - /// )); - ///} - ///``` - final ChartAlignment verticalAlignment; + /// Specifies the value of point index + int? pointIndex; - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } + /// Specifies the value of series + dynamic series; - return other is CircularChartAnnotation && - other.angle == angle && - other.radius == radius && - other.height == height && - other.horizontalAlignment == horizontalAlignment && - other.verticalAlignment == verticalAlignment && - other.widget == widget && - other.width == width; - } + /// Specifies the point value + dynamic point; - @override - int get hashCode { - final List values = [ - angle, - radius, - height, - horizontalAlignment, - verticalAlignment, - widget, - width - ]; - return hashList(values); - } + /// Specifies the value of region + Region? region; } ///To get circular series data label saturation color -Color _getCircularDataLabelColor(ChartPoint currentPoint, - CircularSeriesRenderer seriesRenderer, SfCircularChartState _chartState) { +Color getCircularDataLabelColor( + ChartPoint currentPoint, + CircularSeriesRendererExtension seriesRenderer, + CircularStateProperties stateProperties) { Color color; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; - final String seriesType = seriesRenderer._seriesType == 'pie' + seriesRenderer.dataLabelSettingsRenderer; + final String seriesType = seriesRenderer.seriesType == 'pie' ? 'Pie' - : seriesRenderer._seriesType == 'doughnut' + : seriesRenderer.seriesType == 'doughnut' ? 'Doughnut' - : seriesRenderer._seriesType == 'radialbar' + : seriesRenderer.seriesType == 'radialbar' ? 'RadialBar' : 'Default'; switch (seriesType) { @@ -522,30 +113,30 @@ Color _getCircularDataLabelColor(ChartPoint currentPoint, case 'Doughnut': color = (currentPoint.renderPosition == ChartDataLabelPosition.inside && !currentPoint.saturationRegionOutside) - ? _getInnerColor(dataLabelSettingsRenderer._color, currentPoint.fill, - _chartState._renderingDetails.chartTheme) - : _getOuterColor( - dataLabelSettingsRenderer._color, + ? getInnerColor(dataLabelSettingsRenderer.color, currentPoint.fill, + stateProperties.renderingDetails.chartTheme) + : getOuterColor( + dataLabelSettingsRenderer.color, dataLabel.useSeriesColor ? currentPoint.fill - : (_chartState._chart.backgroundColor ?? - _chartState._renderingDetails.chartTheme - .plotAreaBackgroundColor), - _chartState._renderingDetails.chartTheme); + : (stateProperties.chart.backgroundColor ?? + stateProperties + .renderingDetails.chartTheme.plotAreaBackgroundColor), + stateProperties.renderingDetails.chartTheme); break; case 'RadialBar': final RadialBarSeries radialBar = - seriesRenderer._series as RadialBarSeries; + seriesRenderer.series as RadialBarSeries; color = radialBar.trackColor; break; default: color = Colors.white; } - return _getSaturationColor(color); + return getSaturationColor(color); } ///To get inner data label color -Color _getInnerColor( +Color getInnerColor( Color? dataLabelColor, Color? pointColor, SfChartThemeData theme) => // ignore: prefer_if_null_operators dataLabelColor != null @@ -558,7 +149,7 @@ Color _getInnerColor( : Colors.black; ///To get outer data label color -Color _getOuterColor( +Color getOuterColor( Color? dataLabelColor, Color backgroundColor, SfChartThemeData theme) => // ignore: prefer_if_null_operators dataLabelColor != null @@ -570,15 +161,15 @@ Color _getOuterColor( : Colors.black; /// To check whether any point is selected -bool _checkIsAnyPointSelect(CircularSeriesRenderer seriesRenderer, +bool checkIsAnyPointSelect(CircularSeriesRendererExtension seriesRenderer, ChartPoint? point, SfCircularChart chart) { bool isAnyPointSelected = false; - final CircularSeries series = seriesRenderer._series; + final CircularSeries series = seriesRenderer.series; if (series.initialSelectedDataIndexes.isNotEmpty) { int data; for (int i = 0; i < series.initialSelectedDataIndexes.length; i++) { data = series.initialSelectedDataIndexes[i]; - for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { + for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { if (j == data) { isAnyPointSelected = true; break; @@ -588,3 +179,188 @@ bool _checkIsAnyPointSelect(CircularSeriesRenderer seriesRenderer, } return isAnyPointSelected; } + +/// Represents the circular chart segment +abstract class CircularChartSegment { + /// To get point color of current point + Color? getPointColor( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + Color color, + double opacity); + + /// To get opacity of current point + double getOpacity( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + int pointIndex, + int seriesIndex, + double opacity); + + /// To get Stroke color of current point + Color getPointStrokeColor( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + int pointIndex, + int seriesIndex, + Color strokeColor); + + /// To get Stroke width of current point + num getPointStrokeWidth( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + int pointIndex, + int seriesIndex, + num strokeWidth); +} + +/// Represents the label segment +abstract class LabelSegment { + /// To get label text content + String getLabelContent( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + String content); + + /// To get text style of current point + TextStyle getDataLabelStyle( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + TextStyle style, + SfCircularChartState _chartState); + + /// To get data label color + Color? getDataLabelColor(CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, int pointIndex, int seriesIndex, Color? color); + + /// To get the data label stroke color + Color getDataLabelStrokeColor( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + Color strokeColor); + + /// To get label stroke width + double getDataLabelStrokeWidth( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + double strokeWidth); +} + +/// Represents the chart series renderer +class ChartSeriesRender with CircularChartSegment, LabelSegment { + /// Creates an instance for chart series renderer + ChartSeriesRender(); + + /// Creates an instance for chart series renderer + @override + Color? getPointColor( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + Color color, + double opacity) => + color.withOpacity(opacity); + + /// To get point stroke color + @override + Color getPointStrokeColor( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + int pointIndex, + int seriesIndex, + Color strokeColor) => + strokeColor; + + /// To get point stroke width + @override + num getPointStrokeWidth( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + int pointIndex, + int seriesIndex, + num strokeWidth) => + strokeWidth; + + /// To return label text + @override + String getLabelContent( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + String content) => + content; + + /// To return text style of label + @override + TextStyle getDataLabelStyle( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + TextStyle style, + SfCircularChartState _chartState) { + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; + final Color fontColor = dataLabel.textStyle.color ?? + getCircularDataLabelColor( + point, seriesRenderer, seriesRenderer.stateProperties); + final TextStyle textStyle = TextStyle( + color: fontColor, + fontSize: dataLabel.textStyle.fontSize, + fontFamily: dataLabel.textStyle.fontFamily, + fontStyle: dataLabel.textStyle.fontStyle, + fontWeight: dataLabel.textStyle.fontWeight); + return textStyle; + } + + /// To return label color + @override + Color? getDataLabelColor( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + Color? color) => + color; + + /// To return label stroke color + @override + Color getDataLabelStrokeColor( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + Color? strokeColor) => + strokeColor ?? point.fill; + + /// To return label stroke width + @override + double getDataLabelStrokeWidth( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + int pointIndex, + int seriesIndex, + double strokeWidth) => + strokeWidth; + + /// To return opacity of current point + @override + double getOpacity( + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + int pointIndex, + int seriesIndex, + double opacity) => + opacity; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart index 5d2fba9e5..00241a6c2 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/data_label_renderer.dart @@ -1,25 +1,50 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../chart/chart_series/xy_data_series.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../common/event_args.dart'; +import '../../common/utils/helper.dart'; +import '../../pyramid_chart/utils/helper.dart'; +import '../base/circular_base.dart'; +import '../base/circular_state_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'chart_point.dart'; +import 'circular_chart_annotation.dart'; +import 'renderer_extension.dart'; + +/// Represents the circular data label renderer // ignore: must_be_immutable -class _CircularDataLabelRenderer extends StatefulWidget { +class CircularDataLabelRenderer extends StatefulWidget { + /// Creates the instance of circular data label renderer // ignore: prefer_const_constructors_in_immutables - _CircularDataLabelRenderer( - {required this.circularChartState, required this.show}); + CircularDataLabelRenderer( + {required this.stateProperties, required this.show}); - final SfCircularChartState circularChartState; + /// Specifies the circular chart state + final CircularStateProperties stateProperties; + /// Specifies whether to the data label bool show; - late _CircularDataLabelRendererState state; + /// Specifies the state of circular data label renderer + late CircularDataLabelRendererState state; @override State createState() { - return _CircularDataLabelRendererState(); + return CircularDataLabelRendererState(); } } -class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> +/// Represents the circular data label renderer state +class CircularDataLabelRendererState extends State with SingleTickerProviderStateMixin { + /// Specifies the animation controller list late List animationControllersList; /// Animation controller for series @@ -40,9 +65,8 @@ class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> Widget build(BuildContext context) { widget.state = this; animationController.duration = Duration( - milliseconds: widget.circularChartState._renderingDetails.initialRender! - ? 500 - : 0); + milliseconds: + widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -55,7 +79,7 @@ class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> child: RepaintBoundary( child: CustomPaint( painter: _CircularDataLabelPainter( - circularChartState: widget.circularChartState, + stateProperties: widget.stateProperties, animation: dataLabelAnimation, state: this, notifier: dataLabelRepaintNotifier, @@ -64,14 +88,16 @@ class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> @override void dispose() { - _disposeAnimationController(animationController, repaintDataLabelElements); + disposeAnimationController(animationController, repaintDataLabelElements); super.dispose(); } + /// Method to notify the repaint notifier void repaintDataLabelElements() { dataLabelRepaintNotifier.value++; } + /// Method to render the data label void render() { setState(() { widget.show = true; @@ -81,16 +107,16 @@ class _CircularDataLabelRendererState extends State<_CircularDataLabelRenderer> class _CircularDataLabelPainter extends CustomPainter { _CircularDataLabelPainter( - {required this.circularChartState, + {required this.stateProperties, required this.state, required this.animationController, required this.animation, required ValueNotifier notifier}) : super(repaint: notifier); - final SfCircularChartState circularChartState; + final CircularStateProperties stateProperties; - final _CircularDataLabelRendererState state; + final CircularDataLabelRendererState state; final AnimationController animationController; @@ -99,20 +125,20 @@ class _CircularDataLabelPainter extends CustomPainter { /// To paint data labels @override void paint(Canvas canvas, Size size) { - final List visibleSeriesRenderers = - circularChartState._chartSeries.visibleSeriesRenderers; - CircularSeriesRenderer seriesRenderer; + final List visibleSeriesRenderers = + stateProperties.chartSeries.visibleSeriesRenderers; + CircularSeriesRendererExtension seriesRenderer; for (int seriesIndex = 0; seriesIndex < visibleSeriesRenderers.length; seriesIndex++) { seriesRenderer = visibleSeriesRenderers[seriesIndex]; // ignore: unnecessary_null_comparison - if (seriesRenderer._series.dataLabelSettings != null && - seriesRenderer._series.dataLabelSettings.isVisible) { - seriesRenderer._dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer._series.dataLabelSettings); - _renderCircularDataLabel( - seriesRenderer, canvas, circularChartState, seriesIndex, animation); + if (seriesRenderer.series.dataLabelSettings != null && + seriesRenderer.series.dataLabelSettings.isVisible) { + seriesRenderer.dataLabelSettingsRenderer = + DataLabelSettingsRenderer(seriesRenderer.series.dataLabelSettings); + renderCircularDataLabel( + seriesRenderer, canvas, stateProperties, seriesIndex, animation); } } } @@ -121,18 +147,260 @@ class _CircularDataLabelPainter extends CustomPainter { bool shouldRepaint(_CircularDataLabelPainter oldDelegate) => true; } +/// Decides to increase the angle or not. +bool isIncreaseAngle = false; + +/// To store the points which render at left and positioned outside +List> leftPoints = >[]; + +/// To store the points which render at right and positioned outside +List> rightPoints = >[]; + +/// Decrease the angle of the label if it intersects with labels. +void _decreaseAngle( + ChartPoint currentPoint, + ChartPoint previousPoint, + CircularSeriesRendererExtension seriesRenderer, + bool isRightSide) { + int count = 1; + if (isRightSide) { + while (isOverlap(currentPoint.labelRect, previousPoint.labelRect) || + (seriesRenderer.series.pointRadiusMapper != null && + (!((previousPoint.labelRect.height + previousPoint.labelRect.top) < + currentPoint.labelRect.top)))) { + int newAngle = PointHelper.getNewAngle(previousPoint)!.toInt() - count; + if (newAngle < 0) { + newAngle = 360 + newAngle; + } + if (newAngle <= 270 && newAngle >= 90) { + newAngle = 270; + isIncreaseAngle = true; + break; + } + _changeLabelAngle(previousPoint, newAngle, seriesRenderer); + count++; + } + } else { + if (PointHelper.getNewAngle(currentPoint)! > 270) { + _changeLabelAngle(currentPoint, 270, seriesRenderer); + PointHelper.setNewAngle(previousPoint, 270); + } + while (isOverlap(currentPoint.labelRect, previousPoint.labelRect) || + (seriesRenderer.series.pointRadiusMapper != null && + ((currentPoint.labelRect.top + currentPoint.labelRect.height) > + previousPoint.labelRect.bottom))) { + int newAngle = PointHelper.getNewAngle(previousPoint)!.toInt() - count; + if (!(newAngle <= 270 && newAngle >= 90)) { + newAngle = 270; + isIncreaseAngle = true; + break; + } + _changeLabelAngle(previousPoint, newAngle, seriesRenderer); + if (isOverlap(currentPoint.labelRect, previousPoint.labelRect) && + leftPoints.indexOf(previousPoint) == null && + (newAngle - 1 < 90 && newAngle - 1 > 270)) { + _changeLabelAngle(currentPoint, + PointHelper.getNewAngle(currentPoint)! + 1, seriesRenderer); + _arrangeLeftSidePoints(seriesRenderer); + break; + } + count++; + } + } +} + +/// Increase the angle of the label if it intersects labels. +void _increaseAngle( + ChartPoint currentPoint, + ChartPoint nextPoint, + CircularSeriesRendererExtension seriesRenderer, + bool isRightSide) { + int count = 1; + if (isRightSide) { + while (isOverlap(currentPoint.labelRect, nextPoint.labelRect) || + (seriesRenderer.series.pointRadiusMapper != null && + (!((currentPoint.labelRect.top + currentPoint.labelRect.height) < + nextPoint.labelRect.top)))) { + int newAngle = PointHelper.getNewAngle(nextPoint)!.toInt() + count; + if (newAngle < 270 && newAngle > 90) { + newAngle = 90; + isIncreaseAngle = true; + break; + } + _changeLabelAngle(nextPoint, newAngle, seriesRenderer); + if (isOverlap(currentPoint.labelRect, nextPoint.labelRect) && + (newAngle + 1 > 90 && newAngle + 1 < 270) && + rightPoints.indexOf(nextPoint) == rightPoints.length - 1) { + _changeLabelAngle(currentPoint, + PointHelper.getNewAngle(currentPoint)! - 1, seriesRenderer); + _arrangeRightSidePoints(seriesRenderer); + break; + } + count++; + } + } else { + while (isOverlap(currentPoint.labelRect, nextPoint.labelRect) || + (seriesRenderer.series.pointRadiusMapper != null && + (currentPoint.labelRect.top < + (nextPoint.labelRect.top + nextPoint.labelRect.height)))) { + int newAngle = PointHelper.getNewAngle(nextPoint)!.toInt() + count; + if (!(newAngle < 270 && newAngle > 90)) { + newAngle = 270; + isIncreaseAngle = false; + break; + } + _changeLabelAngle(nextPoint, newAngle, seriesRenderer); + count++; + } + } +} + +/// Change the label angle based on the given new angle. +void _changeLabelAngle(ChartPoint currentPoint, num newAngle, + CircularSeriesRendererExtension seriesRenderer) { + const String defaultConnectorLineLength = '10%'; + final DataLabelSettings dataLabel = + seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings; + final Size textSize = measureText(currentPoint.text!, dataLabel.textStyle); + final Path angleChangedConnectorPath = Path(); + final num connectorLength = percentToValue( + dataLabel.connectorLineSettings.length ?? defaultConnectorLineLength, + currentPoint.outerRadius!)!; + final Offset startPoint = + degreeToPoint(newAngle, currentPoint.outerRadius!, currentPoint.center!); + final Offset endPoint = degreeToPoint(newAngle, + currentPoint.outerRadius! + connectorLength, currentPoint.center!); + angleChangedConnectorPath.moveTo(startPoint.dx, startPoint.dy); + if (dataLabel.connectorLineSettings.type == ConnectorType.line) { + angleChangedConnectorPath.lineTo(endPoint.dx, endPoint.dy); + } + seriesRenderer + .renderPoints![seriesRenderer.renderPoints!.indexOf(currentPoint)] + .labelRect = + getDataLabelRect( + currentPoint.dataLabelPosition, + seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings + .connectorLineSettings.type, + dataLabel.margin, + angleChangedConnectorPath, + endPoint, + textSize)!; + PointHelper.setLabelUpdated(currentPoint, 1); + PointHelper.setNewAngle(currentPoint, newAngle); +} + +/// Left side points alignment calculation. +void _arrangeLeftSidePoints(CircularSeriesRendererExtension seriesRenderer) { + ChartPoint previousPoint; + ChartPoint currentPoint; + bool angleChanged = false; + bool startFresh = false; + for (int i = 1; i < leftPoints.length; i++) { + currentPoint = leftPoints[i]; + previousPoint = leftPoints[i - 1]; + if (isOverlapWithPrevious(currentPoint, leftPoints, i) && + currentPoint.isVisible || + !(PointHelper.getNewAngle(currentPoint)! < 270)) { + angleChanged = true; + if (startFresh) { + isIncreaseAngle = false; + } + if (!isIncreaseAngle) { + for (int k = i; k > 0; k--) { + _decreaseAngle( + leftPoints[k], leftPoints[k - 1], seriesRenderer, false); + for (int index = 1; index < leftPoints.length; index++) { + if (PointHelper.getLabelUpdated(leftPoints[index]) != null && + PointHelper.getNewAngle(leftPoints[index])! - 10 < 100) { + isIncreaseAngle = true; + } + } + } + } else { + for (int k = i; k < leftPoints.length; k++) { + _increaseAngle( + leftPoints[k - 1], leftPoints[k], seriesRenderer, false); + } + } + } else { + if (angleChanged && + previousPoint != null && + PointHelper.getLabelUpdated(previousPoint) == 1) { + startFresh = true; + } + } + } +} + +/// Right side points alignments calculation. +void _arrangeRightSidePoints(CircularSeriesRendererExtension series) { + bool startFresh = false; + bool angleChanged = false; + num checkAngle; + ChartPoint currentPoint; + final ChartPoint? lastPoint = + rightPoints.length > 1 ? rightPoints[rightPoints.length - 1] : null; + ChartPoint nextPoint; + if (lastPoint != null) { + if (PointHelper.getNewAngle(lastPoint)! > 360) { + PointHelper.setNewAngle( + lastPoint, PointHelper.getNewAngle(lastPoint)! - 360); + } + if (PointHelper.getNewAngle(lastPoint)! > 90 && + PointHelper.getNewAngle(lastPoint)! < 270) { + isIncreaseAngle = true; + _changeLabelAngle(lastPoint, 89, series); + } + } + for (int i = rightPoints.length - 2; i >= 0; i--) { + currentPoint = rightPoints[i]; + nextPoint = rightPoints[i + 1]; + if (isOverlapWithNext(currentPoint, rightPoints, i) && + currentPoint.isVisible || + !(PointHelper.getNewAngle(currentPoint)! <= 90 || + PointHelper.getNewAngle(currentPoint)! >= 270)) { + checkAngle = PointHelper.getNewAngle(lastPoint!)! + 1; + angleChanged = true; + // If last's point change angle in beyond the limit, stop the increasing angle and do decrease the angle. + if (startFresh) { + isIncreaseAngle = false; + } else if (checkAngle > 90 && + checkAngle < 270 && + PointHelper.getLabelUpdated(nextPoint) == 1) { + isIncreaseAngle = true; + } + if (!isIncreaseAngle) { + for (int k = i + 1; k < rightPoints.length; k++) { + _increaseAngle(rightPoints[k - 1], rightPoints[k], series, true); + } + } else { + for (int k = i + 1; k > 0; k--) { + _decreaseAngle(rightPoints[k], rightPoints[k - 1], series, true); + } + } + } else { + //If a point did not overlapped with previous points, increase the angle always for right side points. + if (angleChanged && + nextPoint != null && + PointHelper.getLabelUpdated(nextPoint) == 1) { + startFresh = true; + } + } + } +} + /// To render data label -void _renderCircularDataLabel( - CircularSeriesRenderer seriesRenderer, +void renderCircularDataLabel( + CircularSeriesRendererExtension seriesRenderer, Canvas canvas, - SfCircularChartState _chartState, + CircularStateProperties stateProperties, int seriesIndex, Animation? animation) { ChartPoint point; - final SfCircularChart chart = _chartState._chart; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; + final SfCircularChart chart = stateProperties.chart; + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; + seriesRenderer.dataLabelSettingsRenderer; final num angle = dataLabel.angle; Offset labelLocation; const int labelPadding = 2; @@ -143,47 +411,45 @@ void _renderCircularDataLabel( final List renderDataLabelRegions = []; Size textSize; for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints!.length; + pointIndex < seriesRenderer.renderPoints!.length; pointIndex++) { - point = seriesRenderer._renderPoints![pointIndex]; + point = seriesRenderer.renderPoints![pointIndex]; if (point.isVisible && (point.y != 0 || dataLabel.showZeroValue)) { label = point.text; - label = seriesRenderer._series._renderer!.getLabelContent( + label = seriesRenderer.renderer.getLabelContent( seriesRenderer, point, pointIndex, seriesIndex, label!); dataLabelStyle = dataLabel.textStyle; - dataLabelSettingsRenderer._color = - seriesRenderer._series.dataLabelSettings.color; + dataLabelSettingsRenderer.color = + seriesRenderer.series.dataLabelSettings.color; if (chart.onDataLabelRender != null && - !seriesRenderer._renderPoints![pointIndex].labelRenderEvent) { + !seriesRenderer.renderPoints![pointIndex].labelRenderEvent) { dataLabelArgs = DataLabelRenderArgs(seriesRenderer, - seriesRenderer._renderPoints, pointIndex, pointIndex); + seriesRenderer.renderPoints, pointIndex, pointIndex); dataLabelArgs.text = label; dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = dataLabelSettingsRenderer._color; + dataLabelArgs.color = dataLabelSettingsRenderer.color; chart.onDataLabelRender!(dataLabelArgs); label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; pointIndex = dataLabelArgs.pointIndex!; - dataLabelSettingsRenderer._color = dataLabelArgs.color; - if (animation!.status == AnimationStatus.completed) { - seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; - } + dataLabelSettingsRenderer.color = dataLabelArgs.color; + seriesRenderer.dataPoints[pointIndex].labelRenderEvent = true; } textSize = measureText(label, dataLabelStyle); /// condition check for labels after event. if (label != '') { - if (seriesRenderer._seriesType == 'radialbar') { + if (seriesRenderer.seriesType == 'radialbar') { dataLabelStyle = chart.onDataLabelRender == null - ? seriesRenderer._series._renderer!.getDataLabelStyle( + ? seriesRenderer.renderer.getDataLabelStyle( seriesRenderer, point, pointIndex, seriesIndex, dataLabelStyle, - _chartState) + stateProperties.chartState) : dataLabelStyle; - labelLocation = _degreeToPoint(point.startAngle!, + labelLocation = degreeToPoint(point.startAngle!, (point.innerRadius! + point.outerRadius!) / 2, point.center!); labelLocation = Offset( (labelLocation.dx - textSize.width - 5) + @@ -195,7 +461,7 @@ void _renderCircularDataLabel( labelLocation.dy - labelPadding, textSize.width + (2 * labelPadding), textSize.height + (2 * labelPadding)); - _drawLabel( + drawLabel( point.labelRect, labelLocation, label, @@ -210,11 +476,11 @@ void _renderCircularDataLabel( renderDataLabelRegions, animateOpacity); } else { - _setLabelPosition( + setLabelPosition( dataLabel, point, textSize, - _chartState, + stateProperties, canvas, renderDataLabelRegions, pointIndex, @@ -226,36 +492,221 @@ void _renderCircularDataLabel( } } dataLabelStyle = chart.onDataLabelRender == null - ? seriesRenderer._series._renderer!.getDataLabelStyle(seriesRenderer, - point, pointIndex, seriesIndex, dataLabelStyle, _chartState) + ? seriesRenderer.renderer.getDataLabelStyle( + seriesRenderer, + point, + pointIndex, + seriesIndex, + dataLabelStyle, + stateProperties.chartState) : dataLabelStyle; + } else { + point.labelRect = Rect.zero; + } + } + if (seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings + .labelIntersectAction == + LabelIntersectAction.shift && + seriesRenderer.seriesType != 'radialbar') { + const int labelPadding = 2; + leftPoints = >[]; + rightPoints = >[]; + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + if (seriesRenderer.renderPoints![i].isVisible) { + PointHelper.setNewAngle(seriesRenderer.renderPoints![i], + seriesRenderer.renderPoints![i].midAngle); + if (seriesRenderer.renderPoints![i].dataLabelPosition == + Position.left && + seriesRenderer.renderPoints![i].renderPosition == + ChartDataLabelPosition.outside) { + leftPoints.add(seriesRenderer.renderPoints![i]); + } else if (seriesRenderer.renderPoints![i].dataLabelPosition == + Position.right && + seriesRenderer.renderPoints![i].renderPosition == + ChartDataLabelPosition.outside) { + rightPoints.add(seriesRenderer.renderPoints![i]); + } + } + } + leftPoints.sort((ChartPoint a, ChartPoint b) => + PointHelper.getNewAngle(a)!.compareTo(PointHelper.getNewAngle(b)!)); + if (leftPoints.isNotEmpty) { + _arrangeLeftSidePoints(seriesRenderer); + } + isIncreaseAngle = false; + if (rightPoints.isNotEmpty) { + _arrangeRightSidePoints(seriesRenderer); + } + for (int pointIndex = 0; + pointIndex < seriesRenderer.renderPoints!.length; + pointIndex++) { + if (seriesRenderer.renderPoints![pointIndex].isVisible) { + final ChartPoint point = + seriesRenderer.renderPoints![pointIndex]; + final EdgeInsets margin = + seriesRenderer.series.dataLabelSettings.margin; + final Rect rect = point.labelRect; + TextStyle dataLabelStyle = dataLabel.textStyle; + dataLabelStyle = TextStyle( + color: (chart.onDataLabelRender != null && dataLabelSettingsRenderer.color != null) + ? getSaturationColor( + dataLabelSettingsRenderer.color ?? point.fill) + : ((dataLabelStyle.color ?? dataLabel.textStyle.color) ?? + getSaturationColor( + point.renderPosition == ChartDataLabelPosition.outside + ? findthemecolor(stateProperties, point, dataLabel) + : dataLabelSettingsRenderer.color ?? point.fill)), + fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, + fontFamily: + dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, + fontStyle: + dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, + fontWeight: + dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, + inherit: dataLabelStyle.inherit, + backgroundColor: dataLabelStyle.backgroundColor ?? + dataLabel.textStyle.backgroundColor, + letterSpacing: dataLabelStyle.letterSpacing ?? + dataLabel.textStyle.letterSpacing, + wordSpacing: + dataLabelStyle.wordSpacing ?? dataLabel.textStyle.wordSpacing, + textBaseline: + dataLabelStyle.textBaseline ?? dataLabel.textStyle.textBaseline, + height: dataLabelStyle.height ?? dataLabel.textStyle.height, + locale: dataLabelStyle.locale ?? dataLabel.textStyle.locale, + foreground: + dataLabelStyle.foreground ?? dataLabel.textStyle.foreground, + background: + dataLabelStyle.background ?? dataLabel.textStyle.background, + shadows: dataLabelStyle.shadows ?? dataLabel.textStyle.shadows, + fontFeatures: + dataLabelStyle.fontFeatures ?? dataLabel.textStyle.fontFeatures, + decoration: + dataLabelStyle.decoration ?? dataLabel.textStyle.decoration, + decorationColor: dataLabelStyle.decorationColor ?? + dataLabel.textStyle.decorationColor, + decorationStyle: dataLabelStyle.decorationStyle ?? dataLabel.textStyle.decorationStyle, + decorationThickness: dataLabelStyle.decorationThickness ?? dataLabel.textStyle.decorationThickness, + debugLabel: dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, + fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? dataLabel.textStyle.fontFamilyFallback); + textSize = measureText(label!, dataLabelStyle); + labelLocation = Offset( + rect.left + + (point.renderPosition == ChartDataLabelPosition.inside + ? labelPadding + : margin.left), + rect.top + rect.height / 2 - textSize.height / 2); + const String defaultConnectorLineLength = '10%'; + point.trimmedText = point.text; + Path shiftedConnectorPath = Path(); + final num connectorLength = percentToValue( + seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings + .connectorLineSettings.length ?? + defaultConnectorLineLength, + point.outerRadius!)!; + final Offset startPoint = degreeToPoint( + (point.startAngle! + point.endAngle!) / 2, + point.outerRadius!, + point.center!); + final Offset endPoint = degreeToPoint(PointHelper.getNewAngle(point)!, + point.outerRadius! + connectorLength, point.center!); + shiftedConnectorPath.moveTo(startPoint.dx, startPoint.dy); + if (seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings + .connectorLineSettings.type == + ConnectorType.line) { + shiftedConnectorPath.lineTo(endPoint.dx, endPoint.dy); + } + getDataLabelRect( + point.dataLabelPosition, + seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings + .connectorLineSettings.type, + margin, + shiftedConnectorPath, + endPoint, + textSize); + final ChartLocation midAngle = getPerpendicularDistance( + ChartLocation(startPoint.dx, startPoint.dy), point); + if (seriesRenderer.dataLabelSettingsRenderer.dataLabelSettings + .connectorLineSettings.type == + ConnectorType.curve && + PointHelper.getLabelUpdated(point) == 1) { + const int spacing = 10; + shiftedConnectorPath = Path(); + shiftedConnectorPath.moveTo(startPoint.dx, startPoint.dy); + shiftedConnectorPath.quadraticBezierTo( + midAngle.x, + midAngle.y, + endPoint.dx - + (point.dataLabelPosition == Position.left + ? spacing + : -spacing), + endPoint.dy); + } + final Rect containerRect = + stateProperties.renderingDetails.chartAreaRect; + if (containerRect.left > rect.left) { + labelLocation = Offset(containerRect.left, + rect.top + rect.height / 2 - textSize.height / 2); + } + if (point.labelRect.left < containerRect.left && + point.renderPosition == ChartDataLabelPosition.outside) { + point.trimmedText = getTrimmedText(point.trimmedText!, + point.labelRect.right - containerRect.left, dataLabelStyle, null); + } + if (point.labelRect.right > containerRect.right && + point.renderPosition == ChartDataLabelPosition.outside) { + point.trimmedText = getTrimmedText(point.trimmedText!, + containerRect.right - point.labelRect.left, dataLabelStyle, null); + } + if (point.trimmedText != '' && + !isOverlapWithPrevious( + point, seriesRenderer.renderPoints!, pointIndex) && + rect != Rect.zero) { + drawLabel( + rect, + labelLocation, + point.trimmedText!, + point.renderPosition == ChartDataLabelPosition.outside + ? shiftedConnectorPath + : Path(), + canvas, + seriesRenderer, + point, + pointIndex, + seriesIndex, + chart, + dataLabelStyle, + renderDataLabelRegions, + animateOpacity); + } + } } } } /// To set data label position -void _setLabelPosition( +void setLabelPosition( DataLabelSettings dataLabel, ChartPoint point, Size textSize, - SfCircularChartState _chartState, + CircularStateProperties stateProperties, Canvas canvas, List renderDataLabelRegions, int pointIndex, String label, - CircularSeriesRenderer seriesRenderer, + CircularSeriesRendererExtension seriesRenderer, double animateOpacity, TextStyle dataLabelStyle, int seriesIndex) { final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; - final SfCircularChart chart = _chartState._chart; + seriesRenderer.dataLabelSettingsRenderer; + final SfCircularChart chart = stateProperties.chart; final num angle = dataLabel.angle; Offset labelLocation; - final bool smartLabel = seriesRenderer._series.enableSmartLabels; + final bool smartLabel = seriesRenderer.series.enableSmartLabels; const int labelPadding = 2; if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - labelLocation = _degreeToPoint(point.midAngle!, + labelLocation = degreeToPoint(point.midAngle!, (point.innerRadius! + point.outerRadius!) / 2, point.center!); labelLocation = Offset( labelLocation.dx - @@ -270,60 +721,72 @@ void _setLabelPosition( textSize.width + (2 * labelPadding), textSize.height + (2 * labelPadding)); final bool isDataLabelCollide = - _findingCollision(point.labelRect, renderDataLabelRegions); - if (smartLabel && isDataLabelCollide) { + findingCollision(point.labelRect, renderDataLabelRegions); + final TextStyle textStyle = TextStyle( + color: (dataLabelStyle.color ?? dataLabel.textStyle.color) ?? + getSaturationColor( + findthemecolor(stateProperties, point, dataLabel)), + fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, + fontFamily: dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, + fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, + fontWeight: dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, + inherit: dataLabelStyle.inherit, + backgroundColor: dataLabelStyle.backgroundColor ?? + dataLabel.textStyle.backgroundColor, + letterSpacing: + dataLabelStyle.letterSpacing ?? dataLabel.textStyle.letterSpacing, + wordSpacing: + dataLabelStyle.wordSpacing ?? dataLabel.textStyle.wordSpacing, + textBaseline: + dataLabelStyle.textBaseline ?? dataLabel.textStyle.textBaseline, + height: dataLabelStyle.height ?? dataLabel.textStyle.height, + locale: dataLabelStyle.locale ?? dataLabel.textStyle.locale, + foreground: dataLabelStyle.foreground ?? dataLabel.textStyle.foreground, + background: dataLabelStyle.background ?? dataLabel.textStyle.background, + shadows: dataLabelStyle.shadows ?? dataLabel.textStyle.shadows, + fontFeatures: + dataLabelStyle.fontFeatures ?? dataLabel.textStyle.fontFeatures, + decoration: dataLabelStyle.decoration ?? dataLabel.textStyle.decoration, + decorationColor: dataLabelStyle.decorationColor ?? + dataLabel.textStyle.decorationColor, + decorationStyle: dataLabelStyle.decorationStyle ?? + dataLabel.textStyle.decorationStyle, + decorationThickness: dataLabelStyle.decorationThickness ?? + dataLabel.textStyle.decorationThickness, + debugLabel: dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, + fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? + dataLabel.textStyle.fontFamilyFallback); + if (seriesRenderer.series.dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift && + isDataLabelCollide) { point.saturationRegionOutside = true; point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = TextStyle( - color: (dataLabelStyle.color ?? dataLabel.textStyle.color) ?? - _getSaturationColor( - _findthemecolor(_chartState, point, dataLabel)), - fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, - fontFamily: - dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, - fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, - fontWeight: - dataLabelStyle.fontWeight ?? dataLabel.textStyle.fontWeight, - inherit: dataLabelStyle.inherit, - backgroundColor: dataLabelStyle.backgroundColor ?? - dataLabel.textStyle.backgroundColor, - letterSpacing: - dataLabelStyle.letterSpacing ?? dataLabel.textStyle.letterSpacing, - wordSpacing: - dataLabelStyle.wordSpacing ?? dataLabel.textStyle.wordSpacing, - textBaseline: - dataLabelStyle.textBaseline ?? dataLabel.textStyle.textBaseline, - height: dataLabelStyle.height ?? dataLabel.textStyle.height, - locale: dataLabelStyle.locale ?? dataLabel.textStyle.locale, - foreground: - dataLabelStyle.foreground ?? dataLabel.textStyle.foreground, - background: - dataLabelStyle.background ?? dataLabel.textStyle.background, - shadows: dataLabelStyle.shadows ?? dataLabel.textStyle.shadows, - fontFeatures: - dataLabelStyle.fontFeatures ?? dataLabel.textStyle.fontFeatures, - decoration: - dataLabelStyle.decoration ?? dataLabel.textStyle.decoration, - decorationColor: dataLabelStyle.decorationColor ?? - dataLabel.textStyle.decorationColor, - decorationStyle: dataLabelStyle.decorationStyle ?? - dataLabel.textStyle.decorationStyle, - decorationThickness: dataLabelStyle.decorationThickness ?? - dataLabel.textStyle.decorationThickness, - debugLabel: - dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, - fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? - dataLabel.textStyle.fontFamilyFallback); - _renderOutsideDataLabel( + dataLabelStyle = textStyle; + renderOutsideDataLabel( canvas, label, point, textSize, pointIndex, seriesRenderer, - smartLabel, seriesIndex, - _chartState, + stateProperties, + dataLabelStyle, + renderDataLabelRegions, + animateOpacity); + } else if (smartLabel && isDataLabelCollide) { + point.saturationRegionOutside = true; + point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = textStyle; + renderOutsideDataLabel( + canvas, + label, + point, + textSize, + pointIndex, + seriesRenderer, + seriesIndex, + stateProperties, dataLabelStyle, renderDataLabelRegions, animateOpacity); @@ -331,12 +794,12 @@ void _setLabelPosition( point.renderPosition = ChartDataLabelPosition.inside; dataLabelStyle = TextStyle( color: (chart.onDataLabelRender != null && - dataLabelSettingsRenderer._color != null) - ? _getSaturationColor( - dataLabelSettingsRenderer._color ?? point.fill) + dataLabelSettingsRenderer.color != null) + ? getSaturationColor( + dataLabelSettingsRenderer.color ?? point.fill) : ((dataLabelStyle.color ?? dataLabel.textStyle.color) ?? - _getSaturationColor( - dataLabelSettingsRenderer._color ?? point.fill)), + getSaturationColor( + dataLabelSettingsRenderer.color ?? point.fill)), fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, fontFamily: dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, @@ -373,9 +836,27 @@ void _setLabelPosition( dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? dataLabel.textStyle.fontFamilyFallback); - if (!isDataLabelCollide || + if (!isDataLabelCollide && + (dataLabel.labelIntersectAction == LabelIntersectAction.shift)) { + renderDataLabelRegions.add(point.labelRect); + } else if (!isDataLabelCollide && (dataLabel.labelIntersectAction == LabelIntersectAction.hide)) { - _drawLabel( + drawLabel( + point.labelRect, + labelLocation, + label, + null, + canvas, + seriesRenderer, + point, + pointIndex, + seriesIndex, + chart, + dataLabelStyle, + renderDataLabelRegions, + animateOpacity); + } else { + drawLabel( point.labelRect, labelLocation, label, @@ -395,7 +876,8 @@ void _setLabelPosition( point.renderPosition = ChartDataLabelPosition.outside; dataLabelStyle = TextStyle( color: (dataLabelStyle.color ?? dataLabel.textStyle.color) ?? - _getSaturationColor(_findthemecolor(_chartState, point, dataLabel)), + getSaturationColor( + findthemecolor(stateProperties, point, dataLabel)), fontSize: dataLabelStyle.fontSize ?? dataLabel.textStyle.fontSize, fontFamily: dataLabelStyle.fontFamily ?? dataLabel.textStyle.fontFamily, fontStyle: dataLabelStyle.fontStyle ?? dataLabel.textStyle.fontStyle, @@ -426,16 +908,15 @@ void _setLabelPosition( debugLabel: dataLabelStyle.debugLabel ?? dataLabel.textStyle.debugLabel, fontFamilyFallback: dataLabelStyle.fontFamilyFallback ?? dataLabel.textStyle.fontFamilyFallback); - _renderOutsideDataLabel( + renderOutsideDataLabel( canvas, label, point, textSize, pointIndex, seriesRenderer, - smartLabel, seriesIndex, - _chartState, + stateProperties, dataLabelStyle, renderDataLabelRegions, animateOpacity); @@ -443,52 +924,52 @@ void _setLabelPosition( } /// To render outside positioned data labels. -void _renderOutsideDataLabel( +void renderOutsideDataLabel( Canvas canvas, String label, ChartPoint point, Size textSize, int pointIndex, - CircularSeriesRenderer seriesRenderer, - bool smartLabel, + CircularSeriesRendererExtension seriesRenderer, int seriesIndex, - SfCircularChartState _chartState, + CircularStateProperties stateProperties, TextStyle textStyle, List renderDataLabelRegions, double animateOpacity) { Path connectorPath; Rect? rect; Offset labelLocation; - final EdgeInsets margin = seriesRenderer._series.dataLabelSettings.margin; + const String defaultConnectorLineLength = '10%'; + final EdgeInsets margin = seriesRenderer.series.dataLabelSettings.margin; final ConnectorLineSettings connector = - seriesRenderer._series.dataLabelSettings.connectorLineSettings; + seriesRenderer.series.dataLabelSettings.connectorLineSettings; connectorPath = Path(); - final num connectorLength = - _percentToValue(connector.length ?? '10%', point.outerRadius!)!; + final num connectorLength = percentToValue( + connector.length ?? defaultConnectorLineLength, point.outerRadius!)!; final Offset startPoint = - _degreeToPoint(point.midAngle!, point.outerRadius!, point.center!); - final Offset endPoint = _degreeToPoint( + degreeToPoint(point.midAngle!, point.outerRadius!, point.center!); + final Offset endPoint = degreeToPoint( point.midAngle!, point.outerRadius! + connectorLength, point.center!); connectorPath.moveTo(startPoint.dx, startPoint.dy); if (connector.type == ConnectorType.line) { connectorPath.lineTo(endPoint.dx, endPoint.dy); } - rect = _getDataLabelRect(point.dataLabelPosition, connector.type, margin, + rect = getDataLabelRect(point.dataLabelPosition, connector.type, margin, connectorPath, endPoint, textSize); point.labelRect = rect!; labelLocation = Offset(rect.left + margin.left, rect.top + rect.height / 2 - textSize.height / 2); - final Rect containerRect = _chartState._renderingDetails.chartAreaRect; - if (seriesRenderer._series.dataLabelSettings.builder == null) { - if (seriesRenderer._series.dataLabelSettings.labelIntersectAction == + final Rect containerRect = stateProperties.renderingDetails.chartAreaRect; + if (seriesRenderer.series.dataLabelSettings.builder == null) { + if (seriesRenderer.series.dataLabelSettings.labelIntersectAction == LabelIntersectAction.hide) { - if (!_findingCollision(rect, renderDataLabelRegions) && + if (!findingCollision(rect, renderDataLabelRegions) && (rect.left > containerRect.left && rect.left + rect.width < containerRect.left + containerRect.width) && rect.top > containerRect.top && rect.top + rect.height < containerRect.top + containerRect.height) { - _drawLabel( + drawLabel( rect, labelLocation, label, @@ -498,13 +979,16 @@ void _renderOutsideDataLabel( point, pointIndex, seriesIndex, - _chartState._chart, + stateProperties.chart, textStyle, renderDataLabelRegions, animateOpacity); } + } else if (seriesRenderer.series.dataLabelSettings.labelIntersectAction == + LabelIntersectAction.shift) { + renderDataLabelRegions.add(rect); } else { - _drawLabel( + drawLabel( rect, labelLocation, label, @@ -514,31 +998,33 @@ void _renderOutsideDataLabel( point, pointIndex, seriesIndex, - _chartState._chart, + stateProperties.chart, textStyle, renderDataLabelRegions, animateOpacity); } } else { - canvas.drawPath( - connectorPath, - Paint() - ..color = connector.width <= 0 - ? Colors.transparent - : connector.color ?? point.fill.withOpacity(animateOpacity) - ..strokeWidth = connector.width - ..style = PaintingStyle.stroke); + if (seriesRenderer.series.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.shift) + canvas.drawPath( + connectorPath, + Paint() + ..color = connector.width <= 0 + ? Colors.transparent + : connector.color ?? point.fill.withOpacity(animateOpacity) + ..strokeWidth = connector.width + ..style = PaintingStyle.stroke); } } /// To draw label -void _drawLabel( +void drawLabel( Rect labelRect, Offset location, String label, Path? connectorPath, Canvas canvas, - CircularSeriesRenderer seriesRenderer, + CircularSeriesRendererExtension seriesRenderer, ChartPoint point, int pointIndex, int seriesIndex, @@ -547,9 +1033,9 @@ void _drawLabel( List renderDataLabelRegions, double animateOpacity) { Paint rectPaint; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; + seriesRenderer.dataLabelSettingsRenderer; final ConnectorLineSettings connector = dataLabel.connectorLineSettings; if (connectorPath != null) { canvas.drawPath( @@ -563,22 +1049,23 @@ void _drawLabel( } if (dataLabel.builder == null) { - final double strokeWidth = seriesRenderer._series._renderer! - .getDataLabelStrokeWidth(seriesRenderer, point, pointIndex, seriesIndex, - dataLabel.borderWidth); - final Color? labelFill = seriesRenderer._series._renderer! - .getDataLabelColor( - seriesRenderer, - point, - pointIndex, - seriesIndex, - dataLabelSettingsRenderer._color ?? - (dataLabel.useSeriesColor - ? point.fill - : dataLabelSettingsRenderer._color)); - final Color strokeColor = seriesRenderer._series._renderer! - .getDataLabelStrokeColor(seriesRenderer, point, pointIndex, seriesIndex, - dataLabel.borderColor.withOpacity(dataLabel.opacity)); + final double strokeWidth = seriesRenderer.renderer.getDataLabelStrokeWidth( + seriesRenderer, point, pointIndex, seriesIndex, dataLabel.borderWidth); + final Color? labelFill = seriesRenderer.renderer.getDataLabelColor( + seriesRenderer, + point, + pointIndex, + seriesIndex, + dataLabelSettingsRenderer.color ?? + (dataLabel.useSeriesColor + ? point.fill + : dataLabelSettingsRenderer.color)); + final Color strokeColor = seriesRenderer.renderer.getDataLabelStrokeColor( + seriesRenderer, + point, + pointIndex, + seriesIndex, + dataLabel.borderColor.withOpacity(dataLabel.opacity)); // ignore: unnecessary_null_comparison if (strokeWidth != null && strokeWidth > 0) { rectPaint = Paint() @@ -588,7 +1075,7 @@ void _drawLabel( : animateOpacity - (1 - dataLabel.opacity)) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth; - _drawLabelRect( + drawLabelRect( rectPaint, Rect.fromLTRB( labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), @@ -596,7 +1083,7 @@ void _drawLabel( canvas); } if (labelFill != null) { - _drawLabelRect( + drawLabelRect( Paint() ..color = labelFill.withOpacity( (animateOpacity - (1 - dataLabel.opacity)) < 0 @@ -608,72 +1095,80 @@ void _drawLabel( dataLabel.borderRadius, canvas); } - _drawText(canvas, label, location, textStyle, dataLabel.angle); - renderDataLabelRegions.add(labelRect); + drawText(canvas, label, location, textStyle, dataLabel.angle); + if (seriesRenderer.series.dataLabelSettings.labelIntersectAction != + LabelIntersectAction.shift) { + renderDataLabelRegions.add(labelRect); + } } } -void _triggerCircularDataLabelEvent( +/// Method to trigger the data label event +void triggerCircularDataLabelEvent( SfCircularChart chart, - CircularSeriesRenderer seriesRenderer, - SfCircularChartState chartState, + CircularSeriesRendererExtension seriesRenderer, + CircularStateProperties stateProperties, Offset? position) { const int seriesIndex = 0; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; Offset labelLocation; num connectorLength; ChartPoint point; - for (int index = 0; index < seriesRenderer._dataPoints.length; index++) { - point = seriesRenderer._dataPoints[index]; + const String defaultConnectorLineLength = '10%'; + for (int index = 0; index < seriesRenderer.dataPoints.length; index++) { + point = seriesRenderer.dataPoints[index]; if (dataLabel.isVisible && // ignore: unnecessary_null_comparison - seriesRenderer._dataPoints[index].labelRect != null && + seriesRenderer.dataPoints[index].labelRect != null && position != null && - seriesRenderer._dataPoints[index].labelRect.contains(position)) { + seriesRenderer.dataPoints[index].labelRect.contains(position)) { if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - labelLocation = _degreeToPoint(point.midAngle!, + labelLocation = degreeToPoint(point.midAngle!, (point.innerRadius! + point.outerRadius!) / 2, point.center!); position = Offset(labelLocation.dx, labelLocation.dy); } else { - connectorLength = _percentToValue( - dataLabel.connectorLineSettings.length ?? '10%', + connectorLength = percentToValue( + dataLabel.connectorLineSettings.length ?? + defaultConnectorLineLength, point.outerRadius!)!; - labelLocation = _degreeToPoint(point.midAngle!, + labelLocation = degreeToPoint(point.midAngle!, point.outerRadius! + connectorLength, point.center!); position = Offset(labelLocation.dx, labelLocation.dy); } if (chart.onDataLabelTapped != null) { - _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, - index, point, position, seriesIndex); + dataLabelTapEvent(chart, seriesRenderer.series.dataLabelSettings, index, + point, position, seriesIndex); } } } } /// To draw data label rect -void _drawLabelRect( +void drawLabelRect( Paint paint, Rect labelRect, double borderRadius, Canvas canvas) => canvas.drawRRect( RRect.fromRectAndRadius(labelRect, Radius.circular(borderRadius)), paint); /// To find data label position -void _findDataLabelPosition(ChartPoint point) => - point.dataLabelPosition = - ((point.midAngle! >= -90 && point.midAngle! < 0) || - (point.midAngle! >= 0 && point.midAngle! < 90) || - (point.midAngle! >= 270)) - ? Position.right - : Position.left; - -/// Method for setting color to datalabel -Color _findthemecolor(SfCircularChartState _chartState, +void findDataLabelPosition(ChartPoint point) { + point.midAngle = + point.midAngle! > 360 ? point.midAngle! - 360 : point.midAngle!; + point.dataLabelPosition = ((point.midAngle! >= -90 && point.midAngle! < 0) || + (point.midAngle! >= 0 && point.midAngle! < 90) || + (point.midAngle! >= 270)) + ? Position.right + : Position.left; +} + +/// Method for setting color to data label +Color findthemecolor(CircularStateProperties stateProperties, ChartPoint point, DataLabelSettings dataLabel) { return dataLabel.color ?? (dataLabel.useSeriesColor ? point.fill - : (_chartState._chart.backgroundColor ?? - (_chartState._renderingDetails.chartTheme.brightness == + : (stateProperties.chart.backgroundColor ?? + (stateProperties.renderingDetails.chartTheme.brightness == Brightness.light ? Colors.white : Colors.black))); diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart index 3a0801805..b22e6cf97 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/doughnut_series.dart @@ -1,4 +1,20 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/circular_base.dart'; +import '../utils/enum.dart'; +import 'circular_series.dart'; +import 'renderer_base.dart'; +import 'renderer_extension.dart'; /// This class has the properties of the Doughnut series. /// @@ -43,10 +59,12 @@ class DoughnutSeries extends CircularSeries { double? strokeWidth, DataLabelSettings? dataLabelSettings, bool? enableTooltip, - bool? enableSmartLabels, + @Deprecated('Use LabelIntersectAction.shift in dataLabelSettings.labelIntersectAction instead') + bool? enableSmartLabels, String? name, double? opacity, double? animationDuration, + double? animationDelay, SelectionBehavior? selectionBehavior, SortingOrder? sortingOrder, LegendIconType? legendIconType, @@ -79,6 +97,7 @@ class DoughnutSeries extends CircularSeries { ? (int index) => sortFieldValueMapper(dataSource![index], index) : null, animationDuration: animationDuration, + animationDelay: animationDelay, startAngle: startAngle, endAngle: endAngle, radius: radius, @@ -116,7 +135,7 @@ class DoughnutSeries extends CircularSeries { 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; } - return DoughnutSeriesRenderer(); + return DoughnutSeriesRendererExtension(); } @override @@ -130,6 +149,7 @@ class DoughnutSeries extends CircularSeries { return other is DoughnutSeries && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.borderColor == borderColor && other.borderWidth == borderWidth && other.cornerStyle == cornerStyle && @@ -175,6 +195,7 @@ class DoughnutSeries extends CircularSeries { int get hashCode { final List values = [ animationDuration, + animationDelay, borderColor, borderWidth, cornerStyle, @@ -217,119 +238,3 @@ class DoughnutSeries extends CircularSeries { return hashList(values); } } - -class _DoughnutChartPainter extends CustomPainter { - _DoughnutChartPainter({ - required this.chartState, - required this.index, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : super(repaint: notifier); - final SfCircularChartState chartState; - final int index; - final bool isRepaint; - final AnimationController? animationController; - final Animation? seriesAnimation; - - late DoughnutSeriesRenderer seriesRenderer; - - /// To paint series - @override - void paint(Canvas canvas, Size size) { - final SfCircularChart chart = chartState._chart; - num? pointStartAngle; - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index] - as DoughnutSeriesRenderer; - pointStartAngle = seriesRenderer._segmentRenderingValues['start']; - seriesRenderer._innerRadius = - seriesRenderer._segmentRenderingValues['currentInnerRadius']!; - seriesRenderer._radius = - seriesRenderer._segmentRenderingValues['currentRadius']!; - ChartPoint point; - seriesRenderer._pointRegions = <_Region>[]; - ChartPoint? _oldPoint; - final DoughnutSeriesRenderer? oldSeriesRenderer = - (chartState._renderingDetails.widgetNeedUpdate && - !chartState._renderingDetails.isLegendToggled && - chartState._prevSeriesRenderer?._seriesType == 'doughnut') - ? chartState._prevSeriesRenderer as DoughnutSeriesRenderer - : null; - seriesRenderer._renderPaths.clear(); - seriesRenderer._renderList.clear(); - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - point = seriesRenderer._renderPoints![i]; - _oldPoint = (oldSeriesRenderer != null && - oldSeriesRenderer._oldRenderPoints != null && - (oldSeriesRenderer._oldRenderPoints!.length - 1 >= i)) - ? oldSeriesRenderer._oldRenderPoints![i] - : ((chartState._renderingDetails.isLegendToggled && - chartState._prevSeriesRenderer?._seriesType == 'doughnut') - ? chartState._oldPoints![i] - : null); - pointStartAngle = seriesRenderer._circularRenderPoint( - chart, - seriesRenderer, - point, - pointStartAngle, - point.innerRadius, - point.outerRadius, - canvas, - index, - i, - seriesAnimation?.value ?? 1, - 1, - _checkIsAnyPointSelect(seriesRenderer, point, chart), - _oldPoint, - chartState._oldPoints); - } - - if (seriesRenderer._renderList.isNotEmpty) { - Shader? _chartShader; - if (chart.onCreateShader != null) { - ChartShaderDetails chartShaderDetails; - chartShaderDetails = ChartShaderDetails(seriesRenderer._renderList[1], - seriesRenderer._renderList[2], 'series'); - _chartShader = chart.onCreateShader!(chartShaderDetails); - } - for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { - _drawPath( - canvas, - seriesRenderer._renderList[0], - seriesRenderer._renderPaths[k], - seriesRenderer._renderList[1], - _chartShader); - } - if (seriesRenderer._renderList[0].strokeColor != null && - seriesRenderer._renderList[0].strokeWidth != null && - seriesRenderer._renderList[0].strokeWidth > 0 == true) { - final Paint paint = Paint(); - paint.color = seriesRenderer._renderList[0].strokeColor; - paint.strokeWidth = seriesRenderer._renderList[0].strokeWidth; - paint.style = PaintingStyle.stroke; - for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { - canvas.drawPath(seriesRenderer._renderPaths[k], paint); - } - } - } - } - - @override - bool shouldRepaint(_DoughnutChartPainter oldDelegate) => isRepaint; -} - -/// Creates series renderer for Doughnut series -class DoughnutSeriesRenderer extends CircularSeriesRenderer { - /// Calling the default constructor of DoughnutSeriesRenderer class. - DoughnutSeriesRenderer(); - - /// stores the series of the corresponding series for renderer - late CircularSeries series; - - //ignore: unused_field - late num _innerRadius; - - //ignore: unused_field - late num _radius; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart index 7a6136a3e..f3458d23e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/pie_series.dart @@ -1,4 +1,20 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/circular_base.dart'; +import '../utils/enum.dart'; +import 'circular_series.dart'; +import 'renderer_base.dart'; +import 'renderer_extension.dart'; /// This class has the properties of the pie series. /// @@ -41,9 +57,11 @@ class PieSeries extends CircularSeries { double? opacity, DataLabelSettings? dataLabelSettings, bool? enableTooltip, - bool? enableSmartLabels, + @Deprecated('Use LabelIntersectAction.shift in dataLabelSettings.labelIntersectAction instead') + bool? enableSmartLabels, String? name, double? animationDuration, + double? animationDelay, SelectionBehavior? selectionBehavior, SortingOrder? sortingOrder, LegendIconType? legendIconType, @@ -56,6 +74,7 @@ class PieSeries extends CircularSeries { onPointDoubleTap: onPointDoubleTap, onPointLongPress: onPointLongPress, animationDuration: animationDuration, + animationDelay: animationDelay, dataSource: dataSource, xValueMapper: (int index) => xValueMapper!(dataSource![index], index), @@ -111,7 +130,7 @@ class PieSeries extends CircularSeries { 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; } - return PieSeriesRenderer(); + return PieSeriesRendererExtension(); } @override @@ -126,6 +145,7 @@ class PieSeries extends CircularSeries { return other is PieSeries && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.borderColor == borderColor && other.borderWidth == borderWidth && other.dataLabelMapper == dataLabelMapper && @@ -170,6 +190,7 @@ class PieSeries extends CircularSeries { int get hashCode { final List values = [ animationDuration, + animationDelay, borderColor, borderWidth, dataLabelMapper, @@ -210,116 +231,3 @@ class PieSeries extends CircularSeries { return hashList(values); } } - -class _PieChartPainter extends CustomPainter { - _PieChartPainter({ - required this.chartState, - required this.index, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : chart = chartState._chart, - super(repaint: notifier); - final SfCircularChartState chartState; - final SfCircularChart chart; - final int index; - final bool isRepaint; - final AnimationController? animationController; - final Animation? seriesAnimation; - - late PieSeriesRenderer seriesRenderer; - - /// To paint series - @override - void paint(Canvas canvas, Size size) { - num? pointStartAngle; - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index] - as PieSeriesRenderer; - pointStartAngle = seriesRenderer._segmentRenderingValues['start']; - seriesRenderer._pointRegions = <_Region>[]; - bool isAnyPointNeedSelect = false; - if (chartState._renderingDetails.initialRender!) { - isAnyPointNeedSelect = - _checkIsAnyPointSelect(seriesRenderer, seriesRenderer._point, chart); - } - ChartPoint? _oldPoint; - ChartPoint? point = seriesRenderer._point; - final PieSeriesRenderer? oldSeriesRenderer = - (chartState._renderingDetails.widgetNeedUpdate && - !chartState._renderingDetails.isLegendToggled && - chartState._prevSeriesRenderer != null && - chartState._prevSeriesRenderer!._seriesType == 'pie') - ? chartState._prevSeriesRenderer! as PieSeriesRenderer - : null; - seriesRenderer._renderPaths.clear(); - seriesRenderer._renderList.clear(); - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - point = seriesRenderer._renderPoints![i]; - _oldPoint = (oldSeriesRenderer != null && - oldSeriesRenderer._oldRenderPoints != null && - (oldSeriesRenderer._oldRenderPoints!.length - 1 >= i)) - ? oldSeriesRenderer._oldRenderPoints![i] - : ((chartState._renderingDetails.isLegendToggled && - chartState._prevSeriesRenderer?._seriesType == 'pie') - ? chartState._oldPoints![i] - : null); - point.innerRadius = 0.0; - pointStartAngle = seriesRenderer._circularRenderPoint( - chart, - seriesRenderer, - point, - pointStartAngle, - point.innerRadius, - point.outerRadius, - canvas, - index, - i, - seriesAnimation?.value ?? 1, - seriesAnimation?.value ?? 1, - isAnyPointNeedSelect, - _oldPoint, - chartState._oldPoints); - } - if (seriesRenderer._renderList.isNotEmpty) { - Shader? _chartShader; - if (chart.onCreateShader != null) { - ChartShaderDetails chartShaderDetails; - chartShaderDetails = - ChartShaderDetails(seriesRenderer._renderList[1], null, 'series'); - _chartShader = chart.onCreateShader!(chartShaderDetails); - } - for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { - _drawPath( - canvas, - seriesRenderer._renderList[0], - seriesRenderer._renderPaths[k], - seriesRenderer._renderList[1], - _chartShader); - } - if (seriesRenderer._renderList[0].strokeColor != null && - seriesRenderer._renderList[0].strokeWidth != null && - seriesRenderer._renderList[0].strokeWidth > 0 == true) { - final Paint paint = Paint(); - paint.color = seriesRenderer._renderList[0].strokeColor; - paint.strokeWidth = seriesRenderer._renderList[0].strokeWidth; - paint.style = PaintingStyle.stroke; - for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { - canvas.drawPath(seriesRenderer._renderPaths[k], paint); - } - } - } - } - - @override - bool shouldRepaint(_PieChartPainter oldDelegate) => isRepaint; -} - -/// Creates series renderer for Pie series -class PieSeriesRenderer extends CircularSeriesRenderer { - /// Calling the default constructor of PieSeriesRenderer class. - PieSeriesRenderer(); - @override - late CircularSeries _series; - ChartPoint? _point; -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart index 48653c9cb..0d5783f41 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/radial_bar_series.dart @@ -1,4 +1,19 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../base/circular_base.dart'; +import '../utils/enum.dart'; +import 'circular_series.dart'; +import 'renderer_base.dart'; +import 'renderer_extension.dart'; /// Renders the radial bar series. /// @@ -41,9 +56,11 @@ class RadialBarSeries extends CircularSeries { double? opacity, Color? strokeColor, bool? enableTooltip, - bool? enableSmartLabels, + @Deprecated('Use LabelIntersectAction.shift in dataLabelSettings.labelIntersectAction instead') + bool? enableSmartLabels, String? name, double? animationDuration, + double? animationDelay, SelectionBehavior? selectionBehavior, SortingOrder? sortingOrder, LegendIconType? legendIconType, @@ -58,6 +75,7 @@ class RadialBarSeries extends CircularSeries { onPointLongPress: onPointLongPress, dataSource: dataSource, animationDuration: animationDuration, + animationDelay: animationDelay, xValueMapper: (int index) => xValueMapper(dataSource![index], index), yValueMapper: (int index) => yValueMapper(dataSource![index], index), pointColorMapper: (int index) => pointColorMapper != null @@ -222,7 +240,7 @@ class RadialBarSeries extends CircularSeries { 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; } - return RadialBarSeriesRenderer(); + return RadialBarSeriesRendererExtension(); } @override @@ -236,6 +254,7 @@ class RadialBarSeries extends CircularSeries { return other is RadialBarSeries && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.borderColor == borderColor && other.borderWidth == borderWidth && other.cornerStyle == cornerStyle && @@ -277,6 +296,7 @@ class RadialBarSeries extends CircularSeries { int get hashCode { final List values = [ animationDuration, + animationDelay, borderColor, borderWidth, cornerStyle, @@ -315,666 +335,3 @@ class RadialBarSeries extends CircularSeries { return hashList(values); } } - -/// Represents the pointer to draw radial bar series -/// -class _RadialBarPainter extends CustomPainter { - /// Creates the instance for radial bar series - /// - _RadialBarPainter({ - required this.chartState, - required this.index, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - ValueNotifier? notifier, - }) : super(repaint: notifier); - final SfCircularChartState chartState; - final int index; - final bool isRepaint; - final AnimationController? animationController; - final Animation? seriesAnimation; - late RadialBarSeriesRenderer seriesRenderer; - late num _length, _sum, _ringSize, _animationValue, _actualStartAngle; - late int? _firstVisible; - late num? _gap; - late bool _isLegendToggle; - late RadialBarSeriesRenderer? _oldSeriesRenderer; - late double actualDegree; - - /// Method to get length of the visible point - num _getLength(Canvas canvas) { - num length = 0; - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[index] - as RadialBarSeriesRenderer; - seriesRenderer._pointRegions = <_Region>[]; - seriesRenderer._innerRadius = - seriesRenderer._segmentRenderingValues['currentInnerRadius']!; - seriesRenderer._radius = - seriesRenderer._segmentRenderingValues['currentRadius']!; - seriesRenderer._center = seriesRenderer._center!; - canvas.clipRect(chartState._renderingDetails.chartAreaRect); - - /// finding visible points count - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - length += seriesRenderer._renderPoints![i].isVisible ? 1 : 0; - } - return length; - } - - /// Method to initialize the values to draw the radial bar series - /// - void _initializeValues(Canvas canvas) { - _length = _getLength(canvas); - _sum = seriesRenderer._segmentRenderingValues['sumOfPoints']!; - _actualStartAngle = seriesRenderer._segmentRenderingValues['start']!; - - /// finding first visible point - _firstVisible = seriesRenderer._getFirstVisiblePointIndex(seriesRenderer); - _ringSize = (seriesRenderer._segmentRenderingValues['currentRadius']! - - seriesRenderer._segmentRenderingValues['currentInnerRadius']!) - .abs() / - _length; - _gap = _percentToValue( - seriesRenderer._series.gap, - (seriesRenderer._segmentRenderingValues['currentRadius']! - - seriesRenderer._segmentRenderingValues['currentInnerRadius']!) - .abs()); - _animationValue = seriesAnimation?.value ?? 1; - _isLegendToggle = chartState._renderingDetails.isLegendToggled; - _oldSeriesRenderer = (chartState._renderingDetails.widgetNeedUpdate && - !chartState._renderingDetails.isLegendToggled && - chartState._prevSeriesRenderer!._seriesType == 'radialbar') - ? chartState._prevSeriesRenderer! as RadialBarSeriesRenderer - : null; - seriesRenderer._renderPaths.clear(); - } - - /// Method to paint radial bar series - /// - @override - void paint(Canvas canvas, Size size) { - num? pointStartAngle, pointEndAngle, degree; - ChartPoint? _oldPoint; - late ChartPoint point; - _initializeValues(canvas); - late RadialBarSeries series; - num? oldStart, oldEnd, oldRadius, oldInnerRadius; - late bool isDynamicUpdate, hide; - seriesRenderer._shadowPaths.clear(); - seriesRenderer._overFilledPaths.clear(); - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - num? value; - point = seriesRenderer._renderPoints![i]; - if (seriesRenderer._series is RadialBarSeries) { - series = seriesRenderer._series as RadialBarSeries; - } - _oldPoint = (_oldSeriesRenderer != null && - _oldSeriesRenderer!._oldRenderPoints != null && - (_oldSeriesRenderer!._oldRenderPoints!.length - 1 >= i)) - ? _oldSeriesRenderer!._oldRenderPoints![i] - : (_isLegendToggle ? chartState._oldPoints![i] : null); - pointStartAngle = _actualStartAngle; - isDynamicUpdate = _oldPoint != null; - hide = false; - actualDegree = 0; - if (!isDynamicUpdate || - ((_oldPoint.isVisible && point.isVisible) || - (_oldPoint.isVisible && !point.isVisible) || - (!_oldPoint.isVisible && point.isVisible))) { - if (point.isVisible) { - hide = false; - if (isDynamicUpdate && !_isLegendToggle) { - value = (point.y! > _oldPoint.y!) - ? _oldPoint.y! + (point.y! - _oldPoint.y!) * _animationValue - : _oldPoint.y! - (_oldPoint.y! - point.y!) * _animationValue; - } - degree = (value ?? point.y!).abs() / (series.maximumValue ?? _sum); - degree = degree * (360 - 0.001); - actualDegree = degree.toDouble(); - degree = isDynamicUpdate ? degree : degree * _animationValue; - pointEndAngle = pointStartAngle + degree; - point.midAngle = (pointStartAngle + pointEndAngle) / 2; - point.startAngle = pointStartAngle; - point.endAngle = pointEndAngle; - point.center = seriesRenderer._center!; - point.innerRadius = seriesRenderer._innerRadius = - seriesRenderer._innerRadius + - ((i == _firstVisible) ? 0 : _ringSize); - point.outerRadius = seriesRenderer._radius = _ringSize < _gap! - ? 0 - : seriesRenderer._innerRadius + _ringSize - _gap!; - if (_isLegendToggle) { - seriesRenderer._calculateVisiblePointLegendToggleAnimation( - point, _oldPoint, i, _animationValue); - } - } //animate on hiding - else if (_isLegendToggle && !point.isVisible && _oldPoint!.isVisible) { - hide = true; - oldEnd = _oldPoint.endAngle; - oldStart = _oldPoint.startAngle; - degree = _oldPoint.y!.abs() / (series.maximumValue ?? _sum); - degree = degree * (360 - 0.001); - actualDegree = degree.toDouble(); - oldInnerRadius = _oldPoint.innerRadius! + - ((_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2 - - _oldPoint.innerRadius!) * - _animationValue; - oldRadius = _oldPoint.outerRadius! - - (_oldPoint.outerRadius! - - (_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2) * - _animationValue; - } - if (seriesRenderer is RadialBarSeriesRenderer) { - seriesRenderer._drawDataPoint( - point, - degree, - pointStartAngle, - pointEndAngle, - seriesRenderer, - hide, - oldRadius, - oldInnerRadius, - _oldPoint, - oldStart, - oldEnd, - i, - canvas, - index, - chartState._chart, - actualDegree); - } - } - } - - _renderRadialBarSeries(canvas); - } - - /// Method to render the radial bar series - /// - void _renderRadialBarSeries(Canvas canvas) { - if (seriesRenderer._renderList.isNotEmpty) { - Shader? _chartShader; - if (chartState._chart.onCreateShader != null) { - ChartShaderDetails chartShaderDetails; - chartShaderDetails = ChartShaderDetails(seriesRenderer._renderList[1], - seriesRenderer._renderList[2], 'series'); - _chartShader = chartState._chart.onCreateShader!(chartShaderDetails); - } - - for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { - _drawPath( - canvas, - seriesRenderer._renderList[0], - seriesRenderer._renderPaths[k], - seriesRenderer._renderList[1], - _chartShader!); - } - - for (int k = 0; k < seriesRenderer._shadowPaths.length; k++) { - canvas.drawPath( - seriesRenderer._shadowPaths[k], seriesRenderer._shadowPaint); - } - - if (_chartShader != null && seriesRenderer._overFilledPaint != null) { - seriesRenderer._overFilledPaint!.shader = _chartShader; - } - for (int k = 0; k < seriesRenderer._overFilledPaths.length; k++) { - canvas.drawPath(seriesRenderer._overFilledPaths[k], - seriesRenderer._overFilledPaint!); - } - - if (seriesRenderer._renderList[0].strokeColor != null && - seriesRenderer._renderList[0].strokeWidth != null && - (seriesRenderer._renderList[0].strokeWidth > 0) == true) { - final Paint paint = Paint(); - paint.color = seriesRenderer._renderList[0].strokeColor; - paint.strokeWidth = seriesRenderer._renderList[0].strokeWidth; - paint.style = PaintingStyle.stroke; - for (int k = 0; k < seriesRenderer._renderPaths.length; k++) { - canvas.drawPath(seriesRenderer._renderPaths[k], paint); - } - } - } - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => isRepaint; -} - -/// Creates series renderer for RadialBar series -/// -class RadialBarSeriesRenderer extends CircularSeriesRenderer { - /// Calling the default constructor of RadialBarSeriesRenderer class. - /// - RadialBarSeriesRenderer() { - _shadowPaths = []; - _overFilledPaths = []; - } - - @override - late CircularSeries _series; - - late num _innerRadius; - late num _radius; - late Color _fillColor, _strokeColor; - late double _opacity, _strokeWidth; - late List _shadowPaths; - late List _overFilledPaths; - late Paint _shadowPaint; - Paint? _overFilledPaint; - - @override - Offset? _center; - - /// Method to find first visible point - int? _getFirstVisiblePointIndex(RadialBarSeriesRenderer seriesRenderer) { - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - if (seriesRenderer._renderPoints![i].isVisible) { - return i; - } - } - return null; - } - - /// Method for calculating animation for visible points on legend toggle - /// - void _calculateVisiblePointLegendToggleAnimation(ChartPoint point, - ChartPoint? _oldPoint, int i, num animationValue) { - if (!_oldPoint!.isVisible && point.isVisible) { - _radius = i == 0 - ? point.outerRadius! - : (point.innerRadius! + - (point.outerRadius! - point.innerRadius!) * animationValue); - _innerRadius = i == 0 - ? (point.outerRadius! - - (point.outerRadius! - point.innerRadius!) * animationValue) - : _innerRadius; - } else { - _radius = (point.outerRadius! > _oldPoint.outerRadius!) - ? _oldPoint.outerRadius! + - (point.outerRadius! - _oldPoint.outerRadius!) * animationValue - : _oldPoint.outerRadius! - - (_oldPoint.outerRadius! - point.outerRadius!) * animationValue; - _innerRadius = (point.innerRadius! > _oldPoint.innerRadius!) - ? _oldPoint.innerRadius! + - (point.innerRadius! - _oldPoint.innerRadius!) * animationValue - : _oldPoint.innerRadius! - - (_oldPoint.innerRadius! - point.innerRadius!) * animationValue; - } - } - - /// To draw data points of the radial bar series - /// - void _drawDataPoint( - ChartPoint point, - num? degree, - num pointStartAngle, - num? pointEndAngle, - RadialBarSeriesRenderer seriesRenderer, - bool hide, - num? oldRadius, - num? oldInnerRadius, - ChartPoint? _oldPoint, - num? oldStart, - num? oldEnd, - int i, - Canvas canvas, - int index, - SfCircularChart chart, - double actualDegree) { - late RadialBarSeries series; - if (seriesRenderer._series is RadialBarSeries) { - series = seriesRenderer._series as RadialBarSeries; - } - _drawPath( - canvas, - _StyleOptions( - fill: series.useSeriesColor ? point.fill : series.trackColor, - strokeWidth: series.trackBorderWidth, - strokeColor: series.trackBorderColor, - opacity: series.trackOpacity), - _getArcPath( - hide ? oldInnerRadius! : _innerRadius, - hide ? oldRadius! : _radius.toDouble(), - _center!, - 0, - 360 - 0.001, - 360 - 0.001, - chart, - true)); - if (_radius > 0 && degree != null && degree > 0) { - _renderRadialPoints( - point, - degree, - pointStartAngle, - pointEndAngle, - seriesRenderer, - hide, - oldRadius, - oldInnerRadius, - _oldPoint, - oldStart, - oldEnd, - i, - canvas, - index, - chart, - actualDegree); - } - } - - /// Method to render radial data points - /// - void _renderRadialPoints( - ChartPoint point, - num? degree, - num pointStartAngle, - num? pointEndAngle, - RadialBarSeriesRenderer seriesRenderer, - bool hide, - num? oldRadius, - num? oldInnerRadius, - ChartPoint? _oldPoint, - num? oldStart, - num? oldEnd, - int i, - Canvas canvas, - int index, - SfCircularChart chart, - double actualDegree) { - if (point.isVisible) { - final _Region pointRegion = _Region( - _degreesToRadians(point.startAngle!), - _degreesToRadians(point.endAngle!), - point.startAngle!, - point.endAngle!, - index, - i, - point.center, - _innerRadius, - point.outerRadius!); - seriesRenderer._pointRegions.add(pointRegion); - } - - final num angleDeviation = _findAngleDeviation( - hide ? oldInnerRadius! : _innerRadius, - hide ? oldRadius! : _radius, - 360); - final CornerStyle cornerStyle = _series.cornerStyle; - if (cornerStyle == CornerStyle.bothCurve || - cornerStyle == CornerStyle.startCurve) { - hide - ? oldStart = _oldPoint!.startAngle! + angleDeviation - : pointStartAngle += angleDeviation; - } - if (cornerStyle == CornerStyle.bothCurve || - cornerStyle == CornerStyle.endCurve) { - hide - ? oldEnd = _oldPoint!.endAngle! - angleDeviation - : pointEndAngle = pointEndAngle! - angleDeviation; - } - final _StyleOptions? style = - seriesRenderer._selectPoint(i, seriesRenderer, chart, point); - _fillColor = style != null && style.fill != null - ? style.fill! - : (point.fill != Colors.transparent - ? _series._renderer!.getPointColor( - seriesRenderer, point, i, index, point.fill, _series.opacity)! - : point.fill); - _strokeColor = style != null && style.strokeColor != null - ? style.strokeColor! - : _series._renderer!.getPointStrokeColor( - seriesRenderer, point, i, index, point.strokeColor); - _strokeWidth = style != null && style.strokeWidth != null - ? style.strokeWidth!.toDouble() - : _series._renderer! - .getPointStrokeWidth( - seriesRenderer, point, i, index, point.strokeWidth) - .toDouble(); - _opacity = style != null && style.opacity != null - ? style.opacity!.toDouble() - : _series._renderer! - .getOpacity(seriesRenderer, point, i, index, _series.opacity); - seriesRenderer._innerRadialradius = - !point.isVisible || (seriesRenderer._innerRadialradius == null) - ? _innerRadius - : seriesRenderer._innerRadialradius; - seriesRenderer._renderList.clear(); - - _drawRadialBarPath( - canvas, - point, - chart, - seriesRenderer, - hide, - pointStartAngle, - pointEndAngle, - oldRadius, - oldInnerRadius, - oldStart, - oldEnd, - degree, - actualDegree); - } - - /// Method to draw the radial bar series path - /// - void _drawRadialBarPath( - Canvas canvas, - ChartPoint point, - SfCircularChart chart, - RadialBarSeriesRenderer seriesRenderer, - bool hide, - num pointStartAngle, - num? pointEndAngle, - num? oldRadius, - num? oldInnerRadius, - num? oldStart, - num? oldEnd, - num? degree, - double actualDegree) { - Path path; - if (degree! > 360) { - path = _getRoundedCornerArcPath( - hide ? oldInnerRadius! : _innerRadius, - hide ? oldRadius! : _radius, - _center!, - 0, - 360 - 0.001, - 360 - 0.001, - _series.cornerStyle, - point); - final double innerRadius = - hide ? oldInnerRadius!.toDouble() : _innerRadius.toDouble(); - final double outerRadius = - hide ? oldRadius!.toDouble() : _radius.toDouble(); - final double startAngle = - hide ? oldStart!.toDouble() : pointStartAngle.toDouble(); - final double endAngle = - hide ? oldEnd!.toDouble() : pointEndAngle!.toDouble(); - path.arcTo( - Rect.fromCircle(center: _center!, radius: outerRadius.toDouble()), - _degreesToRadians(startAngle).toDouble(), - _degreesToRadians(endAngle - startAngle).toDouble(), - true); - path.arcTo( - Rect.fromCircle(center: _center!, radius: innerRadius.toDouble()), - _degreesToRadians(endAngle.toDouble()).toDouble(), - (_degreesToRadians(startAngle.toDouble()) - - _degreesToRadians(endAngle.toDouble())) - .toDouble(), - false); - } else { - path = _getRoundedCornerArcPath( - hide ? oldInnerRadius! : _innerRadius, - hide ? oldRadius! : _radius, - _center!, - hide ? oldStart! : pointStartAngle, - hide ? oldEnd! : pointEndAngle!, - degree, - _series.cornerStyle, - point); - } - - seriesRenderer._renderPaths.add(path); - - if (chart.onCreateShader != null && point.shader == null) { - point._pathRect = Rect.fromCircle( - center: _center!, - radius: _radius.toDouble(), - ); - Rect innerRect; - innerRect = Rect.fromCircle( - center: _center!, - radius: seriesRenderer._innerRadialradius!.toDouble(), - ); - seriesRenderer._renderList.add(_StyleOptions( - fill: _fillColor, - strokeWidth: - _chartState._renderingDetails.animateCompleted ? _strokeWidth : 0, - strokeColor: _strokeColor, - opacity: _opacity)); - seriesRenderer._renderList.add(point._pathRect); - seriesRenderer._renderList.add(innerRect); - } else { - if (hide - ? (((oldEnd! - oldStart!) > 0) && (oldRadius != oldInnerRadius)) - : ((pointEndAngle! - pointStartAngle) > 0)) { - _drawPath( - canvas, - _StyleOptions( - fill: _fillColor, - strokeWidth: _chartState._renderingDetails.animateCompleted - ? _strokeWidth - : 0, - strokeColor: _strokeColor, - opacity: _opacity), - path, - point._pathRect, - point.shader); - // ignore: unnecessary_null_comparison - if (point.shader != null && - // ignore: unnecessary_null_comparison - _strokeColor != null && - // ignore: unnecessary_null_comparison - _strokeWidth != null && - _strokeWidth > 0 && - _chartState._renderingDetails.animateCompleted) { - final Paint paint = Paint(); - paint.color = _strokeColor; - paint.strokeWidth = _strokeWidth; - paint.style = PaintingStyle.stroke; - canvas.drawPath(path, paint); - } - } - } - - final num? angle = hide ? oldEnd : pointEndAngle; - final num? startAngle = hide ? oldStart : pointStartAngle; - if (actualDegree > 360 && - angle != null && - startAngle != null && - angle >= startAngle + 180) { - _applyShadow(hide, angle, actualDegree, canvas, chart, point, - oldInnerRadius, oldRadius); - } - } - - /// Method to apply shadow at segment's end - void _applyShadow( - bool hide, - num? pointEndAngle, - double actualDegree, - Canvas canvas, - SfCircularChart chart, - ChartPoint point, - num? oldInnerRadius, - num? oldRadius) { - if (pointEndAngle != null && actualDegree > 360) { - final double innerRadius = - hide ? oldInnerRadius!.toDouble() : _innerRadius.toDouble(); - final double outerRadius = - hide ? oldRadius!.toDouble() : _radius.toDouble(); - final double radius = (innerRadius - outerRadius).abs() / 2; - final Offset? midPoint = _degreeToPoint( - pointEndAngle, (innerRadius + outerRadius) / 2, _center!); - if (radius > 0) { - double strokeWidth = radius * 0.2; - strokeWidth = strokeWidth < 3 ? 3 : (strokeWidth > 5 ? 5 : strokeWidth); - _shadowPaint = Paint() - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..maskFilter = - MaskFilter.blur(BlurStyle.normal, _getSigmaFromRadius(3)); - - _overFilledPaint = Paint()..color = _fillColor; - if (point.shader != null) { - _overFilledPaint!.shader = point.shader; - } - - if (_series.cornerStyle == CornerStyle.endCurve || - _series.cornerStyle == CornerStyle.bothCurve) { - pointEndAngle = - (pointEndAngle > 360 ? pointEndAngle : (pointEndAngle - 360)) + - 11.5; - final Path path = Path() - ..addArc( - Rect.fromCircle( - center: midPoint!, radius: radius - (radius * 0.05)), - _degreesToRadians(pointEndAngle + 22.5).toDouble(), - _degreesToRadians(118.125).toDouble()); - final Path overFilledPath = Path() - ..addArc( - Rect.fromCircle(center: midPoint, radius: radius), - _degreesToRadians(pointEndAngle - 20).toDouble(), - _degreesToRadians(225).toDouble()); - if (chart.onCreateShader != null && point.shader == null) { - _shadowPaths.add(path); - _overFilledPaths.add(overFilledPath); - } else { - canvas.drawPath(path, _shadowPaint); - canvas.drawPath(overFilledPath, _overFilledPaint!); - } - } else if (_series.cornerStyle == CornerStyle.bothFlat || - _series.cornerStyle == CornerStyle.startCurve) { - _overFilledPaint! - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth - ..color = _fillColor; - final Offset? startPoint = _degreeToPoint( - pointEndAngle, outerRadius - (outerRadius * 0.025), _center!); - - final Offset? endPoint = _degreeToPoint( - pointEndAngle, innerRadius + (innerRadius * 0.025), _center!); - - final Offset? overFilledStartPoint = - _degreeToPoint(pointEndAngle - 2, outerRadius, _center!); - final Offset? overFilledEndPoint = - _degreeToPoint(pointEndAngle - 2, innerRadius, _center!); - if (chart.onCreateShader != null && point.shader == null) { - final Path path = Path() - ..moveTo(startPoint!.dx, startPoint.dy) - ..lineTo(endPoint!.dx, endPoint.dy); - path.close(); - final Path overFilledPath = Path() - ..moveTo(overFilledStartPoint!.dx, overFilledStartPoint.dy) - ..lineTo(overFilledEndPoint!.dx, overFilledEndPoint.dy); - path.close(); - _shadowPaths.add(path); - _overFilledPaths.add(overFilledPath); - } else { - canvas.drawLine(startPoint!, endPoint!, _shadowPaint); - canvas.drawLine( - overFilledStartPoint!, overFilledEndPoint!, _overFilledPaint!); - } - } - } - } - } - - /// Method to convert the radius to sigma - double _getSigmaFromRadius(double radius) { - return radius * 0.57735 + 0.5; - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_base.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_base.dart new file mode 100644 index 000000000..a36218b2e --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_base.dart @@ -0,0 +1,25 @@ +import '../../chart/chart_series/series.dart'; + +/// Creates a series renderer for Circular series +class CircularSeriesRenderer extends ChartSeriesRenderer { + /// Creates an instance of circular series renderer + CircularSeriesRenderer(); +} + +///Creates series renderer for Doughnut series +class PieSeriesRenderer extends CircularSeriesRenderer { + /// Calling the default constructor of PieSeriesRenderer class. + PieSeriesRenderer(); +} + +/// Creates series renderer for Doughnut series +class DoughnutSeriesRenderer extends CircularSeriesRenderer { + /// Calling the default constructor of DoughnutSeriesRenderer class. + DoughnutSeriesRenderer(); +} + +/// Creates series renderer for RadialBar series +class RadialBarSeriesRenderer extends CircularSeriesRenderer { + /// Calling the default constructor of RadialBarSeriesRenderer class. + RadialBarSeriesRenderer(); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_extension.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_extension.dart new file mode 100644 index 000000000..222a88103 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/renderer/renderer_extension.dart @@ -0,0 +1,1001 @@ +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../chart/common/data_label.dart'; +import '../../common/event_args.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../base/circular_base.dart'; +import '../base/circular_state_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import 'chart_point.dart'; +import 'circular_series.dart'; +import 'circular_series_controller.dart'; +import 'common.dart'; +import 'radial_bar_series.dart'; +import 'renderer_base.dart'; + +/// Creates a series renderer for Circular series +class CircularSeriesRendererExtension implements CircularSeriesRenderer { + /// Specifies the circular series + late CircularSeries series; + + /// Specifies the chart series renderer + late ChartSeriesRender renderer; + + /// Specifies the series type + late String seriesType; + + /// Specifies the list of data points + late List> dataPoints; + + /// Specifies whether to repaint the series + bool needsRepaint = true; + + /// Specifies the list of rendering points + List>? renderPoints; + + /// Specifies the list of old render points + List>? oldRenderPoints; + + /// Specifies the map collection that holds all the values for rendering + /// the segment + Map segmentRenderingValues = {}; + + /// Specifies the value of center + Offset? center; + + /// Specifies the value of point region + late List pointRegions; + + /// Specifies the value of rect + // ignore:unused_field + late Rect rect; + + /// Path saved for radial bar series + List renderPaths = []; + + /// Specifies the value of render list + List renderList = []; + + /// Specifies the value of inner radial radius + num? innerRadialradius; + + /// Specifies the value of selection args + SelectionArgs? selectionArgs; + + /// Determines whether there is a need for animation + late bool needsAnimation; + + ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods + ///in this before we must get the ChartSeriesController onRendererCreated event. + CircularSeriesController? controller; + + /// Specifies the circular state properties + late CircularStateProperties stateProperties; + + /// Repaint notifier for series + late ValueNotifier repaintNotifier; + + /// Specifies the data label setting renderer + late DataLabelSettingsRenderer dataLabelSettingsRenderer; + + /// specifeis the selection behavior renderer + late SelectionBehaviorRenderer selectionBehaviorRenderer; + + /// Specifies the selection behavior + dynamic selectionBehavior; + + /// Check whether the selection is enabled + // ignore: prefer_final_fields + bool isSelectionEnable = false; + + /// To set style properties for selected points + StyleOptions? selectPoint( + int currentPointIndex, + CircularSeriesRendererExtension seriesRenderer, + SfCircularChart chart, + ChartPoint? point) { + StyleOptions? pointStyle; + final dynamic selection = series.selectionBehavior; + if (selection.enable == true) { + if (stateProperties.renderingDetails.selectionData.isNotEmpty) { + int selectionIndex; + for (int i = 0; + i < stateProperties.renderingDetails.selectionData.length; + i++) { + selectionIndex = stateProperties.renderingDetails.selectionData[i]; + if (currentPointIndex == selectionIndex) { + pointStyle = StyleOptions( + fill: seriesRenderer.selectionArgs != null + ? seriesRenderer.selectionArgs!.selectedColor + : selection.selectedColor, + strokeWidth: seriesRenderer.selectionArgs != null + ? seriesRenderer.selectionArgs!.selectedBorderWidth + : selection.selectedBorderWidth, + strokeColor: seriesRenderer.selectionArgs != null + ? seriesRenderer.selectionArgs!.selectedBorderColor + : selection.selectedBorderColor, + opacity: selection.selectedOpacity); + break; + } else if (i == + stateProperties.renderingDetails.selectionData.length - 1) { + pointStyle = StyleOptions( + fill: seriesRenderer.selectionArgs != null + ? seriesRenderer.selectionArgs!.unselectedColor + : selection.unselectedColor, + strokeWidth: seriesRenderer.selectionArgs != null + ? selectionArgs!.unselectedBorderWidth + : selection.unselectedBorderWidth, + strokeColor: seriesRenderer.selectionArgs != null + ? seriesRenderer.selectionArgs!.unselectedBorderColor + : selection.unselectedBorderColor, + opacity: selection.unselectedOpacity); + } + } + } + } + return pointStyle; + } + + /// To calculate point start and end angle + num? circularRenderPoint( + SfCircularChart chart, + CircularSeriesRendererExtension seriesRenderer, + ChartPoint point, + num? pointStartAngle, + num? innerRadius, + num? outerRadius, + Canvas canvas, + int seriesIndex, + int pointIndex, + num animationDegreeValue, + num animationRadiusValue, + bool isAnyPointSelect, + ChartPoint? oldPoint, + List?>? oldPointList) { + final bool isDynamicUpdate = oldPoint != null; + final num? oldStartAngle = oldPoint?.startAngle; + final num? oldEndAngle = oldPoint?.endAngle; + num? degree, pointEndAngle; + + /// below lines for dynamic dataSource changes + if (isDynamicUpdate) { + if (!oldPoint.isVisible && point.isVisible) { + final num val = point.startAngle == + seriesRenderer.segmentRenderingValues['start']! + ? seriesRenderer.segmentRenderingValues['start']! + : oldPointList![ + getVisiblePointIndex(oldPointList, 'before', pointIndex)!]! + .endAngle!; + pointStartAngle = + val - (val - point.startAngle!) * animationDegreeValue; + pointEndAngle = val + (point.endAngle! - val) * animationDegreeValue; + degree = pointEndAngle - pointStartAngle; + } else if (oldPoint.isVisible && !point.isVisible) { + if (oldPoint.startAngle!.round() == + seriesRenderer.segmentRenderingValues['start'] && + (oldPoint.endAngle!.round() == + seriesRenderer.segmentRenderingValues['end'] || + oldPoint.endAngle!.round() == + 360 + seriesRenderer.segmentRenderingValues['end']!)) { + pointStartAngle = oldPoint.startAngle!; + pointEndAngle = oldPoint.endAngle! - + (oldPoint.endAngle! - oldPoint.startAngle!) * + animationDegreeValue; + } else if (oldPoint.startAngle == oldPoint.endAngle) { + pointStartAngle = pointEndAngle = oldPoint.startAngle!; + } else { + pointStartAngle = oldPoint.startAngle! - + (oldPoint.startAngle! - + (oldPoint.startAngle == + seriesRenderer.segmentRenderingValues['start']! + ? seriesRenderer.segmentRenderingValues['start']! + : seriesRenderer + .renderPoints![getVisiblePointIndex( + seriesRenderer.renderPoints!, + 'before', + pointIndex)!] + .endAngle!)) * + animationDegreeValue; + pointEndAngle = oldPoint.endAngle! - + (oldPoint.endAngle! - + ((oldPoint.endAngle!.round() == + seriesRenderer + .segmentRenderingValues['end'] || + oldPoint.endAngle!.round() == + 360 + + seriesRenderer + .segmentRenderingValues['end']!) + ? oldPoint.endAngle! + : seriesRenderer + .renderPoints![getVisiblePointIndex( + seriesRenderer.renderPoints!, + 'after', + pointIndex)!] + .startAngle!)) * + animationDegreeValue; + } + degree = pointEndAngle - pointStartAngle; + } else if (point.isVisible && oldPoint.isVisible) { + pointStartAngle = (point.startAngle! > oldStartAngle!) + ? oldStartAngle + + ((point.startAngle! - oldStartAngle) * animationDegreeValue) + : oldStartAngle - + ((oldStartAngle - point.startAngle!) * animationDegreeValue); + pointEndAngle = (point.endAngle! > oldEndAngle!) + ? oldEndAngle + + ((point.endAngle! - oldEndAngle) * animationDegreeValue) + : oldEndAngle - + ((oldEndAngle - point.endAngle!) * animationDegreeValue); + degree = pointEndAngle - pointStartAngle; + } + } else if (point.isVisible) { + degree = animationDegreeValue * point.degree!; + pointEndAngle = pointStartAngle! + degree; + } + outerRadius = stateProperties.renderingDetails.initialRender! + ? animationRadiusValue * outerRadius! + : outerRadius; + _calculatePath( + pointIndex, + seriesIndex, + chart, + seriesRenderer, + point, + oldPoint, + canvas, + degree, + innerRadius, + outerRadius, + pointStartAngle, + pointEndAngle, + isDynamicUpdate); + return pointEndAngle; + } + + /// calculating the data point path + void _calculatePath( + int pointIndex, + int seriesIndex, + SfCircularChart chart, + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + ChartPoint? oldPoint, + Canvas canvas, + num? degree, + num? innerRadius, + num? outerRadius, + num? pointStartAngle, + num? pointEndAngle, + bool isDynamicUpdate) { + Path? renderPath; + final CornerStyle cornerStyle = series.cornerStyle; + late num actualStartAngle, actualEndAngle; + if (!isDynamicUpdate || + (isDynamicUpdate && + ((oldPoint!.isVisible && point!.isVisible) || + (oldPoint.isVisible && !point!.isVisible) || + (!oldPoint.isVisible && point!.isVisible)))) { + innerRadius = innerRadius ?? oldPoint!.innerRadius; + outerRadius = outerRadius ?? oldPoint!.outerRadius; + if (cornerStyle != CornerStyle.bothFlat) { + final num angleDeviation = + findAngleDeviation(innerRadius!, outerRadius!, 360); + actualStartAngle = (cornerStyle == CornerStyle.startCurve || + cornerStyle == CornerStyle.bothCurve) + ? (pointStartAngle! + angleDeviation) + : pointStartAngle!; + actualEndAngle = (cornerStyle == CornerStyle.endCurve || + cornerStyle == CornerStyle.bothCurve) + ? (pointEndAngle! - angleDeviation) + : pointEndAngle!; + } + renderPath = Path(); + renderPath = (cornerStyle == CornerStyle.startCurve || + cornerStyle == CornerStyle.endCurve || + cornerStyle == CornerStyle.bothCurve) + ? getRoundedCornerArcPath( + innerRadius!, + outerRadius!, + point!.center ?? oldPoint!.center, + actualStartAngle, + actualEndAngle, + degree, + cornerStyle, + point) + : getArcPath( + innerRadius!, + outerRadius!, + point!.center ?? oldPoint!.center!, + pointStartAngle, + pointEndAngle, + degree, + chart, + stateProperties.renderingDetails.animateCompleted); + } + drawDataPoints(pointIndex, seriesIndex, chart, seriesRenderer, point, + canvas, renderPath, degree, innerRadius); + } + + ///draw slice path + void drawDataPoints( + int pointIndex, + int seriesIndex, + SfCircularChart chart, + CircularSeriesRendererExtension seriesRenderer, + ChartPoint? point, + Canvas canvas, + Path? renderPath, + num? degree, + num? innerRadius) { + if (point != null && point.isVisible) { + final Region pointRegion = Region( + degreesToRadians(point.startAngle!), + degreesToRadians(point.endAngle!), + point.startAngle!, + point.endAngle!, + seriesIndex, + pointIndex, + point.center, + innerRadius, + point.outerRadius!); + seriesRenderer.pointRegions.add(pointRegion); + } + final StyleOptions? style = + selectPoint(pointIndex, seriesRenderer, chart, point); + + final Color? fillColor = style != null && style.fill != null + ? style.fill + : (point != null && point.fill != Colors.transparent + ? seriesRenderer.renderer.getPointColor( + seriesRenderer, + point, + pointIndex, + seriesIndex, + point.fill, + seriesRenderer.series.opacity) + : point!.fill); + + final Color? strokeColor = style != null && style.strokeColor != null + ? style.strokeColor + : seriesRenderer.renderer.getPointStrokeColor( + seriesRenderer, point, pointIndex, seriesIndex, point!.strokeColor); + + final num? strokeWidth = style != null && style.strokeWidth != null + ? style.strokeWidth + : seriesRenderer.renderer.getPointStrokeWidth( + seriesRenderer, point, pointIndex, seriesIndex, point!.strokeWidth); + + assert(seriesRenderer.series.opacity >= 0, + 'The opacity value will not accept negative numbers.'); + assert(seriesRenderer.series.opacity <= 1, + 'The opacity value must be less than 1.'); + final double? opacity = style != null && style.opacity != null + ? style.opacity + : seriesRenderer.renderer.getOpacity(seriesRenderer, point, pointIndex, + seriesIndex, seriesRenderer.series.opacity); + + Shader? _renderModeShader; + + if (chart.series[0].pointRenderMode == PointRenderMode.gradient && + point?.shader == null) { + final List colorsList = []; + final List stopsList = []; + num initStops = 0; + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + point = seriesRenderer.renderPoints![i]; + if (point.isVisible) { + colorsList.add(point.fill); + if (stopsList.isEmpty) { + initStops = (point.y! / segmentRenderingValues['sumOfPoints']!) / 4; + stopsList.add( + point.y! / segmentRenderingValues['sumOfPoints']! - initStops); + } else { + if (stopsList.length == 1) { + stopsList.add((point.y! / segmentRenderingValues['sumOfPoints']! + + stopsList.last) + + initStops / 1.5); + } else { + stopsList.add(point.y! / segmentRenderingValues['sumOfPoints']! + + stopsList.last); + } + } + } + } + + _renderModeShader = dart_ui.Gradient.sweep( + center!, + colorsList, + stopsList, + TileMode.clamp, + degreeToRadian(chart.series[0].startAngle), + degreeToRadian(chart.series[0].endAngle), + resolveTransform( + Rect.fromCircle( + center: center!, + radius: point!.outerRadius!.toDouble(), + ), + TextDirection.ltr)); + } + + if (renderPath != null && degree! > 0) { + if (seriesRenderer is DoughnutSeriesRenderer) { + seriesRenderer.innerRadialradius = + !point!.isVisible || (seriesRenderer.innerRadialradius == null) + ? innerRadius + : seriesRenderer.innerRadialradius; + } + if (point != null && + (point.isVisible || + (!point.isVisible && + point.index == seriesRenderer.dataPoints.length - 1 && + chart.onCreateShader != null))) { + PointHelper.setPathRect( + point, + Rect.fromCircle( + center: center!, + radius: point.outerRadius!.toDouble(), + )); + } + seriesRenderer.renderPaths.add(renderPath); + if (chart.onCreateShader != null && + point != null && + (point.isVisible || + (!point.isVisible && + point.index == seriesRenderer.dataPoints.length - 1 && + chart.onCreateShader != null)) && + point.shader == null) { + Rect? innerRect; + if (seriesRenderer is DoughnutSeriesRenderer && + seriesRenderer.innerRadialradius != null) { + innerRect = Rect.fromCircle( + center: center!, + radius: seriesRenderer.innerRadialradius!.toDouble(), + ); + } else { + innerRect = null; + } + if (point.isVisible || + (!point.isVisible && + point.index == seriesRenderer.dataPoints.length - 1 && + chart.onCreateShader != null)) { + renderList.clear(); + seriesRenderer.renderList.add(StyleOptions( + fill: fillColor!, + strokeWidth: stateProperties.renderingDetails.animateCompleted + ? strokeWidth! + : 0, + strokeColor: strokeColor!, + opacity: opacity)); + seriesRenderer.renderList.add(PointHelper.getPathRect(point)); + seriesRenderer.renderList.add(innerRect); + } + } else { + drawPath( + canvas, + StyleOptions( + fill: fillColor!, + strokeWidth: stateProperties.renderingDetails.animateCompleted + ? strokeWidth! + : 0, + strokeColor: strokeColor!, + opacity: opacity), + renderPath, + PointHelper.getPathRect(point!), + point.shader ?? _renderModeShader); + // ignore: unnecessary_null_comparison + if (point != null && + (_renderModeShader != null || point.shader != null)) { + // ignore: unnecessary_null_comparison + if (strokeColor != null && + strokeWidth != null && + strokeWidth > 0 && + stateProperties.renderingDetails.animateCompleted) { + final Paint paint = Paint(); + paint.color = strokeColor; + paint.strokeWidth = strokeWidth.toDouble(); + paint.style = PaintingStyle.stroke; + canvas.drawPath(renderPath, paint); + } + } + } + } + } +} + +/// Creates series renderer for Pie series +class PieSeriesRendererExtension extends PieSeriesRenderer + with CircularSeriesRendererExtension { + /// Calling the default constructor of PieSeriesRenderer class. + PieSeriesRendererExtension() { + seriesType = 'pie'; + } + + @override + late CircularSeries series; + + /// Specifies the chart point info + ChartPoint? point; +} + +/// Creates series renderer for Doughnut series +class DoughnutSeriesRendererExtension extends DoughnutSeriesRenderer + with CircularSeriesRendererExtension { + /// Calling the default constructor of DoughnutSeriesRenderer class. + DoughnutSeriesRendererExtension() { + seriesType = 'doughnut'; + } + + /// stores the series of the corresponding series for renderer + @override + late CircularSeries series; + + /// Specifies the inner radius value + late num innerRadius; + + /// Specifies the radius value + late num radius; +} + +/// Creates series renderer for RadialBar series +/// +class RadialBarSeriesRendererExtension extends RadialBarSeriesRenderer + with CircularSeriesRendererExtension { + /// Calling the default constructor of RadialBarSeriesRenderer class. + RadialBarSeriesRendererExtension() { + shadowPaths = []; + overFilledPaths = []; + seriesType = 'radialbar'; + } + + @override + late CircularSeries series; + + /// Specifies the inner radius value + late num innerRadius; + + /// Specifies the radius value + late num radius; + + /// Specifies the value of fill color and stroke color + late Color fillColor, strokeColor; + + /// Specifies the value of opacity and stroke width + late double opacity, strokeWidth; + + /// Holds the value of shadow path + late List shadowPaths; + + /// Holds the value of over filled path + late List overFilledPaths; + + /// Represents the value of shadow paint + late Paint shadowPaint; + + /// Represents the value of over filles paint + Paint? overFilledPaint; + + @override + Offset? center; + + /// Method to find first visible point + int? getFirstVisiblePointIndex( + RadialBarSeriesRendererExtension seriesRenderer) { + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + if (seriesRenderer.renderPoints![i].isVisible) { + return i; + } + } + return null; + } + + /// Method for calculating animation for visible points on legend toggle + /// + void calculateVisiblePointLegendToggleAnimation(ChartPoint point, + ChartPoint? _oldPoint, int i, num animationValue) { + if (!_oldPoint!.isVisible && point.isVisible) { + radius = i == 0 + ? point.outerRadius! + : (point.innerRadius! + + (point.outerRadius! - point.innerRadius!) * animationValue); + innerRadius = i == 0 + ? (point.outerRadius! - + (point.outerRadius! - point.innerRadius!) * animationValue) + : innerRadius; + } else { + radius = (point.outerRadius! > _oldPoint.outerRadius!) + ? _oldPoint.outerRadius! + + (point.outerRadius! - _oldPoint.outerRadius!) * animationValue + : _oldPoint.outerRadius! - + (_oldPoint.outerRadius! - point.outerRadius!) * animationValue; + innerRadius = (point.innerRadius! > _oldPoint.innerRadius!) + ? _oldPoint.innerRadius! + + (point.innerRadius! - _oldPoint.innerRadius!) * animationValue + : _oldPoint.innerRadius! - + (_oldPoint.innerRadius! - point.innerRadius!) * animationValue; + } + } + + /// To draw data points of the radial bar series + /// + void drawDataPoint( + ChartPoint point, + num? degree, + num pointStartAngle, + num? pointEndAngle, + RadialBarSeriesRendererExtension seriesRenderer, + bool hide, + num? oldRadius, + num? oldInnerRadius, + ChartPoint? _oldPoint, + num? oldStart, + num? oldEnd, + int i, + Canvas canvas, + int index, + SfCircularChart chart, + double actualDegree) { + late RadialBarSeries series; + if (seriesRenderer.series is RadialBarSeries) { + series = seriesRenderer.series as RadialBarSeries; + } + drawPath( + canvas, + StyleOptions( + fill: series.useSeriesColor ? point.fill : series.trackColor, + strokeWidth: series.trackBorderWidth, + strokeColor: series.trackBorderColor, + opacity: series.trackOpacity), + getArcPath( + hide ? oldInnerRadius! : innerRadius, + hide ? oldRadius! : radius.toDouble(), + center!, + 0, + 360 - 0.001, + 360 - 0.001, + chart, + true)); + if (radius > 0 && degree != null && degree > 0) { + _renderRadialPoints( + point, + degree, + pointStartAngle, + pointEndAngle, + seriesRenderer, + hide, + oldRadius, + oldInnerRadius, + _oldPoint, + oldStart, + oldEnd, + i, + canvas, + index, + chart, + actualDegree); + } + } + + /// Method to render radial data points + /// + void _renderRadialPoints( + ChartPoint point, + num? degree, + num pointStartAngle, + num? pointEndAngle, + RadialBarSeriesRendererExtension seriesRenderer, + bool hide, + num? oldRadius, + num? oldInnerRadius, + ChartPoint? _oldPoint, + num? oldStart, + num? oldEnd, + int i, + Canvas canvas, + int index, + SfCircularChart chart, + double actualDegree) { + if (point.isVisible) { + final Region pointRegion = Region( + degreesToRadians(point.startAngle!), + degreesToRadians(point.endAngle!), + point.startAngle!, + point.endAngle!, + index, + i, + point.center, + innerRadius, + point.outerRadius!); + seriesRenderer.pointRegions.add(pointRegion); + } + + final num angleDeviation = findAngleDeviation( + hide ? oldInnerRadius! : innerRadius, hide ? oldRadius! : radius, 360); + final CornerStyle cornerStyle = series.cornerStyle; + if (cornerStyle == CornerStyle.bothCurve || + cornerStyle == CornerStyle.startCurve) { + hide + ? oldStart = _oldPoint!.startAngle! + angleDeviation + : pointStartAngle += angleDeviation; + } + if (cornerStyle == CornerStyle.bothCurve || + cornerStyle == CornerStyle.endCurve) { + hide + ? oldEnd = _oldPoint!.endAngle! - angleDeviation + : pointEndAngle = pointEndAngle! - angleDeviation; + } + final StyleOptions? style = + seriesRenderer.selectPoint(i, seriesRenderer, chart, point); + fillColor = style != null && style.fill != null + ? style.fill! + : (point.fill != Colors.transparent + ? seriesRenderer.renderer.getPointColor( + seriesRenderer, point, i, index, point.fill, series.opacity)! + : point.fill); + strokeColor = style != null && style.strokeColor != null + ? style.strokeColor! + : seriesRenderer.renderer.getPointStrokeColor( + seriesRenderer, point, i, index, point.strokeColor); + strokeWidth = style != null && style.strokeWidth != null + ? style.strokeWidth!.toDouble() + : seriesRenderer.renderer + .getPointStrokeWidth( + seriesRenderer, point, i, index, point.strokeWidth) + .toDouble(); + opacity = style != null && style.opacity != null + ? style.opacity!.toDouble() + : seriesRenderer.renderer + .getOpacity(seriesRenderer, point, i, index, series.opacity); + seriesRenderer.innerRadialradius = + !point.isVisible || (seriesRenderer.innerRadialradius == null) + ? innerRadius + : seriesRenderer.innerRadialradius; + seriesRenderer.renderList.clear(); + + _drawRadialBarPath( + canvas, + point, + chart, + seriesRenderer, + hide, + pointStartAngle, + pointEndAngle, + oldRadius, + oldInnerRadius, + oldStart, + oldEnd, + degree, + actualDegree); + } + + /// Method to draw the radial bar series path + /// + void _drawRadialBarPath( + Canvas canvas, + ChartPoint point, + SfCircularChart chart, + RadialBarSeriesRendererExtension seriesRenderer, + bool hide, + num pointStartAngle, + num? pointEndAngle, + num? oldRadius, + num? oldInnerRadius, + num? oldStart, + num? oldEnd, + num? degree, + double actualDegree) { + Path path; + if (degree! > 360) { + path = getRoundedCornerArcPath( + hide ? oldInnerRadius! : innerRadius, + hide ? oldRadius! : radius, + center!, + 0, + 360 - 0.001, + 360 - 0.001, + series.cornerStyle, + point); + final double currentInnerRadius = + hide ? oldInnerRadius!.toDouble() : innerRadius.toDouble(); + final double outerRadius = + hide ? oldRadius!.toDouble() : radius.toDouble(); + final double startAngle = + hide ? oldStart!.toDouble() : pointStartAngle.toDouble(); + final double endAngle = + hide ? oldEnd!.toDouble() : pointEndAngle!.toDouble(); + path.arcTo( + Rect.fromCircle(center: center!, radius: outerRadius.toDouble()), + degreesToRadians(startAngle).toDouble(), + degreesToRadians(endAngle - startAngle).toDouble(), + true); + path.arcTo( + Rect.fromCircle( + center: center!, radius: currentInnerRadius.toDouble()), + degreesToRadians(endAngle.toDouble()).toDouble(), + (degreesToRadians(startAngle.toDouble()) - + degreesToRadians(endAngle.toDouble())) + .toDouble(), + false); + } else { + path = getRoundedCornerArcPath( + hide ? oldInnerRadius! : innerRadius, + hide ? oldRadius! : radius, + center!, + hide ? oldStart! : pointStartAngle, + hide ? oldEnd! : pointEndAngle!, + degree, + series.cornerStyle, + point); + } + + seriesRenderer.renderPaths.add(path); + + if (chart.onCreateShader != null && point.shader == null) { + PointHelper.setPathRect( + point, + Rect.fromCircle( + center: center!, + radius: radius.toDouble(), + )); + Rect innerRect; + innerRect = Rect.fromCircle( + center: center!, + radius: seriesRenderer.innerRadialradius!.toDouble(), + ); + seriesRenderer.renderList.add(StyleOptions( + fill: fillColor, + strokeWidth: stateProperties.renderingDetails.animateCompleted + ? strokeWidth + : 0, + strokeColor: strokeColor, + opacity: opacity)); + seriesRenderer.renderList.add(PointHelper.getPathRect(point)); + seriesRenderer.renderList.add(innerRect); + } else { + if (hide + ? (((oldEnd! - oldStart!) > 0) && (oldRadius != oldInnerRadius)) + : ((pointEndAngle! - pointStartAngle) > 0)) { + drawPath( + canvas, + StyleOptions( + fill: fillColor, + strokeWidth: stateProperties.renderingDetails.animateCompleted + ? strokeWidth + : 0, + strokeColor: strokeColor, + opacity: opacity), + path, + PointHelper.getPathRect(point), + point.shader); + // ignore: unnecessary_null_comparison + if (point.shader != null && + // ignore: unnecessary_null_comparison + strokeColor != null && + // ignore: unnecessary_null_comparison + strokeWidth != null && + strokeWidth > 0 && + stateProperties.renderingDetails.animateCompleted) { + final Paint paint = Paint(); + paint.color = strokeColor; + paint.strokeWidth = strokeWidth; + paint.style = PaintingStyle.stroke; + canvas.drawPath(path, paint); + } + } + } + + final num? angle = hide ? oldEnd : pointEndAngle; + final num? startAngle = hide ? oldStart : pointStartAngle; + if (actualDegree > 360 && + angle != null && + startAngle != null && + angle >= startAngle + 180) { + _applyShadow(hide, angle, actualDegree, canvas, chart, point, + oldInnerRadius, oldRadius); + } + } + + /// Method to apply shadow at segment's end + void _applyShadow( + bool hide, + num? pointEndAngle, + double actualDegree, + Canvas canvas, + SfCircularChart chart, + ChartPoint point, + num? oldInnerRadius, + num? oldRadius) { + if (pointEndAngle != null && actualDegree > 360) { + final double currentInnerRadius = + hide ? oldInnerRadius!.toDouble() : innerRadius.toDouble(); + final double outerRadius = + hide ? oldRadius!.toDouble() : radius.toDouble(); + final double actualRadius = (currentInnerRadius - outerRadius).abs() / 2; + final Offset? midPoint = degreeToPoint( + pointEndAngle, (currentInnerRadius + outerRadius) / 2, center!); + if (actualRadius > 0) { + double strokeWidth = actualRadius * 0.2; + strokeWidth = strokeWidth < 3 ? 3 : (strokeWidth > 5 ? 5 : strokeWidth); + shadowPaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth + ..maskFilter = + MaskFilter.blur(BlurStyle.normal, _getSigmaFromRadius(3)); + + overFilledPaint = Paint()..color = fillColor; + if (point.shader != null) { + overFilledPaint!.shader = point.shader; + } + + if (series.cornerStyle == CornerStyle.endCurve || + series.cornerStyle == CornerStyle.bothCurve) { + pointEndAngle = + (pointEndAngle > 360 ? pointEndAngle : (pointEndAngle - 360)) + + 11.5; + final Path path = Path() + ..addArc( + Rect.fromCircle( + center: midPoint!, + radius: actualRadius - (actualRadius * 0.05)), + degreesToRadians(pointEndAngle + 22.5).toDouble(), + degreesToRadians(118.125).toDouble()); + final Path overFilledPath = Path() + ..addArc( + Rect.fromCircle(center: midPoint, radius: actualRadius), + degreesToRadians(pointEndAngle - 20).toDouble(), + degreesToRadians(225).toDouble()); + if (chart.onCreateShader != null && point.shader == null) { + shadowPaths.add(path); + overFilledPaths.add(overFilledPath); + } else { + canvas.drawPath(path, shadowPaint); + canvas.drawPath(overFilledPath, overFilledPaint!); + } + } else if (series.cornerStyle == CornerStyle.bothFlat || + series.cornerStyle == CornerStyle.startCurve) { + overFilledPaint! + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth + ..color = fillColor; + final Offset? startPoint = degreeToPoint( + pointEndAngle, outerRadius - (outerRadius * 0.025), center!); + + final Offset? endPoint = degreeToPoint(pointEndAngle, + currentInnerRadius + (currentInnerRadius * 0.025), center!); + + final Offset? overFilledStartPoint = + degreeToPoint(pointEndAngle - 2, outerRadius, center!); + final Offset? overFilledEndPoint = + degreeToPoint(pointEndAngle - 2, currentInnerRadius, center!); + if (chart.onCreateShader != null && point.shader == null) { + final Path path = Path() + ..moveTo(startPoint!.dx, startPoint.dy) + ..lineTo(endPoint!.dx, endPoint.dy); + path.close(); + final Path overFilledPath = Path() + ..moveTo(overFilledStartPoint!.dx, overFilledStartPoint.dy) + ..lineTo(overFilledEndPoint!.dx, overFilledEndPoint.dy); + path.close(); + shadowPaths.add(path); + overFilledPaths.add(overFilledPath); + } else { + canvas.drawLine(startPoint!, endPoint!, shadowPaint); + canvas.drawLine( + overFilledStartPoint!, overFilledEndPoint!, overFilledPaint!); + } + } + } + } + } + + /// Method to convert the radius to sigma + double _getSigmaFromRadius(double radius) { + return radius * 0.57735 + 0.5; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/doughnut_series_painter.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/doughnut_series_painter.dart new file mode 100644 index 000000000..0dbdb397b --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/doughnut_series_painter.dart @@ -0,0 +1,128 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/event_args.dart'; +import '../base/circular_state_properties.dart'; +import '../renderer/chart_point.dart'; +import '../renderer/common.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/helper.dart'; + +/// Represents the painter of doughnut chart +/// +class DoughnutChartPainter extends CustomPainter { + /// Creates the instance of doughnut chart painter + /// + DoughnutChartPainter({ + required this.stateProperties, + required this.index, + required this.isRepaint, + this.animationController, + this.seriesAnimation, + required ValueNotifier notifier, + }) : super(repaint: notifier); + + /// Specifies the circular chart state + final CircularStateProperties stateProperties; + + /// Specifies the value of index + final int index; + + /// Specifies whether to repaint the series + final bool isRepaint; + + /// Specifies the value of animation controller + final AnimationController? animationController; + + /// Specifies the value of series animation + final Animation? seriesAnimation; + + /// Specifies the value of series renderer + late DoughnutSeriesRendererExtension seriesRenderer; + + /// To paint series + @override + void paint(Canvas canvas, Size size) { + num? pointStartAngle; + seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[index] + as DoughnutSeriesRendererExtension; + pointStartAngle = seriesRenderer.segmentRenderingValues['start']; + seriesRenderer.innerRadius = + seriesRenderer.segmentRenderingValues['currentInnerRadius']!; + seriesRenderer.radius = + seriesRenderer.segmentRenderingValues['currentRadius']!; + ChartPoint point; + seriesRenderer.pointRegions = []; + ChartPoint? _oldPoint; + final DoughnutSeriesRendererExtension? oldSeriesRenderer = (stateProperties + .renderingDetails.widgetNeedUpdate && + !stateProperties.renderingDetails.isLegendToggled && + stateProperties.prevSeriesRenderer?.seriesType == 'doughnut') + ? stateProperties.prevSeriesRenderer as DoughnutSeriesRendererExtension + : null; + seriesRenderer.renderPaths.clear(); + seriesRenderer.renderList.clear(); + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + point = seriesRenderer.renderPoints![i]; + _oldPoint = (oldSeriesRenderer != null && + oldSeriesRenderer.oldRenderPoints != null && + (oldSeriesRenderer.oldRenderPoints!.length - 1 >= i)) + ? oldSeriesRenderer.oldRenderPoints![i] + : ((stateProperties.renderingDetails.isLegendToggled && + stateProperties.prevSeriesRenderer?.seriesType == 'doughnut') + ? stateProperties.oldPoints![i] + : null); + pointStartAngle = seriesRenderer.circularRenderPoint( + stateProperties.chart, + seriesRenderer, + point, + pointStartAngle, + point.innerRadius, + point.outerRadius, + canvas, + index, + i, + seriesAnimation?.value ?? 1, + 1, + checkIsAnyPointSelect(seriesRenderer, point, stateProperties.chart), + _oldPoint, + stateProperties.oldPoints); + } + + if (seriesRenderer.renderList.isNotEmpty) { + Shader? _chartShader; + if (stateProperties.chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = ChartShaderDetails(seriesRenderer.renderList[1], + seriesRenderer.renderList[2], 'series'); + _chartShader = + stateProperties.chart.onCreateShader!(chartShaderDetails); + } + for (int k = 0; k < seriesRenderer.renderPaths.length; k++) { + drawPath( + canvas, + seriesRenderer.renderList[0], + seriesRenderer.renderPaths[k], + seriesRenderer.renderList[1], + _chartShader); + } + if (seriesRenderer.renderList[0].strokeColor != null && + seriesRenderer.renderList[0].strokeWidth != null && + seriesRenderer.renderList[0].strokeWidth > 0 == true) { + final Paint paint = Paint(); + paint.color = seriesRenderer.renderList[0].strokeColor; + paint.strokeWidth = seriesRenderer.renderList[0].strokeWidth; + paint.style = PaintingStyle.stroke; + for (int k = 0; k < seriesRenderer.renderPaths.length; k++) { + canvas.drawPath(seriesRenderer.renderPaths[k], paint); + } + } + } + } + + @override + bool shouldRepaint(DoughnutChartPainter oldDelegate) => isRepaint; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/pie_chart_painter.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/pie_chart_painter.dart new file mode 100644 index 000000000..3cbb12726 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/pie_chart_painter.dart @@ -0,0 +1,133 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/event_args.dart'; +import '../base/circular_base.dart'; +import '../base/circular_state_properties.dart'; +import '../renderer/chart_point.dart'; +import '../renderer/common.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/helper.dart'; + +/// Represents the pie chart painter +/// +class PieChartPainter extends CustomPainter { + /// Creates an instance of pie chart painter + PieChartPainter({ + required this.stateProperties, + required this.index, + required this.isRepaint, + this.animationController, + this.seriesAnimation, + required ValueNotifier notifier, + }) : chart = stateProperties.chart, + super(repaint: notifier); + + /// Specifies the circular state properties + final CircularStateProperties stateProperties; + + /// Holds the circularchart + final SfCircularChart chart; + + /// Holds the index value + final int index; + + /// Specifies whether to repaint the series + final bool isRepaint; + + /// Holds the animation controller + final AnimationController? animationController; + + /// Specifies the series animation + final Animation? seriesAnimation; + + /// Specifies the pie series renderer extension + late PieSeriesRendererExtension seriesRenderer; + + /// To paint series + @override + void paint(Canvas canvas, Size size) { + num? pointStartAngle; + seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[index] + as PieSeriesRendererExtension; + pointStartAngle = seriesRenderer.segmentRenderingValues['start']; + seriesRenderer.pointRegions = []; + bool isAnyPointNeedSelect = false; + if (stateProperties.renderingDetails.initialRender!) { + isAnyPointNeedSelect = + checkIsAnyPointSelect(seriesRenderer, seriesRenderer.point, chart); + } + ChartPoint? _oldPoint; + ChartPoint? point = seriesRenderer.point; + final PieSeriesRendererExtension? oldSeriesRenderer = + (stateProperties.renderingDetails.widgetNeedUpdate && + !stateProperties.renderingDetails.isLegendToggled && + stateProperties.prevSeriesRenderer != null && + stateProperties.prevSeriesRenderer!.seriesType == 'pie') + ? stateProperties.prevSeriesRenderer! as PieSeriesRendererExtension + : null; + seriesRenderer.renderPaths.clear(); + seriesRenderer.renderList.clear(); + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + point = seriesRenderer.renderPoints![i]; + _oldPoint = (oldSeriesRenderer != null && + oldSeriesRenderer.oldRenderPoints != null && + (oldSeriesRenderer.oldRenderPoints!.length - 1 >= i)) + ? oldSeriesRenderer.oldRenderPoints![i] + : ((stateProperties.renderingDetails.isLegendToggled && + stateProperties.prevSeriesRenderer?.seriesType == 'pie') + ? stateProperties.oldPoints![i] + : null); + point.innerRadius = 0.0; + pointStartAngle = seriesRenderer.circularRenderPoint( + chart, + seriesRenderer, + point, + pointStartAngle, + point.innerRadius, + point.outerRadius, + canvas, + index, + i, + seriesAnimation?.value ?? 1, + seriesAnimation?.value ?? 1, + isAnyPointNeedSelect, + _oldPoint, + stateProperties.oldPoints); + } + if (seriesRenderer.renderList.isNotEmpty) { + Shader? _chartShader; + if (chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = + ChartShaderDetails(seriesRenderer.renderList[1], null, 'series'); + _chartShader = chart.onCreateShader!(chartShaderDetails); + } + for (int k = 0; k < seriesRenderer.renderPaths.length; k++) { + drawPath( + canvas, + seriesRenderer.renderList[0], + seriesRenderer.renderPaths[k], + seriesRenderer.renderList[1], + _chartShader); + } + if (seriesRenderer.renderList[0].strokeColor != null && + seriesRenderer.renderList[0].strokeWidth != null && + seriesRenderer.renderList[0].strokeWidth > 0 == true) { + final Paint paint = Paint(); + paint.color = seriesRenderer.renderList[0].strokeColor; + paint.strokeWidth = seriesRenderer.renderList[0].strokeWidth; + paint.style = PaintingStyle.stroke; + for (int k = 0; k < seriesRenderer.renderPaths.length; k++) { + canvas.drawPath(seriesRenderer.renderPaths[k], paint); + } + } + } + } + + @override + bool shouldRepaint(PieChartPainter oldDelegate) => isRepaint; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/radial_bar_painter.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/radial_bar_painter.dart new file mode 100644 index 000000000..a0d31db76 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/series_painter/radial_bar_painter.dart @@ -0,0 +1,255 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../common/event_args.dart'; +import '../base/circular_state_properties.dart'; +import '../renderer/chart_point.dart'; +import '../renderer/common.dart'; +import '../renderer/radial_bar_series.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/helper.dart'; + +/// Represents the pointer to draw radial bar series +/// +class RadialBarPainter extends CustomPainter { + /// Creates the instance for radial bar series + /// + RadialBarPainter({ + required this.stateProperties, + required this.index, + required this.isRepaint, + this.animationController, + this.seriesAnimation, + ValueNotifier? notifier, + }) : super(repaint: notifier); + + /// Specifies the value of circular state properties + final CircularStateProperties stateProperties; + + /// Holds the index value + final int index; + + /// Specifies whether to repaint the series + final bool isRepaint; + + /// Specifies the value of animation controller + final AnimationController? animationController; + + /// Specifies the value of series animation + final Animation? seriesAnimation; + + /// Holds the value of radial bar series extension + late RadialBarSeriesRendererExtension seriesRenderer; + late num _length, _sum, _ringSize, _animationValue, _actualStartAngle; + late int? _firstVisible; + late num? _gap; + late bool _isLegendToggle; + late RadialBarSeriesRendererExtension? _oldSeriesRenderer; + + /// Specifies the value of actual length + late double actualDegree; + + /// Method to get length of the visible point + num _getLength(Canvas canvas) { + num length = 0; + seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[index] + as RadialBarSeriesRendererExtension; + seriesRenderer.pointRegions = []; + seriesRenderer.innerRadius = + seriesRenderer.segmentRenderingValues['currentInnerRadius']!; + seriesRenderer.radius = + seriesRenderer.segmentRenderingValues['currentRadius']!; + seriesRenderer.center = seriesRenderer.center!; + canvas.clipRect(stateProperties.renderingDetails.chartAreaRect); + + // finding visible points count + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + length += seriesRenderer.renderPoints![i].isVisible ? 1 : 0; + } + return length; + } + + /// Method to initialize the values to draw the radial bar series + /// + void _initializeValues(Canvas canvas) { + _length = _getLength(canvas); + _sum = seriesRenderer.segmentRenderingValues['sumOfPoints']!; + _actualStartAngle = seriesRenderer.segmentRenderingValues['start']!; + + // finding first visible point + _firstVisible = seriesRenderer.getFirstVisiblePointIndex(seriesRenderer); + _ringSize = (seriesRenderer.segmentRenderingValues['currentRadius']! - + seriesRenderer.segmentRenderingValues['currentInnerRadius']!) + .abs() / + _length; + _gap = percentToValue( + seriesRenderer.series.gap, + (seriesRenderer.segmentRenderingValues['currentRadius']! - + seriesRenderer.segmentRenderingValues['currentInnerRadius']!) + .abs()); + _animationValue = seriesAnimation?.value ?? 1; + _isLegendToggle = stateProperties.renderingDetails.isLegendToggled; + _oldSeriesRenderer = (stateProperties.renderingDetails.widgetNeedUpdate && + !stateProperties.renderingDetails.isLegendToggled && + stateProperties.prevSeriesRenderer!.seriesType == 'radialbar') + ? stateProperties.prevSeriesRenderer! + as RadialBarSeriesRendererExtension + : null; + seriesRenderer.renderPaths.clear(); + } + + /// Method to paint radial bar series + /// + @override + void paint(Canvas canvas, Size size) { + num? pointStartAngle, pointEndAngle, degree; + ChartPoint? _oldPoint; + late ChartPoint point; + _initializeValues(canvas); + late RadialBarSeries series; + num? oldStart, oldEnd, oldRadius, oldInnerRadius; + late bool isDynamicUpdate, hide; + seriesRenderer.shadowPaths.clear(); + seriesRenderer.overFilledPaths.clear(); + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + num? value; + point = seriesRenderer.renderPoints![i]; + if (seriesRenderer.series is RadialBarSeries) { + series = seriesRenderer.series as RadialBarSeries; + } + _oldPoint = (_oldSeriesRenderer != null && + _oldSeriesRenderer!.oldRenderPoints != null && + (_oldSeriesRenderer!.oldRenderPoints!.length - 1 >= i)) + ? _oldSeriesRenderer!.oldRenderPoints![i] + : (_isLegendToggle ? stateProperties.oldPoints![i] : null); + pointStartAngle = _actualStartAngle; + isDynamicUpdate = _oldPoint != null; + hide = false; + actualDegree = 0; + if (!isDynamicUpdate || + ((_oldPoint.isVisible && point.isVisible) || + (_oldPoint.isVisible && !point.isVisible) || + (!_oldPoint.isVisible && point.isVisible))) { + if (point.isVisible) { + hide = false; + if (isDynamicUpdate && !_isLegendToggle) { + value = (point.y! > _oldPoint.y!) + ? _oldPoint.y! + (point.y! - _oldPoint.y!) * _animationValue + : _oldPoint.y! - (_oldPoint.y! - point.y!) * _animationValue; + } + degree = (value ?? point.y!).abs() / (series.maximumValue ?? _sum); + degree = degree * (360 - 0.001); + actualDegree = degree.toDouble(); + degree = isDynamicUpdate ? degree : degree * _animationValue; + pointEndAngle = pointStartAngle + degree; + point.midAngle = (pointStartAngle + pointEndAngle) / 2; + point.startAngle = pointStartAngle; + point.endAngle = pointEndAngle; + point.center = seriesRenderer.center!; + point.innerRadius = seriesRenderer.innerRadius = + seriesRenderer.innerRadius + + ((i == _firstVisible) ? 0 : _ringSize); + point.outerRadius = seriesRenderer.radius = _ringSize < _gap! + ? 0 + : seriesRenderer.innerRadius + _ringSize - _gap!; + if (_isLegendToggle) { + seriesRenderer.calculateVisiblePointLegendToggleAnimation( + point, _oldPoint, i, _animationValue); + } + } //animate on hiding + else if (_isLegendToggle && !point.isVisible && _oldPoint!.isVisible) { + hide = true; + oldEnd = _oldPoint.endAngle; + oldStart = _oldPoint.startAngle; + degree = _oldPoint.y!.abs() / (series.maximumValue ?? _sum); + degree = degree * (360 - 0.001); + actualDegree = degree.toDouble(); + oldInnerRadius = _oldPoint.innerRadius! + + ((_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2 - + _oldPoint.innerRadius!) * + _animationValue; + oldRadius = _oldPoint.outerRadius! - + (_oldPoint.outerRadius! - + (_oldPoint.outerRadius! + _oldPoint.innerRadius!) / 2) * + _animationValue; + } + if (seriesRenderer is RadialBarSeriesRendererExtension) { + seriesRenderer.drawDataPoint( + point, + degree, + pointStartAngle, + pointEndAngle, + seriesRenderer, + hide, + oldRadius, + oldInnerRadius, + _oldPoint, + oldStart, + oldEnd, + i, + canvas, + index, + stateProperties.chart, + actualDegree); + } + } + } + + _renderRadialBarSeries(canvas); + } + + /// Method to render the radial bar series + /// + void _renderRadialBarSeries(Canvas canvas) { + if (seriesRenderer.renderList.isNotEmpty) { + Shader? _chartShader; + if (stateProperties.chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = ChartShaderDetails(seriesRenderer.renderList[1], + seriesRenderer.renderList[2], 'series'); + _chartShader = + stateProperties.chart.onCreateShader!(chartShaderDetails); + } + + for (int k = 0; k < seriesRenderer.renderPaths.length; k++) { + drawPath( + canvas, + seriesRenderer.renderList[0], + seriesRenderer.renderPaths[k], + seriesRenderer.renderList[1], + _chartShader!); + } + + for (int k = 0; k < seriesRenderer.shadowPaths.length; k++) { + canvas.drawPath( + seriesRenderer.shadowPaths[k], seriesRenderer.shadowPaint); + } + + if (_chartShader != null && seriesRenderer.overFilledPaint != null) { + seriesRenderer.overFilledPaint!.shader = _chartShader; + } + for (int k = 0; k < seriesRenderer.overFilledPaths.length; k++) { + canvas.drawPath( + seriesRenderer.overFilledPaths[k], seriesRenderer.overFilledPaint!); + } + + if (seriesRenderer.renderList[0].strokeColor != null && + seriesRenderer.renderList[0].strokeWidth != null && + (seriesRenderer.renderList[0].strokeWidth > 0) == true) { + final Paint paint = Paint(); + paint.color = seriesRenderer.renderList[0].strokeColor; + paint.strokeWidth = seriesRenderer.renderList[0].strokeWidth; + paint.style = PaintingStyle.stroke; + for (int k = 0; k < seriesRenderer.renderPaths.length; k++) { + canvas.drawPath(seriesRenderer.renderPaths[k], paint); + } + } + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => isRepaint; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart index e59aff936..f8dea806f 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/enum.dart @@ -1,5 +1,3 @@ -part of charts; - /// Data points grouping mode. enum CircularChartGroupMode { /// - CircularChartGroupMode.point, groups the points based on length. @@ -20,11 +18,14 @@ enum Position { /// Data labels intersect action. enum LabelIntersectAction { - ///- `LabelIntersectAction.hide,`, hides the intersecting labels. + ///- `LabelIntersectAction.hide`, hides the intersecting labels. hide, /// - `LabelIntersectAction.none`, will not perform any action on intersection. - none + none, + + /// - `LabelIntersectAction.shift`, will shift and position the intersecting labels smartly. If the labels are moved out of the chart area, then the labels will be trimmed and the eclipse will be shown for the trimmed labels. + shift } /// Type of connector line. diff --git a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart index 75669cc3a..1a9bb9d7d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/circular_chart/utils/helper.dart @@ -1,7 +1,23 @@ -part of charts; +import 'dart:math' as math; +import 'dart:math'; +import 'dart:typed_data'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/circular_chart/base/circular_state_properties.dart'; +import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import '../../chart/chart_series/xy_data_series.dart'; +import '../base/circular_base.dart'; +import '../renderer/chart_point.dart'; +import '../renderer/circular_series.dart'; +import '../renderer/common.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/enum.dart'; /// To get equivalent value for the percentage -num? _percentToValue(String? value, num size) { +num? percentToValue(String? value, num size) { return value != null ? value.contains('%') ? (size / 100) * @@ -11,15 +27,15 @@ num? _percentToValue(String? value, num size) { } /// Convert degree to radian -num _degreesToRadians(num deg) => deg * (pi / 180); +num degreesToRadians(num deg) => deg * (pi / 180); /// To get arc path for circular chart render -Path _getArcPath(num innerRadius, num radius, Offset center, num? startAngle, +Path getArcPath(num innerRadius, num radius, Offset center, num? startAngle, num? endAngle, num? degree, SfCircularChart chart, bool isAnimate) { final Path path = Path(); - startAngle = _degreesToRadians(startAngle!); - endAngle = _degreesToRadians(endAngle!); - degree = _degreesToRadians(degree!); + startAngle = degreesToRadians(startAngle!); + endAngle = degreesToRadians(endAngle!); + degree = degreesToRadians(degree!); final math.Point innerRadiusStartPoint = math.Point( innerRadius * cos(startAngle) + center.dx, @@ -76,61 +92,8 @@ Path _getArcPath(num innerRadius, num radius, Offset center, num? startAngle, return path; } -/// To get current point -ChartPoint _getCircularPoint( - CircularSeriesRenderer seriesRenderer, int pointIndex) { - final CircularSeries series = seriesRenderer._series; - late ChartPoint currentPoint; - final ChartIndexedValueMapper? xMap = series.xValueMapper; - final ChartIndexedValueMapper? yMap = series.yValueMapper; - final ChartIndexedValueMapper? sortFieldMap = - series.sortFieldValueMapper; - final ChartIndexedValueMapper? radiusMap = series.pointRadiusMapper; - final ChartIndexedValueMapper? colorMap = series.pointColorMapper; - final ChartShaderMapper? shadeMap = series.pointShaderMapper; - - /// Can be either a string or num value - dynamic xVal; - num? yVal; - String? radiusVal; - Color? colorVal; - String? sortVal; - if (xMap != null) { - xVal = xMap(pointIndex); - } - - if (xVal != null) { - if (yMap != null) { - yVal = yMap(pointIndex); - } - - if (radiusMap != null) { - radiusVal = radiusMap(pointIndex); - } - - if (colorMap != null) { - colorVal = colorMap(pointIndex); - } - if (sortFieldMap != null) { - sortVal = sortFieldMap(pointIndex); - } - - currentPoint = - ChartPoint(xVal, yVal, radiusVal, colorVal, sortVal); - } - currentPoint.index = pointIndex; - if (shadeMap != null) { - currentPoint._pointShaderMapper = shadeMap; - } else { - currentPoint._pointShaderMapper = null; - } - currentPoint.center = seriesRenderer._center; - - return currentPoint; -} - /// To get rounded corners Arc path -Path _getRoundedCornerArcPath( +Path getRoundedCornerArcPath( num innerRadius, num outerRadius, Offset? center, @@ -146,11 +109,11 @@ Path _getRoundedCornerArcPath( if (cornerStyle == CornerStyle.startCurve || cornerStyle == CornerStyle.bothCurve) { _midPoint = - _degreeToPoint(startAngle, (innerRadius + outerRadius) / 2, center!); + degreeToPoint(startAngle, (innerRadius + outerRadius) / 2, center!); - midStartAngle = _degreesToRadians(180); + midStartAngle = degreesToRadians(180); - midEndAngle = midStartAngle + _degreesToRadians(180); + midEndAngle = midStartAngle + degreesToRadians(180); path.addArc( Rect.fromCircle( @@ -161,17 +124,17 @@ Path _getRoundedCornerArcPath( path.addArc( Rect.fromCircle(center: center!, radius: outerRadius.toDouble()), - _degreesToRadians(startAngle).toDouble(), - _degreesToRadians(endAngle - startAngle).toDouble()); + degreesToRadians(startAngle).toDouble(), + degreesToRadians(endAngle - startAngle).toDouble()); if (cornerStyle == CornerStyle.endCurve || cornerStyle == CornerStyle.bothCurve) { _midPoint = - _degreeToPoint(endAngle, (innerRadius + outerRadius) / 2, center); + degreeToPoint(endAngle, (innerRadius + outerRadius) / 2, center); - midStartAngle = _degreesToRadians(endAngle / 2); + midStartAngle = degreesToRadians(endAngle / 2); - midEndAngle = midStartAngle + _degreesToRadians(180); + midEndAngle = midStartAngle + degreesToRadians(180); path.arcTo( Rect.fromCircle( @@ -183,18 +146,18 @@ Path _getRoundedCornerArcPath( path.arcTo( Rect.fromCircle(center: center, radius: innerRadius.toDouble()), - _degreesToRadians(endAngle.toDouble()).toDouble(), - (_degreesToRadians(startAngle.toDouble()) - - _degreesToRadians(endAngle.toDouble())) + degreesToRadians(endAngle.toDouble()).toDouble(), + (degreesToRadians(startAngle.toDouble()) - + degreesToRadians(endAngle.toDouble())) .toDouble(), false); return path; } /// To get point region -_Region? _getCircularPointRegion(SfCircularChart chart, Offset? position, - CircularSeriesRenderer seriesRenderer) { - _Region? pointRegion; +Region? getCircularPointRegion(SfCircularChart chart, Offset? position, + CircularSeriesRendererExtension seriesRenderer) { + Region? pointRegion; const num chartStartAngle = -.5 * pi; num fromCenterX, fromCenterY, @@ -202,18 +165,23 @@ _Region? _getCircularPointRegion(SfCircularChart chart, Offset? position, pointStartAngle, pointEndAngle, distanceFromCenter; - for (final _Region region in seriesRenderer._pointRegions) { + // ignore: prefer_is_empty + if (seriesRenderer.renderPoints?.length == 0 || + seriesRenderer.renderPoints == null) { + seriesRenderer = seriesRenderer.stateProperties.prevSeriesRenderer!; + } + for (final Region region in seriesRenderer.pointRegions) { fromCenterX = position!.dx - region.center!.dx; fromCenterY = position.dy - region.center!.dy; tapAngle = (atan2(fromCenterY, fromCenterX) - chartStartAngle) % (2 * pi); - pointStartAngle = region.start - _degreesToRadians(-90); - pointEndAngle = region.end - _degreesToRadians(-90); + pointStartAngle = region.start - degreesToRadians(-90); + pointEndAngle = region.end - degreesToRadians(-90); if (chart.onDataLabelRender != null) { - seriesRenderer._dataPoints[region.pointIndex].labelRenderEvent = false; + seriesRenderer.dataPoints[region.pointIndex].labelRenderEvent = false; } if (((region.endAngle + 90) > 360) && (region.startAngle + 90) > 360) { - pointEndAngle = _degreesToRadians((region.endAngle + 90) % 360); - pointStartAngle = _degreesToRadians((region.startAngle + 90) % 360); + pointEndAngle = degreesToRadians((region.endAngle + 90) % 360); + pointStartAngle = degreesToRadians((region.startAngle + 90) % 360); } else if ((region.endAngle + 90) > 360) { tapAngle = tapAngle > pointStartAngle ? tapAngle : 2 * pi + tapAngle; } @@ -231,7 +199,7 @@ _Region? _getCircularPointRegion(SfCircularChart chart, Offset? position, } /// Draw the path -void _drawPath(Canvas canvas, _StyleOptions style, Path path, +void drawPath(Canvas canvas, StyleOptions style, Path path, [Rect? rect, Shader? shader]) { final Paint paint = Paint(); if (shader != null) { @@ -255,56 +223,59 @@ void _drawPath(Canvas canvas, _StyleOptions style, Path path, } /// To convert degree to point and return position -Offset _degreeToPoint(num degree, num radius, Offset center) { - degree = _degreesToRadians(degree); +Offset degreeToPoint(num degree, num radius, Offset center) { + degree = degreesToRadians(degree); return Offset( center.dx + cos(degree) * radius, center.dy + sin(degree) * radius); } /// To repaint circular chart -void _needsRepaintCircularChart( - List currentSeriesRenderers, - List oldSeriesRenderers) { +void needsRepaintCircularChart( + List currentSeriesRenderers, + List oldSeriesRenderers) { if (currentSeriesRenderers.length == oldSeriesRenderers.length && - currentSeriesRenderers[0]._series == oldSeriesRenderers[0]!._series) { + currentSeriesRenderers[0].series == oldSeriesRenderers[0]!.series) { for (int seriesIndex = 0; seriesIndex < oldSeriesRenderers.length; seriesIndex++) { - _canRepaintSeries( - currentSeriesRenderers, oldSeriesRenderers, seriesIndex); + canRepaintSeries(currentSeriesRenderers, oldSeriesRenderers, seriesIndex); } } else { // ignore: avoid_function_literals_in_foreach_calls - currentSeriesRenderers.forEach((CircularSeriesRenderer seriesRenderer) => - seriesRenderer._needsRepaint = true); + currentSeriesRenderers.forEach( + (CircularSeriesRendererExtension seriesRenderer) => + seriesRenderer.needsRepaint = true); } } /// To repaint series -void _canRepaintSeries(List currentSeriesRenderers, - List oldSeriesRenderers, int seriesIndex) { - final CircularSeriesRenderer seriesRenderer = currentSeriesRenderers[0]; - final CircularSeriesRenderer oldWidgetSeriesRenderer = +void canRepaintSeries( + List currentSeriesRenderers, + List oldSeriesRenderers, + int seriesIndex) { + final CircularSeriesRendererExtension seriesRenderer = + currentSeriesRenderers[0]; + final CircularSeriesRendererExtension oldWidgetSeriesRenderer = oldSeriesRenderers[seriesIndex]!; - final CircularSeries series = seriesRenderer._series; + final CircularSeries series = seriesRenderer.series; final CircularSeries oldWidgetSeries = - oldWidgetSeriesRenderer._series; - if (seriesRenderer._center?.dy != oldWidgetSeriesRenderer._center?.dy || - seriesRenderer._center?.dx != oldWidgetSeriesRenderer._center?.dx || + oldWidgetSeriesRenderer.series; + if (seriesRenderer.center?.dy != oldWidgetSeriesRenderer.center?.dy || + seriesRenderer.center?.dx != oldWidgetSeriesRenderer.center?.dx || series.borderWidth != oldWidgetSeries.borderWidth || series.name != oldWidgetSeries.name || series.borderColor.value != oldWidgetSeries.borderColor.value || - seriesRenderer._segmentRenderingValues['currentInnerRadius'] != + seriesRenderer.segmentRenderingValues['currentInnerRadius'] != oldWidgetSeriesRenderer - ._segmentRenderingValues['currentInnerRadius'] || - seriesRenderer._segmentRenderingValues['currentRadius'] != - oldWidgetSeriesRenderer._segmentRenderingValues['currentRadius'] || - seriesRenderer._segmentRenderingValues['start'] != - oldWidgetSeriesRenderer._segmentRenderingValues['start'] || - seriesRenderer._segmentRenderingValues['totalAngle'] != - oldWidgetSeriesRenderer._segmentRenderingValues['totalAngle'] || - seriesRenderer._dataPoints.length != - oldWidgetSeriesRenderer._dataPoints.length || + .segmentRenderingValues['currentInnerRadius'] || + seriesRenderer.segmentRenderingValues['currentRadius'] != + oldWidgetSeriesRenderer.segmentRenderingValues['currentRadius'] || + seriesRenderer.segmentRenderingValues['start'] != + oldWidgetSeriesRenderer.segmentRenderingValues['start'] || + seriesRenderer.segmentRenderingValues['totalAngle'] != + oldWidgetSeriesRenderer.segmentRenderingValues['totalAngle'] || + seriesRenderer.dataPoints.length != + oldWidgetSeriesRenderer.dataPoints.length || series.emptyPointSettings.borderWidth != oldWidgetSeries.emptyPointSettings.borderWidth || series.emptyPointSettings.borderColor.value != @@ -350,27 +321,23 @@ void _canRepaintSeries(List currentSeriesRenderers, series.xValueMapper != oldWidgetSeries.xValueMapper || series.yValueMapper != oldWidgetSeries.yValueMapper || series.enableTooltip != oldWidgetSeries.enableTooltip) { - seriesRenderer._needsRepaint = true; + seriesRenderer.needsRepaint = true; } else { - seriesRenderer._needsRepaint = false; + seriesRenderer.needsRepaint = false; } } /// To return deviation angle -num _findAngleDeviation(num innerRadius, num outerRadius, num totalAngle) { +num findAngleDeviation(num innerRadius, num outerRadius, num totalAngle) { final num calcRadius = (innerRadius + outerRadius) / 2; - final num circumference = 2 * pi * calcRadius; - final num rimSize = (innerRadius - outerRadius).abs(); - final num deviation = ((rimSize / 2) / circumference) * 100; - return (deviation * 360) / 100; } /// It returns the actual label value for tooltip and data label etc -String _getDecimalLabelValue(num? value, [int? showDigits]) { +String getDecimalLabelValue(num? value, [int? showDigits]) { if (value != null && value.toString().split('.').length > 1) { final String str = value.toString(); final List list = str.split('.'); @@ -390,7 +357,114 @@ String _getDecimalLabelValue(num? value, [int? showDigits]) { } /// Method to rotate Sweep gradient -Float64List _resolveTransform(Rect bounds, TextDirection textDirection) { +Float64List resolveTransform(Rect bounds, TextDirection textDirection) { final GradientTransform transform = GradientRotation(degreeToRadian(-90)); return transform.transform(bounds, textDirection: textDirection)!.storage; } + +/// Circular pixel to point +ChartPoint circularPixelToPoint( + Offset position, CircularStateProperties chartState) { + int pointIndex; + ChartPoint? dataPoint; + final Region? pointRegion = getCircularPointRegion(chartState.chart, position, + chartState.chartSeries.visibleSeriesRenderers[0]); + if (pointRegion != null) { + pointIndex = pointRegion.pointIndex; + // ignore: prefer_is_empty + if (chartState.chartSeries.visibleSeriesRenderers[0].renderPoints?.length == + 0 || + chartState.chartSeries.visibleSeriesRenderers[0].renderPoints == null) { + dataPoint = chartState.chartSeries.visibleSeriesRenderers[0] + .stateProperties.prevSeriesRenderer!.dataPoints[pointIndex]; + } else { + dataPoint = chartState + .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; + } + } + return dataPoint!; +} + +/// Circular point to pixel +Offset circularPointToPixel( + ChartPoint point, CircularStateProperties chartState) { + Offset location; + + if (point.midAngle == null) { + final String x = point.x; + final num y = point.y!; + final CircularSeriesRendererExtension seriesRenderer = + chartState.chartSeries.visibleSeriesRenderers[0]; + for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { + if (seriesRenderer.dataPoints[i].x == x && + seriesRenderer.dataPoints[i].y == y) { + point = seriesRenderer.dataPoints[i]; + } + } + } + + location = degreeToPoint(point.midAngle!, + (point.innerRadius! + point.outerRadius!) / 2, point.center!); + location = Offset(location.dx, location.dy); + return location; +} + +/// To find the current point overlapped with previous points +bool isOverlapWithPrevious(ChartPoint currentPoint, + List> points, int currentPointIndex) { + for (int i = 0; i < currentPointIndex; i++) { + if (i != points.indexOf(currentPoint) && + points[i].isVisible && + isOverlap(currentPoint.labelRect, points[i].labelRect)) { + return true; + } + } + return false; +} + +/// To find the current point overlapped with next points +bool isOverlapWithNext(ChartPoint point, + List> points, int pointIndex) { + for (int i = pointIndex; i < points.length; i++) { + if (i != points.indexOf(point) && + points[i].isVisible && + points[i].labelRect != null && + point.labelRect != null && + isOverlap(point.labelRect, points[i].labelRect)) { + return true; + } + } + return false; +} + +/// Calculate the connected line path for shifted data label. +ChartLocation getPerpendicularDistance( + ChartLocation startPoint, ChartPoint point) { + ChartLocation increasedLocation; + const num add = 10; + final num height = add + 10 * math.sin(point.midAngle! * math.pi / 360); + if (point.midAngle! > 270 && point.midAngle! < 360) { + increasedLocation = ChartLocation( + startPoint.x + + height * (math.cos((360 - point.midAngle!) * math.pi / 180)), + startPoint.y - + height * (math.sin((360 - point.midAngle!) * math.pi / 180))); + } else if (point.midAngle! > 0 && point.midAngle! < 90) { + increasedLocation = ChartLocation( + startPoint.x + height * (math.cos((point.midAngle)! * math.pi / 180)), + startPoint.y + height * (math.sin((point.midAngle)! * math.pi / 180))); + } else if (point.midAngle! > 0 && point.midAngle! < 90) { + increasedLocation = ChartLocation( + startPoint.x - + height * (math.cos((point.midAngle! - 90) * math.pi / 180)), + startPoint.y + + height * (math.sin((point.midAngle! - 90) * math.pi / 180))); + } else { + increasedLocation = ChartLocation( + startPoint.x - + height * (math.cos((point.midAngle! - 180) * math.pi / 180)), + startPoint.y - + height * (math.sin((point.midAngle! - 180) * math.pi / 180))); + } + return increasedLocation; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/common.dart b/packages/syncfusion_flutter_charts/lib/src/common/common.dart index fd73d8b7a..378312b78 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/common.dart @@ -1,7 +1,19 @@ -part of charts; +import 'dart:ui'; -class _ChartContainer extends SingleChildRenderObjectWidget { - const _ChartContainer({required Widget child}) : super(child: child); +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../chart/utils/enum.dart'; +import 'utils/enum.dart'; +import 'utils/helper.dart'; +import 'utils/typedef.dart'; + +/// Represents the chart container +class ChartContainer extends SingleChildRenderObjectWidget { + /// Creates an instance for chart container + const ChartContainer({required Widget child}) : super(child: child); @override RenderObject createRenderObject(BuildContext context) { @@ -60,7 +72,7 @@ class ChartTitle { this.borderColor = Colors.transparent, this.borderWidth = 0, this.backgroundColor}) - : textStyle = _getTextStyle( + : textStyle = getTextStyle( textStyle: textStyle, fontSize: 15.0, fontFamily: 'Segoe UI', @@ -210,108 +222,6 @@ class ChartTitle { } } -/// This class has the property of the chart text style. -/// -/// ChartTextStyle is the type of argument in the [ChartTitle] class, it used to set the text style of the chart title. -/// It has the properties to customize the chart title text. -/// -/// Provides the options of color, font family, font style, font size, and font-weight to customize the appearance. -/// -@Deprecated('Use [TextStyle] instead of [ChartTextStyle] ') -class ChartTextStyle extends TextStyle { - /// Creating an argument constructor of ChartTextStyle class. - const ChartTextStyle( - {this.color, - this.fontFamily = 'Roboto', - this.fontStyle = FontStyle.normal, - this.fontWeight = FontWeight.normal, - this.fontSize = 12}) - : super( - color: color, - fontFamily: fontFamily, - fontStyle: fontStyle, - fontWeight: fontWeight, - fontSize: fontSize, - inherit: true, - ); - - /// To set the color of chart text. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis( - /// labelStyle: ChartTextStyle(color: Colors.black), - /// )); - ///} - ///``` - @override - final Color? color; - - /// To set the font family of chart text. - /// - /// Defaults to `Roboto` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis( - /// labelStyle: ChartTextStyle(fontFamily: 'Roboto'), - /// )); - ///} - ///``` - @override - final String fontFamily; - - /// To set the font style of chart text. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis( - /// labelStyle: ChartTextStyle(fontStyle: FontStyle.normal), - /// )); - ///} - ///``` - @override - final FontStyle fontStyle; - - /// To set the font weight of chart text. - /// - /// Defaults to `FontStyle.normal` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis( - /// labelStyle: ChartTextStyle(fontWeight: FontWeight.normal), - /// )); - ///} - ///``` - @override - final FontWeight fontWeight; - - /// To change the font size of chart text - /// - /// Defaults to `12` - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfCartesianChart( - /// primaryXAxis: CategoryAxis( - /// labelStyle: ChartTextStyle(fontSize: 12), - /// )); - ///} - ///``` - @override - final double fontSize; -} - ///Identify the series in chart. /// ///Legend contains list of chart series/data points in chart. It helps to @@ -360,7 +270,7 @@ class Legend { iconBorderWidth = iconBorderWidth ?? 0.0, opacity = opacity ?? 1.0, padding = padding ?? 10.0, - textStyle = _getTextStyle( + textStyle = getTextStyle( textStyle: textStyle, fontSize: 13.0, fontStyle: FontStyle.normal, @@ -802,6 +712,7 @@ class Legend { ///} ///``` final ImageProvider? image; + @override bool operator ==(Object other) { if (identical(this, other)) { @@ -870,25 +781,44 @@ class Legend { /// Legend renderer class for mutable fields and methods class LegendRenderer { - /// Creates an argument constructor for Leged renderer class - LegendRenderer(this._legend); - - //ignore: unused_field - final Legend? _legend; - final _LegendRenderer _renderer = _LegendRenderer(); - late LegendPosition _legendPosition; - late LegendItemOrientation _orientation; + /// Creates an argument constructor for Legend renderer class + LegendRenderer(this.legend); + + /// Holds the legend value + final Legend? legend; + + /// Specifies the legend position value + late LegendPosition legendPosition; + + /// Specifies the value of legend item orientation + late LegendItemOrientation orientation; } -class _MeasureWidgetContext { - _MeasureWidgetContext( +/// Represents the class of measure widget context +class MeasureWidgetContext { + /// Creates an instance of measure widget context + MeasureWidgetContext( {this.context, this.key, this.widget, this.seriesIndex, this.pointIndex}); + + /// Specifies the context value BuildContext? context; + + /// Holds the series index value int? seriesIndex; + + /// Holds the point index value int? pointIndex; + + /// Holds the value of key Key? key; + + /// Holds the value of size Size? size; + + /// Specifies the widget value Widget? widget; + + /// Specifies whether to render the legend bool isRender = false; } @@ -902,7 +832,7 @@ class _MeasureWidgetContext { class LegendTitle { /// Creating an argument constructor of LegendTitle class. LegendTitle({this.text, TextStyle? textStyle, ChartAlignment? alignment}) - : textStyle = _getTextStyle( + : textStyle = getTextStyle( textStyle: textStyle, fontSize: 12.0, fontStyle: FontStyle.normal, @@ -1008,7 +938,7 @@ class LegendTitle { /// /// Defaults to `EmptyPointMode.gap`. /// -/// _Note:_ This is common for cartesian, circular, pyramid and funnel charts. +/// _Note:_ This is common for Cartesian, circular, pyramid and funnel charts. class EmptyPointSettings { /// Creating an argument constructor of EmptyPointSettings class. EmptyPointSettings( diff --git a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart b/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart index 4c73d5c89..a094f1300 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/event_args.dart @@ -1,5 +1,13 @@ -//Event Arguments -part of charts; +import 'package:flutter/material.dart'; +import '../chart/axis/axis.dart'; +import '../chart/chart_series/xy_data_series.dart'; +import '../chart/common/common.dart'; +import '../chart/common/data_label.dart'; +import '../chart/common/interactive_tooltip.dart'; +import '../chart/technical_indicators/technical_indicator.dart'; +import '../chart/utils/enum.dart'; +import 'user_interaction/tooltip.dart'; +import 'utils/enum.dart'; ///Holds the arguments for the event onTooltipRender. /// @@ -33,7 +41,7 @@ class TooltipArgs { /// Get the overall index value of the tooltip. final num? pointIndex; - /// Get the viewport index value of the tooltip. + /// Get the view port index value of the tooltip. final num? viewportPointIndex; } @@ -44,7 +52,6 @@ class TooltipArgs { /// /// It has the public properties of axis name, axis type, actual minimum, and maximum, visible minimum and maximum and axis orientation. class ActualRangeChangedArgs { - //doubt /// Creating an argument constructor of ActualRangeChangedArgs class. ActualRangeChangedArgs( [this.axisName, @@ -117,7 +124,7 @@ class AxisLabelRenderArgs { } /// Holds label text, axis name, orientation of the axis, trimmed text and text styles such as color, -/// font size, and font weight for label formatter evet +/// font size, and font weight for label formatter event class AxisLabelRenderDetails { /// Creating an argument constructor of AxisLabelRenderDetails class. AxisLabelRenderDetails(this.value, this.text, this.actualText, this.textStyle, @@ -228,7 +235,7 @@ class DataLabelRenderArgs { /// argument sets the vertical component to dy. Offset? offset; - /// Get the viewport index value of a data label. + /// Get the view port index value of a data label. final int? viewportPointIndex; } @@ -257,45 +264,46 @@ class LegendRenderArgs { Color? color; } -///Holds the arguments for the event onTrendlineRender. -/// -/// Event is triggered when the trend line is rendered, trendline arguments such as [opacity], [color], and [dashArray] can be customized. -class TrendlineRenderArgs { - /// Creating an argument constructor of TrendlineRenderArgs class. - TrendlineRenderArgs( +/// Holds the onRenderDetailsUpdate callback arguments of trendline. +class TrendlineRenderParams { + /// Creating an argument constructor of TrendlineRenderParams class. + TrendlineRenderParams( [this.intercept, - this.trendlineIndex, this.seriesIndex, this.trendlineName, this.seriesName, - this.data]); + this.calculatedDataPoints, + this.slope, + this.rSquaredValue]); /// Get the intercept value. final double? intercept; - /// Get the index of the trendline. - final int? trendlineIndex; - /// Get the index of the series. final int? seriesIndex; - /// Get the name of the trendline. + ///Gets the name of the trendline. + /// + ///If the user specifies a value for the `name` property in the series, + ///that value can be fetched here. If it is null, then the name generated + ///internally for the trendline can be fetched here. final String? trendlineName; - /// Get the name of the series. + /// Gets the name of the series. + /// + ///If the user specifies a value for the `name` property in the series, + ///that value can be fetched here. If it is null, then the name generated + ///internally for the series can be fetched here. final String? seriesName; - /// Get and set the color of the trendline. - late Color color; - - /// Get and set the opacity value. - late double opacity; + /// Get the data points of the trendline. + final List? calculatedDataPoints; - /// Get and set the dash array value of a trendline. - List? dashArray; + /// Gets the r-squared value. + final double? rSquaredValue; - /// Get the data points of the trendline. - final List>? data; + /// Gets the slope value. + final List? slope; } /// Holds arguments for onTrackballPositionChanging event. @@ -303,7 +311,7 @@ class TrendlineRenderArgs { /// The event is triggered when the trackball is rendered and provides options to customize the label text. class TrackballArgs { /// Get and set the trackball tooltip text. - _ChartPointInfo chartPointInfo = _ChartPointInfo(); + ChartPointInfo chartPointInfo = ChartPointInfo(); } /// Holds the onCrosshairPositionChanging event arguments. @@ -320,7 +328,7 @@ class CrosshairRenderArgs { /// Get the type of chart axis and its properties. final ChartAxis? axis; - /// Get and set the crosshair tooltip text. + /// Get and set the crosshair text. late String text; /// Get and set the color of the crosshair line. @@ -366,7 +374,7 @@ class ZoomPanArgs { /// Get and set the current zoom factor. late double currentZoomFactor; - /// Get the previous zooom position. + /// Get the previous zoom position. final double? previousZoomPosition; /// Get the previous zoom factor. @@ -394,13 +402,13 @@ class PointTapArgs { /// Get the list of data points. final List? dataPoints; - /// Get the viewport index value. + /// Get the view port index value. final num? viewportPointIndex; } ///Holds the arguments of `onPointTap`, `onPointDoubleTap` and `onPointLongPress` callbacks. /// -///The user can fetch the series index, point index, viewport point index and data of the current point. +///The user can fetch the series index, point index, view port point index and data of the current point. class ChartPointDetails { /// Creating an argument constructor of PointTapArgs class. ChartPointDetails( @@ -418,7 +426,7 @@ class ChartPointDetails { /// Get the list of data points. final List? dataPoints; - /// Get the viewport index value. + /// Get the view port index value. final num? viewportPointIndex; } @@ -522,7 +530,7 @@ class SelectionArgs { /// Get the overall index value of the selected data points. final int pointIndex; - /// Get the viewport index value of the selected data points. + /// Get the view port index value of the selected data points. final int viewportPointIndex; } @@ -602,7 +610,7 @@ class MarkerRenderArgs { /// Get and set the border width of marker. late double borderWidth; - /// Get the viewport index value of the marker. + /// Get the view port index value of the marker. final num? viewportPointIndex; } @@ -631,7 +639,7 @@ class DataLabelTapDetails { /// Get the data label customization options specified in that particular series. final DataLabelSettings dataLabelSettings; - /// Get the viewport index value of the tapped data label. + /// Get the view port index value of the tapped data label. final int viewportPointIndex; } @@ -653,6 +661,18 @@ class ChartShaderDetails { final Rect? innerRect; } +/// Holds the onCreateShader callback arguments +class ShaderDetails { + /// Creating an argument constructor of ShaderDetails class. + ShaderDetails(this.rect, this.renderType); + + /// Holds the chart area rect. + final Rect rect; + + ///Conveys whether the current rendering element is 'series' or 'legend'. + final String renderType; +} + /// Holds the onRenderDetailsUpdate callback arguments. class IndicatorRenderParams { /// Creating an argument constructor of IndicatorRenderParams class. @@ -770,3 +790,42 @@ class TechnicalIndicatorRenderDetails { ///Dash array of the signal line final List? signalLineDashArray; } + +/// Holds the ErrorBarRenderDetails values. +class ErrorBarRenderDetails { + /// Creating an argument constructor of ErrorBarRenderDetails class. + ErrorBarRenderDetails( + this.pointIndex, this.viewPortPointIndex, this.calculatedErrorBarValues); + + /// Specifies the overall point index. + final int? pointIndex; + + /// Specifies the current point index. + final int? viewPortPointIndex; + + /// Specifies the current data point's error values. + final ErrorBarValues? calculatedErrorBarValues; +} + +/// Holds the error values of data point. +class ErrorBarValues { + /// Creating an argument constructor of ErrorBarValues class. + ErrorBarValues( + this.horizontalPositiveErrorValue, + this.horizontalNegativeErrorValue, + this.verticalPositiveErrorValue, + this.verticalNegativeErrorValue, + ); + + /// Holds the positive error value in horizontal point. + final double? horizontalPositiveErrorValue; + + /// Holds the negative error value in horizontal point. + final double? horizontalNegativeErrorValue; + + /// Holds the positive error value in vertical point. + final double? verticalPositiveErrorValue; + + /// Holds the negative error value in vertical point. + final double? verticalNegativeErrorValue; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart index a5bc14684..178404b1a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/handcursor/web.dart @@ -10,20 +10,22 @@ class HandCursor extends MouseRegion { HandCursor({required Widget child}) : super( child: child, - onHover: _mouseHover, - onExit: _mouseExit, + onHover: mouseHover, + onExit: mouseExit, ); static final html.Element? _appContainer = html.window.document.getElementById('app-container'); - static void _mouseHover(PointerEvent event) { + /// Method called when the mouse is hovered + static void mouseHover(PointerEvent event) { if (_appContainer != null) { _appContainer!.style.cursor = 'pointer'; } } - static void _mouseExit(PointerEvent event) { + /// Method is called when the mouse point exit + static void mouseExit(PointerEvent event) { if (_appContainer != null) { _appContainer!.style.cursor = 'default'; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart b/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart index b26d0e009..4b74fbf07 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/legend/legend.dart @@ -1,27 +1,90 @@ -part of charts; +import 'dart:math'; -class _ChartLegend { - _ChartLegend(this._chartState); - dynamic get chart => _chartState._chart; - final dynamic _chartState; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_charts/src/circular_chart/renderer/chart_point.dart'; +import 'package:syncfusion_flutter_charts/src/circular_chart/renderer/circular_series.dart'; +import 'package:syncfusion_flutter_charts/src/circular_chart/renderer/renderer_extension.dart'; +import 'package:syncfusion_flutter_charts/src/common/utils/typedef.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/legend_internal.dart' + hide LegendPosition; + +import '../../chart/base/chart_base.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/common/cartesian_state_properties.dart'; +import '../../chart/technical_indicators/technical_indicator.dart'; +import '../../chart/trendlines/trendlines.dart'; +import '../../chart/utils/helper.dart'; +import '../common.dart'; +import '../event_args.dart'; +import '../rendering_details.dart'; +import '../state_properties.dart'; +import '../utils/enum.dart'; +import 'renderer.dart'; + +/// Represents the chart legend class +class ChartLegend { + /// Creates an instance of chart legend + ChartLegend(this.stateProperties); + + /// Specifies the value of state properties + final StateProperties stateProperties; + + /// Holds the chart + dynamic get chart => stateProperties.chart; + + /// Specifies the value of legend Legend? legend; - List<_LegendRenderContext>? legendCollections; + + /// Specifies the list of legend renderer context + List? legendCollections; + + /// Specifies the list of legend items for SfLegend widget. + late List legendItems; + + /// Specifies the value of row count late int rowCount; + + /// Specifies the value of column count late int columnCount; + + /// Specifies the legend size value Size legendSize = const Size(0, 0); + + /// Specifies the value of chart size Size chartSize = const Size(0, 0); + + /// Specifies whether to render the legend bool shouldRenderLegend = false; + + /// Specifies the legend repaint notifier late ValueNotifier legendRepaintNotifier; + + /// Specifies whether the legend is scrollable late bool isNeedScrollable; + /// Specifies the legend's title height value + double titleHeight = 0.0; + + /// Specifies the list of toggled legend indices for SfLegend + List toggledIndices = []; + + /// Specifies the sum of points for circular chart types. + num sumOfPoints = 0; + + /// Specifies the toggled item color for Sflegend + Color toggledItemColor = const Color.fromRGBO(211, 211, 211, 1); + /// To calculate legend bounds - void _calculateLegendBounds(Size size) { + void calculateLegendBounds(Size size) { legend = chart.legend; final LegendRenderer legendRenderer = - _chartState._renderingDetails.legendRenderer; - final List<_MeasureWidgetContext> legendWidgetContext = - _chartState._renderingDetails.legendWidgetContext; - final _ChartLegend chartLegend = _chartState._renderingDetails.chartLegend; + stateProperties.renderingDetails.legendRenderer; + final List legendWidgetContext = + stateProperties.renderingDetails.legendWidgetContext; + final ChartLegend chartLegend = + stateProperties.renderingDetails.chartLegend; shouldRenderLegend = false; assert( !(legend != null && legend!.width != null) || @@ -34,7 +97,8 @@ class _ChartLegend { !legend!.height!.contains(RegExp(r'[A-Z]')), 'Legend height must be number or percentage value, it should not contain any alphabets in the string.'); if (legend != null && legend!.isVisible!) { - legendCollections = <_LegendRenderContext>[]; + legendCollections = []; + legendItems = []; _calculateSeriesLegends(); // ignore: unnecessary_null_comparison assert(!(legend!.itemPadding != null) || legend!.itemPadding >= 0, @@ -57,9 +121,9 @@ class _ChartLegend { final num padding = legend!.itemPadding; chartLegend.isNeedScrollable = false; final bool isBottomOrTop = - legendRenderer._legendPosition == LegendPosition.bottom || - legendRenderer._legendPosition == LegendPosition.top; - legendRenderer._orientation = + legendRenderer.legendPosition == LegendPosition.bottom || + legendRenderer.legendPosition == LegendPosition.top; + legendRenderer.orientation = (legend!.orientation == LegendItemOrientation.auto) ? (isBottomOrTop ? LegendItemOrientation.horizontal @@ -67,16 +131,16 @@ class _ChartLegend { : legend!.orientation; maxRenderHeight = legend!.height != null - ? _percentageToValue(legend!.height, size.height) + ? percentageToValue(legend!.height, size.height) : isBottomOrTop - ? _percentageToValue('30%', size.height) + ? percentageToValue('30%', size.height) : size.height; maxRenderWidth = legend!.width != null - ? _percentageToValue(legend!.width, size.width) + ? percentageToValue(legend!.width, size.width) : isBottomOrTop ? size.width - : _percentageToValue('30%', size.width); + : percentageToValue('30%', size.width); // To reduce the container width based on offset. if (chartLegend.legend!.offset != null && (chartLegend.legend!.offset?.dx.isNegative == false)) { @@ -93,8 +157,8 @@ class _ChartLegend { final bool isTemplate = legend!.legendItemBuilder != null; final int length = isTemplate ? legendWidgetContext.length : legendCollections!.length; - late _MeasureWidgetContext legendContext; - late _LegendRenderContext legendRenderContext; + late MeasureWidgetContext legendContext; + late LegendRenderContext legendRenderContext; String legendText; Size textSize; // ignore: unnecessary_null_comparison @@ -138,7 +202,7 @@ class _ChartLegend { } shouldRenderLegend = true; bool needRender = false; - if (legendRenderer._orientation == LegendItemOrientation.horizontal) { + if (legendRenderer.orientation == LegendItemOrientation.horizontal) { if (legend!.overflowMode == LegendItemOverflowMode.wrap) { if ((legendWidth + currentWidth) > maxRenderWidth!) { legendWidth = currentWidth; @@ -205,27 +269,34 @@ class _ChartLegend { } /// To calculate legends in chart - void _calculateLegends( - SfCartesianChart chart, int index, CartesianSeriesRenderer seriesRenderer, + void _calculateLegends(SfCartesianChart chart, int index, + SeriesRendererDetails seriesRendererDetails, [Trendline? trendline, int? trendlineIndex]) { LegendRenderArgs? legendEventArgs; bool isTrendlineadded = false; TrendlineRenderer? trendlineRenderer; - final CartesianSeries series = seriesRenderer._series; - final _RenderingDetails _renderingDetails = _chartState._renderingDetails; + final CartesianSeries series = + seriesRendererDetails.series; + final CartesianStateProperties stateProperties = + this.stateProperties as CartesianStateProperties; + final RenderingDetails _renderingDetails = stateProperties.renderingDetails; + final List palette = stateProperties.chart.palette; if (trendline != null) { isTrendlineadded = true; - trendlineRenderer = seriesRenderer._trendlineRenderer[trendlineIndex!]; + trendlineRenderer = + seriesRendererDetails.trendlineRenderer[trendlineIndex!]; } - seriesRenderer._seriesName = seriesRenderer._seriesName ?? 'series $index'; + seriesRendererDetails.seriesName = + seriesRendererDetails.seriesName ?? 'series $index'; if (series.isVisibleInLegend && - (seriesRenderer._seriesName != null || series.legendItemText != null)) { + (seriesRendererDetails.seriesName != null || + series.legendItemText != null)) { if (chart.onLegendItemRender != null) { legendEventArgs = LegendRenderArgs(index); legendEventArgs.text = series.legendItemText ?? (isTrendlineadded - ? trendlineRenderer!._name! - : seriesRenderer._seriesName!); + ? trendlineRenderer!.name! + : seriesRendererDetails.seriesName!); legendEventArgs.legendIconType = isTrendlineadded ? trendline!.legendIconType : series.legendIconType; @@ -233,19 +304,19 @@ class _ChartLegend { isTrendlineadded ? trendline!.color : series.color; chart.onLegendItemRender!(legendEventArgs); } - final _LegendRenderContext legendRenderContext = _LegendRenderContext( - seriesRenderer: seriesRenderer, + final LegendRenderContext legendRenderContext = LegendRenderContext( + seriesRenderer: seriesRendererDetails, trendline: trendline, seriesIndex: index, trendlineIndex: isTrendlineadded ? trendlineIndex : null, - isSelect: _chartState._isTrendlineToggled == true - ? (!isTrendlineadded || !trendlineRenderer!._visible) + isSelect: stateProperties.isTrendlineToggled == true + ? (!isTrendlineadded || !trendlineRenderer!.visible) : !series.isVisible, text: legendEventArgs?.text ?? series.legendItemText ?? (isTrendlineadded - ? trendlineRenderer!._name! - : seriesRenderer._seriesName!), + ? trendlineRenderer!.name! + : seriesRendererDetails.seriesName!), iconColor: legendEventArgs?.color ?? (isTrendlineadded ? trendline!.color : series.color), isTrendline: isTrendlineadded, @@ -254,42 +325,85 @@ class _ChartLegend { ? trendline!.legendIconType : series.legendIconType)); legendCollections!.add(legendRenderContext); - if (!seriesRenderer._visible! && + final LegendItem legendItem = LegendItem( + text: legendRenderContext.text, + color: (legendRenderContext.iconColor ?? + palette[legendRenderContext.seriesIndex % palette.length]) + .withOpacity(legend!.opacity), + shader: _getCartesianSeriesGradientShader( + legendRenderContext, chart.legend), + imageProvider: legendRenderContext.iconType == LegendIconType.image && + chart.legend.image != null + ? chart.legend.image + : null, + iconType: legendRenderContext.iconType == LegendIconType.seriesType + ? _getEffectiveLegendIconType( + legendRenderContext.iconType, + legendRenderContext, + legendRenderContext.seriesRenderer.seriesType) + : _getEffectiveLegendIconType(legendRenderContext.iconType), + iconStrokeWidth: legendRenderContext.iconType == + LegendIconType.seriesType + ? (legendRenderContext.seriesRenderer.seriesType == 'line' || + legendRenderContext.seriesRenderer.seriesType == + 'fastline' || + legendRenderContext.seriesRenderer.seriesType + .contains('stackedline') == + true) + ? (chart.legend.iconBorderWidth > 0 + ? chart.legend.iconBorderWidth + : 3) + : ((legendRenderContext.seriesRenderer.seriesType == 'candle' || + legendRenderContext.seriesRenderer.seriesType == + 'boxandWhisker' || + legendRenderContext.seriesRenderer.seriesType.contains('hilo') == true) + ? (chart.legend.iconBorderWidth > 0 ? chart.legend.iconBorderWidth : 2) + : (legendRenderContext.seriesRenderer.seriesType == 'spline' || legendRenderContext.seriesRenderer.seriesType == 'stepline') + ? (chart.legend.iconBorderWidth > 0 ? chart.legend.iconBorderWidth : 1) + : null) + : ((legendRenderContext.iconType == LegendIconType.horizontalLine || legendRenderContext.iconType == LegendIconType.verticalLine) ? (chart.legend.iconBorderWidth > 0 ? chart.legend.iconBorderWidth : 2) : null), + overlayMarkerType: _getOverlayMarkerType(legendRenderContext)); + legendItems.add(legendItem); + if (seriesRendererDetails.visible! == false && series.isVisibleInLegend && (_renderingDetails.widgetNeedUpdate || _renderingDetails.initialRender!) && - (seriesRenderer._oldSeries == null || - (!series.isVisible && seriesRenderer._oldSeries!.isVisible))) { + (seriesRendererDetails.oldSeries == null || + (!series.isVisible && + seriesRendererDetails.oldSeries!.isVisible == true))) { legendRenderContext.isSelect = true; - if (_chartState._renderingDetails.legendToggleStates + if (stateProperties.renderingDetails.legendToggleStates .contains(legendRenderContext) == false) { - _chartState._renderingDetails.legendToggleStates + stateProperties.renderingDetails.legendToggleStates .add(legendRenderContext); } } else if (_renderingDetails.widgetNeedUpdate && - (seriesRenderer._oldSeries != null && + (seriesRendererDetails.oldSeries != null && (series.isVisible && - _chartState._legendToggling == false && - seriesRenderer._visible!))) { + stateProperties.legendToggling == false && + seriesRendererDetails.visible! == true))) { final List visibleSeriesRenderers = - _chartState._chartSeries.visibleSeriesRenderers; + stateProperties.chartSeries.visibleSeriesRenderers; final String legendItemText = - visibleSeriesRenderers[index]._series.legendItemText ?? + SeriesHelper.getSeriesRendererDetails(visibleSeriesRenderers[index]) + .series + .legendItemText ?? series.name ?? 'Series $index'; - final int seriesIndex = visibleSeriesRenderers.indexOf(seriesRenderer); - final List<_LegendRenderContext> legendToggle = <_LegendRenderContext>[] + final int seriesIndex = + visibleSeriesRenderers.indexOf(seriesRendererDetails.renderer); + final List legendToggle = [] //ignore: prefer_spread_collections - ..addAll(_chartState._renderingDetails.legendToggleStates); - for (final _LegendRenderContext legendContext - in _chartState._renderingDetails.legendToggleStates) { + ..addAll(stateProperties.renderingDetails.legendToggleStates); + for (final LegendRenderContext legendContext + in stateProperties.renderingDetails.legendToggleStates) { if (seriesIndex == legendContext.seriesIndex && legendContext.text == legendItemText) { legendToggle.remove(legendContext); } } - _chartState._renderingDetails.legendToggleStates = legendToggle; + stateProperties.renderingDetails.legendToggleStates = legendToggle; } } } @@ -299,27 +413,32 @@ class _ChartLegend { LegendRenderArgs? legendEventArgs; if (chart.legend.legendItemBuilder == null) { if (chart is SfCartesianChart) { + final CartesianStateProperties stateProperties = + this.stateProperties as CartesianStateProperties; for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - if (!seriesRenderer._isIndicator) { - _calculateLegends(chart, i, seriesRenderer); + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + if (seriesRendererDetails.isIndicator == false) { + _calculateLegends(chart, i, seriesRendererDetails); } - if (seriesRenderer is CartesianSeriesRenderer) { - final CartesianSeriesRenderer xYSeriesRenderer = seriesRenderer; + if (seriesRendererDetails.renderer is CartesianSeriesRenderer) { + final SeriesRendererDetails xYseriesRendererDetails = + seriesRendererDetails; // ignore: unnecessary_null_comparison - if (xYSeriesRenderer._series != null && - xYSeriesRenderer._series.trendlines != null) { + if (xYseriesRendererDetails.series != null && + xYseriesRendererDetails.series.trendlines != null) { for (int j = 0; - j < xYSeriesRenderer._series.trendlines!.length; + j < xYseriesRendererDetails.series.trendlines!.length; j++) { final Trendline trendline = - xYSeriesRenderer._series.trendlines![j]; + xYseriesRendererDetails.series.trendlines![j]; if (trendline.isVisibleInLegend) { - _chartState._renderingDetails.chartLegend._calculateLegends( - chart, i, xYSeriesRenderer, trendline, j); + stateProperties.renderingDetails.chartLegend + ._calculateLegends( + chart, i, xYseriesRendererDetails, trendline, j); } } } @@ -328,29 +447,84 @@ class _ChartLegend { if (chart.indicators.isNotEmpty == true) { _calculateIndicatorLegends(); } - } else if (_chartState._chartSeries.visibleSeriesRenderers.isNotEmpty == - true) { - final dynamic seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - final dynamic chartPoint = seriesRenderer._renderPoints[j]; - if (chart.onLegendItemRender != null) { - legendEventArgs = LegendRenderArgs(0, j); - legendEventArgs.text = chartPoint.x; - legendEventArgs.legendIconType = - seriesRenderer._series.legendIconType; - legendEventArgs.color = chartPoint.fill; - chart.onLegendItemRender(legendEventArgs); + } else { + final dynamic stateProperties = this.stateProperties; + if (stateProperties.chartSeries.visibleSeriesRenderers.isNotEmpty == + true) { + final dynamic seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { + final dynamic chartPoint = seriesRenderer.renderPoints[j]; + if (chart.onLegendItemRender != null) { + legendEventArgs = LegendRenderArgs(0, j); + legendEventArgs.text = chartPoint.x; + legendEventArgs.legendIconType = + seriesRenderer.series.legendIconType; + legendEventArgs.color = chartPoint.fill; + chart.onLegendItemRender(legendEventArgs); + } + + final LegendRenderContext legendRenderContext = LegendRenderContext( + seriesRenderer: seriesRenderer, + seriesIndex: j, + isSelect: false, + point: chartPoint, + text: legendEventArgs?.text ?? chartPoint.x, + iconColor: legendEventArgs?.color ?? chartPoint.fill, + iconType: legendEventArgs?.legendIconType ?? + seriesRenderer.series.legendIconType); + legendCollections!.add(legendRenderContext); + + double? degree; + double? pointStartAngle; + double? totalAngle; + double? pointEndAngle; + final bool isRadialBarSeries = legendRenderContext.iconType == + LegendIconType.seriesType && + legendRenderContext.seriesRenderer.seriesType == 'radialbar'; + if (isRadialBarSeries) { + pointStartAngle = -90; + totalAngle = 360; + _getSumOfPoints(seriesRenderer); + degree = + legendRenderContext.seriesRenderer.renderPoints[j].y.abs() / + (legendRenderContext.series.maximumValue ?? sumOfPoints); + degree = (degree! > 1 ? 1 : degree) * (totalAngle - 0.001); + pointEndAngle = pointStartAngle + degree; + } + + final LegendItem legendItem = LegendItem( + text: legendRenderContext.text, + color: (legendRenderContext.iconColor)! + .withOpacity(legend!.opacity), + shader: + _getCircularSeriesShader(legendRenderContext, chart.legend), + imageProvider: legendRenderContext.iconType == LegendIconType.image && + chart.legend.image != null + ? chart.legend.image + : null, + iconType: legendRenderContext.iconType == LegendIconType.seriesType + ? _getEffectiveLegendIconType( + legendRenderContext.iconType, + legendRenderContext, + legendRenderContext.seriesRenderer.seriesType) + : _getEffectiveLegendIconType( + legendRenderContext.iconType, legendRenderContext), + iconStrokeWidth: ((legendRenderContext.iconType == + LegendIconType.seriesType && + (legendRenderContext.seriesRenderer.seriesType == + 'radialbar' || + legendRenderContext.seriesRenderer.seriesType == + 'doughnut')) || + legendRenderContext.iconType == LegendIconType.horizontalLine || + legendRenderContext.iconType == LegendIconType.verticalLine) + ? (chart.legend.iconBorderWidth > 0 == true ? chart.legend.iconBorderWidth : 1) + : null, + degree: degree, + startAngle: pointStartAngle, + endAngle: pointEndAngle); + legendItems.add(legendItem); } - legendCollections!.add(_LegendRenderContext( - seriesRenderer: seriesRenderer, - seriesIndex: j, - isSelect: false, - point: chartPoint, - text: legendEventArgs?.text ?? chartPoint.x, - iconColor: legendEventArgs?.color ?? chartPoint.fill, - iconType: legendEventArgs?.legendIconType ?? - seriesRenderer._series.legendIconType)); } } } @@ -361,13 +535,16 @@ class _ChartLegend { LegendRenderArgs? legendEventArgs; final List textCollection = []; TechnicalIndicatorsRenderer? technicalIndicatorsRenderer; + final CartesianStateProperties stateProperties = + this.stateProperties as CartesianStateProperties; for (int i = 0; i < chart.indicators.length; i++) { final TechnicalIndicators indicator = chart.indicators[i]; - technicalIndicatorsRenderer = _chartState._technicalIndicatorRenderer[i]; - _chartState._chartSeries - ?._setIndicatorType(indicator, technicalIndicatorsRenderer); - textCollection.add(technicalIndicatorsRenderer!._indicatorType); + technicalIndicatorsRenderer = + stateProperties.technicalIndicatorRenderer[i]; + stateProperties.chartSeries + .setIndicatorType(indicator, technicalIndicatorsRenderer); + textCollection.add(technicalIndicatorsRenderer.indicatorType); } //ignore: prefer_collection_literals final Map _map = Map(); @@ -379,196 +556,344 @@ class _ChartLegend { for (int i = 0; i < chart.indicators.length; i++) { final TechnicalIndicators indicator = chart.indicators[i]; - technicalIndicatorsRenderer = _chartState._technicalIndicatorRenderer[i]; + technicalIndicatorsRenderer = + stateProperties.technicalIndicatorRenderer[i]; final int count = indicatorTextCollection - .contains(technicalIndicatorsRenderer?._indicatorType) - ? _chartState._chartSeries?._getIndicatorId(indicatorTextCollection, - technicalIndicatorsRenderer?._indicatorType) + .contains(technicalIndicatorsRenderer.indicatorType) + ? stateProperties.chartSeries.getIndicatorId(indicatorTextCollection, + technicalIndicatorsRenderer.indicatorType) : 0; - indicatorTextCollection.add(technicalIndicatorsRenderer!._indicatorType); - technicalIndicatorsRenderer._name = indicator.name ?? - (technicalIndicatorsRenderer._indicatorType + - (_map[technicalIndicatorsRenderer._indicatorType] == 1 + indicatorTextCollection.add(technicalIndicatorsRenderer.indicatorType); + technicalIndicatorsRenderer.name = indicator.name ?? + (technicalIndicatorsRenderer.indicatorType + + (_map[technicalIndicatorsRenderer.indicatorType] == 1 ? '' : ' ' + count.toString())); if (indicator.isVisible && indicator.isVisibleInLegend) { if (chart.onLegendItemRender != null) { legendEventArgs = LegendRenderArgs(i); legendEventArgs.text = - indicator.legendItemText ?? technicalIndicatorsRenderer._name; + indicator.legendItemText ?? technicalIndicatorsRenderer.name; legendEventArgs.legendIconType = indicator.legendIconType; legendEventArgs.color = indicator.signalLineColor; chart.onLegendItemRender(legendEventArgs); } - final _LegendRenderContext legendRenderContext = _LegendRenderContext( + final LegendRenderContext legendRenderContext = LegendRenderContext( seriesRenderer: indicator, - indicatorRenderer: _chartState._technicalIndicatorRenderer[i], + indicatorRenderer: stateProperties.technicalIndicatorRenderer[i], seriesIndex: - _chartState._chartSeries.visibleSeriesRenderers.length + i, + stateProperties.chartSeries.visibleSeriesRenderers.length + i, isSelect: !indicator.isVisible, text: legendEventArgs?.text ?? indicator.legendItemText ?? - technicalIndicatorsRenderer._name, + technicalIndicatorsRenderer.name, iconColor: legendEventArgs?.color ?? indicator.signalLineColor, iconType: legendEventArgs?.legendIconType ?? indicator.legendIconType); legendCollections!.add(legendRenderContext); + final LegendItem legendIndicatorItem = LegendItem( + text: legendRenderContext.text, + color: legendRenderContext.iconColor!.withOpacity(legend!.opacity), + imageProvider: legendRenderContext.iconType == LegendIconType.image && + chart.legend.image != null + ? chart.legend.image + : null, + iconType: legendRenderContext.series.legendIconType == + LegendIconType.seriesType + ? ShapeMarkerType.horizontalLine + : _getEffectiveLegendIconType( + legendRenderContext.iconType, legendRenderContext), + iconStrokeWidth: ((legendRenderContext.series + is TechnicalIndicators && + legendRenderContext.indicatorRenderer!.isIndicator) && + (legendRenderContext.iconType == LegendIconType.seriesType || + legendRenderContext.iconType == + LegendIconType.horizontalLine || + legendRenderContext.iconType == + LegendIconType.verticalLine)) + ? (chart.legend.iconBorderWidth > 0 == true + ? chart.legend.iconBorderWidth + : 2) + : null); + legendItems.add(legendIndicatorItem); if (!indicator.isVisible && indicator.isVisibleInLegend && - _chartState._renderingDetails.initialRender == true) { + stateProperties.renderingDetails.initialRender! == true) { legendRenderContext.isSelect = true; - _chartState._renderingDetails.legendToggleStates + stateProperties.renderingDetails.legendToggleStates .add(legendRenderContext); } } } } -} -class _LegendContainer extends StatelessWidget { - _LegendContainer({this.chartState}) : chart = chartState._chart; - - final dynamic chart; - final dynamic chartState; - - @override - Widget build(BuildContext context) { - final _ChartLegend chartLegend = chartState._renderingDetails.chartLegend; - final List<_LegendRenderContext> legendCollections = - chartLegend.legendCollections!; - final List legendWidgets = []; - final Legend legend = chart.legend; - num titleHeight = 0; - - final List<_MeasureWidgetContext> legendWidgetContext = - chartState._renderingDetails.legendWidgetContext; - chartLegend.legendRepaintNotifier = ValueNotifier(0); - if (legend.legendItemBuilder != null) { - for (int i = 0; i < legendWidgetContext.length; i++) { - final _MeasureWidgetContext legendRenderContext = - legendWidgetContext[i]; - if (!(legend.overflowMode == LegendItemOverflowMode.none) || - legendRenderContext.isRender) { - legendWidgets.add(_RenderLegend( - index: i, - template: legendRenderContext.widget!, - size: legendRenderContext.size!, - chartState: chartState)); - } - } - } else { - for (int i = 0; i < legendCollections.length; i++) { - final _LegendRenderContext legendRenderContext = legendCollections[i]; - if (!(legend.overflowMode == LegendItemOverflowMode.none) || - legendRenderContext.isRender) { - legendWidgets.add(_RenderLegend( - index: i, - size: legendRenderContext.size!, - chartState: chartState)); - } + /// To find sum of points in radial bar series + void _getSumOfPoints(CircularSeriesRendererExtension seriesRenderer) { + num sum = 0; + for (final ChartPoint point in seriesRenderer.renderPoints!) { + if (point.isVisible) { + sum += point.y!.abs(); } } + sumOfPoints = sum; + } + + /// To get the cartesian series gradient shader for SfLegend + Shader? _getCartesianSeriesGradientShader( + LegendRenderContext legendRenderContext, Legend legend) { + Shader? legendShader; + Shader? cartesianShader; + TrendlineRenderer? trendlineRenderer; - final bool needLegendTitle = - legend.title.text != null && legend.title.text!.isNotEmpty; + final Size size = Size(legend.iconWidth, legend.iconHeight); + final Rect pathRect = Rect.fromCenter( + center: Offset(size.width / 2, size.height / 2), + width: size.width, + height: size.height); + final Rect rectBounds = Offset.zero & size; + final String seriesType = legendRenderContext.seriesRenderer.seriesType; + final LinearGradient? gradientFill = legendRenderContext.series.gradient; + final Shader toggledLegendShader = + LinearGradient(colors: [toggledItemColor, toggledItemColor]) + .createShader(pathRect); - if (needLegendTitle) { - titleHeight = - measureText(legend.title.text!, legend.title.textStyle).height + 10; + if (legendRenderContext.trendline != null) { + trendlineRenderer = legendRenderContext.seriesRenderer + .trendlineRenderer[legendRenderContext.trendlineIndex!]; + } + legendRenderContext.isSelect = (legendRenderContext.trendline != null) + ? !trendlineRenderer!.visible + : legendRenderContext.seriesRenderer + is TechnicalIndicators + ? !legendRenderContext.indicatorRenderer!.visible! + : legendRenderContext.seriesRenderer.visible == false; + if (legendRenderContext.series is CartesianSeries && + legendRenderContext.series.onCreateShader != null && + !legendRenderContext.isSelect) { + ShaderDetails shaderDetails; + shaderDetails = ShaderDetails(pathRect, 'legend'); + legendShader = legendRenderContext.series.onCreateShader(shaderDetails); } - return _getWidget(legendWidgets, needLegendTitle, titleHeight); + + // ignore: prefer_if_null_operators + cartesianShader = legendShader == null + ? !seriesType.contains('line') && + (legendRenderContext.series is CartesianSeries && + legendRenderContext.series.gradient != null && + !legendRenderContext.isTrendline!) + ? !legendRenderContext.isSelect + ? gradientFill!.createShader(rectBounds) + : toggledLegendShader + : null + : !legendRenderContext.isSelect + ? legendShader + : toggledLegendShader; + + return cartesianShader; } - Widget _getWidget( - List legendWidgets, bool needLegendTitle, num titleHeight) { - Widget widget; - final Legend legend = chart.legend; - final _RenderingDetails renderingDetails = chartState._renderingDetails; - final _ChartLegend chartLegend = chartState._renderingDetails.chartLegend; - final LegendRenderer legedRenderer = - chartState._renderingDetails.legendRenderer; - final double legendHeight = chartLegend.legendSize.height; - if (chartLegend.isNeedScrollable) { - widget = Container( - height: needLegendTitle - ? (legendHeight - titleHeight).abs() - : legendHeight, - child: SingleChildScrollView( - scrollDirection: - legedRenderer._orientation == LegendItemOrientation.horizontal - ? Axis.vertical - : Axis.horizontal, - child: legedRenderer._orientation == - LegendItemOrientation.horizontal - ? Wrap(direction: Axis.horizontal, children: legendWidgets) - : Wrap(direction: Axis.vertical, children: legendWidgets))); - } else if (legend.overflowMode == LegendItemOverflowMode.scroll) { - widget = Container( - height: needLegendTitle - ? (legendHeight - titleHeight).abs() - : legendHeight, - child: SingleChildScrollView( - scrollDirection: - legedRenderer._orientation == LegendItemOrientation.horizontal - ? Axis.horizontal - : Axis.vertical, - child: - legedRenderer._orientation == LegendItemOrientation.horizontal - ? Row(children: legendWidgets) - : Column(children: legendWidgets))); - } else if (legend.overflowMode == LegendItemOverflowMode.none) { - widget = Container( - height: needLegendTitle - ? (legendHeight - titleHeight).abs() - : legendHeight, - child: legedRenderer._orientation == LegendItemOrientation.horizontal - ? Row(children: legendWidgets) - : Column(children: legendWidgets)); - } else { - widget = Container( - height: needLegendTitle - ? (legendHeight - titleHeight).abs() - : legendHeight, - width: chartLegend.legendSize.width, - child: Wrap( - direction: - legedRenderer._orientation == LegendItemOrientation.horizontal - ? Axis.horizontal - : Axis.vertical, - children: legendWidgets)); + /// To get the circular series shader for SfLegend + Shader? _getCircularSeriesShader( + LegendRenderContext legendRenderContext, Legend legend) { + Shader? legendShader; + Shader? shader; + ChartShaderMapper? pointShaderMapper; + final Size size = Size(legend.iconWidth, legend.iconHeight); + final Rect pathRect = Rect.fromCenter( + center: Offset(size.width / 2, size.height / 2), + width: size.width, + height: size.height); + final Shader toggledLegendShader = + LinearGradient(colors: [toggledItemColor, toggledItemColor]) + .createShader(pathRect); + legendRenderContext.isSelect = legendRenderContext.point.isVisible == false; + if (legendRenderContext.series is CircularSeries && + stateProperties.chart.onCreateShader != null) { + ChartShaderDetails chartShaderDetails; + chartShaderDetails = ChartShaderDetails(pathRect, null, 'legend'); + legendShader = stateProperties.chart.onCreateShader(chartShaderDetails); + } + if (legendRenderContext.series is CircularSeries) { + pointShaderMapper = + PointHelper.getPointShaderMapper(legendRenderContext.point); } - if (needLegendTitle) { - final ChartAlignment titleAlign = legend.title.alignment; - final Color color = chart.legend.title.textStyle.color ?? - renderingDetails.chartTheme.legendTitleColor; - final double fontSize = chart.legend.title.textStyle.fontSize; - final String fontFamily = chart.legend.title.textStyle.fontFamily; - final FontStyle fontStyle = chart.legend.title.textStyle.fontStyle; - final FontWeight? fontWeight = chart.legend.title.textStyle.fontWeight; - widget = Container( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Container( - height: titleHeight.toDouble(), - alignment: titleAlign == ChartAlignment.center - ? Alignment.center - : titleAlign == ChartAlignment.near - ? Alignment.centerLeft - : Alignment.centerRight, - child: Container( - child: Text(legend.title.text!, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: color, - fontSize: fontSize, - fontFamily: fontFamily, - fontStyle: fontStyle, - fontWeight: fontWeight)), - )), - widget - ])); + + shader = legendRenderContext.series is CircularSeries && + (pointShaderMapper != null || legendShader != null) + ? pointShaderMapper != null + ? !legendRenderContext.isSelect + ? pointShaderMapper(null, legendRenderContext.point?.index, + legendRenderContext.point?.fill, pathRect) + : toggledLegendShader + : !legendRenderContext.isSelect + ? legendShader + : toggledLegendShader + : null; + return shader; + } + + /// To get overlayMarker type for line series type legend icons. + ShapeMarkerType? _getOverlayMarkerType( + LegendRenderContext? legendRenderContext) { + ShapeMarkerType? overlayMarkerType; + if (legendRenderContext!.iconType == LegendIconType.seriesType) { + overlayMarkerType = (legendRenderContext.seriesRenderer.seriesType == + 'line' || + legendRenderContext.seriesRenderer.seriesType == 'fastline' || + legendRenderContext.seriesRenderer.seriesType + .contains('stackedline') == + true) && + legendRenderContext.series.markerSettings.isVisible == true && + legendRenderContext.series.markerSettings.shape != + DataMarkerType.image + ? _getMarkerIconType(legendRenderContext.series.markerSettings.shape) + : null; + } + return overlayMarkerType; + } + + /// To get legend icon shape based on series marker shape + ShapeMarkerType _getMarkerIconType(DataMarkerType shape) { + ShapeMarkerType? iconType; + switch (shape) { + case DataMarkerType.circle: + iconType = ShapeMarkerType.circle; + break; + case DataMarkerType.rectangle: + iconType = ShapeMarkerType.rectangle; + break; + case DataMarkerType.image: + iconType = ShapeMarkerType.image; + break; + case DataMarkerType.pentagon: + iconType = ShapeMarkerType.pentagon; + break; + case DataMarkerType.verticalLine: + iconType = ShapeMarkerType.verticalLine; + break; + case DataMarkerType.invertedTriangle: + iconType = ShapeMarkerType.invertedTriangle; + break; + case DataMarkerType.horizontalLine: + iconType = ShapeMarkerType.horizontalLine; + break; + case DataMarkerType.diamond: + iconType = ShapeMarkerType.diamond; + break; + case DataMarkerType.triangle: + iconType = ShapeMarkerType.triangle; + break; + case DataMarkerType.none: + break; + } + return iconType!; + } + + /// To get the legend icon type for SfLegend + ShapeMarkerType _getEffectiveLegendIconType(LegendIconType iconType, + [LegendRenderContext? legendRenderContext, String? seriesType]) { + ShapeMarkerType legendIconType; + switch (iconType) { + case LegendIconType.circle: + legendIconType = ShapeMarkerType.circle; + break; + case LegendIconType.rectangle: + legendIconType = ShapeMarkerType.rectangle; + break; + case LegendIconType.pentagon: + legendIconType = ShapeMarkerType.pentagon; + break; + case LegendIconType.verticalLine: + legendIconType = ShapeMarkerType.verticalLine; + break; + case LegendIconType.horizontalLine: + legendIconType = ShapeMarkerType.horizontalLine; + break; + case LegendIconType.diamond: + legendIconType = ShapeMarkerType.diamond; + break; + case LegendIconType.triangle: + legendIconType = ShapeMarkerType.triangle; + break; + case LegendIconType.invertedTriangle: + legendIconType = ShapeMarkerType.invertedTriangle; + break; + case LegendIconType.image: + legendIconType = ShapeMarkerType.image; + break; + case LegendIconType.seriesType: + legendIconType = + _getSeriesLegendIconType(seriesType!, legendRenderContext!); + break; + } + return legendIconType; + } + + /// To get effective series type legend icon for SfLegend + ShapeMarkerType _getSeriesLegendIconType( + String seriesType, LegendRenderContext context) { + switch (seriesType) { + case 'line': + case 'fastline': + case 'stackedline': + case 'stackedline100': + return context.series.dashArray[0] != 0 + ? ShapeMarkerType.lineSeriesWithDashArray + : ShapeMarkerType.lineSeries; + case 'spline': + return context.series.dashArray[0] != 0 + ? ShapeMarkerType.splineSeriesWithDashArray + : ShapeMarkerType.splineSeries; + case 'splinearea': + case 'splinerangearea': + return ShapeMarkerType.splineAreaSeries; + case 'bar': + case 'stackedbar': + case 'stackedbar100': + return ShapeMarkerType.barSeries; + case 'column': + case 'stackedcolumn': + case 'stackedcolumn100': + case 'rangecolumn': + case 'histogram': + return ShapeMarkerType.columnSeries; + case 'area': + case 'stackedarea': + case 'rangearea': + case 'stackedarea100': + return ShapeMarkerType.areaSeries; + case 'stepline': + return context.series.dashArray[0] != 0 + ? ShapeMarkerType.stepLineSeriesWithDashArray + : ShapeMarkerType.stepLineSeries; + case 'scatter': + return _getMarkerIconType(context.series.markerSettings.shape); + case 'bubble': + return ShapeMarkerType.bubbleSeries; + case 'hilo': + return ShapeMarkerType.hiloSeries; + case 'hiloopenclose': + case 'candle': + return ShapeMarkerType.hiloOpenCloseSeries; + case 'waterfall': + case 'boxandwhisker': + return ShapeMarkerType.waterfallSeries; + case 'pie': + return ShapeMarkerType.pieSeries; + case 'doughnut': + return ShapeMarkerType.doughnutSeries; + case 'radialbar': + return ShapeMarkerType.radialBarSeries; + case 'steparea': + return ShapeMarkerType.stepAreaSeries; + case 'pyramid': + return ShapeMarkerType.pyramidSeries; + case 'funnel': + return ShapeMarkerType.funnelSeries; + case 'errorbar': + return ShapeMarkerType.verticalLine; + default: + return ShapeMarkerType.circle; } - return widget; } } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart b/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart index 0ae4a1866..4f5d6d20e 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/legend/renderer.dart @@ -1,721 +1,14 @@ -part of charts; - -abstract class _CustomizeLegend { - /// To draw legend items - void drawLegendItem(int index, _LegendRenderContext legendItem, Legend legend, - dynamic chartState, Canvas canvas, Size size); - - /// To get legend text color - Color getLegendTextColor( - int index, _LegendRenderContext legendItem, Color textColor); - - /// To get legend icon color - Color getLegendIconColor( - int index, _LegendRenderContext legendItem, Color iconColor); - - /// To get legend icon border color - Color getLegendIconBorderColor( - int index, _LegendRenderContext legendItem, Color iconBorderColor); - - /// To get legend icon border width - double getLegendIconBorderWidth( - int index, _LegendRenderContext legendItem, double iconBorderWidth); -} - -class _LegendRenderer with _CustomizeLegend { - /// To return legend icon border color - @override - Color getLegendIconBorderColor( - int index, _LegendRenderContext legendItem, Color iconBorderColor) => - iconBorderColor; - - /// To return legend icon color - @override - Color getLegendIconColor( - int index, _LegendRenderContext legendItem, Color iconColor) => - iconColor; - - /// To return legend text color - @override - Color getLegendTextColor( - int index, _LegendRenderContext legendItem, Color textColor) => - textColor; - - /// To return legend icon border width - @override - double getLegendIconBorderWidth( - int index, _LegendRenderContext legendItem, double iconBorderWidth) => - iconBorderWidth; - - /// To draw legend chart items in chart - @override - void drawLegendItem(int index, _LegendRenderContext legendItem, Legend legend, - dynamic chartState, Canvas canvas, Size size) { - final LegendRenderer legendRenderer = - chartState._renderingDetails.legendRenderer; - final dynamic chart = chartState._chart; - final String legendText = legendItem.text; - final List palette = chartState._chart.palette; - TrendlineRenderer? trendlineRenderer; - Color color = legendItem.iconColor ?? - palette[legendItem.seriesIndex % palette.length]; - color = - legendRenderer._renderer.getLegendIconColor(index, legendItem, color); - final Size textSize = legendItem.textSize!; - final Offset iconOffset = - Offset(legend.itemPadding + legend.iconWidth / 2, size.height / 2); - if (legendItem.trendline != null) { - trendlineRenderer = legendItem - .seriesRenderer._trendlineRenderer[legendItem.trendlineIndex]; - } - legendItem.isSelect = chart is SfCartesianChart - ? ((legendItem.trendline != null) - ? !trendlineRenderer!._visible - : legendItem.seriesRenderer is TechnicalIndicators - ? !legendItem.indicatorRenderer!._visible! - : legendItem.seriesRenderer._visible == false) - : legendItem.point.isVisible == false; - final Color legendTextColor = - chartState._renderingDetails.chartTheme.legendTextColor; - final TextStyle textStyle = legendItem.isSelect - ? _getTextStyle( - textStyle: legend.textStyle, - takeFontColorValue: true, - fontColor: const Color.fromRGBO(211, 211, 211, 1)) - : _getTextStyle( - textStyle: legend.textStyle, - fontColor: legendRenderer._renderer - .getLegendTextColor(index, legendItem, legendTextColor)); - canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height)); - _drawLegendShape( - index, - iconOffset, - canvas, - Size(legend.iconWidth.toDouble(), legend.iconHeight.toDouble()), - legend, - legendItem.iconType, - color, - legendItem, - chartState); - _drawText( - canvas, - legendText, - Offset(iconOffset.dx + legend.padding + legend.iconWidth / 2, - (size.height / 2) - textSize.height / 2), - textStyle); - } - - /// To get legend icon shape - LegendIconType _getIconType(DataMarkerType shape) { - LegendIconType? iconType; - switch (shape) { - case DataMarkerType.circle: - iconType = LegendIconType.circle; - break; - case DataMarkerType.rectangle: - iconType = LegendIconType.rectangle; - break; - case DataMarkerType.image: - iconType = LegendIconType.image; - break; - case DataMarkerType.pentagon: - iconType = LegendIconType.pentagon; - break; - case DataMarkerType.verticalLine: - iconType = LegendIconType.verticalLine; - break; - case DataMarkerType.invertedTriangle: - iconType = LegendIconType.invertedTriangle; - break; - case DataMarkerType.horizontalLine: - iconType = LegendIconType.horizontalLine; - break; - case DataMarkerType.diamond: - iconType = LegendIconType.diamond; - break; - case DataMarkerType.triangle: - iconType = LegendIconType.triangle; - break; - case DataMarkerType.none: - break; - } - return iconType!; - } - - /// To draw legend icon shapes - void _drawLegendShape( - int index, - Offset location, - Canvas canvas, - Size size, - Legend legend, - LegendIconType iconType, - Color color, - _LegendRenderContext legendRenderContext, - dynamic chartState) { - final Path path = Path(); - final LegendIconType actualIconType = iconType; - final LegendRenderer legendRenderer = - chartState._renderingDetails.legendRenderer; - PaintingStyle style = PaintingStyle.fill; - final String seriesType = legendRenderContext.seriesRenderer - is TechnicalIndicators - ? legendRenderContext.indicatorRenderer!._seriesType - : legendRenderContext.seriesRenderer._seriesType; - iconType = _getLegendIconType(iconType, legendRenderContext); - final double width = (legendRenderContext.series.legendIconType == - LegendIconType.seriesType && - (seriesType == 'line' || - seriesType == 'fastline' || - seriesType.contains('stackedline'))) - ? size.width / 1.5 - : size.width; - final double height = (legendRenderContext.series.legendIconType == - LegendIconType.seriesType && - (seriesType == 'line' || - seriesType == 'fastline' || - seriesType.contains('stackedline'))) - ? size.height / 1.5 - : size.height; - Shader? _legendShader; - final Rect _pathRect = Rect.fromLTWH( - location.dx - width / 2, location.dy - height / 2, width, height); - if (legendRenderContext.series is CircularSeries && - chartState._chart.onCreateShader != null) { - ChartShaderDetails chartShaderDetails; - chartShaderDetails = ChartShaderDetails(_pathRect, null, 'legend'); - _legendShader = chartState._chart.onCreateShader(chartShaderDetails); - } - style = _getPathAndStyle(iconType, style, path, location, width, height, - legendRenderContext.seriesRenderer, chartState, canvas); - // ignore: unnecessary_null_comparison - assert(!(legend.iconBorderWidth != null) || legend.iconBorderWidth >= 0, - 'The icon border width of legend should not be less than 0.'); - // ignore: unnecessary_null_comparison - if (color != null) { - final Paint fillPaint = Paint() - ..color = !legendRenderContext.isSelect - ? (color == Colors.transparent - ? color - : color.withOpacity(legend.opacity)) - : const Color.fromRGBO(211, 211, 211, 1) - ..strokeWidth = legend.iconBorderWidth > 0 - ? legend.iconBorderWidth - : (seriesType.contains('hilo') || - seriesType == 'candle' || - seriesType == 'boxandwhisker' || - (legendRenderContext.series is TechnicalIndicators && - legendRenderContext.indicatorRenderer!._isIndicator)) - ? 2 - : 1 - ..style = (iconType == LegendIconType.seriesType) - ? style - : (iconType == LegendIconType.horizontalLine || - iconType == LegendIconType.verticalLine - ? PaintingStyle.stroke - : PaintingStyle.fill); - final String _seriesType = seriesType; - if (legendRenderContext.series is CartesianSeries && - legendRenderContext.series.gradient != null && - !legendRenderContext.isTrendline! && - (iconType == LegendIconType.horizontalLine || - iconType == LegendIconType.verticalLine) && - !legendRenderContext.isSelect) { - fillPaint.color = legendRenderContext.series.gradient.colors.first; - } - if ((actualIconType == LegendIconType.seriesType && - (_seriesType == 'line' || - _seriesType == 'fastline' || - _seriesType.contains('stackedline'))) || - (iconType == LegendIconType.seriesType && - (_seriesType == 'radialbar' || _seriesType == 'doughnut'))) { - _drawIcon( - iconType, - index, - _seriesType, - legendRenderContext, - chartState, - width, - height, - location, - size, - canvas, - fillPaint, - path, - legendRenderContext.point?.shader ?? _legendShader); - } else { - (legendRenderContext.series.legendIconType == - LegendIconType.seriesType && - (_seriesType == 'spline' || _seriesType == 'stepline') && - legendRenderContext.series.dashArray[0] != 0) - ? canvas.drawPath( - !kIsWeb - ? _dashPath(path, - dashArray: - _CircularIntervalList([3, 2]))! - : path, - fillPaint) - : canvas.drawPath( - path, - (legendRenderContext.series is CartesianSeries && - !legendRenderContext.isSelect && - legendRenderContext.series.gradient != null && - !legendRenderContext.isTrendline! && - !(iconType == LegendIconType.horizontalLine || - iconType == LegendIconType.verticalLine)) - ? _getLinearGradientPaint( - legendRenderContext.series.gradient, - path.getBounds(), - chartState._requireInvertedAxis) - : (legendRenderContext.series is CircularSeries && - !legendRenderContext.isSelect && - legendRenderContext.point?.center != null && - (legendRenderContext.point?.shader != null || - _legendShader != null) - ? _getShaderPaint( - legendRenderContext.point?.shader ?? _legendShader) - : fillPaint)); - } - } - final double iconBorderWidth = legendRenderer._renderer - .getLegendIconBorderWidth( - index, legendRenderContext, legend.iconBorderWidth); - // ignore: unnecessary_null_comparison - if (iconBorderWidth != null && iconBorderWidth > 0) { - final Paint strokePaint = Paint() - ..color = !legendRenderContext.isSelect - ? legendRenderer._renderer.getLegendIconBorderColor( - index, - legendRenderContext, - legend.iconBorderColor.withOpacity(legend.opacity)) - : const Color.fromRGBO(211, 211, 211, 1) - ..strokeWidth = iconBorderWidth - ..style = PaintingStyle.stroke; - canvas.drawPath(path, strokePaint); - } - } - - /// To get legend icon type - LegendIconType _getLegendIconType( - LegendIconType iconType, _LegendRenderContext legendRenderContext) { - if (legendRenderContext.series is TechnicalIndicators && - legendRenderContext.indicatorRenderer!._isIndicator) { - return legendRenderContext.series.legendIconType == - LegendIconType.seriesType - ? LegendIconType.horizontalLine - : iconType; - } else { - final String seriesType = legendRenderContext.seriesRenderer._seriesType; - return seriesType == 'scatter' - ? (iconType != LegendIconType.seriesType - ? iconType - : _getIconType(legendRenderContext.series.markerSettings.shape)) - : (iconType == LegendIconType.seriesType && - (seriesType == 'line' || - seriesType == 'fastline' || - seriesType.contains('stackedline')) && - legendRenderContext.series.markerSettings.isVisible == true && - legendRenderContext.series.markerSettings.shape != - DataMarkerType.image - ? _getIconType(legendRenderContext.series.markerSettings.shape) - : iconType); - } - } - - /// To get path and painting style for legends - PaintingStyle _getPathAndStyle( - LegendIconType iconType, - PaintingStyle style, - Path path, - Offset location, - double width, - double height, - dynamic seriesRenderer, - dynamic chartState, - Canvas canvas) { - final double x = location.dx - - (iconType == LegendIconType.image ? 0 : width / 2).toDouble(); - final double y = location.dy - - (iconType == LegendIconType.image ? 0 : height / 2).toDouble(); - final Rect rect = Rect.fromLTWH(x, y, width, height); - switch (iconType) { - case LegendIconType.seriesType: - style = _calculateLegendShapes(path, rect, seriesRenderer._seriesType); - break; - case LegendIconType.circle: - ShapePainter.getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.circle); - break; - - case LegendIconType.rectangle: - ShapePainter.getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.rectangle); - break; - case LegendIconType.image: - { - /// To draw legend image - void _drawLegendImage(Canvas canvas, dart_ui.Image image) { - final Rect rect = - Rect.fromLTWH(x - width / 2, y - height / 2, width, height); - paintImage( - canvas: canvas, rect: rect, image: image, fit: BoxFit.fill); - } - - if (chartState._chart.legend.image != null && - chartState._legendIconImage != null) { - _drawLegendImage(canvas, chartState._legendIconImage); - } else if (seriesRenderer._seriesType == 'scatter' && - seriesRenderer._series.markerSettings.shape == - DataMarkerType.image && - seriesRenderer._markerSettingsRenderer._image != null) { - _drawLegendImage( - canvas, seriesRenderer._markerSettingsRenderer._image); - } - break; - } - case LegendIconType.pentagon: - ShapePainter.getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.pentagon); - break; - - case LegendIconType.verticalLine: - ShapePainter.getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.verticalLine); - break; - - case LegendIconType.invertedTriangle: - ShapePainter.getShapesPath( - path: path, - rect: rect, - shapeType: ShapeMarkerType.invertedTriangle); - break; - - case LegendIconType.horizontalLine: - ShapePainter.getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.horizontalLine); - break; - - case LegendIconType.diamond: - ShapePainter.getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.diamond); - break; - - case LegendIconType.triangle: - ShapePainter.getShapesPath( - path: path, rect: rect, shapeType: ShapeMarkerType.triangle); - break; - } - return style; - } - - /// To draw legend icon - void _drawIcon( - LegendIconType iconType, - int index, - String seriesType, - _LegendRenderContext legendRenderContext, - dynamic chartState, - double width, - double height, - Offset location, - Size size, - Canvas canvas, - Paint fillPaint, - Path path, - Shader? shader) { - final dynamic chart = chartState._chart; - final double x = location.dx - width / 2; - final double y = location.dy - height / 2; - final Rect rect = Rect.fromLTWH(x, y, width, height); - if (seriesType.contains('line')) { - if (iconType != LegendIconType.seriesType) { - canvas.drawPath(path, fillPaint); - } - final Path linePath = Path(); - linePath.moveTo(location.dx - size.width / 1.5, location.dy); - linePath.lineTo(location.dx + size.width / 1.5, location.dy); - final Paint paint = Paint() - ..color = fillPaint.color.withOpacity(chart.legend.opacity) - ..style = PaintingStyle.stroke - ..strokeWidth = chart.legend.iconBorderWidth > 0 == true - ? chart.legend.iconBorderWidth - : 3; - legendRenderContext.series.dashArray[0] != 0 - ? canvas.drawPath( - !kIsWeb - ? _dashPath(linePath, - dashArray: _CircularIntervalList([3, 2]))! - : linePath, - paint) - : canvas.drawPath(linePath, paint); - } else if (seriesType == 'radialbar') { - final double radius = (width + height) / 2; - _drawPath( - canvas, - _StyleOptions( - fill: Colors.grey[100]!, - strokeWidth: fillPaint.strokeWidth, - strokeColor: Colors.grey[300]!.withOpacity(0.5)), - ShapePainter.getShapesPath( - rect: rect, - shapeType: ShapeMarkerType.radialBarSeries, - borderPaint: Paint(), - radius: radius)); - const double pointStartAngle = -90; - double degree = - legendRenderContext.seriesRenderer._renderPoints[index].y.abs() / - (legendRenderContext.series.maximumValue ?? - legendRenderContext - .seriesRenderer._segmentRenderingValues['sumOfPoints']!); - degree = (degree > 1 ? 1 : degree) * (360 - 0.001); - final double pointEndAngle = pointStartAngle + degree; - _drawPath( - canvas, - _StyleOptions( - fill: fillPaint.color, - strokeWidth: fillPaint.strokeWidth, - strokeColor: Colors.transparent), - ShapePainter.getShapesPath( - rect: rect, - shapeType: ShapeMarkerType.radialBarSeries, - radius: radius, - startAngle: pointStartAngle, - degree: degree, - endAngle: pointEndAngle), - null, - !legendRenderContext.isSelect ? shader : null); - } else { - final double radius = (width + height) / 2; - _drawPath( - canvas, - _StyleOptions( - fill: fillPaint.color, - strokeWidth: fillPaint.strokeWidth, - strokeColor: Colors.grey[300]!.withOpacity(0.5)), - ShapePainter.getShapesPath( - rect: rect, - shapeType: ShapeMarkerType.doughnutSeries, - borderPaint: Paint(), - radius: radius), - null, - !legendRenderContext.isSelect ? shader : null); - _drawPath( - canvas, - _StyleOptions( - fill: fillPaint.color, - strokeWidth: fillPaint.strokeWidth, - strokeColor: Colors.grey[300]!.withOpacity(0.5)), - ShapePainter.getShapesPath( - rect: rect, - shapeType: ShapeMarkerType.doughnutSeries, - radius: radius), - null, - !legendRenderContext.isSelect ? shader : null); - } - } -} - -class _RenderLegend extends StatelessWidget { - _RenderLegend( - {required this.index, required this.size, this.chartState, this.template}) - : chart = chartState._chart; - - final int index; - - final Size size; - - final dynamic chartState; - - final dynamic chart; - - final Widget? template; - - @override - Widget build(BuildContext context) { - bool? isSelect; - if (chart.legend.legendItemBuilder != null) { - final _MeasureWidgetContext _measureWidgetContext = - chartState._renderingDetails.legendWidgetContext[index]; - isSelect = chart is SfCartesianChart - ? chartState - ._chartSeries - .visibleSeriesRenderers[_measureWidgetContext.seriesIndex] - ._visible - : chartState - ._chartSeries - .visibleSeriesRenderers[_measureWidgetContext.seriesIndex] - ._renderPoints[_measureWidgetContext.pointIndex] - .isVisible; - } - final _ChartLegend chartLegend = chartState._renderingDetails.chartLegend; - final LegendRenderer legendRenderer = - chartState._renderingDetails.legendRenderer; - return Container( - height: size.height, - width: legendRenderer._orientation == LegendItemOrientation.vertical && - (chart.legend.overflowMode == LegendItemOverflowMode.scroll || - chart.legend.overflowMode == LegendItemOverflowMode.none) - ? chartLegend.legendSize.width - : size.width, - child: HandCursor( - child: GestureDetector( - onTapUp: (TapUpDetails details) { - if (chart is SfCartesianChart) { - _processCartesianSeriesToggle(); - } else { - _processCircularPointsToggle(); - } - }, - child: template != null - ? !isSelect! - ? Opacity(child: template, opacity: 0.5) - : template - : CustomPaint( - painter: _ChartLegendPainter( - chartState: chartState, - legendIndex: index, - isSelect: - chartLegend.legendCollections![index].isSelect, - notifier: chartLegend.legendRepaintNotifier))))); - } - - /// To process chart on circular chart legend toggle - void _processCircularPointsToggle() { - LegendTapArgs legendTapArgs; - const int seriesIndex = 0; - final _ChartLegend chartLegend = chartState._renderingDetails.chartLegend; - if (chart.onLegendTapped != null) { - if (chart != null) { - legendTapArgs = LegendTapArgs(chart.series, seriesIndex, index); - } else { - legendTapArgs = - LegendTapArgs(chart._series[seriesIndex], seriesIndex, index); - } - chart.onLegendTapped(legendTapArgs); - } - if (chart.legend.toggleSeriesVisibility == true) { - if (chart.legend.legendItemBuilder != null) { - final _MeasureWidgetContext legendWidgetContext = - chartState._renderingDetails.legendWidgetContext[index]; - _legendToggleTemplateState(legendWidgetContext, chartState, ''); - } else { - _legendToggleState(chartLegend.legendCollections![index], chartState); - } - chartState._renderingDetails.isLegendToggled = true; - chartState._redraw(); - } - } - - /// To process chart on cartesian series toggle - void _processCartesianSeriesToggle() { - LegendTapArgs legendTapArgs; - _MeasureWidgetContext _measureWidgetContext; - _LegendRenderContext _legendRenderContext; - if (chart.onLegendTapped != null) { - if (chart.legend.legendItemBuilder != null) { - _measureWidgetContext = - chartState._renderingDetails.legendWidgetContext[index]; - legendTapArgs = LegendTapArgs( - chartState._chartSeries - .visibleSeriesRenderers[_measureWidgetContext.seriesIndex], - _measureWidgetContext.seriesIndex!, - 0); - } else { - _legendRenderContext = - chartState._renderingDetails.chartLegend.legendCollections[index]; - legendTapArgs = LegendTapArgs( - _legendRenderContext.series, _legendRenderContext.seriesIndex, 0); - } - chart.onLegendTapped(legendTapArgs); - } - if (chart.legend.toggleSeriesVisibility == true) { - if (chart.legend.legendItemBuilder != null) { - _legendToggleTemplateState( - chartState._renderingDetails.legendWidgetContext[index], - chartState, - ''); - } else { - _cartesianLegendToggleState( - chartState._renderingDetails.chartLegend.legendCollections[index], - chartState); - } - chartState._renderingDetails.isLegendToggled = true; - chartState._renderingDetails.isImageDrawn = false; - chartState._legendToggling = true; - chartState._redraw(); - } - } -} - -class _ChartLegendStylePainter extends CustomPainter { - _ChartLegendStylePainter({this.chartState}) : chart = chartState._chart; - - final dynamic chartState; - - final dynamic chart; - - /// To paint legend - @override - void paint(Canvas canvas, Size size) { - final Legend legend = chart.legend; - final Color legendBackgroundColor = - chartState._renderingDetails.chartTheme.legendBackgroundColor; - if (legend.backgroundColor != null) { - canvas.drawRect( - Rect.fromLTWH(0, 0, size.width, size.height), - Paint() - ..color = legend.backgroundColor ?? legendBackgroundColor - ..style = PaintingStyle.fill); - } - // ignore: unnecessary_null_comparison - if (legend.borderColor != null && legend.borderWidth > 0) { - canvas.drawRect( - Rect.fromLTWH(0, 0, size.width, size.height), - Paint() - ..color = legend.borderColor - ..strokeWidth = legend.borderWidth - ..style = PaintingStyle.stroke); - } - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) => true; -} - -class _ChartLegendPainter extends CustomPainter { - _ChartLegendPainter( - {required this.chartState, - required this.legendIndex, - required this.isSelect, - required ValueNotifier notifier}) - : chart = chartState._chart, - super(repaint: notifier); - - final dynamic chartState; - - final dynamic chart; - - final int legendIndex; - - final bool isSelect; - - @override - void paint(Canvas canvas, Size size) { - final Legend legend = chart.legend; - final LegendRenderer legendRenderer = - chartState._renderingDetails.legendRenderer; - final _LegendRenderContext legendRenderContext = - chartState._renderingDetails.chartLegend.legendCollections[legendIndex]; - legendRenderer._renderer.drawLegendItem( - legendIndex, legendRenderContext, legend, chartState, canvas, size); - } - - @override - bool shouldRepaint(_ChartLegendPainter oldDelegate) => true; -} - -class _LegendRenderContext { - _LegendRenderContext( +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/technical_indicators/technical_indicator.dart'; +import '../../chart/trendlines/trendlines.dart'; +import '../utils/enum.dart'; + +/// Represents the legend render context chart +class LegendRenderContext { + /// Creates an instance of legend render context + LegendRenderContext( {this.size, required this.text, this.textSize, @@ -731,37 +24,55 @@ class _LegendRenderContext { this.indicatorRenderer}) : series = seriesRenderer is TechnicalIndicators ? seriesRenderer - : seriesRenderer?._series; + : seriesRenderer is CartesianSeriesRenderer + ? SeriesHelper.getSeriesRendererDetails(seriesRenderer).series + : seriesRenderer.series; + /// Specifies the value of text String text; + /// Holds the value of icon color Color? iconColor; + /// Holds the value of text size Size? textSize; + /// Holds the value legend icon type LegendIconType iconType; + /// Specifies the size value Size? size; + /// Specifies the value of template size Size? templateSize; + /// Specifies the value of series dynamic series; + /// Holds the series renderer value dynamic seriesRenderer; + /// Holds the indicator renderer TechnicalIndicatorsRenderer? indicatorRenderer; + /// Holds the value if trendline Trendline? trendline; + /// Holds the value of point dynamic point; + /// Specifies the series index value int seriesIndex; + /// Specifies the value of trendline index int? trendlineIndex; + /// Specifies whether the legend is selected bool isSelect; + /// Specifies whether the legend is rendered bool isRender = false; + /// Specifies whether it is trendline legend bool? isTrendline = false; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/rendering_details.dart b/packages/syncfusion_flutter_charts/lib/src/common/rendering_details.dart index 4a61a629a..88500d856 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/rendering_details.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/rendering_details.dart @@ -1,7 +1,14 @@ -part of charts; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../circular_chart/renderer/common.dart'; +import 'common.dart'; +import 'legend/legend.dart'; +import 'legend/renderer.dart'; +import 'template/rendering.dart'; +import 'user_interaction/tooltip.dart'; /// Represents the rendering details of the chart -class _RenderingDetails { +class RenderingDetails { /// Specifies the animation controller of chart late AnimationController animationController; @@ -11,23 +18,23 @@ class _RenderingDetails { /// Specifies the series repaint notifier late ValueNotifier seriesRepaintNotifier; - /// Specifies the chart element animatioon + /// Specifies the chart element animation late Animation chartElementAnimation; /// Specifies the context for legend - late List<_MeasureWidgetContext> legendWidgetContext; + late List legendWidgetContext; /// Specifies the context for legend toggle template - late List<_MeasureWidgetContext> legendToggleTemplateStates; + late List legendToggleTemplateStates; /// Specifies the legend toggle states - late List<_LegendRenderContext> legendToggleStates; + late List legendToggleStates; /// Specifies whether the legend is toggled late bool isLegendToggled; /// Specifies the chart legend - late _ChartLegend chartLegend; + late ChartLegend chartLegend; /// Specifies the chart legend renderer late LegendRenderer legendRenderer; @@ -36,7 +43,7 @@ class _RenderingDetails { late TooltipBehaviorRenderer tooltipBehaviorRenderer; /// Specifies the chart interaction - _ChartInteraction? currentActive; + ChartInteraction? currentActive; /// Specifies the tap position Offset? tapPosition; @@ -54,10 +61,10 @@ class _RenderingDetails { late List dataLabelTemplateRegions; /// Specifies the list of template info - late List<_ChartTemplateInfo> templates; + late List templates; /// Specifies the chart template - _ChartTemplate? chartTemplate; + ChartTemplate? chartTemplate; /// Specifies the chart theme late SfChartThemeData chartTheme; @@ -88,7 +95,4 @@ class _RenderingDetails { /// Specifies the list of chart widget List? chartWidgets; - - /// Specifies the image drawn in the marker or not. - bool isImageDrawn = false; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart b/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart index 71aebaa8e..a8077e7cf 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/series/chart_series.dart @@ -1,11 +1,20 @@ -part of charts; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../circular_chart/renderer/chart_point.dart'; +import '../../circular_chart/renderer/renderer_base.dart'; +import '../common.dart'; +import '../user_interaction/selection_behavior.dart'; +import '../utils/enum.dart'; +import '../utils/typedef.dart'; /// This class holds the property of series. /// -/// ChartSeries has roperty to render the series if the Property data source is empty it renders an empty chart. +/// ChartSeries has property to render the series if the Property data source is empty it renders an empty chart. /// ChartSeries is the base class, it has the property to set the name, data source, border color and width to customize the series. /// -/// Provides options that are extended by the other subclasses such as name, point color mapper, data label mapper, animation +/// Provides options that are extended by the other sub classes such as name, point color mapper, data label mapper, animation /// duration and border-width and color for customize the appearance of the chart. /// class ChartSeries { @@ -29,6 +38,7 @@ class ChartSeries { this.legendIconType, this.opacity, this.sortingOrder, + this.animationDelay, this.isVisible}) : enableTooltip = enableTooltip ?? true; @@ -465,6 +475,28 @@ class ChartSeries { ///} ///``` final bool? isVisible; + + /// Delay duration of the series animation.It takes a millisecond value as input. + /// By default, the series will get animated for the specified duration. + /// If animationDelay is specified, then the series will begin to animate + /// after the specified duration. + /// + /// Defaults to 0 for all the series except ErrorBarSeries. + /// The default value for the ErrorBarSeries is 1500. + /// + ///```dart + /// Widget build(BuildContext context) { + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// BarSeries( + /// animationDelay: 300, + /// ), + /// ], + /// )); + /// } + ///``` + final double? animationDelay; } /// This class Provides method to calculate the Empty Point value. @@ -478,9 +510,9 @@ abstract class CircularChartEmptyPointBehavior { /// Data points with a null value are considered empty points. /// Empty data points are ignored and are not plotted in the chart. /// -/// Provides Emptypoints value calculations. +/// Provides Empty points value calculations. abstract class TriangularChartEmptyPointBehavior { - /// to calculte the values of the empty points. + /// to calculate the values of the empty points. void calculateEmptyPointValue( int pointIndex, dynamic currentPoint, dynamic seriesRenderer); } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/common/state_properties.dart new file mode 100644 index 000000000..257c44300 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/common/state_properties.dart @@ -0,0 +1,16 @@ +import 'rendering_details.dart'; + +/// Represents the state properties class +class StateProperties { + /// Creates an instance of state properties class + StateProperties(this.renderingDetails, this.chartState); + + /// Holds the value of rendering details + final RenderingDetails renderingDetails; + + /// Holds the value of chart state + final dynamic chartState; + + /// Specifies the chart instance + dynamic get chart => chartState.widget; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart b/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart index 12e6dad07..2ba2b5557 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/template/rendering.dart @@ -1,26 +1,52 @@ -part of charts; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/chart_series/series_renderer_properties.dart'; +import '../../chart/chart_series/xy_data_series.dart'; +import '../../chart/common/cartesian_state_properties.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/common/data_label_renderer.dart'; +import '../../chart/utils/helper.dart'; +import '../state_properties.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; + +/// Represents the render template class // ignore: must_be_immutable -class _RenderTemplate extends StatefulWidget { +class RenderTemplate extends StatefulWidget { + /// Creates an instance of render template // ignore: prefer_const_constructors_in_immutables - _RenderTemplate( + RenderTemplate( {required this.template, this.needMeasure, required this.templateLength, required this.templateIndex, - required this.chartState}); - final _ChartTemplateInfo template; + required this.stateProperties}); + + /// Hold the value of chart template info + final ChartTemplateInfo template; + + /// Specifies whether to measure the template bool? needMeasure; + + /// Specifies the template length final int templateLength; + + /// Specifies the template index value final int templateIndex; - final dynamic chartState; + + /// Holds the value of state properties + final StateProperties stateProperties; + + /// Specifies whether it is annotation bool? isAnnotation; @override State createState() => _RenderTemplateState(); } -class _RenderTemplateState extends State<_RenderTemplate> +class _RenderTemplateState extends State with TickerProviderStateMixin { late List templateControllerList; AnimationController? animationController; @@ -34,34 +60,40 @@ class _RenderTemplateState extends State<_RenderTemplate> @override Widget build(BuildContext context) { - final _ChartTemplateInfo templateInfo = widget.template; + final ChartTemplateInfo templateInfo = widget.template; Widget currentWidget = Container(); Widget renderWidget; if (templateInfo.templateType == 'DataLabel') { renderWidget = _ChartTemplateRenderObject( child: templateInfo.widget!, templateInfo: templateInfo, - chartState: widget.chartState, + stateProperties: widget.stateProperties, animationController: animationController); } else { renderWidget = _ChartTemplateRenderObject( child: templateInfo.widget!, templateInfo: templateInfo, - chartState: widget.chartState, + stateProperties: widget.stateProperties, animationController: animationController); } if (templateInfo.animationDuration > 0) { - final dynamic seriesRenderer = (templateInfo.templateType == 'DataLabel') - ? widget.chartState._chartSeries - .visibleSeriesRenderers[templateInfo.seriesIndex] - : null; + final dynamic stateProperties = widget.stateProperties; + final dynamic seriesRendererDetails = + (templateInfo.templateType == 'DataLabel') + ? stateProperties is CartesianStateProperties + ? SeriesHelper.getSeriesRendererDetails(stateProperties + .chartSeries + .visibleSeriesRenderers[templateInfo.seriesIndex!]) + : stateProperties.chartSeries + .visibleSeriesRenderers[templateInfo.seriesIndex!] + : null; final Orientation? orientation = - widget.chartState._renderingDetails.oldDeviceOrientation; + widget.stateProperties.renderingDetails.oldDeviceOrientation; final bool needsAnimate = orientation == MediaQuery.of(context).orientation && - (!(seriesRenderer != null && - seriesRenderer is CartesianSeriesRenderer) || - seriesRenderer._needAnimateSeriesElements); + (!(seriesRendererDetails != null && + seriesRendererDetails is SeriesRendererDetails) || + seriesRendererDetails.needAnimateSeriesElements == true); animationController = AnimationController( duration: Duration(milliseconds: widget.template.animationDuration), vsync: this); @@ -101,20 +133,20 @@ class _ChartTemplateRenderObject extends SingleChildRenderObjectWidget { {Key? key, required Widget child, required this.templateInfo, - required this.chartState, + required this.stateProperties, required this.animationController}) : super(key: key, child: child); - final _ChartTemplateInfo templateInfo; + final ChartTemplateInfo templateInfo; - final dynamic chartState; + final StateProperties stateProperties; final AnimationController? animationController; @override RenderObject createRenderObject(BuildContext context) { return _ChartTemplateRenderBox( - templateInfo, chartState, animationController); + templateInfo, stateProperties, animationController); } @override @@ -127,19 +159,19 @@ class _ChartTemplateRenderObject extends SingleChildRenderObjectWidget { /// Render the annotation widget in the respective position. class _ChartTemplateRenderBox extends RenderShiftedBox { _ChartTemplateRenderBox( - this._templateInfo, this._chartState, this._animationController, + this._templateInfo, this.stateProperties, this._animationController, [RenderBox? child]) : super(child); - _ChartTemplateInfo _templateInfo; + ChartTemplateInfo _templateInfo; - final dynamic _chartState; + final dynamic stateProperties; final AnimationController? _animationController; - _ChartTemplateInfo get templateInfo => _templateInfo; + ChartTemplateInfo get templateInfo => _templateInfo; - set templateInfo(_ChartTemplateInfo value) { + set templateInfo(ChartTemplateInfo value) { if (_templateInfo != value) { _templateInfo = value; markNeedsLayout(); @@ -173,70 +205,83 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { ? child!.size.height / 2 : child!.size.height); if (_templateInfo.templateType == 'DataLabel' && - _chartState is SfCartesianChartState) { - final dynamic seriesRenderer = _chartState - ._chartSeries.visibleSeriesRenderers[_templateInfo.seriesIndex]; - seriesRenderer._chartState = _chartState; - seriesRenderer._dataLabelSettingsRenderer = DataLabelSettingsRenderer( - seriesRenderer._series.dataLabelSettings); - final CartesianChartPoint? point = - seriesRenderer._dataPoints != null - ? (seriesRenderer._dataPoints.isNotEmpty == true) - ? seriesRenderer._dataPoints[_templateInfo.pointIndex] - : null - : null; - if (seriesRenderer._isRectSeries == true || - seriesRenderer._seriesType.contains('hilo') == true || - seriesRenderer._seriesType.contains('candle') == true || - seriesRenderer._seriesType.contains('box') == true) { - seriesRenderer._sideBySideInfo = - _calculateSideBySideInfo(seriesRenderer, _chartState); + stateProperties is CartesianStateProperties) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(stateProperties.chartSeries + .visibleSeriesRenderers[_templateInfo.seriesIndex]); + seriesRendererDetails.stateProperties = stateProperties; + seriesRendererDetails.dataLabelSettingsRenderer = + DataLabelSettingsRenderer( + seriesRendererDetails.series.dataLabelSettings); + final CartesianChartPoint? point = seriesRendererDetails + .dataPoints != + null + ? (seriesRendererDetails.dataPoints.isNotEmpty == true) + ? seriesRendererDetails.dataPoints[_templateInfo.pointIndex!] + : null + : null; + if (seriesRendererDetails.isRectSeries == true || + seriesRendererDetails.seriesType.contains('hilo') == true || + seriesRendererDetails.seriesType.contains('candle') == true || + seriesRendererDetails.seriesType.contains('box') == true) { + seriesRendererDetails.sideBySideInfo = calculateSideBySideInfo( + seriesRendererDetails.renderer, stateProperties); } - seriesRenderer._hasDataLabelTemplate = true; - if (seriesRenderer._seriesType.contains('spline') == true) { - if (seriesRenderer._drawControlPoints.isNotEmpty == true) { - seriesRenderer._drawControlPoints.clear(); + seriesRendererDetails.hasDataLabelTemplate = true; + if (seriesRendererDetails.seriesType.contains('spline') == true) { + if (seriesRendererDetails.drawControlPoints.isNotEmpty == true) { + seriesRendererDetails.drawControlPoints.clear(); } - _calculateSplineAreaControlPoints(seriesRenderer); + calculateSplineAreaControlPoints(seriesRendererDetails.renderer); } - if (point != null && seriesRenderer._seriesType != 'boxandwhisker') { + if (point != null && + seriesRendererDetails.seriesType != 'boxandwhisker') { if (point.region == null) { - if (seriesRenderer._visibleDataPoints == null || - seriesRenderer._visibleDataPoints.length >= - seriesRenderer._dataPoints.length == + if (seriesRendererDetails.visibleDataPoints == null || + seriesRendererDetails.visibleDataPoints!.length >= + seriesRendererDetails.dataPoints.length == true) { - seriesRenderer._visibleDataPoints = + seriesRendererDetails.visibleDataPoints = >[]; } - seriesRenderer._calculateRegionData(_chartState, seriesRenderer, - 0, point, _templateInfo.pointIndex, null, null, null, null); + seriesRendererDetails.calculateRegionData( + stateProperties, + seriesRendererDetails, + 0, + point, + _templateInfo.pointIndex!, + null, + null, + null, + null); } - _calculateDataLabelPosition( - seriesRenderer, + calculateDataLabelPosition( + seriesRendererDetails, point, _templateInfo.pointIndex!, - _chartState, - seriesRenderer._dataLabelSettingsRenderer, + stateProperties, + seriesRendererDetails.dataLabelSettingsRenderer, _animationController!, size, _templateInfo.location); locationX = point.labelLocation!.x; locationY = point.labelLocation!.y; - isLabelWithInRange = _isLabelWithinRange(seriesRenderer, point); + isLabelWithInRange = + isLabelWithinRange(seriesRendererDetails, point); } } final Rect rect = Rect.fromLTWH(locationX, locationY, size.width, size.height); final List dataLabelTemplateRegions = - _chartState._renderingDetails.dataLabelTemplateRegions; + stateProperties.renderingDetails.dataLabelTemplateRegions; final bool isCollide = (_templateInfo.templateType == 'DataLabel') && - _findingCollision(rect, dataLabelTemplateRegions); + findingCollision(rect, dataLabelTemplateRegions); if (!isCollide && _isTemplateWithinBounds(_templateInfo.clipRect, rect) && isLabelWithInRange) { (_templateInfo.templateType == 'DataLabel') ? dataLabelTemplateRegions.add(rect) - : _chartState._annotationRegions.add(rect); + : stateProperties.annotationRegions.add(rect); childParentData.offset = Offset(locationX, locationY); } else { child!.layout(constraints.copyWith(maxWidth: 0, maxHeight: 0), @@ -256,27 +301,33 @@ class _ChartTemplateRenderBox extends RenderShiftedBox { templateRect.top + templateRect.height <= bounds.top + bounds.height; } +/// Represents the chart template class // ignore: must_be_immutable -class _ChartTemplate extends StatefulWidget { +class ChartTemplate extends StatefulWidget { + /// Creates an instance of chart template class // ignore: prefer_const_constructors_in_immutables - _ChartTemplate( + ChartTemplate( {required this.templates, required this.render, - required this.chartState}); + required this.stateProperties}); - List<_ChartTemplateInfo> templates; + /// Holds the list of chart template info + List templates; + /// Specifies whether the template is rendered bool render = false; - dynamic chartState; + /// Holds the value of state properties + StateProperties stateProperties; + /// Holds the value of chart template state late _ChartTemplateState state; @override State createState() => _ChartTemplateState(); } -class _ChartTemplateState extends State<_ChartTemplate> { +class _ChartTemplateState extends State { @override void initState() { widget.state = this; @@ -288,15 +339,15 @@ class _ChartTemplateState extends State<_ChartTemplate> { widget.state = this; Widget renderTemplate = Container(); final bool animationCompleted = - widget.chartState._renderingDetails.animateCompleted; + widget.stateProperties.renderingDetails.animateCompleted; if (animationCompleted && widget.templates.isNotEmpty) { final List renderWidgets = []; for (int i = 0; i < widget.templates.length; i++) { - renderWidgets.add(_RenderTemplate( + renderWidgets.add(RenderTemplate( template: widget.templates[i], templateIndex: i, templateLength: widget.templates.length, - chartState: widget.chartState, + stateProperties: widget.stateProperties, )); } renderTemplate = Stack(children: renderWidgets); @@ -313,8 +364,10 @@ class _ChartTemplateState extends State<_ChartTemplate> { } } -class _ChartTemplateInfo { - _ChartTemplateInfo( +/// Represents the chart template info class +class ChartTemplateInfo { + /// Creates an instance of chart template info class + ChartTemplateInfo( {required this.key, required this.widget, required this.location, @@ -328,19 +381,49 @@ class _ChartTemplateInfo { ChartAlignment? verticalAlignment}) : horizontalAlignment = horizontalAlignment ?? ChartAlignment.center, verticalAlignment = verticalAlignment ?? ChartAlignment.center; + + /// Specifies the key value Key? key; + + /// Specifies the widget Widget? widget; + + /// Holds the size value late Size size; + + /// Holds the point value late dynamic point; + + /// Holds the value of location Offset location; + + /// Specifies the build context value late BuildContext context; + + /// Specifies the animation duration int animationDuration; + + /// Specifies the value of animation controller late AnimationController animationController; + + /// Specifies the point index value int? pointIndex; + + /// Specifies the series index value int? seriesIndex; + + /// Specifies the clip rect value Rect clipRect; + + /// Holds the template type String templateType; + + /// Holds the value of horizontal alignment ChartAlignment horizontalAlignment; + + /// Holds the value of vertical alignment ChartAlignment verticalAlignment; + + /// Specifies whether to measure the template bool needMeasure; } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart new file mode 100644 index 000000000..6045f65c3 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection.dart @@ -0,0 +1,272 @@ +part of charts; + +// @Deprecated('Use SelectionBehavior instead. ' +// 'This feature was deprecated from next release onwards') + +// ///Provides options for the selection of series or data points. +// /// +// ///By using this class, The color and width of the selected and unselected series or data points can be customized. +// class SelectionSettings { +// /// Creating an argument constructor of SelectionSettings class. +// SelectionSettings( +// {bool? enable, +// this.selectedColor, +// this.selectedBorderColor, +// this.selectedBorderWidth, +// this.unselectedColor, +// this.unselectedBorderColor, +// this.unselectedBorderWidth, +// double? selectedOpacity, +// double? unselectedOpacity, +// this.selectionController}) +// : enable = enable ?? false, +// selectedOpacity = selectedOpacity ?? 1.0, +// unselectedOpacity = unselectedOpacity ?? 0.5; + +// ///Enables or disables the selection. +// /// +// ///By enabling this, each data point or series in the chart can be selected. +// /// +// ///Defaults to `false`. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// enable: true +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final bool enable; + +// ///Color of the selected data points or series. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// selectedColor: Colors.red +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final Color? selectedColor; + +// ///Border color of the selected data points or series. +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// selectedBorderColor: Colors.red, +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final Color? selectedBorderColor; + +// ///Border width of the selected data points or series. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// selectedColor: Colors.red, +// /// selectedBorderWidth: 2 +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final double? selectedBorderWidth; + +// ///Color of the unselected data points or series. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// unselectedColor: Colors.grey, +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final Color? unselectedColor; + +// ///Border color of the unselected data points or series. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// unselectedBorderColor: Colors.grey, +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final Color? unselectedBorderColor; + +// ///Border width of the unselected data points or series. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// unselectedBorderWidth: 2 +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final double? unselectedBorderWidth; + +// ///Opacity of the selected series or data point. +// /// +// ///Default to `1.0`. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// selectedOpacity: 0.5, +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///``` +// final double selectedOpacity; + +// ///Opacity of the unselected series or data point. +// /// +// ///Defaults to `0.5`. +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// series: >[ +// /// BarSeries( +// /// selectionSettings: SelectionSettings( +// /// unselectedOpacity: 0.4, +// /// ), +// /// ), +// /// ], +// /// )); +// ///} +// ///`` +// final double unselectedOpacity; + +// /// Controller used to set the maximum and minimum values for the chart.By providing the selection controller, the maximum and +// ///The minimum range of selected series or points can be customized +// /// +// ///```dart +// ///Widget build(BuildContext context) { +// /// return Container( +// /// child: SfCartesianChart( +// /// selectionSettings: SelectionSettings( +// /// selectionController: controller, +// /// ), +// /// )); +// ///} +// ///``` +// final RangeController? selectionController; + +// dynamic _chartState; + +// ////Selects or deselects the specified data point in the series. +// /// +// ///The following are the arguments to be passed. +// ///* `pointIndex` - index of the data point that needs to be selected. +// ///* `seriesIndex` - index of the series in which the data point is selected. +// /// +// ///Where the `pointIndex` is a required argument and `seriesIndex` is an optional argument. By default, 0 will +// /// be considered as the series index. Thus it will take effect on the first series if no value is specified. +// /// +// ///For Circular, Pyramid and Funnel charts, seriesIndex should always be 0, as it has only one series. +// /// +// ///If the specified data point is already selected, it will be deselected, else it will be selected. +// /// Selection type and multi-selection functionality is also applicable for this, but it is based on +// /// the API values specified in [ChartSelectionBehavior]. +// /// +// ///_Note:_ Even though, the [enable] property in [ChartSelectionBehavior] is set to false, this method +// /// will work. +// /// ```dart +// /// Widget build(BuildContext context) { +// /// selection = SelectionSettings(enable: true); +// /// chart = SfCartesianChart(series:getChartData); +// /// return Scaffold( +// /// child: Column( +// /// children: [ +// /// FlatButton( +// /// child: Text('Select'), +// /// onPressed: select), +// /// Container(child: chart) +// /// ])); +// ///} +// /// void select() { +// /// selection.selectionIndex(1, 0); +// ///} +// ///```dart + +// void selectDataPoints(int pointIndex, [int seriesIndex = 0]) { +// final dynamic seriesRenderer = +// _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; +// final SelectionBehaviorRenderer selectionBehaviorRenderer = +// seriesRenderer.seriesRendererDetails.selectionBehaviorRenderer; +// selectionBehaviorRenderer.selectionDetails.selectionRenderer +// ?.selectDataPoints(pointIndex, seriesIndex); +// } + +// /// provides the list of selected point indices for given series. +// List getSelectedDataPoints(CartesianSeries _series) { +// List selectedItems = []; +// final dynamic seriesRenderer = +// _stateProperties.chartSeries.visibleSeriesRenderers[0]; +// final SelectionBehaviorRenderer selectionBehaviorRenderer = +// seriesRenderer.seriesRendererDetails.selectionBehaviorRenderer; +// final List selectedPoints = []; +// selectedItems = +// selectionBehaviorRenderer.selectionDetails.selectionRenderer!.selectedSegments; +// for (int i = 0; i < selectedItems.length; i++) { +// selectedPoints.add(selectedItems[i].currentSegmentIndex!); +// } +// return selectedPoints; +// } +// } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart index d4853c97c..6ea50b808 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/selection_behavior.dart @@ -1,4 +1,18 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/cartesian_state_properties.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../chart/chart_behavior/selection_behavior.dart'; +import '../../chart/chart_segment/chart_segment.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/chart_series/series_renderer_properties.dart'; +import '../../chart/user_interaction/selection_renderer.dart'; +import '../../chart/utils/helper.dart'; +import '../../circular_chart/renderer/common.dart'; ///Provides options for the selection of series or data points. /// @@ -274,7 +288,8 @@ class SelectionBehavior { return hashList(values); } - dynamic _chartState; + /// Specifies the value of selection behavior renderer + SelectionBehaviorRenderer? _selectionBehaviorRenderer; ////Selects or deselects the specified data point in the series. /// @@ -312,28 +327,38 @@ class SelectionBehavior { ///```dart void selectDataPoints(int pointIndex, [int seriesIndex = 0]) { - final dynamic seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; + final dynamic seriesRenderer = _selectionBehaviorRenderer!._selectionDetails + .stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; assert( - seriesRenderer._chartState! is SfCartesianChartState == false || - _getVisibleDataPointIndex(pointIndex, seriesRenderer) != null, + seriesRenderer is CartesianSeriesRenderer == false || + getVisibleDataPointIndex(pointIndex, + SeriesHelper.getSeriesRendererDetails(seriesRenderer)) != + null, 'Provided point index is not in the visible range. Provide point index which is in the visible range.'); - final SelectionBehaviorRenderer selectionBehaviorRenderer = - seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer + _selectionBehaviorRenderer = seriesRenderer is CartesianSeriesRenderer + ? SeriesHelper.getSeriesRendererDetails(seriesRenderer) + .selectionBehaviorRenderer + : _selectionBehaviorRenderer; + _selectionBehaviorRenderer!._selectionDetails.selectionRenderer ?.selectDataPoints(pointIndex, seriesIndex); } /// provides the list of selected point indices for given series. List getSelectedDataPoints(CartesianSeries _series) { List selectedItems = []; - final dynamic seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - final SelectionBehaviorRenderer selectionBehaviorRenderer = - seriesRenderer._selectionBehaviorRenderer; + final dynamic seriesRenderer = _selectionBehaviorRenderer!._selectionDetails + .stateProperties.chartSeries.visibleSeriesRenderers[0]; + SelectionBehaviorRenderer _selectionRenderer; + if (seriesRenderer is CartesianSeriesRenderer) { + _selectionRenderer = SeriesHelper.getSeriesRendererDetails(seriesRenderer) + .selectionBehaviorRenderer!; + } else { + _selectionRenderer = seriesRenderer.selectionBehaviorRenderer; + } + final List selectedPoints = []; - selectedItems = - selectionBehaviorRenderer._selectionRenderer!.selectedSegments; + selectedItems = _selectionRenderer + ._selectionDetails.selectionRenderer!.selectedSegments; for (int i = 0; i < selectedItems.length; i++) { selectedPoints.add(selectedItems[i].currentSegmentIndex!); } @@ -343,68 +368,23 @@ class SelectionBehavior { /// Selection renderer class for mutable fields and methods class SelectionBehaviorRenderer with ChartSelectionBehavior { - /// Creates an argument constructor for SelectionBehavior renderer class - SelectionBehaviorRenderer( - this._selectionBehavior, this._chart, this._sfChartState); - - final dynamic _chart; - - final dynamic _sfChartState; - - final dynamic _selectionBehavior; - - _SelectionRenderer? _selectionRenderer; - - // ignore: unused_element - void _selectRange() { - bool isSelect = false; - final CartesianSeriesRenderer seriesRenderer = - _selectionRenderer!.seriesRenderer; - final SfCartesianChartState chartState = _selectionRenderer!._chartState; - if (_selectionBehavior.enable == true && - _selectionBehavior.selectionController != null) { - _selectionBehavior.selectionController.addListener(() { - chartState._isRangeSelectionSlider = true; - _selectionRenderer!.selectedSegments.clear(); - _selectionRenderer!.unselectedSegments?.clear(); - final dynamic start = _selectionBehavior.selectionController.start; - final dynamic end = _selectionBehavior.selectionController.end; - for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { - final num xValue = seriesRenderer._dataPoints[i].xValue; - isSelect = start is DateTime - ? (xValue >= start.millisecondsSinceEpoch && - xValue <= end.millisecondsSinceEpoch) - : (xValue >= start && xValue <= end); - - isSelect - ? _selectionRenderer!.selectedSegments - .add(seriesRenderer._segments[i]) - : _selectionRenderer!.unselectedSegments - ?.add(seriesRenderer._segments[i]); - } - _selectionRenderer! - ._selectedSegmentsColors(_selectionRenderer!.selectedSegments); - _selectionRenderer! - ._unselectedSegmentsColors(_selectionRenderer!.unselectedSegments!); - - for (final CartesianSeriesRenderer _seriesRenderer - in _sfChartState._chartSeries.visibleSeriesRenderers) { - ValueNotifier(_seriesRenderer._repaintNotifier.value++); - } - }); - } - if (chartState._renderingDetails.initialRender!) { - chartState._isRangeSelectionSlider = false; - } - _selectionRenderer!._chartState = chartState; + /// Creates an argument constructor for SelectionSettings renderer class + SelectionBehaviorRenderer(SelectionBehavior selectionBehavior, dynamic chart, + dynamic stateProperties) { + _selectionDetails = + SelectionDetails(selectionBehavior, chart, stateProperties, this); } + /// Holds the selection details instance for the chart + late SelectionDetails _selectionDetails; + /// Specifies the index of the data point that needs to be selected initially while /// rendering a chart. /// ignore: unused_element void _selectedDataPointIndex( CartesianSeriesRenderer seriesRenderer, List selectedData) => - _selectionRenderer?.selectedDataPointIndex(seriesRenderer, selectedData); + _selectionDetails.selectionRenderer + ?.selectedDataPointIndex(seriesRenderer, selectedData); /// Gets the selected item color of a Cartesian series. @override @@ -433,39 +413,126 @@ class SelectionBehaviorRenderer with ChartSelectionBehavior { /// Gets the selected item color of a circular series. @override Color getCircularSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List<_Region> selectedRegions) => + int pointIndex, List selectedRegions) => color; /// Gets the unselected item color of a circular series. @override Color getCircularUnSelectedItemFill(Color color, int seriesIndex, - int pointIndex, List<_Region> unselectedRegions) => + int pointIndex, List unselectedRegions) => color; /// Gets the selected item border color of a circular series. @override Color getCircularSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List<_Region> selectedRegions) => + int pointIndex, List selectedRegions) => color; /// Gets the unselected item border color of a circular series. @override Color getCircularUnSelectedItemBorder(Color color, int seriesIndex, - int pointIndex, List<_Region> unselectedRegions) => + int pointIndex, List unselectedRegions) => color; /// Performs the double-tap action on the chart. @override void onDoubleTap(double xPos, double yPos) => - _selectionRenderer?.performSelection(Offset(xPos, yPos)); + _selectionDetails.selectionRenderer?.performSelection(Offset(xPos, yPos)); /// Performs the long press action on the chart. @override void onLongPress(double xPos, double yPos) => - _selectionRenderer?.performSelection(Offset(xPos, yPos)); + _selectionDetails.selectionRenderer?.performSelection(Offset(xPos, yPos)); /// Performs the touch-down action on the chart. @override void onTouchDown(double xPos, double yPos) => - _selectionRenderer?.performSelection(Offset(xPos, yPos)); + _selectionDetails.selectionRenderer?.performSelection(Offset(xPos, yPos)); +} + +/// Holds the properties of the selection behavior renderer +class SelectionDetails { + /// Argument constructor for SelectionDetails class + SelectionDetails(this.selectionBehavior, this.chart, this.stateProperties, + this.selectionBehaviorRenderer); + + /// Specifies the chart instance + final dynamic chart; + + /// Holds the state properties value + final dynamic stateProperties; + + /// Holds the value of selection behavior + // ignore: deprecated_member_use_from_same_package + final dynamic selectionBehavior; + + /// Holds the selection renderer value + SelectionRenderer? selectionRenderer; + + /// Holds the selection behavior renderer instance + final SelectionBehaviorRenderer selectionBehaviorRenderer; + + /// Method to select the range + void selectRange() { + bool isSelect = false; + final SeriesRendererDetails seriesRendererDetails = + selectionRenderer!.seriesRendererDetails; + final CartesianStateProperties stateProperties = + selectionRenderer!.stateProperties; + if (selectionBehavior.enable == true && + selectionBehavior.selectionController != null) { + selectionBehavior.selectionController.addListener(() { + stateProperties.isRangeSelectionSlider = true; + selectionRenderer!.selectedSegments.clear(); + selectionRenderer!.unselectedSegments?.clear(); + final dynamic start = selectionBehavior.selectionController.start; + final dynamic end = selectionBehavior.selectionController.end; + for (int i = 0; i < seriesRendererDetails.dataPoints.length; i++) { + final num xValue = seriesRendererDetails.dataPoints[i].xValue; + isSelect = start is DateTime + ? (xValue >= start.millisecondsSinceEpoch && + xValue <= end.millisecondsSinceEpoch) + : (xValue >= start && xValue <= end); + + isSelect + ? selectionRenderer!.selectedSegments + .add(seriesRendererDetails.segments[i]) + : selectionRenderer!.unselectedSegments + ?.add(seriesRendererDetails.segments[i]); + } + selectionRenderer! + .selectedSegmentsColors(selectionRenderer!.selectedSegments); + selectionRenderer! + .unselectedSegmentsColors(selectionRenderer!.unselectedSegments!); + + for (final CartesianSeriesRenderer _seriesRenderer + in stateProperties.chartSeries.visibleSeriesRenderers) { + ValueNotifier( + SeriesHelper.getSeriesRendererDetails(_seriesRenderer) + .repaintNotifier + .value++); + } + }); + } + if (stateProperties.renderingDetails.initialRender! == true) { + stateProperties.isRangeSelectionSlider = false; + } + selectionRenderer!.stateProperties = stateProperties; + } +} + +// ignore: avoid_classes_with_only_static_members +/// Helper class to get the selection details instance from its renderer. +class SelectionHelper { + /// Returns the selection details instance from its renderer + static SelectionDetails getRenderingDetails( + SelectionBehaviorRenderer renderer) { + return renderer._selectionDetails; + } + + /// Method to set the selection behavior renderer + static void setSelectionBehaviorRenderer(SelectionBehavior selectionBehavior, + SelectionBehaviorRenderer selectionBehaviorRenderer) { + selectionBehavior._selectionBehaviorRenderer = selectionBehaviorRenderer; + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart index e4ece77bb..69c07a2dd 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip.dart @@ -1,4 +1,35 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_core/tooltip_internal.dart'; + +import '../../chart/axis/axis.dart'; +import '../../chart/axis/category_axis.dart'; +import '../../chart/axis/datetime_category_axis.dart'; +import '../../chart/base/chart_base.dart'; +import '../../chart/chart_behavior/chart_behavior.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/chart_series/series_renderer_properties.dart'; +import '../../chart/chart_series/xy_data_series.dart'; +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../circular_chart/base/circular_base.dart'; +import '../../circular_chart/renderer/chart_point.dart'; +import '../../circular_chart/utils/helper.dart'; +import '../../funnel_chart/base/funnel_base.dart'; +import '../../pyramid_chart/base/pyramid_base.dart'; +import '../rendering_details.dart'; +import '../utils/enum.dart'; +import '../utils/helper.dart'; +import '../utils/typedef.dart'; +import 'tooltip_rendering_details.dart'; + +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; /// Customizes the tooltip. /// @@ -412,7 +443,8 @@ class TooltipBehavior { return hashList(values); } - dynamic _chartState; + /// Holds the state properties value + dynamic _stateProperties; /// Displays the tooltip at the specified x and y-positions. /// @@ -423,152 +455,10 @@ class TooltipBehavior { // Defaults to true. void showByPixel(double x, double y) { //, [bool shouldInsidePointRegion]) { - final dynamic chartState = _chartState; - final dynamic chart = chartState._chart; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._renderingDetails.tooltipBehaviorRenderer; - bool? isInsidePointRegion; - String text = ''; - String trimmedText = ''; - Offset? axisLabelPosition; - if (chart is SfCartesianChart) { - _chartState._requireAxisTooltip = false; - for (int i = 0; - i < _chartState._chartAxis._axisRenderersCollection.length; - i++) { - final List labels = - _chartState._chartAxis._axisRenderersCollection[i]._visibleLabels; - for (int k = 0; k < labels.length; k++) { - if (_chartState - ._chartAxis._axisRenderersCollection[i]._axis.isVisible == - true && - labels[k]._labelRegion != null && - labels[k]._labelRegion!.contains(Offset(x, y))) { - _chartState._requireAxisTooltip = true; - text = labels[k].text; - trimmedText = labels[k].renderText ?? ''; - axisLabelPosition = labels[k]._labelRegion!.center; - // -3 to indicte axis tooltip - tooltipBehaviorRenderer._currentTooltipValue = - TooltipValue(null, k, 0); - } - } - } - } - if (chart is SfCartesianChart && _chartState._requireAxisTooltip == false) { - for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; - i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - if (seriesRenderer._visible! && - seriesRenderer._series.enableTooltip && - seriesRenderer._regionalData != null) { - final String seriesType = seriesRenderer._seriesType; - final double padding = (seriesType == 'bubble' || - seriesType == 'scatter' || - seriesType.contains('column') || - seriesType.contains('bar')) - ? 0 - : _chartState._renderingDetails.tooltipBehaviorRenderer - ._isHovering == - true - ? 0 - : 15; // regional padding to detect smooth touch - seriesRenderer._regionalData! - .forEach((dynamic regionRect, dynamic values) { - final Rect region = regionRect[0]; - final Rect paddedRegion = Rect.fromLTRB( - region.left - padding, - region.top - padding, - region.right + padding, - region.bottom + padding); - bool outlierTooltip = false; - if (seriesRenderer._seriesType == 'boxandwhisker') { - final List? outlierRegion = regionRect[5]; - if (outlierRegion != null) { - for (int rectIndex = 0; - rectIndex < outlierRegion.length; - rectIndex++) { - if (outlierRegion[rectIndex].contains(Offset(x, y))) { - outlierTooltip = true; - break; - } - } - } - } - if (paddedRegion.contains(Offset(x, y)) || outlierTooltip) { - isInsidePointRegion = true; - } - }); - } - } - } - if (chart is SfCartesianChart && - activationMode != ActivationMode.none && - // ignore: unnecessary_null_comparison - x != null && - // ignore: unnecessary_null_comparison - y != null && - _chartState._requireAxisTooltip == true) { - final SfTooltipState? tooltipState = - tooltipBehaviorRenderer._chartTooltipState; - if (trimmedText.contains('...')) { - tooltipBehaviorRenderer._show = true; - tooltipState?.needMarker = false; - tooltipBehaviorRenderer._showAxisTooltip( - axisLabelPosition!, chart, text); - } else { - tooltipBehaviorRenderer._show = false; - if (!shouldAlwaysShow) { - tooltipBehaviorRenderer._chartTooltipState - ?.hide(hideDelay: duration.toInt()); - } - } - } else if (tooltipBehaviorRenderer._chartTooltip != null && - activationMode != ActivationMode.none && - // ignore: unnecessary_null_comparison - x != null && - // ignore: unnecessary_null_comparison - y != null) { - final SfTooltipState? tooltipState = - tooltipBehaviorRenderer._chartTooltipState; - if ((chart is SfCartesianChart) == false || - tooltipBehaviorRenderer._isInteraction || - (isInsidePointRegion ?? false)) { - final bool isHovering = - _chartState._renderingDetails.tooltipBehaviorRenderer._isHovering; - if (isInsidePointRegion == true || - isHovering || - (chart is SfCartesianChart) == false) { - tooltipBehaviorRenderer._showTooltip(x, y); - } else { - tooltipBehaviorRenderer._show = false; - if (chart.tooltipBehavior.shouldAlwaysShow == false) { - hide(); - } - } - } else if (tooltipBehaviorRenderer._renderBox != null) { - tooltipBehaviorRenderer._show = true; - tooltipState?.needMarker = false; - tooltipBehaviorRenderer._showChartAreaTooltip( - Offset(x, y), - _chartState._chartAxis._primaryXAxisRenderer, - _chartState._chartAxis._primaryYAxisRenderer, - chart); - } - } - if (chart is SfCartesianChart && - chart.tooltipBehavior.builder != null && - x != null && // ignore: unnecessary_null_comparison - // ignore: unnecessary_null_comparison - y != null) { - tooltipBehaviorRenderer._showTemplateTooltip(Offset(x, y)); - } - // ignore: unnecessary_null_comparison - if (tooltipBehaviorRenderer != null) { - tooltipBehaviorRenderer._isInteraction = false; - } + final TooltipRenderingDetails renderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + renderingDetails.internalShowByPixel(x, y); } /// Displays the tooltip at the specified x and y-values. @@ -583,59 +473,63 @@ class TooltipBehavior { /// /// * yAxisName - name of the y axis the given point must be bind to. void show(dynamic x, double y, [String? xAxisName, String? yAxisName]) { - if (_chartState._chart is SfCartesianChart) { - final dynamic chart = _chartState._chart; - final _RenderingDetails renderingDetails = _chartState._renderingDetails; + if (_stateProperties.chart is SfCartesianChart) { + final dynamic chart = _stateProperties.chart; + final RenderingDetails renderingDetails = + _stateProperties.renderingDetails; final TooltipBehaviorRenderer tooltipBehaviorRenderer = renderingDetails.tooltipBehaviorRenderer; bool? isInsidePointRegion = false; - ChartAxisRenderer? xAxisRenderer, yAxisRenderer; + ChartAxisRendererDetails? xAxisDetails, yAxisDetails; if (xAxisName != null && yAxisName != null) { for (final ChartAxisRenderer axisRenderer - in _chartState._chartAxis._axisRenderersCollection) { - if (axisRenderer._name == xAxisName) { - xAxisRenderer = axisRenderer; - } else if (axisRenderer._name == yAxisName) { - yAxisRenderer = axisRenderer; + in _stateProperties.chartAxis.axisRenderersCollection) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails(axisRenderer); + if (axisDetails.name == xAxisName) { + xAxisDetails = axisDetails; + } else if (axisDetails.name == yAxisName) { + yAxisDetails = axisDetails; } } } else { - xAxisRenderer = _chartState._chartAxis._primaryXAxisRenderer; - yAxisRenderer = _chartState._chartAxis._primaryYAxisRenderer; + xAxisDetails = _stateProperties.chartAxis.primaryXAxisDetails; + yAxisDetails = _stateProperties.chartAxis.primaryYAxisDetails; } - final _ChartLocation position = _calculatePoint( + final ChartLocation position = calculatePoint( (x is DateTime && - (xAxisRenderer! is DateTimeCategoryAxisRenderer) == false) + (xAxisDetails! is DateTimeCategoryAxisDetails) == false) ? x.millisecondsSinceEpoch - : ((x is DateTime && - xAxisRenderer! is DateTimeCategoryAxisRenderer) - ? (xAxisRenderer as DateTimeCategoryAxisRenderer) - ._labels - .indexOf(xAxisRenderer._dateFormat.format(x)) - : ((x is String && xAxisRenderer is CategoryAxisRenderer) - ? xAxisRenderer._labels.indexOf(x) + : ((x is DateTime && xAxisDetails! is DateTimeCategoryAxisDetails) + ? (xAxisDetails as DateTimeCategoryAxisDetails) + .labels + .indexOf(xAxisDetails.dateFormat.format(x)) + : ((x is String && xAxisDetails is CategoryAxisDetails) + ? xAxisDetails.labels.indexOf(x) : x)), y, - xAxisRenderer!, - yAxisRenderer!, - _chartState._requireInvertedAxis, + xAxisDetails!, + yAxisDetails!, + _stateProperties.requireInvertedAxis, null, - _chartState._chartAxis._axisClipRect); + _stateProperties.chartAxis.axisClipRect); for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < _stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - if (seriesRenderer._visible! && - seriesRenderer._series.enableTooltip && - seriesRenderer._regionalData != null) { - final double padding = (seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType == 'scatter' || - seriesRenderer._seriesType.contains('column') || - seriesRenderer._seriesType.contains('bar')) + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _stateProperties.chartSeries.visibleSeriesRenderers[i]); + if (seriesRendererDetails.visible! == true && + seriesRendererDetails.series.enableTooltip == true && + seriesRendererDetails.regionalData != null) { + final double padding = (seriesRendererDetails.seriesType == + 'bubble' || + seriesRendererDetails.seriesType == 'scatter' || + seriesRendererDetails.seriesType.contains('column') == true || + seriesRendererDetails.seriesType.contains('bar') == true) ? 0 : 15; // regional padding to detect smooth touch - seriesRenderer._regionalData! + seriesRendererDetails.regionalData! .forEach((dynamic regionRect, dynamic values) { final Rect region = regionRect[0]; final Rect paddedRegion = Rect.fromLTRB( @@ -649,23 +543,24 @@ class TooltipBehavior { }); } } - if (renderingDetails.tooltipBehaviorRenderer._tooltipTemplate == null) { + if (renderingDetails.tooltipBehaviorRenderer._tooltipRenderingDetails + .tooltipTemplate == + null) { final SfTooltipState? tooltipState = - tooltipBehaviorRenderer._chartTooltipState; + tooltipBehaviorRenderer._tooltipRenderingDetails.chartTooltipState; if (isInsidePointRegion ?? false) { - tooltipBehaviorRenderer._showTooltip(position.x, position.y); + tooltipBehaviorRenderer._tooltipRenderingDetails + .showTooltip(position.x, position.y); } else { //to show tooltip when the position is out of point region - tooltipBehaviorRenderer._show = true; + tooltipBehaviorRenderer._tooltipRenderingDetails.show = true; tooltipState?.needMarker = false; - renderingDetails.tooltipBehaviorRenderer._showChartAreaTooltip( - Offset(position.x, position.y), - xAxisRenderer, - yAxisRenderer, - chart); + renderingDetails.tooltipBehaviorRenderer._tooltipRenderingDetails + .showChartAreaTooltip(Offset(position.x, position.y), + xAxisDetails, yAxisDetails, chart); } } - tooltipBehaviorRenderer._isInteraction = false; + tooltipBehaviorRenderer._tooltipRenderingDetails.isInteraction = false; } } @@ -675,97 +570,102 @@ class TooltipBehavior { /// /// * pointIndex - index of the point for which the tooltip should be shown void showByIndex(int seriesIndex, int pointIndex) { - final dynamic chartState = _chartState; - final dynamic chart = chartState._chart; + final dynamic chart = _stateProperties.chart; final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._renderingDetails.tooltipBehaviorRenderer; + _stateProperties.renderingDetails.tooltipBehaviorRenderer; dynamic x, y; if (chart is SfCartesianChart) { - if (_validIndex(pointIndex, seriesIndex, chart)) { - final CartesianSeriesRenderer cSeriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - if (cSeriesRenderer._visible!) { - x = cSeriesRenderer._dataPoints[pointIndex].markerPoint!.x; - y = cSeriesRenderer._dataPoints[pointIndex].markerPoint!.y; + if (validIndex(pointIndex, seriesIndex, chart)) { + final SeriesRendererDetails cSeriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(_stateProperties + .chartSeries.visibleSeriesRenderers[seriesIndex]); + if (cSeriesRendererDetails.visible! == true) { + x = cSeriesRendererDetails.dataPoints[pointIndex].markerPoint!.x; + y = cSeriesRendererDetails.dataPoints[pointIndex].markerPoint!.y; } } if (x != null && y != null && chart.series[seriesIndex].enableTooltip) { if (chart.tooltipBehavior.builder != null) { - tooltipBehaviorRenderer._showTemplateTooltip(Offset(x, y)); + tooltipBehaviorRenderer._tooltipRenderingDetails + .showTemplateTooltip(Offset(x, y)); } else if (chart.series[seriesIndex].enableTooltip) { - tooltipBehaviorRenderer._showTooltip(x, y); + tooltipBehaviorRenderer._tooltipRenderingDetails.showTooltip(x, y); } } } else if (chart is SfCircularChart) { if (chart.tooltipBehavior.builder != null && seriesIndex < chart.series.length && pointIndex < - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._dataPoints.length && + _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .dataPoints.length && chart.series[seriesIndex].enableTooltip) { //to show the tooltip template when the provided indices are valid - _chartState._circularArea + _stateProperties.circularArea ._showCircularTooltipTemplate(seriesIndex, pointIndex); } else if (chart.tooltipBehavior.builder == null && - chartState._animationCompleted == true && + _stateProperties.animationCompleted == true && pointIndex >= 0 && (pointIndex + 1 <= - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._renderPoints.length)) { - final ChartPoint chartPoint = _chartState._chartSeries - .visibleSeriesRenderers[seriesIndex]._renderPoints[pointIndex]; + _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .renderPoints.length)) { + final ChartPoint chartPoint = _stateProperties.chartSeries + .visibleSeriesRenderers[seriesIndex].renderPoints[pointIndex]; if (chartPoint.isVisible) { - final Offset position = _degreeToPoint( + final Offset position = degreeToPoint( chartPoint.midAngle!, (chartPoint.innerRadius! + chartPoint.outerRadius!) / 2, chartPoint.center!); x = position.dx; y = position.dy; - tooltipBehaviorRenderer._showTooltip(x, y); + tooltipBehaviorRenderer._tooltipRenderingDetails.showTooltip(x, y); } } } else if (pointIndex != null && // ignore: unnecessary_null_comparison pointIndex < - _chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints.length) { - //this shows the tooltip for triangular type of charts (funnerl and pyramid) + _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints.length) { + //this shows the tooltip for triangular type of charts (funnel and pyramid) if (chart.tooltipBehavior.builder == null) { - _chartState._tooltipPointIndex = pointIndex; - final Offset? position = _chartState._chartSeries - .visibleSeriesRenderers[0]._dataPoints[pointIndex].region?.center; + _stateProperties.tooltipPointIndex = pointIndex; + final Offset? position = _stateProperties.chartSeries + .visibleSeriesRenderers[0].dataPoints[pointIndex].region?.center; x = position?.dx; y = position?.dy; - tooltipBehaviorRenderer._showTooltip(x, y); + tooltipBehaviorRenderer._tooltipRenderingDetails.showTooltip(x, y); } else { - if (chart is SfFunnelChart && chartState._animationCompleted == true) { - _chartState._funnelplotArea._showFunnelTooltipTemplate(pointIndex); + if (chart is SfFunnelChart && + _stateProperties.animationCompleted == true) { + _stateProperties.funnelplotArea.showFunnelTooltipTemplate(pointIndex); } else if (chart is SfPyramidChart && - chartState._animationCompleted == true) { - _chartState._chartPlotArea._showPyramidTooltipTemplate(pointIndex); + _stateProperties.animationCompleted == true) { + _stateProperties.chartPlotArea.showPyramidTooltipTemplate(pointIndex); } } } - tooltipBehaviorRenderer._isInteraction = false; + tooltipBehaviorRenderer._tooltipRenderingDetails.isInteraction = false; } /// Hides the tooltip if it is displayed. void hide() { final TooltipBehaviorRenderer tooltipBehaviorRenderer = - _chartState._renderingDetails.tooltipBehaviorRenderer; + _stateProperties.renderingDetails.tooltipBehaviorRenderer; // ignore: unnecessary_null_comparison if (tooltipBehaviorRenderer != null) { - tooltipBehaviorRenderer._showLocation = null; - tooltipBehaviorRenderer._show = false; + tooltipBehaviorRenderer._tooltipRenderingDetails.showLocation = null; + tooltipBehaviorRenderer._tooltipRenderingDetails.show = false; } if (builder != null) { // hides tooltip template - tooltipBehaviorRenderer._chartTooltipState?.hide(hideDelay: 0); + tooltipBehaviorRenderer._tooltipRenderingDetails.chartTooltipState + ?.hide(hideDelay: 0); } else { //hides default tooltip - tooltipBehaviorRenderer._currentTooltipValue = - tooltipBehaviorRenderer._prevTooltipValue = null; + tooltipBehaviorRenderer._tooltipRenderingDetails.currentTooltipValue = + tooltipBehaviorRenderer._tooltipRenderingDetails.prevTooltipValue = + null; - tooltipBehaviorRenderer._chartTooltipState?.hide(hideDelay: 0); + tooltipBehaviorRenderer._tooltipRenderingDetails.chartTooltipState + ?.hide(hideDelay: 0); } } } @@ -773,59 +673,17 @@ class TooltipBehavior { ///Tooltip behavior renderer class for mutable fields and methods class TooltipBehaviorRenderer with ChartBehavior { /// Creates an argument constructor for Tooltip renderer class - TooltipBehaviorRenderer(this._chartState); - - dynamic get _chart => _chartState?._chart; - - final dynamic _chartState; - - TooltipBehavior get _tooltipBehavior => - _chart?.tooltipBehavior as TooltipBehavior; - - SfTooltip? _chartTooltip; - - //ignore: prefer_final_fields - bool _isInteraction = false; - - //ignore: prefer_final_fields - bool _isHovering = false, _mouseTooltip = false; - - Widget? _tooltipTemplate; - - TooltipRenderBox? get _renderBox => _chartTooltipState?.renderBox; - - SfTooltipState? get _chartTooltipState { - if (_chartTooltip != null) { - final State? state = (_chartTooltip?.key as GlobalKey).currentState; - //ignore: avoid_as - return state != null ? state as SfTooltipState : null; - } + TooltipBehaviorRenderer(this._stateProperties) { + _tooltipRenderingDetails = TooltipRenderingDetails(_stateProperties); } - List _textValues = []; - List _seriesRendererCollection = - []; - - TooltipValue? _prevTooltipValue; - TooltipValue? _presentTooltipValue; - TooltipValue? _currentTooltipValue; + final dynamic _stateProperties; - CartesianSeriesRenderer? _seriesRenderer; - dynamic _currentSeries, _dataPoint; - int? _pointIndex; - late int _seriesIndex; - late Color _markerColor; - late DataMarkerType _markerType; - bool _show = false; - Offset? _showLocation; - Rect? _tooltipBounds; - String? _stringVal, _header; - set _stringValue(String? value) { - _stringVal = value; - } + /// Specifies the rendering details of tooltip; + late TooltipRenderingDetails _tooltipRenderingDetails; /// Hides the Mouse tooltip if it is displayed. - void _hideMouseTooltip() => _hide(); + void _hideMouseTooltip() => _tooltipRenderingDetails.hide(); /// Draws tooltip /// @@ -840,7 +698,7 @@ class TooltipBehaviorRenderer with ChartBehavior { /// * yPos - Y value of the touch position. @override void onDoubleTap(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + _tooltipRenderingDetails.tooltipBehavior.showByPixel(xPos, yPos); /// Performs the double-tap action of appropriate point. /// @@ -850,7 +708,7 @@ class TooltipBehaviorRenderer with ChartBehavior { /// * yPos - Y value of the touch position. @override void onLongPress(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + _tooltipRenderingDetails.tooltipBehavior.showByPixel(xPos, yPos); /// Performs the touch-down action of appropriate point. /// @@ -860,7 +718,7 @@ class TooltipBehaviorRenderer with ChartBehavior { /// * yPos - Y value of the touch position. @override void onTouchDown(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + _tooltipRenderingDetails.tooltipBehavior.showByPixel(xPos, yPos); /// Performs the touch move action of chart. /// @@ -881,7 +739,7 @@ class TooltipBehaviorRenderer with ChartBehavior { /// * yPos - Y value of the touch position. @override void onTouchUp(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + _tooltipRenderingDetails.tooltipBehavior.showByPixel(xPos, yPos); /// Performs the mouse hover action of chart. /// @@ -891,7 +749,7 @@ class TooltipBehaviorRenderer with ChartBehavior { /// * yPos - Y value of the touch position. @override void onEnter(double xPos, double yPos) => - _tooltipBehavior.showByPixel(xPos, yPos); + _tooltipRenderingDetails.tooltipBehavior.showByPixel(xPos, yPos); /// Performs the mouse exit action of chart. /// @@ -901,1139 +759,19 @@ class TooltipBehaviorRenderer with ChartBehavior { /// * yPos - Y value of the touch position. @override void onExit(double xPos, double yPos) { - if (_renderBox != null && _tooltipBehavior.builder != null) { + if (_tooltipRenderingDetails.renderBox != null && + _tooltipRenderingDetails.tooltipBehavior.builder != null) { _hideMouseTooltip(); + } else if (_tooltipRenderingDetails.tooltipTemplate != null) { + //ignore: unused_local_variable + _tooltipRenderingDetails.timer?.cancel(); + _tooltipRenderingDetails.timer = Timer( + Duration( + milliseconds: + _tooltipRenderingDetails.tooltipBehavior.duration.toInt()), + () {}); } } - - /// To render chart tooltip - // ignore:unused_element - void _renderTooltipView(Offset position) { - if (_chart is SfCartesianChart) { - _renderCartesianChartTooltip(position); - } else if (_chart is SfCircularChart) { - _renderCircularChartTooltip(position); - } else { - _renderTriangularChartTooltip(position); - } - } - - /// To show tooltip with position offsets - void _showTooltip(double? x, double? y) { - if (x != null && - y != null && - _renderBox != null && - _chartTooltipState != null) { - _show = true; - _mouseTooltip = false; - _isHovering ? _showMouseTooltip(x, y) : _showTooltipView(x, y); - } - } - - /// To show the chart tooltip - void _showTooltipView(double x, double y) { - if (_tooltipBehavior.enable && - _renderBox != null && - _chartState._animationCompleted == true) { - _renderTooltipView(Offset(x, y)); - if (_presentTooltipValue != null && - _tooltipBehavior.tooltipPosition != TooltipPosition.pointer) { - _chartTooltipState!.boundaryRect = _tooltipBounds!; - if (_showLocation != null) { - _chartTooltipState?.needMarker = _chart is SfCartesianChart; - _resolveLocation(); - _chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: _showLocation, - duration: _tooltipBehavior.animationDuration); - } - } else { - if (_tooltipBehavior.tooltipPosition == TooltipPosition.pointer && - ((_chart is SfCartesianChart) == false || - _currentSeries._isRectSeries == true)) { - _presentTooltipValue?.pointerPosition = _showLocation; - _chartTooltipState!.boundaryRect = _tooltipBounds!; - if (_showLocation != null) { - _chartTooltipState?.needMarker = _chart is SfCartesianChart; - _chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: _showLocation, - duration: _tooltipBehavior.animationDuration); - } - _currentTooltipValue = _presentTooltipValue; - } - } - assert( - // ignore: unnecessary_null_comparison - !(_tooltipBehavior.duration != null) || - _tooltipBehavior.duration >= 0, - 'The duration time for the tooltip must not be less than 0.'); - if (!_tooltipBehavior.shouldAlwaysShow) { - _show = false; - _currentTooltipValue = _presentTooltipValue = null; - if (_chartTooltipState != null && _renderBox != null) { - _chartTooltipState?.hide( - hideDelay: _tooltipBehavior.duration.toInt()); - } - } - } - } - - // this method resolves the position issue when the markerPoint is residing - // out of the axis cliprect - void _resolveLocation() { - if (_chart is SfCartesianChart && - _tooltipBehavior.tooltipPosition == TooltipPosition.auto && - (_seriesRenderer!._isRectSeries || - _seriesRenderer!._seriesType.contains('bubble') || - _seriesRenderer!._seriesType.contains('candle') || - _seriesRenderer!._seriesType.contains('boxandwhisker') || - _seriesRenderer!._seriesType.contains('waterfall'))) { - Offset position = _showLocation!; - final Rect bounds = _chartState._chartAxis._axisClipRect; - if (!_isPointWithInRect(position, bounds)) { - if (position.dy < bounds.top) { - position = Offset(position.dx, bounds.top); - } - if (position.dx < bounds.left) { - position = Offset(bounds.left, position.dy); - } else if (position.dx > bounds.right) { - position = Offset(bounds.right, position.dy); - } - } - _showLocation = position; - } - } - - /// This method shows the tooltip for any logical pixel outside point region - //ignore: unused_element - void _showChartAreaTooltip(Offset position, ChartAxisRenderer xAxisRenderer, - ChartAxisRenderer yAxisRenderer, dynamic chart) { - _showLocation = position; - final ChartAxis xAxis = xAxisRenderer._axis, yAxis = yAxisRenderer._axis; - if (_tooltipBehavior.enable && - _renderBox != null && - _chartState._animationCompleted == true) { - _tooltipBounds = _chartState._chartAxis._axisClipRect; - _chartTooltipState!.boundaryRect = _tooltipBounds!; - if (_isPointWithInRect(position, _chartState._chartAxis._axisClipRect)) { - _currentSeries = _chartState._chartSeries.visibleSeriesRenderers[0]; - _currentSeries = _chartState._chartSeries.visibleSeriesRenderers[0]; - _renderBox!.normalPadding = 5; - _renderBox!.inversePadding = 5; - _header = ''; - dynamic xValue = _pointToXValue( - _chartState._requireInvertedAxis, - xAxisRenderer, - xAxisRenderer._bounds, - position.dx - - (_chartState._chartAxis._axisClipRect.left + xAxis.plotOffset), - position.dy - - (_chartState._chartAxis._axisClipRect.top + xAxis.plotOffset)); - dynamic yValue = _pointToYValue( - _chartState._requireInvertedAxis, - yAxisRenderer, - yAxisRenderer._bounds, - position.dx - - (_chartState._chartAxis._axisClipRect.left + yAxis.plotOffset), - position.dy - - (_chartState._chartAxis._axisClipRect.top + yAxis.plotOffset)); - if (xAxisRenderer is DateTimeAxisRenderer) { - final DateTimeAxis xAxis = xAxisRenderer._axis as DateTimeAxis; - xValue = (xAxis.dateFormat ?? _getDateTimeLabelFormat(xAxisRenderer)) - .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); - } else if (xAxisRenderer is DateTimeCategoryAxisRenderer) { - xValue = xAxisRenderer._dateFormat - .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); - } else if (xAxisRenderer is CategoryAxisRenderer) { - xValue = xAxisRenderer._visibleLabels[xValue.toInt()].text; - } else if (xAxisRenderer is NumericAxisRenderer) { - xValue = xValue.toStringAsFixed(2).contains('.00') == true - ? xValue.floor() - : xValue.toStringAsFixed(2); - } - - if (yAxisRenderer is NumericAxisRenderer || - yAxisRenderer is LogarithmicAxisRenderer) { - yValue = yValue.toStringAsFixed(2).contains('.00') == true - ? yValue.floor() - : yValue.toStringAsFixed(2); - } - _stringValue = ' $xValue : $yValue '; - _showLocation = position; - } - - if (_showLocation != null && - _stringVal != null && - _tooltipBounds != null) { - _chartTooltipState?.needMarker = false; - _chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: _showLocation, - duration: _tooltipBehavior.animationDuration); - } - - if (!_tooltipBehavior.shouldAlwaysShow) { - _show = false; - if (_chartTooltipState != null && _renderBox != null) { - _chartTooltipState?.hide(); - } - } - } - } - - void _showTemplateTooltip(Offset position, [dynamic xValue, dynamic yValue]) { - final dynamic chart = _chartState._chart; - _presentTooltipValue = null; - _tooltipBounds = _chartState._chartAxis._axisClipRect; - dynamic series; - double yPadding = 0; - if (_isPointWithInRect(position, _chartState._chartAxis._axisClipRect) && - _chartState._animationCompleted == true) { - int? seriesIndex, pointIndex; - int outlierIndex = -1; - bool isTooltipRegion = false; - if (!_isHovering) { - //assingning null for the previous and current tooltip values in case of mouse not hovering - _prevTooltipValue = null; - _currentTooltipValue = null; - } - for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; - i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - series = seriesRenderer._series; - - int j = 0; - if (seriesRenderer._visible! && - series.enableTooltip == true && - seriesRenderer._regionalData != null) { - seriesRenderer._regionalData! - .forEach((dynamic regionRect, dynamic values) { - final bool isTrendLine = values[values.length - 1].contains('true'); - final double padding = ((seriesRenderer._seriesType == 'bubble' || - seriesRenderer._seriesType == 'scatter' || - seriesRenderer._seriesType.contains('column') || - seriesRenderer._seriesType.contains('bar') || - seriesRenderer._seriesType == 'histogram') && - !isTrendLine) - ? 0 - : _isHovering - ? 0 - : 15; - final Rect region = regionRect[0]; - final double left = region.left - padding; - final double right = region.right + padding; - final double top = region.top - padding; - final double bottom = region.bottom + padding; - Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); - final List? outlierRegion = regionRect[5]; - - if (outlierRegion != null) { - for (int rectIndex = 0; - rectIndex < outlierRegion.length; - rectIndex++) { - if (outlierRegion[rectIndex].contains(position)) { - paddedRegion = outlierRegion[rectIndex]; - outlierIndex = rectIndex; - } - } - } - - if (paddedRegion.contains(position)) { - _seriesIndex = seriesIndex = i; - _currentSeries = seriesRenderer; - _pointIndex = pointIndex = - seriesRenderer._dataPoints.indexOf(regionRect[4]); - Offset tooltipPosition = !(seriesRenderer._isRectSeries && - _tooltipBehavior.tooltipPosition != TooltipPosition.auto) - ? ((outlierIndex >= 0) - ? regionRect[6][outlierIndex] - : regionRect[1]) - : position; - final List paddingData = _getTooltipPaddingData( - seriesRenderer, - isTrendLine, - region, - paddedRegion, - tooltipPosition); - yPadding = paddingData[0]!.dy; - tooltipPosition = paddingData[1] ?? tooltipPosition; - _showLocation = tooltipPosition; - _seriesRenderer = seriesRenderer; - _renderBox!.normalPadding = - _seriesRenderer is BubbleSeriesRenderer ? 0 : yPadding; - _renderBox!.inversePadding = yPadding; - _tooltipTemplate = chart.tooltipBehavior.builder( - series.dataSource[j], regionRect[4], series, pointIndex, i); - isTooltipRegion = true; - } - j++; - }); - } - } - if (_isHovering && isTooltipRegion) { - _prevTooltipValue = _currentTooltipValue; - _currentTooltipValue = - TooltipValue(seriesIndex, pointIndex!, outlierIndex); - } - final TooltipValue? presentTooltip = _presentTooltipValue; - if (presentTooltip == null || - seriesIndex != presentTooltip.seriesIndex || - pointIndex != presentTooltip.pointIndex || - outlierIndex != presentTooltip.outlierIndex || - (_currentSeries != null && - _currentSeries._isRectSeries == true && - _tooltipBehavior.tooltipPosition != TooltipPosition.auto)) { - //Current point is different than previous one so tooltip re-renders - if (seriesIndex != null && pointIndex != null) { - _presentTooltipValue = - TooltipValue(seriesIndex, pointIndex!, outlierIndex); - } - if (isTooltipRegion && _tooltipTemplate != null) { - _show = isTooltipRegion; - _performTooltip(); - if (!_isHovering && _renderBox != null) { - _hideTooltipTemplate(); - } - } - } else { - //Current point is same as previous one so timer is reset and tooltip is not re-rendered - if (!_isHovering) { - _hideTooltipTemplate(); - } - } - - if (!isTooltipRegion && - !_isInteraction && - chart.series.isNotEmpty == true) { - //to show tooltip temlate when the position resides outside point region - final dynamic x = xValue ?? - _pointToXValue( - _chartState._requireInvertedAxis, - _chartState._chartAxis._primaryXAxisRenderer, - _chartState._chartAxis._primaryXAxisRenderer._bounds, - position.dx - - (_chartState._chartAxis._axisClipRect.left + - chart.primaryXAxis.plotOffset), - position.dy - - (_chartState._chartAxis._axisClipRect.top + - chart.primaryXAxis.plotOffset)); - final dynamic y = yValue ?? - _pointToYValue( - _chartState._requireInvertedAxis, - _chartState._chartAxis._primaryYAxisRenderer, - _chartState._chartAxis._primaryYAxisRenderer._bounds, - position.dx - - (_chartState._chartAxis._axisClipRect.left + - chart.primaryYAxis.plotOffset), - position.dy - - (_chartState._chartAxis._axisClipRect.top + - chart.primaryYAxis.plotOffset)); - _renderBox!.normalPadding = 5; - _renderBox!.inversePadding = 5; - _showLocation = position; - _tooltipTemplate = chart.tooltipBehavior.builder( - null, CartesianChartPoint(x, y), null, null, null); - isTooltipRegion = true; - _show = isTooltipRegion; - _performTooltip(); - } - if (!isTooltipRegion) { - _hideTooltipTemplate(); - } - } - _isInteraction = false; - } - - /// To hide the tooltip when the timer ends - void _hide() { - if (!_tooltipBehavior.shouldAlwaysShow) { - _show = false; - _currentTooltipValue = _presentTooltipValue = null; - if (_chartTooltipState != null && _renderBox != null) { - _chartTooltipState?.hide(hideDelay: _tooltipBehavior.duration.toInt()); - } - } - } - - /// To hide tooltip templates - void _hideTooltipTemplate() { - if (_tooltipBehavior.shouldAlwaysShow == false) { - _show = false; - _chartTooltipState?.hide(hideDelay: _tooltipBehavior.duration.toInt()); - _prevTooltipValue = null; - _currentTooltipValue = null; - _presentTooltipValue = null; - } - } - - /// To perform rendering of tooltip - void _performTooltip() { - //for mouse hover the tooltip is redrawn only when the current tooltip value differs from the previous one - if (_show && - ((_prevTooltipValue == null && _currentTooltipValue == null) || - (_chartState is SfCartesianChartState && - (_currentSeries?._isRectSeries ?? false) == true && - _tooltipBehavior.tooltipPosition != TooltipPosition.auto) || - (_prevTooltipValue?.seriesIndex != - _currentTooltipValue?.seriesIndex || - _prevTooltipValue?.outlierIndex != - _currentTooltipValue?.outlierIndex || - _prevTooltipValue?.pointIndex != - _currentTooltipValue?.pointIndex))) { - final bool reRender = _isHovering && - _prevTooltipValue != null && - _currentTooltipValue != null && - _prevTooltipValue!.seriesIndex == _currentTooltipValue!.seriesIndex && - _prevTooltipValue!.pointIndex == _currentTooltipValue!.pointIndex && - _prevTooltipValue!.outlierIndex == _currentTooltipValue!.outlierIndex; - if (_tooltipBehavior.builder != null && _tooltipBounds != null) { - _chartTooltipState!.boundaryRect = _tooltipBounds!; - if (_tooltipBehavior.tooltipPosition != TooltipPosition.auto) - _presentTooltipValue!.pointerPosition = _showLocation; - if (_showLocation != null) { - _resolveLocation(); - _chartTooltipState?.show( - tooltipData: _presentTooltipValue, - position: _showLocation, - duration: - (!reRender) ? _tooltipBehavior.animationDuration.toInt() : 0, - template: _tooltipTemplate); - } - } - } - } - - /// To show tooltip on mouse pointer actions - void _showMouseTooltip(double x, double y) { - if (_tooltipBehavior.enable && - _renderBox != null && - _chartState._animationCompleted == true) { - _renderTooltipView(Offset(x, y)); - if (!_mouseTooltip) { - _chartTooltipState?.hide(hideDelay: _tooltipBehavior.duration.toInt()); - _currentTooltipValue = null; - } else { - if (_presentTooltipValue != null && - (_currentTooltipValue == null || - _tooltipBehavior.tooltipPosition == TooltipPosition.auto)) { - _chartTooltipState!.boundaryRect = _tooltipBounds!; - if (_tooltipBehavior.tooltipPosition != TooltipPosition.auto) - _presentTooltipValue!.pointerPosition = _showLocation; - if (_showLocation != null) { - _chartTooltipState?.needMarker = _chart is SfCartesianChart; - _resolveLocation(); - _chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: _showLocation, - duration: _tooltipBehavior.animationDuration); - } - _currentTooltipValue = _presentTooltipValue; - } else if (_presentTooltipValue != null && - _currentTooltipValue != null && - _tooltipBehavior.tooltipPosition != TooltipPosition.auto && - ((_seriesRenderer is CartesianSeriesRenderer) == false || - _seriesRenderer!._isRectSeries)) { - _presentTooltipValue!.pointerPosition = _showLocation; - _chartTooltipState!.boundaryRect = _tooltipBounds!; - if (_showLocation != null) { - _chartTooltipState?.needMarker = _chart is SfCartesianChart; - _resolveLocation(); - _chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _presentTooltipValue, - position: _showLocation, - duration: 0); - } - } - } - } - } - - void _tooltipRenderingEvent(TooltipRenderArgs args) { - String? header = args.header; - String? stringValue = args.text; - double? x = args.location?.dx, y = args.location?.dy; - TooltipArgs tooltipArgs; - if (x != null && - y != null && - stringValue != null && - _currentSeries != null) { - final int seriesIndex = _chart is SfCartesianChart - ? _currentSeries._segments[0]._seriesIndex - : 0; - if ((_chart is SfCartesianChart && - _chartState._requireAxisTooltip == false) || - (_chart is SfCartesianChart) == false) { - if (_chart.onTooltipRender != null && - _dataPoint != null && - (_dataPoint.isTooltipRenderEvent ?? false) == false) { - _dataPoint.isTooltipRenderEvent = true; - tooltipArgs = TooltipArgs( - seriesIndex, - _chartState - ._chartSeries.visibleSeriesRenderers[seriesIndex]._dataPoints, - _pointIndex, - _chart is SfCartesianChart - ? _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._visibleDataPoints[_pointIndex].overallDataPointIndex - : _pointIndex); - tooltipArgs.text = stringValue; - tooltipArgs.header = header; - _dataPoint._tooltipLabelText = stringValue; - _dataPoint._tooltipHeaderText = header; - tooltipArgs.locationX = x; - tooltipArgs.locationY = y; - _chart.onTooltipRender(tooltipArgs); - stringValue = tooltipArgs.text; - header = tooltipArgs.header; - x = tooltipArgs.locationX; - y = tooltipArgs.locationY; - _dataPoint._tooltipLabelText = tooltipArgs.text; - _dataPoint._tooltipHeaderText = tooltipArgs.header; - _dataPoint.isTooltipRenderEvent = false; - args.text = stringValue!; - args.header = header; - args.location = Offset(x!, y!); - } else if (_chart.onTooltipRender != null) { - //Fires the on tooltip render event when the tooltip is shown outside point region - tooltipArgs = TooltipArgs(null, null, null); - tooltipArgs.text = stringValue; - tooltipArgs.header = header; - tooltipArgs.locationX = x; - tooltipArgs.locationY = y; - _chart.onTooltipRender(tooltipArgs); - args.text = tooltipArgs.text; - args.header = tooltipArgs.header; - args.location = - Offset(tooltipArgs.locationX!, tooltipArgs.locationY!); - } - if (_chart.onTooltipRender != null && _dataPoint != null) { - stringValue = _dataPoint._tooltipLabelText; - header = _dataPoint._tooltipHeaderText; - } - } - } - } - - /// To render a chart tooltip for circular series - void _renderCircularChartTooltip(Offset position) { - final SfCircularChart chart = _chartState._chart; - _tooltipBounds = _chartState._renderingDetails.chartContainerRect; - bool isContains = false; - final _Region? pointRegion = _getCircularPointRegion( - chart, position, _chartState._chartSeries.visibleSeriesRenderers[0]); - if (pointRegion != null && - _chartState._chartSeries.visibleSeriesRenderers[pointRegion.seriesIndex] - ._series.enableTooltip == - true) { - _prevTooltipValue = - TooltipValue(pointRegion.seriesIndex, pointRegion.pointIndex); - _presentTooltipValue = _prevTooltipValue; - if (_prevTooltipValue != null && - _currentTooltipValue != null && - _prevTooltipValue!.pointIndex != _currentTooltipValue!.pointIndex) { - _currentTooltipValue = null; - } - final ChartPoint chartPoint = _chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._renderPoints[pointRegion.pointIndex]; - final Offset location = - chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer - ? position - : _degreeToPoint( - chartPoint.midAngle!, - (chartPoint.innerRadius! + chartPoint.outerRadius!) / 2, - chartPoint.center!); - _currentSeries = pointRegion.seriesIndex; - _pointIndex = pointRegion.pointIndex; - _dataPoint = _chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints[_pointIndex]; - final int digits = chart.tooltipBehavior.decimalPlaces; - String? header = chart.tooltipBehavior.header; - header = (header == null) - // ignore: prefer_if_null_operators - ? _chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._series - .name != - null - ? _chartState._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex]._series.name - : null - : header; - _header = header ?? ''; - if (chart.tooltipBehavior.format != null) { - final String resultantString = chart.tooltipBehavior.format! - .replaceAll('point.x', chartPoint.x.toString()) - .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) - .replaceAll( - 'series.name', - _chartState - ._chartSeries - .visibleSeriesRenderers[pointRegion.seriesIndex] - ._series - .name ?? - 'series.name'); - _stringValue = resultantString; - _showLocation = location; - } else { - _stringValue = chartPoint.x.toString() + - ' : ' + - _getDecimalLabelValue(chartPoint.y, digits); - _showLocation = location; - } - if (chart.series[0].explode) { - _presentTooltipValue!.pointerPosition = _showLocation; - } - isContains = true; - } else { - isContains = false; - } - _mouseTooltip = isContains; - if (!isContains) { - _prevTooltipValue = _currentTooltipValue = null; - } - } - - /// To render a chart tooltip for triangular series - void _renderTriangularChartTooltip(Offset position) { - final dynamic chart = _chart; - final dynamic chartState = _chartState; - - _tooltipBounds = chartState._renderingDetails.chartContainerRect; - bool isContains = false; - const int seriesIndex = 0; - _pointIndex = _chartState._tooltipPointIndex; - if (_pointIndex == null && - _chartState._renderingDetails.currentActive == null) { - int? _pointIndex; - bool isPoint; - final dynamic seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - if (seriesRenderer._renderPoints[j].isVisible == true) { - isPoint = _isPointInPolygon( - seriesRenderer._renderPoints[j].pathRegion, position); - if (isPoint) { - _pointIndex = j; - break; - } - } - } - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - _pointIndex, - seriesRenderer._series, - seriesRenderer._renderPoints[_pointIndex], - ); - } - _pointIndex ??= chartState._renderingDetails.currentActive.pointIndex; - _dataPoint = _chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints[_pointIndex]; - _chartState._tooltipPointIndex = null; - final int digits = chart.tooltipBehavior.decimalPlaces; - if (chart.tooltipBehavior.enable == true) { - _prevTooltipValue = TooltipValue(seriesIndex, _pointIndex!); - _presentTooltipValue = _prevTooltipValue; - if (_prevTooltipValue != null && - _currentTooltipValue != null && - _prevTooltipValue!.pointIndex != _currentTooltipValue!.pointIndex) { - _currentTooltipValue = null; - } - final PointInfo chartPoint = _chartState._chartSeries - .visibleSeriesRenderers[seriesIndex]._renderPoints[_pointIndex]; - final Offset location = chart.tooltipBehavior.tooltipPosition == - TooltipPosition.pointer && - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._series.explode == - true - ? chartPoint.symbolLocation - : chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._series.explode == - false - ? position - : chartPoint.symbolLocation; - _currentSeries = seriesIndex; - String? header = chart.tooltipBehavior.header; - header = (header == null) - // ignore: prefer_if_null_operators - ? _chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series - .name != - null - ? _chartState - ._chartSeries.visibleSeriesRenderers[seriesIndex]._series.name - : null - : header; - _header = header ?? ''; - if (chart.tooltipBehavior.format != null) { - final String resultantString = chart.tooltipBehavior.format - .replaceAll('point.x', chartPoint.x.toString()) - .replaceAll('point.y', _getDecimalLabelValue(chartPoint.y, digits)) - .replaceAll( - 'series.name', - _chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._series.name ?? - 'series.name'); - _stringValue = resultantString; - _showLocation = location; - } else { - _stringValue = chartPoint.x.toString() + - ' : ' + - _getDecimalLabelValue(chartPoint.y, digits); - _showLocation = location; - } - isContains = true; - } else { - isContains = false; - } - if (chart.series.explode == true) { - _presentTooltipValue!.pointerPosition = _showLocation; - } - _mouseTooltip = isContains; - if (!isContains) { - _prevTooltipValue = _currentTooltipValue = null; - } - } - - /// To show the axis label tooltip for trimmed axes label texts. - void _showAxisTooltip(Offset position, dynamic chart, String text) { - final _RenderingDetails renderingDetails = _chartState._renderingDetails; - if (_renderBox != null) { - _header = ''; - _stringValue = text; - _showLocation = position; - _tooltipBounds = renderingDetails.chartContainerRect; - _renderBox!.inversePadding = 0; - _chartTooltipState!.boundaryRect = _tooltipBounds!; - if (_showLocation != null) { - _chartTooltipState?.needMarker = false; - _chartTooltipState?.show( - tooltipHeader: _header, - tooltipContent: _stringVal, - tooltipData: _currentTooltipValue, - position: _showLocation, - duration: 0); - } - if (!_isHovering) { - _chartTooltipState?.hide(hideDelay: _tooltipBehavior.duration.toInt()); - } - } - } - - /// To render a chart tooltip for cartesian series - void _renderCartesianChartTooltip(Offset position) { - bool isContains = false; - if (_isPointWithInRect(position, _chartState._chartAxis._axisClipRect)) { - Offset? tooltipPosition; - double touchPadding; - Offset? padding; - bool? isTrendLine; - dynamic dataRect; - dynamic dataValues; - bool outlierTooltip = false; - int outlierTooltipIndex = -1; - final List markerGradients = []; - final List markerPaints = []; - final List markerTypes = []; - final List markerImages = []; - for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; - i++) { - _seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[i]; - final CartesianSeries series = - _seriesRenderer!._series; - if (_seriesRenderer!._visible! && - series.enableTooltip && - _seriesRenderer?._regionalData != null) { - int count = 0; - _seriesRenderer!._regionalData! - .forEach((dynamic regionRect, dynamic values) { - isTrendLine = values[values.length - 1].contains('true'); - touchPadding = ((_seriesRenderer!._seriesType == 'bubble' || - _seriesRenderer!._seriesType == 'scatter' || - _seriesRenderer!._seriesType.contains('column') || - _seriesRenderer!._seriesType.contains('bar') || - _seriesRenderer!._seriesType == 'histogram') && - !isTrendLine!) - ? 0 - : _isHovering - ? 0 - : 15; // regional padding to detect smooth touch - final Rect region = regionRect[0]; - final List? outlierRegion = regionRect[5]; - final double left = region.left - touchPadding; - final double right = region.right + touchPadding; - final double top = region.top - touchPadding; - final double bottom = region.bottom + touchPadding; - Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); - if (outlierRegion != null) { - for (int rectIndex = 0; - rectIndex < outlierRegion.length; - rectIndex++) { - if (outlierRegion[rectIndex].contains(position)) { - paddedRegion = outlierRegion[rectIndex]; - outlierTooltipIndex = rectIndex; - outlierTooltip = true; - } - } - } - - if (paddedRegion.contains(position) && - (isTrendLine! ? regionRect[4].isVisible : true) == true) { - _tooltipBounds = _chartState._chartAxis._axisClipRect; - if (_seriesRenderer!._seriesType != 'boxandwhisker' - ? !region.contains(position) - : (paddedRegion.contains(position) || - !region.contains(position))) { - _tooltipBounds = _chartState._chartAxis._axisClipRect; - } - _presentTooltipValue = - TooltipValue(i, count, outlierTooltipIndex); - _currentSeries = _seriesRenderer; - _pointIndex = _chart is SfCartesianChart - ? regionRect[4].visiblePointIndex - : count; - _dataPoint = regionRect[4]; - _markerType = _seriesRenderer!._series.markerSettings.shape; - Color? seriesColor = _seriesRenderer!._seriesColor; - if (_seriesRenderer!._seriesType == 'waterfall') { - seriesColor = _getWaterfallSeriesColor( - _seriesRenderer!._series - as WaterfallSeries, - _seriesRenderer!._dataPoints[_pointIndex!], - seriesColor)!; - } - _markerColor = regionRect[2] ?? - _seriesRenderer!._series.markerSettings.borderColor ?? - seriesColor!; - tooltipPosition = (outlierTooltipIndex >= 0) - ? regionRect[6][outlierTooltipIndex] - : regionRect[1]; - final Paint markerPaint = Paint(); - markerPaint.color = (!_tooltipBehavior.shared - ? _markerColor - : _seriesRenderer!._series.markerSettings.borderColor ?? - _seriesRenderer!._seriesColor ?? - _seriesRenderer!._series.color)! - .withOpacity(_tooltipBehavior.opacity); - if (!_tooltipBehavior.shared) { - markerGradients - ..clear() - ..add(_seriesRenderer!._series.gradient); - markerImages - ..clear() - ..add(_seriesRenderer!._markerSettingsRenderer?._image); - markerPaints - ..clear() - ..add(markerPaint); - markerTypes - ..clear() - ..add(_markerType); - } - final List paddingData = !(_seriesRenderer! - ._isRectSeries && - _tooltipBehavior.tooltipPosition != TooltipPosition.auto) - ? _getTooltipPaddingData(_seriesRenderer!, isTrendLine!, - region, paddedRegion, tooltipPosition) - : [const Offset(2, 2), tooltipPosition]; - padding = paddingData[0]; - tooltipPosition = paddingData[1]; - _showLocation = tooltipPosition; - dataValues = values; - dataRect = regionRect; - isContains = _mouseTooltip = true; - } - count++; - }); - if (_tooltipBehavior.shared) { - int indexValue = 0; - int tooltipElementsLength = 0; - final Paint markerPaint = Paint(); - markerPaint.color = - _seriesRenderer!._series.markerSettings.borderColor ?? - _seriesRenderer!._seriesColor ?? - _seriesRenderer!._series.color! - .withOpacity(_tooltipBehavior.opacity); - markerGradients.add(_seriesRenderer!._series.gradient); - markerImages.add(_seriesRenderer!._markerSettingsRenderer?._image); - markerTypes.add(_seriesRenderer!._series.markerSettings.shape); - markerPaints.add(markerPaint); - if ((_seriesRenderer!._seriesType.contains('range') || - _seriesRenderer!._seriesType == 'hilo') && - !isTrendLine!) { - // Assigned value '2' for this variable because for range and - // hilo series there will be two display value types - // such as high and low. - tooltipElementsLength = 2; - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } else if (_seriesRenderer!._seriesType == 'hiloopenclose' || - _seriesRenderer!._seriesType == 'candle') { - // Assigned value '4' for this variable because for hiloopenclose - // and candle series there will be four display values - // such as high, low, open and close. - tooltipElementsLength = 4; - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } else if (_seriesRenderer!._seriesType == 'boxandwhisker') { - // Assigned value '1' or '6' this variable field because for the - // box and whiskers series there will be one display values if - // the outlier's tooltip is activated otherwise there will be - // six display values such as maximum, minimum, mean, median, - // lowerQuartile and upperQuartile. - tooltipElementsLength = outlierTooltip ? 1 : 6; - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } else { - indexValue = _getTooltipNewLineLength(tooltipElementsLength); - } - for (int j = 0; j < indexValue; j++) { - markerTypes.add(null); - markerImages.add(null); - markerGradients.add(null); - markerPaints.add(null); - } - } - } - } - if (isContains) { - _renderBox!.markerGradients = markerGradients; - _renderBox!.markerImages = markerImages; - _renderBox!.markerPaints = markerPaints; - _renderBox!.markerTypes = markerTypes; - _seriesRenderer = _currentSeries ?? _seriesRenderer; - if (_currentSeries._isRectSeries == true && - _tooltipBehavior.tooltipPosition == TooltipPosition.pointer) { - tooltipPosition = position; - _showLocation = tooltipPosition; - } - _renderBox!.normalPadding = - _seriesRenderer is BubbleSeriesRenderer ? 0 : padding!.dy; - _renderBox!.inversePadding = padding!.dy; - String? header = _tooltipBehavior.header; - header = (header == null) - ? (_tooltipBehavior.shared - ? dataValues[0] - : (isTrendLine! - ? dataValues[dataValues.length - 2] - : _currentSeries._series.name ?? - _currentSeries._seriesName)) - : header; - _header = header ?? ''; - _stringValue = ''; - if (_tooltipBehavior.shared) { - _textValues = []; - _seriesRendererCollection = []; - for (int j = 0; - j < _chartState._chartSeries.visibleSeriesRenderers.length; - j++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[j]; - if (seriesRenderer._visible! && - seriesRenderer._series.enableTooltip) { - final int index = seriesRenderer._xValues!.indexOf(dataRect[4].x); - if (index > -1) { - final String text = (_stringVal != '' ? '\n' : '') + - _calculateCartesianTooltipText( - seriesRenderer, - seriesRenderer._dataPoints[index], - dataValues, - tooltipPosition!, - outlierTooltip, - outlierTooltipIndex); - _stringValue = _stringVal! + text; - _textValues.add(text); - _seriesRendererCollection.add(seriesRenderer); - } - } - } - } else { - _stringValue = _calculateCartesianTooltipText( - _currentSeries, - dataRect[4], - dataValues, - tooltipPosition!, - outlierTooltip, - outlierTooltipIndex); - } - _showLocation = tooltipPosition; - } else { - _stringValue = null; - if (!_isHovering) { - _presentTooltipValue = _currentTooltipValue = null; - } else { - _mouseTooltip = isContains; - } - } - } - } - - /// It returns the tooltip text of cartesian series - String _calculateCartesianTooltipText( - CartesianSeriesRenderer seriesRenderer, - CartesianChartPoint point, - dynamic values, - Offset tooltipPosition, - bool outlierTooltip, - int outlierTooltipIndex) { - final bool isTrendLine = values[values.length - 1].contains('true'); - String resultantString; - final ChartAxisRenderer axisRenderer = seriesRenderer._yAxisRenderer!; - final TooltipBehavior tooltip = _tooltipBehavior; - final int digits = seriesRenderer._chart.tooltipBehavior.decimalPlaces; - String? minimumValue, - maximumValue, - lowerQuartileValue, - upperQuartileValue, - medianValue, - meanValue, - outlierValue, - highValue, - lowValue, - openValue, - closeValue, - cumulativeValue, - boxPlotString; - if (seriesRenderer._seriesType == 'boxandwhisker') { - minimumValue = _getLabelValue(point.minimum, axisRenderer._axis, digits); - maximumValue = _getLabelValue(point.maximum, axisRenderer._axis, digits); - lowerQuartileValue = - _getLabelValue(point.lowerQuartile, axisRenderer._axis, digits); - upperQuartileValue = - _getLabelValue(point.upperQuartile, axisRenderer._axis, digits); - medianValue = _getLabelValue(point.median, axisRenderer._axis, digits); - meanValue = _getLabelValue(point.mean, axisRenderer._axis, digits); - outlierValue = (point.outliers!.isNotEmpty && outlierTooltipIndex >= 0) - ? _getLabelValue( - point.outliers![outlierTooltipIndex], axisRenderer._axis, digits) - : null; - boxPlotString = '\nMinimum : ' + - minimumValue + - '\nMaximum : ' + - maximumValue + - '\nMedian : ' + - medianValue + - '\nMean : ' + - meanValue + - '\nLQ : ' + - lowerQuartileValue + - '\nHQ : ' + - upperQuartileValue; - } else if (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo' || - seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle') { - highValue = _getLabelValue(point.high, axisRenderer._axis, digits); - lowValue = _getLabelValue(point.low, axisRenderer._axis, digits); - if (seriesRenderer._seriesType == 'candle' || - seriesRenderer._seriesType == 'hiloopenclose') { - openValue = _getLabelValue(point.open, axisRenderer._axis, digits); - closeValue = _getLabelValue(point.close, axisRenderer._axis, digits); - } - } else if (seriesRenderer._seriesType.contains('stacked')) { - cumulativeValue = - _getLabelValue(point.cumulativeValue, axisRenderer._axis, digits); - } - if (_tooltipBehavior.format != null) { - resultantString = (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo') && - !isTrendLine - ? (tooltip.format! - .replaceAll('point.x', values[0]) - .replaceAll('point.high', highValue!) - .replaceAll('point.low', lowValue!) - .replaceAll('series.name', - seriesRenderer._series.name ?? seriesRenderer._seriesName!)) - : (seriesRenderer._seriesType.contains('hiloopenclose') || - seriesRenderer._seriesType.contains('candle')) && - !isTrendLine - ? (tooltip.format! - .replaceAll('point.x', values[0]) - .replaceAll('point.high', highValue!) - .replaceAll('point.low', lowValue!) - .replaceAll('point.open', openValue!) - .replaceAll('point.close', closeValue!) - .replaceAll( - 'series.name', - seriesRenderer._series.name ?? - seriesRenderer._seriesName!)) - : (seriesRenderer._seriesType.contains('boxandwhisker')) && - !isTrendLine - ? (tooltip.format! - .replaceAll('point.x', values[0]) - .replaceAll('point.minimum', minimumValue!) - .replaceAll('point.maximum', maximumValue!) - .replaceAll('point.lowerQuartile', lowerQuartileValue!) - .replaceAll('point.upperQuartile', upperQuartileValue!) - .replaceAll('point.mean', meanValue!) - .replaceAll('point.median', medianValue!) - .replaceAll('series.name', - seriesRenderer._series.name ?? seriesRenderer._seriesName!)) - : seriesRenderer._seriesType.contains('stacked') - ? tooltip.format!.replaceAll('point.cumulativeValue', cumulativeValue!) - : seriesRenderer._seriesType == 'bubble' - ? tooltip.format!.replaceAll('point.x', values[0]).replaceAll('point.y', _getLabelValue(point.y, axisRenderer._axis, digits)).replaceAll('series.name', seriesRenderer._series.name ?? seriesRenderer._seriesName!).replaceAll('point.size', _getLabelValue(point.bubbleSize, axisRenderer._axis, digits)) - : tooltip.format!.replaceAll('point.x', values[0]).replaceAll('point.y', _getLabelValue(point.y, axisRenderer._axis, digits)).replaceAll('series.name', seriesRenderer._series.name ?? seriesRenderer._seriesName!); - } else { - resultantString = (_tooltipBehavior.shared - ? seriesRenderer._series.name ?? seriesRenderer._seriesName - : values[0]) + - (((seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType == 'hilo') && - !isTrendLine) - ? ('\nHigh : ' + highValue! + '\nLow : ' + lowValue!) - : (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle' - ? ('\nHigh : ' + - highValue! + - '\nLow : ' + - lowValue! + - '\nOpen : ' + - openValue! + - '\nClose : ' + - closeValue!) - : seriesRenderer._seriesType == 'boxandwhisker' - ? outlierValue != null - ? ('\nOutliers : ' + outlierValue) - : boxPlotString - : ' : ' + - _getLabelValue(point.y, axisRenderer._axis, digits))); - } - return resultantString; - } - - // Returns the cumulative length of the number of new line character '\n' - // available in series name and tooltip format string. - int _getTooltipNewLineLength(int value) { - value += _seriesRenderer!._series.name != null - ? '\n'.allMatches(_seriesRenderer!._series.name!).length - : 0; - if (_tooltipBehavior.format != null) { - value += '\n'.allMatches(_tooltipBehavior.format!).length; - } - return value; - } - - //finds whether the point resides inside the given rect including its edges - bool _isPointWithInRect(Offset point, Rect rect) { - return point != null && - point.dx >= rect.left && - point.dx <= rect.right && - point.dy <= rect.bottom && - point.dy >= rect.top; - } } /// Holds the tooltip series and point index @@ -2068,3 +806,25 @@ class TooltipValue { } } } + +// ignore: avoid_classes_with_only_static_members +/// Helper class to get the cross hair rendering details instance from its renderer +class TooltipHelper { + /// Returns the cross hair rendering details instance from its renderer + static TooltipRenderingDetails getRenderingDetails( + TooltipBehaviorRenderer renderer) { + return renderer._tooltipRenderingDetails; + } + + // /// Returns the Cartesian state properties from its instance + // static CartesianStateProperties getStateProperties( + // CrosshairBehavior crosshairBehavior) { + // return crosshairBehavior._stateProperties; + // } + + /// Method to set the Cartesian state properties + static void setStateProperties( + TooltipBehavior tooltipBehavior, dynamic stateProperties) { + tooltipBehavior._stateProperties = stateProperties; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip_rendering_details.dart b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip_rendering_details.dart new file mode 100644 index 000000000..9b23c265f --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/common/user_interaction/tooltip_rendering_details.dart @@ -0,0 +1,1596 @@ +import 'dart:async'; +import 'dart:ui'; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_segment/chart_segment.dart'; +import 'package:syncfusion_flutter_charts/src/chart/chart_series/series_renderer_properties.dart'; +import 'package:syncfusion_flutter_charts/src/chart/common/cartesian_state_properties.dart'; +import 'package:syncfusion_flutter_charts/src/circular_chart/base/circular_state_properties.dart'; +import 'package:syncfusion_flutter_charts/src/circular_chart/utils/enum.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/tooltip_internal.dart'; + +import '../../chart/axis/axis.dart'; +import '../../chart/axis/category_axis.dart'; +import '../../chart/axis/datetime_axis.dart'; +import '../../chart/axis/datetime_category_axis.dart'; +import '../../chart/axis/logarithmic_axis.dart'; +import '../../chart/axis/numeric_axis.dart'; +import '../../chart/base/chart_base.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/chart_series/waterfall_series.dart'; +import '../../chart/chart_series/xy_data_series.dart'; +import '../../chart/series_painter/bubble_painter.dart'; +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../circular_chart/base/circular_base.dart'; +import '../../circular_chart/renderer/chart_point.dart'; +import '../../circular_chart/renderer/common.dart'; +import '../../circular_chart/utils/helper.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../../pyramid_chart/utils/helper.dart'; +import '../event_args.dart'; +import '../rendering_details.dart'; +import 'tooltip.dart'; + +export 'package:syncfusion_flutter_core/core.dart' + show DataMarkerType, TooltipAlignment; + +/// Represents the tooltip rendering details +class TooltipRenderingDetails { + /// Creates an instance of tooltip rendering details + TooltipRenderingDetails(this._stateProperties); + final dynamic _stateProperties; + + /// Gets the instance of tooltip behavior + TooltipBehavior get tooltipBehavior => + _stateProperties.chart.tooltipBehavior as TooltipBehavior; + + /// Specifies the chart tooltip value + SfTooltip? chartTooltip; + + /// Specifies whether interaction is done + bool isInteraction = false; + + /// Specifies the tooltip is hovered + bool isHovering = false, _mouseTooltip = false; + + /// Specifies the toopltip template + Widget? tooltipTemplate; + + /// specifies the value of tooltip render box + TooltipRenderBox? get renderBox => chartTooltipState?.renderBox; + + /// Specifies the chart tooltip state + SfTooltipState? get chartTooltipState { + if (chartTooltip != null) { + final State? state = (chartTooltip?.key as GlobalKey).currentState; + //ignore: avoid_as + return state != null ? state as SfTooltipState : null; + } + } + + List _textValues = []; + List _seriesRendererCollection = + []; + + /// Specifies the previous tooltip value + TooltipValue? prevTooltipValue; + TooltipValue? _presentTooltipValue; + + /// Specifies the current tooltip value + TooltipValue? currentTooltipValue; + + SeriesRendererDetails? _seriesRendererDetails; + + /// Specifies the value of current series and the current datat point + dynamic currentSeriesDetails, _dataPoint; + int? _pointIndex; + + /// Holds the series index value + late int seriesIndex; + late Color _markerColor; + late DataMarkerType _markerType; + + /// Specifies the tooltip timer + Timer? timer; + + /// Specifies whether the tooltip is shown + bool show = false; + + /// Holds the offset value of show location + Offset? showLocation; + + /// Holds the value of tooltip bounds + Rect? tooltipBounds; + String? _stringVal, _header; + set _stringValue(String? value) { + _stringVal = value; + } + + /// To render chart tooltip + // ignore:unused_element + void _renderTooltipView(Offset position) { + if (_stateProperties.chart is SfCartesianChart) { + _renderCartesianChartTooltip(position); + } else if (_stateProperties.chart is SfCircularChart) { + _renderCircularChartTooltip(position); + } else { + _renderTriangularChartTooltip(position); + } + } + + /// To show tooltip with position offsets + void showTooltip(double? x, double? y) { + if (x != null && + y != null && + renderBox != null && + chartTooltipState != null) { + show = true; + chartTooltipState?.needMarker = true; + _mouseTooltip = false; + isHovering ? _showMouseTooltip(x, y) : showTooltipView(x, y); + } + } + + /// To show the chart tooltip + void showTooltipView(double x, double y) { + if (tooltipBehavior.enable && + renderBox != null && + _stateProperties.animationCompleted == true) { + _renderTooltipView(Offset(x, y)); + if (_presentTooltipValue != null && + tooltipBehavior.tooltipPosition != TooltipPosition.pointer) { + chartTooltipState!.boundaryRect = tooltipBounds!; + if (showLocation != null) { + chartTooltipState?.needMarker = + _stateProperties.chart is SfCartesianChart; + _resolveLocation(); + chartTooltipState?.show( + tooltipHeader: _header, + tooltipContent: _stringVal, + tooltipData: _presentTooltipValue, + position: showLocation, + duration: _stateProperties.isTooltipOrientationChanged == true + ? 0 + : tooltipBehavior.animationDuration); + } + } else { + if (tooltipBehavior.tooltipPosition == TooltipPosition.pointer && + ((_stateProperties.chart is SfCartesianChart) == false || + currentSeriesDetails.isRectSeries == true)) { + _presentTooltipValue?.pointerPosition = showLocation; + chartTooltipState!.boundaryRect = tooltipBounds!; + if (showLocation != null) { + chartTooltipState?.needMarker = + _stateProperties.chart is SfCartesianChart; + chartTooltipState?.show( + tooltipHeader: _header, + tooltipContent: _stringVal, + tooltipData: _presentTooltipValue, + position: showLocation, + duration: _stateProperties.isTooltipOrientationChanged == true + ? 0 + : tooltipBehavior.animationDuration); + } + currentTooltipValue = _presentTooltipValue; + } + } + assert( + // ignore: unnecessary_null_comparison + !(tooltipBehavior.duration != null) || tooltipBehavior.duration >= 0, + 'The duration time for the tooltip must not be less than 0.'); + if (!tooltipBehavior.shouldAlwaysShow) { + show = false; + currentTooltipValue = _presentTooltipValue = null; + if (chartTooltipState != null && renderBox != null) { + if (_stateProperties.isTooltipOrientationChanged == false) { + // Cancelled timer as we have used timer in chart now. + if (timer != null) { + timer?.cancel(); + } + timer = Timer( + Duration(milliseconds: tooltipBehavior.duration.toInt()), () { + chartTooltipState?.hide(hideDelay: 0); + _stateProperties.isTooltipHidden = true; + if (_stateProperties.isTooltipOrientationChanged == true) { + _stateProperties.isTooltipOrientationChanged = false; + } + }); + } + } + } + } + } + + /// this method resolves the position issue when the markerPoint is residing + /// out of the axis cliprect + void _resolveLocation() { + if (_stateProperties.chart is SfCartesianChart && + tooltipBehavior.tooltipPosition == TooltipPosition.auto && + (_seriesRendererDetails!.isRectSeries == true || + _seriesRendererDetails!.seriesType.contains('bubble') == true || + _seriesRendererDetails!.seriesType.contains('candle') == true || + _seriesRendererDetails!.seriesType.contains('boxandwhisker') == + true || + _seriesRendererDetails!.seriesType.contains('waterfall') == true)) { + Offset position = showLocation!; + final CartesianStateProperties cartesianStateProperties = + _stateProperties as CartesianStateProperties; + final Rect bounds = cartesianStateProperties.chartAxis.axisClipRect; + if (!_isPointWithInRect(position, bounds)) { + if (position.dy < bounds.top) { + position = Offset(position.dx, bounds.top); + } + if (position.dx < bounds.left) { + position = Offset(bounds.left, position.dy); + } else if (position.dx > bounds.right) { + position = Offset(bounds.right, position.dy); + } + } + showLocation = position; + } + } + + /// This method shows the tooltip for any logical pixel outside point region + //ignore: unused_element + void showChartAreaTooltip( + Offset position, + ChartAxisRendererDetails xAxisDetails, + ChartAxisRendererDetails yAxisDetails, + dynamic chart) { + showLocation = position; + final CartesianStateProperties cartesianStateProperties = + _stateProperties as CartesianStateProperties; + final ChartAxis xAxis = xAxisDetails.axis, yAxis = yAxisDetails.axis; + if (tooltipBehavior.enable && + renderBox != null && + cartesianStateProperties.animationCompleted == true) { + tooltipBounds = cartesianStateProperties.chartAxis.axisClipRect; + chartTooltipState!.boundaryRect = tooltipBounds!; + if (_isPointWithInRect( + position, cartesianStateProperties.chartAxis.axisClipRect)) { + currentSeriesDetails = SeriesHelper.getSeriesRendererDetails( + cartesianStateProperties.chartSeries.visibleSeriesRenderers[0]); + renderBox!.normalPadding = 5; + renderBox!.inversePadding = 5; + _header = ''; + dynamic xValue = pointToXValue( + cartesianStateProperties.requireInvertedAxis, + xAxisDetails.axisRenderer, + xAxisDetails.bounds, + position.dx - + (cartesianStateProperties.chartAxis.axisClipRect.left + + xAxis.plotOffset), + position.dy - + (cartesianStateProperties.chartAxis.axisClipRect.top + + xAxis.plotOffset)); + dynamic yValue = pointToYValue( + cartesianStateProperties.requireInvertedAxis, + yAxisDetails.axisRenderer, + yAxisDetails.bounds, + position.dx - + (cartesianStateProperties.chartAxis.axisClipRect.left + + yAxis.plotOffset), + position.dy - + (cartesianStateProperties.chartAxis.axisClipRect.top + + yAxis.plotOffset)); + if (xAxisDetails is DateTimeAxisDetails) { + final DateTimeAxis xAxis = xAxisDetails.axis as DateTimeAxis; + xValue = (xAxis.dateFormat ?? + getDateTimeLabelFormat(xAxisDetails.axisRenderer)) + .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); + } else if (xAxisDetails is DateTimeCategoryAxisDetails) { + xValue = xAxisDetails.dateFormat + .format(DateTime.fromMillisecondsSinceEpoch(xValue.floor())); + } else if (xAxisDetails is CategoryAxisDetails) { + xValue = xAxisDetails.visibleLabels[xValue.toInt()].text; + } else if (xAxisDetails is NumericAxisDetails) { + xValue = xValue.toStringAsFixed(2).contains('.00') == true + ? xValue.floor() + : xValue.toStringAsFixed(2); + } + + if (yAxisDetails is NumericAxisDetails || + yAxisDetails is LogarithmicAxisDetails) { + yValue = yValue.toStringAsFixed(2).contains('.00') == true + ? yValue.floor() + : yValue.toStringAsFixed(2); + } + _stringValue = ' $xValue : $yValue '; + showLocation = position; + } + + if (showLocation != null && _stringVal != null && tooltipBounds != null) { + chartTooltipState?.needMarker = false; + chartTooltipState?.show( + tooltipHeader: _header, + tooltipContent: _stringVal, + tooltipData: _presentTooltipValue, + position: showLocation, + duration: _stateProperties.isTooltipOrientationChanged == true + ? 0 + : tooltipBehavior.animationDuration); + } + + if (!tooltipBehavior.shouldAlwaysShow) { + show = false; + if (chartTooltipState != null && renderBox != null) { + chartTooltipState?.hide(); + } + } + } + } + + /// Method to show the tooltip with template + void showTemplateTooltip(Offset position, [dynamic xValue, dynamic yValue]) { + _stateProperties.isTooltipHidden = false; + final CartesianStateProperties cartesianStateProperties = + _stateProperties as CartesianStateProperties; + final dynamic chart = cartesianStateProperties.chart; + _presentTooltipValue = null; + tooltipBounds = cartesianStateProperties.chartAxis.axisClipRect; + dynamic series; + double yPadding = 0; + if (_isPointWithInRect( + position, cartesianStateProperties.chartAxis.axisClipRect) && + cartesianStateProperties.animationCompleted == true) { + int? seriesIndex, pointIndex; + int outlierIndex = -1; + bool isTooltipRegion = false; + if (!isHovering) { + //assigning null for the previous and current tooltip values in case of mouse not hovering + prevTooltipValue = null; + currentTooltipValue = null; + } + for (int i = 0; + i < + cartesianStateProperties + .chartSeries.visibleSeriesRenderers.length; + i++) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + cartesianStateProperties.chartSeries.visibleSeriesRenderers[i]); + series = seriesRendererDetails.series; + + int j = 0; + if (seriesRendererDetails.visible! == true && + series.enableTooltip == true && + seriesRendererDetails.regionalData != null) { + seriesRendererDetails.regionalData! + .forEach((dynamic regionRect, dynamic values) { + final bool isTrendLine = values[values.length - 1].contains('true'); + final double padding = ((seriesRendererDetails.seriesType == + 'bubble' || + seriesRendererDetails.seriesType == 'scatter' || + seriesRendererDetails.seriesType.contains('column') == + true || + seriesRendererDetails.seriesType.contains('bar') == + true || + seriesRendererDetails.seriesType == 'histogram') && + !isTrendLine) + ? 0 + : isHovering + ? 0 + : 15; + final Rect region = regionRect[0]; + final double left = region.left - padding; + final double right = region.right + padding; + final double top = region.top - padding; + final double bottom = region.bottom + padding; + Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); + final List? outlierRegion = regionRect[5]; + + if (outlierRegion != null) { + for (int rectIndex = 0; + rectIndex < outlierRegion.length; + rectIndex++) { + if (outlierRegion[rectIndex].contains(position)) { + paddedRegion = outlierRegion[rectIndex]; + outlierIndex = rectIndex; + } + } + } + + if (paddedRegion.contains(position)) { + seriesIndex = seriesIndex = i; + currentSeriesDetails = seriesRendererDetails; + _pointIndex = + seriesRendererDetails.dataPoints.indexOf(regionRect[4]); + Offset tooltipPosition = !(seriesRendererDetails.isRectSeries == + true && + tooltipBehavior.tooltipPosition != TooltipPosition.auto) + ? ((outlierIndex >= 0) + ? regionRect[6][outlierIndex] + : regionRect[1]) + : position; + final List paddingData = getTooltipPaddingData( + seriesRendererDetails, + isTrendLine, + region, + paddedRegion, + tooltipPosition); + yPadding = paddingData[0]!.dy; + tooltipPosition = paddingData[1] ?? tooltipPosition; + showLocation = tooltipPosition; + _seriesRendererDetails = seriesRendererDetails; + renderBox!.normalPadding = + _seriesRendererDetails!.renderer is BubbleSeriesRenderer + ? 0 + : yPadding; + renderBox!.inversePadding = yPadding; + tooltipTemplate = chart.tooltipBehavior.builder( + series.dataSource[j], regionRect[4], series, _pointIndex, i); + isTooltipRegion = true; + } + j++; + }); + } + } + if (isHovering && isTooltipRegion) { + prevTooltipValue = currentTooltipValue; + currentTooltipValue = + TooltipValue(seriesIndex, _pointIndex!, outlierIndex); + } + final TooltipValue? presentTooltip = _presentTooltipValue; + if (presentTooltip == null || + seriesIndex != presentTooltip.seriesIndex || + pointIndex != presentTooltip.pointIndex || + outlierIndex != presentTooltip.outlierIndex || + (currentSeriesDetails != null && + currentSeriesDetails.isRectSeries == true && + tooltipBehavior.tooltipPosition != TooltipPosition.auto)) { + //Current point is different than previous one so tooltip re-renders + if (seriesIndex != null && pointIndex != null) { + _presentTooltipValue = + TooltipValue(seriesIndex, pointIndex, outlierIndex); + } + if (isTooltipRegion && tooltipTemplate != null) { + show = isTooltipRegion; + performTooltip(); + if (!isHovering && renderBox != null) { + hideTooltipTemplate(); + } + } + } else { + //Current point is same as previous one so timer is reset and tooltip is not re-rendered + if (!isHovering) { + hideTooltipTemplate(); + } + } + + if (!isTooltipRegion && + !isInteraction && + chart.series.isNotEmpty == true) { + //to show tooltip template when the position resides outside point region + final dynamic x = xValue ?? + pointToXValue( + cartesianStateProperties.requireInvertedAxis, + cartesianStateProperties + .chartAxis.primaryXAxisDetails.axisRenderer, + cartesianStateProperties.chartAxis.primaryXAxisDetails.bounds, + position.dx - + (cartesianStateProperties.chartAxis.axisClipRect.left + + chart.primaryXAxis.plotOffset), + position.dy - + (cartesianStateProperties.chartAxis.axisClipRect.top + + chart.primaryXAxis.plotOffset)); + final dynamic y = yValue ?? + pointToYValue( + cartesianStateProperties.requireInvertedAxis, + cartesianStateProperties + .chartAxis.primaryYAxisDetails.axisRenderer, + cartesianStateProperties.chartAxis.primaryYAxisDetails.bounds, + position.dx - + (cartesianStateProperties.chartAxis.axisClipRect.left + + chart.primaryYAxis.plotOffset), + position.dy - + (cartesianStateProperties.chartAxis.axisClipRect.top + + chart.primaryYAxis.plotOffset)); + renderBox!.normalPadding = 5; + renderBox!.inversePadding = 5; + showLocation = position; + tooltipTemplate = chart.tooltipBehavior.builder( + null, CartesianChartPoint(x, y), null, null, null); + isTooltipRegion = true; + show = isTooltipRegion; + performTooltip(); + } + if (!isTooltipRegion) { + hideTooltipTemplate(); + } + } + isInteraction = false; + } + + /// Tooltip show by pixel + void internalShowByPixel(double x, double y) { + _stateProperties.isTooltipHidden = false; + final dynamic chart = _stateProperties.chart; + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + bool? isInsidePointRegion; + String text = ''; + String trimmedText = ''; + Offset? axisLabelPosition; + if (chart is SfCartesianChart) { + _stateProperties.requireAxisTooltip = false; + for (int i = 0; + i < _stateProperties.chartAxis.axisRenderersCollection.length; + i++) { + final ChartAxisRendererDetails axisDetails = + AxisHelper.getAxisRendererDetails( + _stateProperties.chartAxis.axisRenderersCollection[i]); + final List labels = axisDetails.visibleLabels; + for (int k = 0; k < labels.length; k++) { + if (axisDetails.axis.isVisible == true && + AxisHelper.getLabelRegion(labels[k]) != null && + AxisHelper.getLabelRegion(labels[k])!.contains(Offset(x, y))) { + _stateProperties.requireAxisTooltip = true; + text = labels[k].text; + trimmedText = labels[k].renderText ?? ''; + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue; + axisLabelPosition = AxisHelper.getLabelRegion(labels[k])!.center; + // -3 to indicate axis tooltip + tooltipRenderingDetails.currentTooltipValue = + TooltipValue(null, k, 0); + } + } + } + } + if (chart is SfCartesianChart && + _stateProperties.requireAxisTooltip == false) { + for (int i = 0; + i < _stateProperties.chartSeries.visibleSeriesRenderers.length; + i++) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + _stateProperties.chartSeries.visibleSeriesRenderers[i]); + if (seriesRendererDetails.visible! == true && + seriesRendererDetails.series.enableTooltip == true && + seriesRendererDetails.regionalData != null) { + final String seriesType = seriesRendererDetails.seriesType; + final double padding = (seriesType == 'bubble' || + seriesType == 'scatter' || + seriesType.contains('column') || + seriesType.contains('bar')) + ? 0 + : tooltipRenderingDetails.isHovering == true + ? 0 + : 15; // regional padding to detect smooth touch + seriesRendererDetails.regionalData! + .forEach((dynamic regionRect, dynamic values) { + final Rect region = regionRect[0]; + final Rect paddedRegion = Rect.fromLTRB( + region.left - padding, + region.top - padding, + region.right + padding, + region.bottom + padding); + bool outlierTooltip = false; + if (seriesRendererDetails.seriesType == 'boxandwhisker') { + final List? outlierRegion = regionRect[5]; + if (outlierRegion != null) { + for (int rectIndex = 0; + rectIndex < outlierRegion.length; + rectIndex++) { + if (outlierRegion[rectIndex].contains(Offset(x, y))) { + outlierTooltip = true; + break; + } + } + } + } + if (paddedRegion.contains(Offset(x, y)) || outlierTooltip) { + isInsidePointRegion = true; + } + }); + } + } + } + if (chart is SfCartesianChart && + chart.tooltipBehavior.activationMode != ActivationMode.none && + // ignore: unnecessary_null_comparison + x != null && + // ignore: unnecessary_null_comparison + y != null && + _stateProperties.requireAxisTooltip == true) { + final SfTooltipState? tooltipState = + tooltipRenderingDetails.chartTooltipState; + if (trimmedText.contains('...')) { + tooltipRenderingDetails.show = true; + tooltipState?.needMarker = false; + tooltipRenderingDetails.showTrimmedTooltip( + axisLabelPosition!, chart, text); + } else { + tooltipRenderingDetails.show = false; + if (!chart.tooltipBehavior.shouldAlwaysShow) { + if (_stateProperties.isTooltipOrientationChanged == false) { + // Cancelled timer as we have used timer in chart now. + if (timer != null) { + timer?.cancel(); + } + timer = Timer( + Duration(milliseconds: chart.tooltipBehavior.duration.toInt()), + () { + tooltipRenderingDetails.chartTooltipState?.hide(hideDelay: 0); + _stateProperties.isTooltipHidden = true; + if (_stateProperties.isTooltipOrientationChanged == true) { + _stateProperties.isTooltipOrientationChanged = false; + } + }); + } + } + } + } else if (chart is SfCircularChart && + _stateProperties.requireDataLabelTooltip == true) { + final List> renderPoints = + _stateProperties.chartSeries.visibleSeriesRenderers[0].renderPoints; + final SfTooltipState? tooltipState = + tooltipRenderingDetails.chartTooltipState; + for (int i = 0; i < renderPoints.length; i++) { + if (renderPoints[i].labelRect.contains(Offset(x, y)) && + renderPoints[i].trimmedText != null && + renderPoints[i].trimmedText!.contains('...')) { + Offset position; + final num textWidth = measureText( + renderPoints[i].trimmedText!, + _stateProperties.chartSeries.visibleSeriesRenderers[0] + .dataLabelSettingsRenderer.dataLabelSettings.textStyle) + .width; + if (renderPoints[i].dataLabelPosition == Position.left) { + position = Offset(renderPoints[i].labelRect.right - textWidth / 2, + renderPoints[i].labelRect.center.dy); + } else { + position = Offset(renderPoints[i].labelRect.left + textWidth / 2, + renderPoints[i].labelRect.center.dy); + } + tooltipRenderingDetails.show = true; + tooltipState?.needMarker = false; + tooltipRenderingDetails.showTrimmedTooltip( + position, chart, renderPoints[i].text!); + } + } + } else if (tooltipRenderingDetails.chartTooltip != null && + chart.tooltipBehavior.activationMode != ActivationMode.none && + // ignore: unnecessary_null_comparison + x != null && + // ignore: unnecessary_null_comparison + y != null) { + final SfTooltipState? tooltipState = + tooltipRenderingDetails.chartTooltipState; + if ((chart is SfCartesianChart) == false || + tooltipRenderingDetails.isInteraction == true || + (isInsidePointRegion ?? false)) { + final bool isHovering = tooltipRenderingDetails.isHovering; + if (isInsidePointRegion == true || + isHovering || + (chart is SfCartesianChart) == false) { + tooltipRenderingDetails.showTooltip(x, y); + } else { + tooltipRenderingDetails.show = false; + if (chart.tooltipBehavior.shouldAlwaysShow == false) { + hide(); + } + } + } else if (tooltipRenderingDetails.renderBox != null) { + tooltipRenderingDetails.show = true; + tooltipState?.needMarker = false; + tooltipRenderingDetails.showChartAreaTooltip( + Offset(x, y), + _stateProperties.chartAxis.primaryXAxisDetails, + _stateProperties.chartAxis.primaryYAxisDetails, + chart); + } + } + if (chart is SfCartesianChart && + chart.tooltipBehavior.builder != null && + x != null && // ignore: unnecessary_null_comparison + // ignore: unnecessary_null_comparison + y != null) { + tooltipRenderingDetails.showTemplateTooltip(Offset(x, y)); + } + // ignore: unnecessary_null_comparison + if (tooltipBehaviorRenderer != null) { + tooltipRenderingDetails.isInteraction = false; + } + } + + /// To hide the tooltip when the timer ends + void hide() { + if (!tooltipBehavior.shouldAlwaysShow) { + show = false; + currentTooltipValue = _presentTooltipValue = null; + if (chartTooltipState != null && renderBox != null) { + if (_stateProperties.isTooltipOrientationChanged == false) { + // Cancelled timer as we have used timer in chart now. + if (timer != null) { + timer?.cancel(); + } + timer = Timer( + Duration(milliseconds: tooltipBehavior.duration.toInt()), () { + chartTooltipState?.hide(hideDelay: 0); + _stateProperties.isTooltipHidden = true; + if (_stateProperties.isTooltipOrientationChanged == true) { + _stateProperties.isTooltipOrientationChanged = false; + } + }); + } + } + } + } + + /// To hide tooltip template with timer + // void hideOnTimer() { + // if (prevTooltipValue == null && currentTooltipValue == null) { + // hideTooltipTemplate(); + // } else { + // timer?.cancel(); + // timer = Timer(Duration(milliseconds: tooltipBehavior.duration.toInt()), + // hideTooltipTemplate); + // } + // } + + /// To hide tooltip templates + void hideTooltipTemplate() { + if (tooltipBehavior.shouldAlwaysShow == false) { + show = false; + if (_stateProperties.isTooltipOrientationChanged == false) { + // Cancelled timer as we have used timer in chart now. + if (timer != null) { + timer?.cancel(); + } + timer = + Timer(Duration(milliseconds: tooltipBehavior.duration.toInt()), () { + chartTooltipState?.hide(hideDelay: 0); + _stateProperties.isTooltipHidden = true; + if (_stateProperties.isTooltipOrientationChanged == true) { + _stateProperties.isTooltipOrientationChanged = false; + } + }); + } + prevTooltipValue = null; + currentTooltipValue = null; + _presentTooltipValue = null; + } + } + + /// To perform rendering of tooltip + void performTooltip() { + //for mouse hover the tooltip is redrawn only when the current tooltip value differs from the previous one + if (show && + ((prevTooltipValue == null && currentTooltipValue == null) || + (_stateProperties.chartState is SfCartesianChartState && + (currentSeriesDetails?.isRectSeries ?? false) == true && + tooltipBehavior.tooltipPosition != TooltipPosition.auto) || + (prevTooltipValue?.seriesIndex != + currentTooltipValue?.seriesIndex || + prevTooltipValue?.outlierIndex != + currentTooltipValue?.outlierIndex || + prevTooltipValue?.pointIndex != + currentTooltipValue?.pointIndex))) { + final bool reRender = isHovering && + prevTooltipValue != null && + currentTooltipValue != null && + prevTooltipValue!.seriesIndex == currentTooltipValue!.seriesIndex && + prevTooltipValue!.pointIndex == currentTooltipValue!.pointIndex && + prevTooltipValue!.outlierIndex == currentTooltipValue!.outlierIndex; + if (tooltipBehavior.builder != null && tooltipBounds != null) { + chartTooltipState!.boundaryRect = tooltipBounds!; + if (tooltipBehavior.tooltipPosition != TooltipPosition.auto) + _presentTooltipValue!.pointerPosition = showLocation; + if (showLocation != null) { + _resolveLocation(); + chartTooltipState?.show( + tooltipData: _presentTooltipValue, + position: showLocation, + duration: _stateProperties.isTooltipOrientationChanged == true + ? 0 + : ((!reRender) + ? tooltipBehavior.animationDuration.toInt() + : 0), + template: tooltipTemplate); + } + } + } + } + + /// To show tooltip on mouse pointer actions + void _showMouseTooltip(double x, double y) { + if (tooltipBehavior.enable && + renderBox != null && + _stateProperties.animationCompleted == true) { + _renderTooltipView(Offset(x, y)); + if (!_mouseTooltip) { + if (_stateProperties.isTooltipOrientationChanged == false) { + // Cancelled timer as we have used timer in chart now. + if (timer != null) { + timer?.cancel(); + } + timer = Timer( + Duration(milliseconds: tooltipBehavior.duration.toInt()), () { + chartTooltipState?.hide(hideDelay: 0); + _stateProperties.isTooltipHidden = true; + if (_stateProperties.isTooltipOrientationChanged == true) { + _stateProperties.isTooltipOrientationChanged = false; + } + }); + } + + currentTooltipValue = null; + } else { + if (_presentTooltipValue != null && + (currentTooltipValue == null || + tooltipBehavior.tooltipPosition == TooltipPosition.auto)) { + chartTooltipState!.boundaryRect = tooltipBounds!; + if (tooltipBehavior.tooltipPosition != TooltipPosition.auto) + _presentTooltipValue!.pointerPosition = showLocation; + if (showLocation != null) { + chartTooltipState?.needMarker = + _stateProperties.chart is SfCartesianChart; + _resolveLocation(); + chartTooltipState?.show( + tooltipHeader: _header, + tooltipContent: _stringVal, + tooltipData: _presentTooltipValue, + position: showLocation, + duration: _stateProperties.isTooltipOrientationChanged == true + ? 0 + : tooltipBehavior.animationDuration); + } + currentTooltipValue = _presentTooltipValue; + } else if (_presentTooltipValue != null && + currentTooltipValue != null && + tooltipBehavior.tooltipPosition != TooltipPosition.auto && + ((_seriesRendererDetails != null && + _seriesRendererDetails!.renderer + is CartesianSeriesRenderer) == + false || + _seriesRendererDetails!.isRectSeries == true)) { + _presentTooltipValue!.pointerPosition = showLocation; + chartTooltipState!.boundaryRect = tooltipBounds!; + if (showLocation != null) { + chartTooltipState?.needMarker = + _stateProperties.chart is SfCartesianChart; + _resolveLocation(); + chartTooltipState?.show( + tooltipHeader: _header, + tooltipContent: _stringVal, + tooltipData: _presentTooltipValue, + position: showLocation, + duration: 0); + } + } + } + } + } + + /// Method to trigger the tooltip rendering event + void tooltipRenderingEvent(TooltipRenderArgs args) { + String? header = args.header; + String? stringValue = args.text; + double? x = args.location?.dx, y = args.location?.dy; + String? tooltipHeaderText, tooltipLabelText; + TooltipArgs tooltipArgs; + if (x != null && + y != null && + stringValue != null && + currentSeriesDetails != null) { + final int seriesIndex = _stateProperties.chart is SfCartesianChart + ? SegmentHelper.getSegmentProperties(currentSeriesDetails.segments[0]) + .seriesIndex + : 0; + if ((_stateProperties.chart is SfCartesianChart && + _stateProperties.requireAxisTooltip == false) || + (_stateProperties.chart is SfCartesianChart) == false) { + if (_stateProperties.chart.onTooltipRender != null && + _dataPoint != null && + (_dataPoint.isTooltipRenderEvent ?? false) == false) { + _dataPoint.isTooltipRenderEvent = true; + final bool isCartesian = _stateProperties.chart is SfCartesianChart; + late SeriesRendererDetails seriesRendererDetails; + if (isCartesian) { + seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + _stateProperties + .chartSeries.visibleSeriesRenderers[seriesIndex]); + } + + tooltipArgs = TooltipArgs( + seriesIndex, + isCartesian + ? seriesRendererDetails.dataPoints + : _stateProperties.chartSeries + .visibleSeriesRenderers[seriesIndex].dataPoints, + _pointIndex, + isCartesian + ? seriesRendererDetails + .visibleDataPoints![_pointIndex!].overallDataPointIndex + : _pointIndex); + + tooltipArgs.text = stringValue; + tooltipArgs.header = header; + tooltipLabelText = stringValue; + tooltipHeaderText = header; + tooltipArgs.locationX = x; + tooltipArgs.locationY = y; + _stateProperties.chart.onTooltipRender(tooltipArgs); + stringValue = tooltipArgs.text; + header = tooltipArgs.header; + x = tooltipArgs.locationX; + y = tooltipArgs.locationY; + tooltipLabelText = tooltipArgs.text; + tooltipHeaderText = tooltipArgs.header; + _dataPoint.isTooltipRenderEvent = false; + args.text = stringValue!; + args.header = header; + args.location = Offset(x!, y!); + } else if (_stateProperties.chart.onTooltipRender != null) { + //Fires the on tooltip render event when the tooltip is shown outside point region + tooltipArgs = TooltipArgs(null, null, null); + tooltipArgs.text = stringValue; + tooltipArgs.header = header; + tooltipArgs.locationX = x; + tooltipArgs.locationY = y; + _stateProperties.chart.onTooltipRender(tooltipArgs); + args.text = tooltipArgs.text; + args.header = tooltipArgs.header; + args.location = + Offset(tooltipArgs.locationX!, tooltipArgs.locationY!); + } + if (_stateProperties.chart.onTooltipRender != null && + _dataPoint != null) { + stringValue = tooltipLabelText; + header = tooltipHeaderText; + } + } + } + } + + /// To render a chart tooltip for circular series + void _renderCircularChartTooltip(Offset position) { + final CircularStateProperties circularStateProperties = + _stateProperties as CircularStateProperties; + final SfCircularChart chart = circularStateProperties.chart; + tooltipBounds = circularStateProperties.renderingDetails.chartContainerRect; + bool isContains = false; + final Region? pointRegion = getCircularPointRegion(chart, position, + circularStateProperties.chartSeries.visibleSeriesRenderers[0]); + if (pointRegion != null && + circularStateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex] + .series + .enableTooltip == + true) { + prevTooltipValue = + TooltipValue(pointRegion.seriesIndex, pointRegion.pointIndex); + _presentTooltipValue = prevTooltipValue; + if (prevTooltipValue != null && + currentTooltipValue != null && + prevTooltipValue!.pointIndex != currentTooltipValue!.pointIndex) { + currentTooltipValue = null; + } + final ChartPoint chartPoint = circularStateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex] + .renderPoints![pointRegion.pointIndex]; + final Offset location = + chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer + ? position + : degreeToPoint( + chartPoint.midAngle!, + (chartPoint.innerRadius! + chartPoint.outerRadius!) / 2, + chartPoint.center!); + currentSeriesDetails = pointRegion.seriesIndex; + _pointIndex = pointRegion.pointIndex; + _dataPoint = circularStateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[_pointIndex!]; + final int digits = chart.tooltipBehavior.decimalPlaces; + String? header = chart.tooltipBehavior.header; + header = (header == null) + // ignore: prefer_if_null_operators + ? circularStateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex] + .series + .name != + null + ? circularStateProperties.chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex].series.name + : null + : header; + _header = header ?? ''; + if (chart.tooltipBehavior.format != null) { + final String resultantString = chart.tooltipBehavior.format! + .replaceAll('point.x', chartPoint.x.toString()) + .replaceAll('point.y', getDecimalLabelValue(chartPoint.y, digits)) + .replaceAll( + 'series.name', + circularStateProperties + .chartSeries + .visibleSeriesRenderers[pointRegion.seriesIndex] + .series + .name ?? + 'series.name'); + _stringValue = resultantString; + showLocation = location; + } else { + _stringValue = chartPoint.x.toString() + + ' : ' + + getDecimalLabelValue(chartPoint.y, digits); + showLocation = location; + } + if (chart.series[0].explode) { + _presentTooltipValue!.pointerPosition = showLocation; + } + isContains = true; + } else { + isContains = false; + } + _mouseTooltip = isContains; + if (!isContains) { + prevTooltipValue = currentTooltipValue = null; + } + } + + /// To render a chart tooltip for triangular series + void _renderTriangularChartTooltip(Offset position) { + final dynamic chart = _stateProperties.chart; + + tooltipBounds = _stateProperties.renderingDetails.chartContainerRect; + bool isContains = false; + const int seriesIndex = 0; + _pointIndex = _stateProperties.tooltipPointIndex; + if (_pointIndex == null && + _stateProperties.renderingDetails.currentActive == null) { + int? _pointIndex; + bool isPoint; + final dynamic seriesRenderer = + _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; + for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { + if (seriesRenderer.renderPoints[j].isVisible == true) { + isPoint = isPointInPolygon( + seriesRenderer.renderPoints[j].pathRegion, position); + if (isPoint) { + _pointIndex = j; + break; + } + } + } + _stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + _pointIndex, + seriesRenderer.series, + seriesRenderer.renderPoints[_pointIndex], + ); + } + _pointIndex ??= _stateProperties.renderingDetails.currentActive!.pointIndex; + _dataPoint = _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[_pointIndex]; + _stateProperties.tooltipPointIndex = null; + final int digits = chart.tooltipBehavior.decimalPlaces; + if (chart.tooltipBehavior.enable == true) { + prevTooltipValue = TooltipValue(seriesIndex, _pointIndex!); + _presentTooltipValue = prevTooltipValue; + if (prevTooltipValue != null && + currentTooltipValue != null && + prevTooltipValue!.pointIndex != currentTooltipValue!.pointIndex) { + currentTooltipValue = null; + } + final PointInfo chartPoint = _stateProperties.chartSeries + .visibleSeriesRenderers[seriesIndex].renderPoints[_pointIndex]; + final Offset location = chart.tooltipBehavior.tooltipPosition == + TooltipPosition.pointer && + _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .series.explode == + true + ? chartPoint.symbolLocation + : chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && + _stateProperties.chartSeries + .visibleSeriesRenderers[seriesIndex].series.explode == + false + ? position + : chartPoint.symbolLocation; + currentSeriesDetails = seriesIndex; + String? header = chart.tooltipBehavior.header; + header = (header == null) + // ignore: prefer_if_null_operators + ? _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .series.name != + null + ? _stateProperties + .chartSeries.visibleSeriesRenderers[seriesIndex].series.name + : null + : header; + _header = header ?? ''; + if (chart.tooltipBehavior.format != null) { + final String resultantString = chart.tooltipBehavior.format + .replaceAll('point.x', chartPoint.x.toString()) + .replaceAll('point.y', getDecimalLabelValue(chartPoint.y, digits)) + .replaceAll( + 'series.name', + _stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .series.name ?? + 'series.name'); + _stringValue = resultantString; + showLocation = location; + } else { + _stringValue = chartPoint.x.toString() + + ' : ' + + getDecimalLabelValue(chartPoint.y, digits); + showLocation = location; + } + isContains = true; + } else { + isContains = false; + } + if (chart.series.explode == true) { + _presentTooltipValue!.pointerPosition = showLocation; + } + _mouseTooltip = isContains; + if (!isContains) { + prevTooltipValue = currentTooltipValue = null; + } + } + + /// To show the axis label tooltip for trimmed axes label texts. + void showTrimmedTooltip(Offset position, dynamic chart, String text) { + final RenderingDetails renderingDetails = _stateProperties.renderingDetails; + if (renderBox != null) { + _header = ''; + _stringValue = text; + showLocation = position; + tooltipBounds = renderingDetails.chartContainerRect; + renderBox!.inversePadding = 0; + chartTooltipState!.boundaryRect = tooltipBounds!; + if (showLocation != null) { + chartTooltipState?.needMarker = false; + chartTooltipState?.show( + tooltipHeader: _header, + tooltipContent: _stringVal, + tooltipData: currentTooltipValue, + position: showLocation, + duration: 0); + } + if (!isHovering) { + if (_stateProperties.isTooltipOrientationChanged == false) { + // Cancelled timer as we have used timer in chart now. + if (timer != null) { + timer?.cancel(); + } + timer = Timer( + Duration(milliseconds: tooltipBehavior.duration.toInt()), () { + chartTooltipState?.hide(hideDelay: 0); + _stateProperties.isTooltipHidden = true; + if (_stateProperties.isTooltipOrientationChanged == true) { + _stateProperties.isTooltipOrientationChanged = false; + } + }); + } + } + } + } + + /// To render a chart tooltip for Cartesian series + void _renderCartesianChartTooltip(Offset position) { + final CartesianStateProperties stateProperties = + _stateProperties as CartesianStateProperties; + bool isContains = false; + if (_isPointWithInRect(position, stateProperties.chartAxis.axisClipRect)) { + Offset? tooltipPosition; + double touchPadding; + Offset? padding; + bool? isTrendLine; + dynamic dataRect; + dynamic dataValues; + bool outlierTooltip = false; + int outlierTooltipIndex = -1; + final List markerGradients = []; + final List markerPaints = []; + final List markerTypes = []; + final List markerImages = []; + for (int i = 0; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; + i++) { + _seriesRendererDetails = SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + final CartesianSeries series = + _seriesRendererDetails!.series; + if (_seriesRendererDetails!.visible! == true && + series.enableTooltip && + _seriesRendererDetails?.regionalData != null) { + int count = 0; + _seriesRendererDetails!.regionalData! + .forEach((dynamic regionRect, dynamic values) { + isTrendLine = values[values.length - 1].contains('true'); + touchPadding = ((_seriesRendererDetails!.seriesType == 'bubble' || + _seriesRendererDetails!.seriesType == 'scatter' || + _seriesRendererDetails!.seriesType.contains('column') == + true || + _seriesRendererDetails!.seriesType.contains('bar') == + true || + _seriesRendererDetails!.seriesType == 'histogram') && + !isTrendLine!) + ? 0 + : isHovering + ? 0 + : 15; // regional padding to detect smooth touch + final Rect region = regionRect[0]; + final List? outlierRegion = regionRect[5]; + final double left = region.left - touchPadding; + final double right = region.right + touchPadding; + final double top = region.top - touchPadding; + final double bottom = region.bottom + touchPadding; + Rect paddedRegion = Rect.fromLTRB(left, top, right, bottom); + if (outlierRegion != null) { + for (int rectIndex = 0; + rectIndex < outlierRegion.length; + rectIndex++) { + if (outlierRegion[rectIndex].contains(position)) { + paddedRegion = outlierRegion[rectIndex]; + outlierTooltipIndex = rectIndex; + outlierTooltip = true; + } + } + } + + if (paddedRegion.contains(position) && + (isTrendLine! ? regionRect[4].isVisible : true) == true) { + tooltipBounds = stateProperties.chartAxis.axisClipRect; + if (_seriesRendererDetails!.seriesType != 'boxandwhisker' + ? !region.contains(position) + : (paddedRegion.contains(position) || + !region.contains(position))) { + tooltipBounds = stateProperties.chartAxis.axisClipRect; + } + _presentTooltipValue = + TooltipValue(i, count, outlierTooltipIndex); + currentSeriesDetails = _seriesRendererDetails; + _pointIndex = _stateProperties.chart is SfCartesianChart + ? regionRect[4].visiblePointIndex + : count; + _dataPoint = regionRect[4]; + _markerType = _seriesRendererDetails!.series.markerSettings.shape; + Color? seriesColor = _seriesRendererDetails!.seriesColor; + if (_seriesRendererDetails!.seriesType == 'waterfall') { + seriesColor = getWaterfallSeriesColor( + _seriesRendererDetails!.series + as WaterfallSeries, + _seriesRendererDetails!.dataPoints[_pointIndex!], + seriesColor)!; + } + _markerColor = regionRect[2] ?? + _seriesRendererDetails!.series.markerSettings.borderColor ?? + seriesColor!; + tooltipPosition = (outlierTooltipIndex >= 0) + ? regionRect[6][outlierTooltipIndex] + : regionRect[1]; + final Paint markerPaint = Paint(); + markerPaint.color = (!tooltipBehavior.shared + ? _markerColor + : _seriesRendererDetails! + .series.markerSettings.borderColor ?? + _seriesRendererDetails!.seriesColor ?? + _seriesRendererDetails!.series.color)! + .withOpacity(tooltipBehavior.opacity); + if (!tooltipBehavior.shared) { + markerGradients + ..clear() + ..add(_seriesRendererDetails!.series.gradient); + markerImages + ..clear() + ..add(_seriesRendererDetails!.markerSettingsRenderer?.image); + markerPaints + ..clear() + ..add(markerPaint); + markerTypes + ..clear() + ..add(_markerType); + } + final List paddingData = + !(_seriesRendererDetails!.isRectSeries == true && + tooltipBehavior.tooltipPosition != + TooltipPosition.auto) + ? getTooltipPaddingData(_seriesRendererDetails!, + isTrendLine!, region, paddedRegion, tooltipPosition) + : [const Offset(2, 2), tooltipPosition]; + padding = paddingData[0]; + tooltipPosition = paddingData[1]; + showLocation = tooltipPosition; + dataValues = values; + dataRect = regionRect; + isContains = _mouseTooltip = true; + } + count++; + }); + if (tooltipBehavior.shared) { + int indexValue = 0; + int tooltipElementsLength = 0; + final Paint markerPaint = Paint(); + markerPaint.color = + _seriesRendererDetails!.series.markerSettings.borderColor ?? + _seriesRendererDetails!.seriesColor ?? + _seriesRendererDetails!.series.color! + .withOpacity(tooltipBehavior.opacity); + markerGradients.add(_seriesRendererDetails!.series.gradient); + markerImages + .add(_seriesRendererDetails!.markerSettingsRenderer?.image); + markerTypes + .add(_seriesRendererDetails!.series.markerSettings.shape); + markerPaints.add(markerPaint); + if ((_seriesRendererDetails!.seriesType.contains('range') == true || + _seriesRendererDetails!.seriesType == 'hilo') && + !isTrendLine!) { + // Assigned value '2' for this variable because for range and + // hilo series there will be two display value types + // such as high and low. + tooltipElementsLength = 2; + indexValue = _getTooltipNewLineLength(tooltipElementsLength); + } else if (_seriesRendererDetails!.seriesType == 'hiloopenclose' || + _seriesRendererDetails!.seriesType == 'candle') { + // Assigned value '4' for this variable because for hiloopenclose + // and candle series there will be four display values + // such as high, low, open and close. + tooltipElementsLength = 4; + indexValue = _getTooltipNewLineLength(tooltipElementsLength); + } else if (_seriesRendererDetails!.seriesType == 'boxandwhisker') { + // Assigned value '1' or '6' this variable field because for the + // box and whiskers series there will be one display values if + // the outlier's tooltip is activated otherwise there will be + // six display values such as maximum, minimum, mean, median, + // lowerQuartile and upperQuartile. + tooltipElementsLength = outlierTooltip ? 1 : 6; + indexValue = _getTooltipNewLineLength(tooltipElementsLength); + } else { + indexValue = _getTooltipNewLineLength(tooltipElementsLength); + } + for (int j = 0; j < indexValue; j++) { + markerTypes.add(null); + markerImages.add(null); + markerGradients.add(null); + markerPaints.add(null); + } + } + } + } + if (isContains) { + renderBox!.markerGradients = markerGradients; + renderBox!.markerImages = markerImages; + renderBox!.markerPaints = markerPaints; + renderBox!.markerTypes = markerTypes; + _seriesRendererDetails = currentSeriesDetails ?? _seriesRendererDetails; + if (currentSeriesDetails.isRectSeries == true && + tooltipBehavior.tooltipPosition == TooltipPosition.pointer) { + tooltipPosition = position; + showLocation = tooltipPosition; + } + renderBox!.normalPadding = + _seriesRendererDetails!.renderer is BubbleSeriesRenderer + ? 0 + : padding!.dy; + renderBox!.inversePadding = padding!.dy; + String? header = tooltipBehavior.header; + header = (header == null) + ? (tooltipBehavior.shared + ? dataValues[0] + : (isTrendLine! + ? dataValues[dataValues.length - 2] + : currentSeriesDetails.series.name ?? + currentSeriesDetails.seriesName)) + : header; + _header = header ?? ''; + _stringValue = ''; + if (tooltipBehavior.shared) { + _textValues = []; + _seriesRendererCollection = []; + for (int j = 0; + j < stateProperties.chartSeries.visibleSeriesRenderers.length; + j++) { + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[j]); + if (seriesRendererDetails.visible! == true && + seriesRendererDetails.series.enableTooltip == true) { + final int index = + seriesRendererDetails.xValues!.indexOf(dataRect[4].x); + if (index > -1) { + final String text = (_stringVal != '' ? '\n' : '') + + _calculateCartesianTooltipText( + seriesRendererDetails, + seriesRendererDetails.dataPoints[index], + dataValues, + tooltipPosition!, + outlierTooltip, + outlierTooltipIndex); + _stringValue = _stringVal! + text; + _textValues.add(text); + _seriesRendererCollection.add(seriesRendererDetails.renderer); + } + } + } + } else { + _stringValue = _calculateCartesianTooltipText( + currentSeriesDetails, + dataRect[4], + dataValues, + tooltipPosition!, + outlierTooltip, + outlierTooltipIndex); + } + showLocation = tooltipPosition; + } else { + _stringValue = null; + if (!isHovering) { + _presentTooltipValue = currentTooltipValue = null; + } else { + _mouseTooltip = isContains; + } + } + } + } + + /// It returns the tooltip text of Cartesian series + String _calculateCartesianTooltipText( + SeriesRendererDetails seriesRendererDetails, + CartesianChartPoint point, + dynamic values, + Offset tooltipPosition, + bool outlierTooltip, + int outlierTooltipIndex) { + final bool isTrendLine = values[values.length - 1].contains('true'); + String resultantString; + final ChartAxisRendererDetails axisRenderer = + seriesRendererDetails.yAxisDetails!; + final TooltipBehavior tooltip = tooltipBehavior; + final int digits = + seriesRendererDetails.chart.tooltipBehavior.decimalPlaces; + String? minimumValue, + maximumValue, + lowerQuartileValue, + upperQuartileValue, + medianValue, + meanValue, + outlierValue, + highValue, + lowValue, + openValue, + closeValue, + cumulativeValue, + boxPlotString; + if (seriesRendererDetails.seriesType == 'boxandwhisker') { + minimumValue = getLabelValue(point.minimum, axisRenderer.axis, digits); + maximumValue = getLabelValue(point.maximum, axisRenderer.axis, digits); + lowerQuartileValue = + getLabelValue(point.lowerQuartile, axisRenderer.axis, digits); + upperQuartileValue = + getLabelValue(point.upperQuartile, axisRenderer.axis, digits); + medianValue = getLabelValue(point.median, axisRenderer.axis, digits); + meanValue = getLabelValue(point.mean, axisRenderer.axis, digits); + outlierValue = (point.outliers!.isNotEmpty && outlierTooltipIndex >= 0) + ? getLabelValue( + point.outliers![outlierTooltipIndex], axisRenderer.axis, digits) + : null; + boxPlotString = '\nMinimum : ' + + minimumValue + + '\nMaximum : ' + + maximumValue + + '\nMedian : ' + + medianValue + + '\nMean : ' + + meanValue + + '\nLQ : ' + + lowerQuartileValue + + '\nHQ : ' + + upperQuartileValue; + } else if (seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType == 'hilo' || + seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType == 'candle') { + highValue = getLabelValue(point.high, axisRenderer.axis, digits); + lowValue = getLabelValue(point.low, axisRenderer.axis, digits); + if (seriesRendererDetails.seriesType == 'candle' || + seriesRendererDetails.seriesType == 'hiloopenclose') { + openValue = getLabelValue(point.open, axisRenderer.axis, digits); + closeValue = getLabelValue(point.close, axisRenderer.axis, digits); + } + } else if (seriesRendererDetails.seriesType.contains('stacked') == true) { + cumulativeValue = + getLabelValue(point.cumulativeValue, axisRenderer.axis, digits); + } + if (tooltipBehavior.format != null) { + resultantString = (seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType == 'hilo') && + !isTrendLine + ? (tooltip.format! + .replaceAll('point.x', values[0]) + .replaceAll('point.high', highValue!) + .replaceAll('point.low', lowValue!) + .replaceAll( + 'series.name', + seriesRendererDetails.series.name ?? + seriesRendererDetails.seriesName!)) + : (seriesRendererDetails.seriesType.contains('hiloopenclose') == true || + seriesRendererDetails.seriesType.contains('candle') == + true) && + !isTrendLine + ? (tooltip.format! + .replaceAll('point.x', values[0]) + .replaceAll('point.high', highValue!) + .replaceAll('point.low', lowValue!) + .replaceAll('point.open', openValue!) + .replaceAll('point.close', closeValue!) + .replaceAll( + 'series.name', + seriesRendererDetails.series.name ?? + seriesRendererDetails.seriesName!)) + : (seriesRendererDetails.seriesType.contains('boxandwhisker') == true) && + !isTrendLine + ? (tooltip.format! + .replaceAll('point.x', values[0]) + .replaceAll('point.minimum', minimumValue!) + .replaceAll('point.maximum', maximumValue!) + .replaceAll('point.lowerQuartile', lowerQuartileValue!) + .replaceAll('point.upperQuartile', upperQuartileValue!) + .replaceAll('point.mean', meanValue!) + .replaceAll('point.median', medianValue!) + .replaceAll( + 'series.name', seriesRendererDetails.series.name ?? seriesRendererDetails.seriesName!)) + : seriesRendererDetails.seriesType.contains('stacked') == true + ? tooltip.format!.replaceAll('point.cumulativeValue', cumulativeValue!) + : seriesRendererDetails.seriesType == 'bubble' + ? tooltip.format!.replaceAll('point.x', values[0]).replaceAll('point.y', getLabelValue(point.y, axisRenderer.axis, digits)).replaceAll('series.name', seriesRendererDetails.series.name ?? seriesRendererDetails.seriesName!).replaceAll('point.size', getLabelValue(point.bubbleSize, axisRenderer.axis, digits)) + : tooltip.format!.replaceAll('point.x', values[0]).replaceAll('point.y', getLabelValue(point.y, axisRenderer.axis, digits)).replaceAll('series.name', seriesRendererDetails.series.name ?? seriesRendererDetails.seriesName!); + } else { + resultantString = (tooltipBehavior.shared + ? seriesRendererDetails.series.name ?? + seriesRendererDetails.seriesName + : values[0]) + + (((seriesRendererDetails.seriesType.contains('range') == true || + seriesRendererDetails.seriesType == 'hilo') && + !isTrendLine) + ? ('\nHigh : ' + highValue! + '\nLow : ' + lowValue!) + : (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType == 'candle' + ? ('\nHigh : ' + + highValue! + + '\nLow : ' + + lowValue! + + '\nOpen : ' + + openValue! + + '\nClose : ' + + closeValue!) + : seriesRendererDetails.seriesType == 'boxandwhisker' + ? outlierValue != null + ? ('\nOutliers : ' + outlierValue) + : boxPlotString + : ' : ' + + getLabelValue(point.y, axisRenderer.axis, digits))); + } + return resultantString; + } + + /// Returns the cumulative length of the number of new line character '\n' + /// available in series name and tooltip format string. + int _getTooltipNewLineLength(int value) { + value += _seriesRendererDetails!.series.name != null + ? '\n'.allMatches(_seriesRendererDetails!.series.name!).length + : 0; + if (tooltipBehavior.format != null) { + value += '\n'.allMatches(tooltipBehavior.format!).length; + } + return value; + } + + /// finds whether the point resides inside the given rect including its edges + bool _isPointWithInRect(Offset point, Rect rect) { + return point != null && + point.dx >= rect.left && + point.dx <= rect.right && + point.dy <= rect.bottom && + point.dy >= rect.top; + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/enum.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/enum.dart index 20cea8aca..852fce1fe 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/enum.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/utils/enum.dart @@ -1,5 +1,3 @@ -part of charts; - /// Legend Position in charts. enum LegendPosition { diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart index 432491be8..e67418230 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/utils/helper.dart @@ -1,7 +1,43 @@ -part of charts; +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/chart/axis/axis.dart'; +import 'package:syncfusion_flutter_charts/src/common/rendering_details.dart'; +import 'package:syncfusion_flutter_charts/src/pyramid_chart/utils/common.dart'; +import 'package:syncfusion_flutter_charts/src/pyramid_chart/utils/helper.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/legend_internal.dart' + hide LegendPosition; +import 'package:syncfusion_flutter_core/legend_internal.dart' as legend_common; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../chart/base/chart_base.dart'; +import '../../chart/chart_series/financial_series_base.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/chart_series/series_renderer_properties.dart'; +import '../../chart/chart_series/xy_data_series.dart'; +import '../../chart/common/cartesian_state_properties.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/technical_indicators/technical_indicator.dart'; +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../circular_chart/base/circular_base.dart'; +import '../../circular_chart/renderer/common.dart'; +import '../../funnel_chart/base/funnel_base.dart'; +import '../../pyramid_chart/base/pyramid_base.dart'; +import '../common.dart'; +import '../event_args.dart'; +import '../legend/legend.dart'; +import '../legend/renderer.dart'; +import '../state_properties.dart'; +import '../utils/enum.dart'; +import 'typedef.dart'; /// `onDataLabelTapped` event for all series. -void _dataLabelTapEvent(dynamic chart, DataLabelSettings dataLabelSettings, +void dataLabelTapEvent(dynamic chart, DataLabelSettings dataLabelSettings, int pointIndex, dynamic point, Offset position, int seriesIndex) { DataLabelTapDetails datalabelArgs; datalabelArgs = DataLabelTapDetails( @@ -16,7 +52,7 @@ void _dataLabelTapEvent(dynamic chart, DataLabelSettings dataLabelSettings, } ///To get saturation color -Color _getSaturationColor(Color color) { +Color getSaturationColor(Color color) { Color saturationColor; final num contrast = ((color.red * 299 + color.green * 587 + color.blue * 114) / 1000).round(); @@ -25,32 +61,32 @@ Color _getSaturationColor(Color color) { } /// To get point from data and return point data -CartesianChartPoint _getPointFromData( - CartesianSeriesRenderer seriesRenderer, int pointIndex) { +CartesianChartPoint getPointFromData( + SeriesRendererDetails seriesRendererDetails, int pointIndex) { final XyDataSeries series = - seriesRenderer._series as XyDataSeries; + seriesRendererDetails.series as XyDataSeries; final ChartIndexedValueMapper? xValue = series.xValueMapper; final ChartIndexedValueMapper? yValue = series.yValueMapper; final dynamic xVal = xValue!(pointIndex); - final dynamic yVal = (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle') + final dynamic yVal = (seriesRendererDetails.seriesType.contains('range') || + seriesRendererDetails.seriesType.contains('hilo') || + seriesRendererDetails.seriesType == 'candle') ? null : yValue!(pointIndex); final CartesianChartPoint point = CartesianChartPoint(xVal, yVal); - if (seriesRenderer._seriesType.contains('range') || - seriesRenderer._seriesType.contains('hilo') || - seriesRenderer._seriesType == 'candle') { + if (seriesRendererDetails.seriesType.contains('range') || + seriesRendererDetails.seriesType.contains('hilo') || + seriesRendererDetails.seriesType == 'candle') { final ChartIndexedValueMapper? highValue = series.highValueMapper; final ChartIndexedValueMapper? lowValue = series.lowValueMapper; point.high = highValue!(pointIndex); point.low = lowValue!(pointIndex); } - if (series is _FinancialSeriesBase) { - if (seriesRenderer._seriesType == 'hiloopenclose' || - seriesRenderer._seriesType == 'candle') { + if (series is FinancialSeriesBase) { + if (seriesRendererDetails.seriesType == 'hiloopenclose' || + seriesRendererDetails.seriesType == 'candle') { final ChartIndexedValueMapper? openValue = series.openValueMapper; final ChartIndexedValueMapper? closeValue = series.closeValueMapper; point.open = openValue!(pointIndex); @@ -60,8 +96,34 @@ CartesianChartPoint _getPointFromData( return point; } +/// To calculate dash array path for series +Path? dashPath( + Path? source, { + required CircularIntervalList dashArray, +}) { + if (source == null) { + return null; + } + const double intialValue = 0.0; + final Path path = Path(); + for (final PathMetric measurePath in source.computeMetrics()) { + double distance = intialValue; + bool draw = true; + while (distance < measurePath.length) { + final double length = dashArray.next; + if (draw) { + path.addPath( + measurePath.extractPath(distance, distance + length), Offset.zero); + } + distance += length; + draw = !draw; + } + } + return path; +} + /// To return textstyle -TextStyle _getTextStyle( +TextStyle getTextStyle( {TextStyle? textStyle, Color? fontColor, double? fontSize, @@ -109,12 +171,19 @@ TextStyle _getTextStyle( } } -Widget? _getElements( - dynamic _chartState, Widget chartWidget, BoxConstraints constraints) { - final dynamic chart = _chartState._chart; - final _ChartLegend chartLegend = _chartState._renderingDetails.chartLegend; +/// Method to get the elements +Widget? getElements(StateProperties stateProperties, Widget chartWidget, + BoxConstraints constraints) { + final dynamic chart = stateProperties.chart; + final ChartLegend chartLegend = stateProperties.renderingDetails.chartLegend; final LegendPosition legendPosition = - _chartState._renderingDetails.legendRenderer._legendPosition; + stateProperties.renderingDetails.legendRenderer.legendPosition; + final LegendRenderer legendRenderer = + stateProperties.renderingDetails.legendRenderer; + final Legend legend = chart.legend; + final List legendWidgetContext = + stateProperties.renderingDetails.legendWidgetContext; + double legendHeight, legendWidth, chartHeight, chartWidth; Widget? element; @@ -136,419 +205,540 @@ Widget? _getElements( legendWidth = chartLegend.legendSize.width; chartHeight = chartLegend.chartSize.height - legendHeight; chartWidth = chartLegend.chartSize.width - legendWidth; - final Widget legendBorderWidget = - CustomPaint(painter: _ChartLegendStylePainter(chartState: _chartState)); - final Widget legendWidget = Container( - height: legendHeight, - width: legendWidth, - decoration: BoxDecoration(color: chart.legend.backgroundColor), - child: _LegendContainer(chartState: _chartState)); - switch (legendPosition) { - case LegendPosition.bottom: - case LegendPosition.top: - element = _getBottomAndTopLegend( - _chartState, - chartWidget, - constraints, - legendWidget, - legendBorderWidget, - legendHeight, - legendWidth, - chartHeight); - break; - case LegendPosition.right: - case LegendPosition.left: - element = _getLeftAndRightLegend( - _chartState, - chartWidget, - constraints, - legendWidget, - legendBorderWidget, - legendHeight, - legendWidth, - chartWidth); - break; - case LegendPosition.auto: - break; - } - } - return element!; -} - -Widget _getBottomAndTopLegend( - dynamic _chartState, - Widget chartWidget, - BoxConstraints constraints, - Widget legendWidget, - Widget legendBorderWidget, - double legendHeight, - double legendWidth, - double chartHeight) { - Widget element; - final dynamic chart = _chartState._chart; - const double legendPadding = 5; - const double padding = 10; - final bool needPadding = chart is SfCircularChart || - chart is SfPyramidChart || - chart is SfFunnelChart; - final _ChartLegend chartLegend = _chartState._renderingDetails.chartLegend; - final LegendPosition legendPosition = - _chartState._renderingDetails.legendRenderer._legendPosition; - final double legendLeft = (chartLegend.chartSize.width < legendWidth) - ? 0 - : ((chart.legend.alignment == ChartAlignment.near) - ? 0 - : (chart.legend.alignment == ChartAlignment.center) - ? chartLegend.chartSize.width / 2 - - chartLegend.legendSize.width / 2 - : chartLegend.chartSize.width - chartLegend.legendSize.width); - bool needRender = true; - final EdgeInsets margin; - if (chartLegend.legend?.offset != null) { - if (chart.legend.offset.dx.isNegative == true) { - if (legendLeft + chart.legend.offset.dx < 0) { - needRender = false; - } else { - if (legendLeft + chart.legend.offset.dx > chartLegend.chartSize.width) { - needRender = false; - } - } - } - if (legendPosition == LegendPosition.top) { - if (chart.legend.offset.dy.isNegative == true) { - if (legendPadding + chart.legend.offset.dy < 0) { - needRender = false; - } - } else { - if (legendPadding + chart.legend.offset.dy > - chartLegend.chartSize.height) { - needRender = false; - } - } - } else if (legendPosition == LegendPosition.bottom) { - if (chart.legend.offset.dy.isNegative == true) { - if (chartHeight + legendPadding + chart.legend.offset.dy < 0) { - needRender = false; + + // To determine the toggled indices of the legend items + final List toggledIndices = chartLegend.toggledIndices; + if (chart is SfCartesianChart) { + toggledIndices.clear(); + if (legend.legendItemBuilder == null) { + final List legendCollections = + stateProperties.renderingDetails.chartLegend.legendCollections!; + for (int i = 0; i < legendCollections.length; i++) { + final LegendRenderContext context = legendCollections[i]; + for (final LegendRenderContext toggledItem + in stateProperties.renderingDetails.legendToggleStates) { + final bool isTrendline = context.isTrendline ?? false; + if (context.text == toggledItem.text && + (!isTrendline || + (context.series == toggledItem.series) || + (context.seriesRenderer.seriesName == + toggledItem.seriesRenderer.seriesName))) { + if (!isTrendline || !toggledIndices.contains(i)) + toggledIndices.add(i); + if (!isTrendline && + context.indicatorRenderer == null && + context.series.trendlines != null) { + int trendlineCount = context.series.trendlines.length; + while (trendlineCount > 0) { + toggledIndices.add(i + trendlineCount); + trendlineCount--; + } + } + break; + } + } } } else { - if (chartHeight + - legendPadding + - chart.legend.offset.dy + - legendHeight / 2 > - chartLegend.chartSize.height) { - needRender = false; + final List legendToggles = + stateProperties.renderingDetails.legendToggleTemplateStates; + for (final MeasureWidgetContext currentItem in legendWidgetContext) { + for (int i = 0; i < legendToggles.length; i++) { + final MeasureWidgetContext item = legendToggles[i]; + if (currentItem.seriesIndex == item.seriesIndex && + currentItem.pointIndex == item.pointIndex) { + toggledIndices.add(legendWidgetContext.indexOf(currentItem)); + break; + } + } } } + toggledIndices.sort(); } - } - if (chartLegend.legend?.offset == null) { - margin = (legendPosition == LegendPosition.top) - ? EdgeInsets.fromLTRB( - legendLeft + (needPadding ? padding : 0), legendPadding, 0, 0) - : EdgeInsets.fromLTRB(legendLeft, chartHeight + legendPadding, 0, 0); - } else { - if (needRender) { - margin = (legendPosition == LegendPosition.top) - ? EdgeInsets.fromLTRB( - legendLeft + (needPadding ? padding : 0) + chart.legend.offset.dx, - legendPadding + chart.legend.offset.dy, - 0, - 0) - : EdgeInsets.fromLTRB(legendLeft + chart.legend.offset.dx, - chartHeight + legendPadding + chart.legend.offset.dy, 0, 0); - } else { - margin = const EdgeInsets.all(0); - } - } - legendWidget = Container( - child: Stack(children: [ - Container( - margin: margin, - height: legendHeight, + + if (legend.legendItemBuilder != null) { + element = SfLegend.builder( + title: getLegendTitleWidget(legend, stateProperties.renderingDetails), + child: chartWidget, + itemCount: legendWidgetContext.length, + toggledIndices: chartLegend.toggledIndices, + color: chartLegend.legend!.backgroundColor, + border: getLegendBorder( + chartLegend.legend!.borderColor, chartLegend.legend!.borderWidth), + position: getEffectiveChartLegendPosition(legendPosition), + direction: getEffectiveChartLegendOrientation( + chartLegend.legend!, legendRenderer), + scrollDirection: getEffectiveChartLegendOrientation( + chartLegend.legend!, legendRenderer), + alignment: getEffectiveLegendAlignment(chartLegend.legend!.alignment), + itemBuilder: (BuildContext context, int index) { + if (legendWidgetContext.isNotEmpty) { + final MeasureWidgetContext legendRenderContext = + legendWidgetContext[index]; + return legendRenderContext.widget!; + } + return Container(); + }, + overflowMode: getEffectiveLegendItemOverflowMode( + chartLegend.legend!.overflowMode, chartLegend), width: legendWidth, - child: legendBorderWidget), - Container( - margin: margin, height: legendHeight, - width: legendWidth, - child: legendWidget) - ]), - ); - if (legendPosition == LegendPosition.top) { - if (chartLegend.legend?.offset == null) { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - legendWidget, - Container( - margin: EdgeInsets.fromLTRB( - needPadding ? padding : 0, legendHeight + padding, 0, 0), - height: chartHeight, - width: chartLegend.chartSize.width, - child: chartWidget, - ) - ], - )); + spacing: chartLegend.legend!.padding, + itemSpacing: 0, + itemRunSpacing: 0, + padding: const EdgeInsets.all(0), + margin: getEffectiveLegendMargin(chartLegend, legendPosition), + toggledItemColor: + stateProperties.renderingDetails.chartTheme.brightness == + Brightness.light + ? Colors.white.withOpacity(0.5) + : Colors.grey[850]!.withOpacity(0.5), + onToggledIndicesChanged: + (List toggledIndices, int toggledIndex) { + if (chart is SfCartesianChart) { + cartesianToggle( + toggledIndex, stateProperties as CartesianStateProperties); + } else { + circularAndTriangularToggle(toggledIndex, stateProperties); + chartLegend.toggledIndices = toggledIndices; + } + }); } else { - if (needRender) { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - chartWidget, - legendWidget, - ], - )); - } else { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, + element = SfLegend( + title: getLegendTitleWidget(legend, stateProperties.renderingDetails), child: chartWidget, - ); - } + toggledIndices: chartLegend.toggledIndices, + items: chartLegend.legendItems, + offset: legend.offset, + width: legendWidth, + height: legendHeight, + onItemRenderer: (ItemRendererDetails args) { + args.text = chartLegend.legendItems[args.index].text; + if (chartLegend.legendItems[args.index].shader == null || + chartLegend.legendItems[args.index].iconStrokeWidth != null) { + args.color = chartLegend.legendItems[args.index].color; + } + args.iconType = chartLegend.legendItems[args.index].iconType!; + }, + color: chartLegend.legend!.backgroundColor, + border: getLegendBorder( + chartLegend.legend!.borderColor, chartLegend.legend!.borderWidth), + position: getEffectiveChartLegendPosition(legendPosition), + direction: getEffectiveChartLegendOrientation( + chartLegend.legend!, legendRenderer), + scrollDirection: getEffectiveChartLegendOrientation( + chartLegend.legend!, legendRenderer), + alignment: getEffectiveLegendAlignment(chartLegend.legend!.alignment), + overflowMode: getEffectiveLegendItemOverflowMode( + chartLegend.legend!.overflowMode, chartLegend), + iconSize: Size( + chartLegend.legend!.iconWidth, chartLegend.legend!.iconHeight), + iconBorder: getLegendIconBorder(chartLegend.legend!.iconBorderColor, + chartLegend.legend!.iconBorderWidth), + textStyle: chartLegend.legend!.textStyle.copyWith( + color: legend.textStyle.color ?? + stateProperties.renderingDetails.chartTheme.legendTextColor, + fontSize: legend.textStyle.fontSize! / + MediaQuery.of(stateProperties.chartState.context) + .textScaleFactor), + spacing: legend.padding, + itemSpacing: legend.itemPadding, + itemRunSpacing: legend.itemPadding, + padding: getEffectiveLegendPadding( + chartLegend, legendRenderer, legendPosition), + margin: getEffectiveLegendMargin(chartLegend, legendPosition), + toggledIconColor: const Color.fromRGBO(211, 211, 211, 1), + toggledTextOpacity: 0.2, + onToggledIndicesChanged: + (List toggledIndices, int toggledIndex) { + if (chart is SfCartesianChart) { + cartesianToggle( + toggledIndex, stateProperties as CartesianStateProperties); + } else { + circularAndTriangularToggle(toggledIndex, stateProperties); + chartLegend.toggledIndices = toggledIndices; + } + }); + } + } + return element; +} + +/// To get effective legend position for SfLegend +legend_common.LegendPosition getEffectiveChartLegendPosition( + LegendPosition position) { + switch (position) { + case LegendPosition.top: + return legend_common.LegendPosition.top; + case LegendPosition.bottom: + return legend_common.LegendPosition.bottom; + case LegendPosition.left: + return legend_common.LegendPosition.left; + case LegendPosition.right: + return legend_common.LegendPosition.right; + default: + return legend_common.LegendPosition.bottom; + } +} + +/// To get effective legend orientation for SfLegend +Axis getEffectiveChartLegendOrientation( + Legend legend, LegendRenderer renderer) { + final LegendItemOrientation legendOrientation = renderer.orientation; + if (legend.orientation == LegendItemOrientation.auto) { + if (legendOrientation == LegendItemOrientation.horizontal) { + return Axis.horizontal; + } else { + return Axis.vertical; } } else { - if (chartLegend.legend?.offset == null) { - element = Container( - margin: EdgeInsets.fromLTRB( - needPadding ? padding : 0, needPadding ? padding : 0, 0, 0), - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - Container( - height: chartHeight, - width: chartLegend.chartSize.width, - child: chartWidget, - ), - legendWidget, - ], - )); + if (legend.orientation == LegendItemOrientation.horizontal) { + return Axis.horizontal; } else { - if (needRender) { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - chartWidget, - legendWidget, - ], - )); + return Axis.vertical; + } + } +} + +/// To get effective legend alignment for SfLegend +LegendAlignment getEffectiveLegendAlignment(ChartAlignment alignment) { + LegendAlignment legendAlignment; + switch (alignment) { + case ChartAlignment.near: + legendAlignment = LegendAlignment.near; + break; + case ChartAlignment.far: + legendAlignment = LegendAlignment.far; + break; + case ChartAlignment.center: + legendAlignment = LegendAlignment.center; + break; + } + return legendAlignment; +} + +/// To get effective legend items overflow mode for SfLegend +LegendOverflowMode getEffectiveLegendItemOverflowMode( + LegendItemOverflowMode overflowMode, ChartLegend chartLegend) { + LegendOverflowMode mode; + switch (overflowMode) { + case LegendItemOverflowMode.wrap: + if (chartLegend.isNeedScrollable) { + mode = LegendOverflowMode.wrapScroll; } else { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: chartWidget, - ); + mode = LegendOverflowMode.wrap; } - } + break; + case LegendItemOverflowMode.scroll: + mode = LegendOverflowMode.scroll; + break; + case LegendItemOverflowMode.none: + mode = LegendOverflowMode.none; + break; } - return element; + return mode; } -/// To return top and bottom position legend widget -Widget _getLeftAndRightLegend( - dynamic _chartState, - Widget chartWidget, - BoxConstraints constraints, - Widget legendWidget, - Widget legendBorderWidget, - double legendHeight, - double legendWidth, - double chartWidth) { - Widget element; - const double legendPadding = 5; - const double padding = 10; - final dynamic chart = _chartState._chart; - bool needRender = true; +/// To get border for SfLegend container +BorderSide? getLegendBorder(Color borderColor, double borderWidth) { + if (borderColor != null && borderWidth > 0) { + return BorderSide(color: borderColor, width: borderWidth); + } + + return null; +} + +/// To get icon border for legendItemsin SfLegend +BorderSide? getLegendIconBorder(Color iconBorderColor, double iconBorderWidth) { + if (iconBorderColor != null && iconBorderWidth > 0) { + return BorderSide(color: iconBorderColor, width: iconBorderWidth); + } + + return null; +} + +/// To get legend title widget for SfLegend +Widget? getLegendTitleWidget(Legend legend, RenderingDetails renderingDetails) { + final LegendTitle legendTitle = legend.title; + if (legendTitle.text != null && legendTitle.text!.isNotEmpty) { + final ChartAlignment titleAlign = legendTitle.alignment; + final Color color = legendTitle.textStyle.color ?? + renderingDetails.chartTheme.legendTitleColor; + final double? fontSize = legendTitle.textStyle.fontSize; + final String? fontFamily = legendTitle.textStyle.fontFamily; + final FontStyle? fontStyle = legendTitle.textStyle.fontStyle; + final FontWeight? fontWeight = legendTitle.textStyle.fontWeight; + final num titleHeight = + measureText(legend.title.text!, legend.title.textStyle).height + 10; + renderingDetails.chartLegend.titleHeight = titleHeight.toDouble(); + return Container( + height: titleHeight.toDouble(), + width: renderingDetails.chartLegend.legendSize.width, + alignment: titleAlign == ChartAlignment.center + ? Alignment.center + : titleAlign == ChartAlignment.near + ? Alignment.centerLeft + : Alignment.centerRight, + child: Container( + child: Text(legend.title.text!, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: color, + fontSize: fontSize, + fontFamily: fontFamily, + fontStyle: fontStyle, + fontWeight: fontWeight)), + )); + } else { + return null; + } +} + +/// To get outer padding or margin for legend container +EdgeInsetsGeometry getEffectiveLegendMargin( + ChartLegend chartLegend, LegendPosition legendPosition) { + final dynamic chart = chartLegend.chart; + final EdgeInsets margin; final bool needPadding = chart is SfCircularChart || chart is SfPyramidChart || chart is SfFunnelChart; - final _ChartLegend chartLegend = _chartState._renderingDetails.chartLegend; - final LegendPosition legendPosition = - _chartState._renderingDetails.legendRenderer._legendPosition; - final double legendTop = (chartLegend.chartSize.height < legendHeight) - ? 0 - : ((chart.legend.alignment == ChartAlignment.near) - ? 0 - : (chart.legend.alignment == ChartAlignment.center) - ? chartLegend.chartSize.height / 2 - - chartLegend.legendSize.height / 2 - : chartLegend.chartSize.height - chartLegend.legendSize.height); - final EdgeInsets margin; + final ChartAlignment legendAlignment = chartLegend.legend!.alignment; + const double legendMargin = 5; - if (chartLegend.legend?.offset != null) { - if (legendPosition == LegendPosition.left) { - if (chart.legend.offset.dx.isNegative == true) { - if (legendPadding / 2 + chart.legend.offset.dx < legendPadding) { - needRender = false; - } + if (legendPosition == LegendPosition.top) { + margin = EdgeInsets.fromLTRB( + needPadding && legendAlignment == ChartAlignment.near + ? legendMargin * 2 + : 0, + legendMargin, + needPadding && legendAlignment == ChartAlignment.far + ? legendMargin * 2 + : 0, + legendMargin); + } else if (legendPosition == LegendPosition.bottom) { + margin = EdgeInsets.fromLTRB( + needPadding && legendAlignment == ChartAlignment.near + ? legendMargin * 2 + : 0, + legendMargin, + needPadding && legendAlignment == ChartAlignment.far + ? legendMargin * 2 + : 0, + needPadding ? legendMargin : 0); + } else if (legendPosition == LegendPosition.right) { + margin = EdgeInsets.fromLTRB(legendMargin, 0, + needPadding ? 3 * legendMargin : 0, needPadding ? 3 * legendMargin : 0); + } else if (legendPosition == LegendPosition.left) { + margin = EdgeInsets.fromLTRB(needPadding ? legendMargin / 2 : 0, 0, 0, + needPadding ? 3 * legendMargin : 0); + } else { + margin = const EdgeInsets.all(0); + } + + return margin; +} + +/// To get internal padding for legend container +EdgeInsetsGeometry getEffectiveLegendPadding(ChartLegend chartLegend, + LegendRenderer legendRenderer, LegendPosition legendPosition) { + final dynamic chart = chartLegend.chart; + final Legend legend = chartLegend.legend!; + final LegendItemOrientation legendOrientation = legendRenderer.orientation; + final LegendItemOverflowMode overflowMode = legend.overflowMode; + final EdgeInsetsGeometry padding; + final double legendPadding = legend.itemPadding; + final bool needPadding = chart is SfCircularChart || + chart is SfPyramidChart || + chart is SfFunnelChart; + final bool needTitle = + legend.title.text != null && legend.title.text!.isNotEmpty; + + if (legendPosition == LegendPosition.top || + legendPosition == LegendPosition.bottom) { + if (legendOrientation == LegendItemOrientation.horizontal) { + if (overflowMode == LegendItemOverflowMode.wrap) { + padding = !chartLegend.isNeedScrollable + ? EdgeInsets.fromLTRB(legendPadding, 0, 0, 0) + : EdgeInsets.fromLTRB( + legendPadding, legendPadding / 2, 0, legendPadding / 2); + } else if (overflowMode == LegendItemOverflowMode.scroll) { + padding = EdgeInsets.fromLTRB(legendPadding, 0, 0, 0); } else { - if (chart.legend.offset.dx > chartWidth == true) { - needRender = false; - } + padding = EdgeInsets.fromLTRB( + legendPadding, + needPadding && needTitle ? (legendPadding / 2) : 0, + needPadding && needTitle ? legendPadding : 0, + needPadding && needTitle ? (legendPadding / 2) : 0); } - } else if (legendPosition == LegendPosition.right) { - if (chart.legend.offset.dx.isNegative == true) { - if (chartWidth + legendPadding + chart.legend.offset.dx < 0) { - needRender = false; - } + } else { + if (overflowMode == LegendItemOverflowMode.wrap) { + padding = !chartLegend.isNeedScrollable + ? EdgeInsets.fromLTRB(0, legendPadding / 2, 0, 0) + : EdgeInsets.fromLTRB(legendPadding, legendPadding / 2, 0, 0); + } else if (overflowMode == LegendItemOverflowMode.scroll) { + padding = EdgeInsets.fromLTRB(legendPadding / 2, legendPadding / 2, + needTitle ? (legendPadding / 2) : 0, legendPadding / 2); } else { - if (chartWidth + chart.legend.offset.dx - legendPadding > chartWidth) { - needRender = false; - } + padding = EdgeInsets.fromLTRB(needTitle ? legendPadding : 0, + legendPadding / 2, needTitle ? legendPadding : 0, 0); } } - - if (chart.legend.offset.dy.isNegative == true) { - if (legendTop + chart.legend.offset.dy < 0) { - needRender = false; + } else if (legendPosition == LegendPosition.right || + legendPosition == LegendPosition.left) { + if (legendOrientation == LegendItemOrientation.horizontal) { + if (overflowMode == LegendItemOverflowMode.wrap) { + padding = !chartLegend.isNeedScrollable + ? EdgeInsets.fromLTRB( + needTitle ? 0 : legendPadding, + needTitle ? (legendPadding / 2) : 0, + 0, + needTitle ? (legendPadding / 2) : 0) + : EdgeInsets.fromLTRB( + needTitle ? (legendPadding / 2) : legendPadding, + legendPadding / 2, + 0, + legendPadding / 2); + } else if (overflowMode == LegendItemOverflowMode.scroll) { + padding = EdgeInsets.fromLTRB( + legendPadding, + needTitle ? (legendPadding / 2) : 0, + 0, + needTitle ? (legendPadding / 2) : 0); + } else { + padding = EdgeInsets.fromLTRB( + legendPadding, + needTitle ? (legendPadding / 2) : 0, + needTitle ? legendPadding : 0, + 0); } } else { - if (chart.legend.offset.dy + legendTop > chartLegend.chartSize.height == - true) { - needRender = false; + if (overflowMode == LegendItemOverflowMode.wrap) { + padding = !chartLegend.isNeedScrollable + ? EdgeInsets.fromLTRB(needPadding ? (legendPadding / 2) : 0, + legendPadding / 2, 0, needTitle ? (legendPadding / 2) : 0) + : EdgeInsets.fromLTRB(legendPadding, legendPadding / 2, 0, + needTitle ? (legendPadding / 2) : 0); + } else if (overflowMode == LegendItemOverflowMode.scroll) { + padding = EdgeInsets.fromLTRB(legendPadding / 2, legendPadding / 2, + legendPadding / 2, legendPadding / 2); + } else { + padding = EdgeInsets.fromLTRB( + 0, legendPadding / 2, 0, needTitle ? legendPadding / 2 : 0); } } - } - if (chartLegend.legend?.offset == null) { - margin = (legendPosition == LegendPosition.left) - ? EdgeInsets.fromLTRB(legendPadding / 2, legendTop, 0, 0) - : EdgeInsets.fromLTRB(chartWidth + legendPadding, legendTop, 0, 0); } else { - if (needRender) { - margin = (legendPosition == LegendPosition.left) - ? EdgeInsets.fromLTRB(legendPadding / 2 + chart.legend.offset.dx, - legendTop + chart.legend.offset.dy, 0, 0) - : EdgeInsets.fromLTRB( - chartWidth + legendPadding + chart.legend.offset.dx, - legendTop + chart.legend.offset.dy, - 0, - 0); + padding = const EdgeInsets.all(0); + } + return padding; +} + +/// Method to handle cartesian series legend toggling for SfLegend +void cartesianToggle(int index, CartesianStateProperties stateProperties) { + LegendTapArgs legendTapArgs; + MeasureWidgetContext _measureWidgetContext; + LegendRenderContext _legendRenderContext; + final SfCartesianChart chart = stateProperties.chart; + stateProperties.isTooltipHidden = true; + if (chart.onLegendTapped != null) { + if (chart.legend.legendItemBuilder != null) { + _measureWidgetContext = + stateProperties.renderingDetails.legendWidgetContext[index]; + legendTapArgs = LegendTapArgs( + stateProperties.chartSeries + .visibleSeriesRenderers[_measureWidgetContext.seriesIndex!], + _measureWidgetContext.seriesIndex!, + 0); } else { - margin = const EdgeInsets.all(0); + _legendRenderContext = stateProperties + .renderingDetails.chartLegend.legendCollections![index]; + legendTapArgs = LegendTapArgs( + _legendRenderContext.series, _legendRenderContext.seriesIndex, 0); } + chart.onLegendTapped!(legendTapArgs); } - legendWidget = Container( - child: Stack(children: [ - Container( - margin: margin, - height: legendHeight, - width: legendWidth, - child: legendBorderWidget, - ), - Container( - margin: margin, - height: legendHeight, - width: legendWidth, - child: legendWidget, - ) - ]), - ); - if (legendPosition == LegendPosition.left) { - if (chartLegend.legend?.offset == null) { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - legendWidget, - Container( - margin: EdgeInsets.fromLTRB( - legendWidth + (needPadding ? padding : 0), - needPadding ? chart.margin.top : 0, - 0, - 0), - height: chartLegend.chartSize.height, - width: chartWidth, - child: chartWidget, - ) - ], - )); + if (chart.legend.toggleSeriesVisibility == true) { + if (chart.legend.legendItemBuilder != null) { + legendToggleTemplateState( + stateProperties.renderingDetails.legendWidgetContext[index], + stateProperties, + ''); } else { - if (needRender) { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - chartWidget, - legendWidget, - ], - )); - } else { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: chartWidget, - ); - } + cartesianLegendToggleState( + stateProperties + .renderingDetails.chartLegend.legendCollections![index], + stateProperties); } - } else { - if (chartLegend.legend?.offset == null) { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - Container( - margin: - EdgeInsets.only(top: needPadding ? chart.margin.top : 0), - height: chartLegend.chartSize.height, - width: chartWidth, - child: chartWidget, - ), - legendWidget - ], - )); + stateProperties.renderingDetails.isLegendToggled = true; + stateProperties.legendToggling = true; + stateProperties.redraw(); + } +} + +/// Method to handle Circular and triangular series legend toggling for SfLegend +void circularAndTriangularToggle(int index, dynamic stateProperties) { + LegendTapArgs legendTapArgs; + const int seriesIndex = 0; + final dynamic chart = stateProperties.chart; + final ChartLegend chartLegend = stateProperties.renderingDetails.chartLegend; + stateProperties.isTooltipHidden = true; + if (chart.onLegendTapped != null) { + if (chart != null) { + legendTapArgs = LegendTapArgs(chart.series, seriesIndex, index); } else { - if (needRender) { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: Stack( - children: [ - chartWidget, - legendWidget, - ], - )); - } else { - element = Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - child: chartWidget, - ); - } + legendTapArgs = + LegendTapArgs(chart._series[seriesIndex], seriesIndex, index); } + chart.onLegendTapped(legendTapArgs); + } + if (chart.legend.toggleSeriesVisibility == true) { + if (chart.legend.legendItemBuilder != null) { + final MeasureWidgetContext legendWidgetContext = + stateProperties.renderingDetails.legendWidgetContext[index]; + legendToggleTemplateState(legendWidgetContext, stateProperties, ''); + } else { + legendToggleState(chartLegend.legendCollections![index], stateProperties); + } + stateProperties.renderingDetails.isLegendToggled = true; + stateProperties.redraw(); } - return element; } -class _MeasureWidgetSize extends StatelessWidget { - const _MeasureWidgetSize( - {this.chartState, +/// Represents the value of measure widget size +class MeasureWidgetSize extends StatelessWidget { + /// Creates an instance of measure widget size + const MeasureWidgetSize( + {required this.stateProperties, this.currentWidget, this.opacityValue, this.currentKey, this.seriesIndex, this.pointIndex, this.type}); - final dynamic chartState; + + /// Holds the state properties value + final StateProperties stateProperties; + + /// Holds the value of current widget final Widget? currentWidget; + + /// Holds the opacity value final double? opacityValue; + + /// Holds the current key value final Key? currentKey; + + /// Holds the series index value final int? seriesIndex; + + /// Holds the point index value final int? pointIndex; + + /// Holds the value of type final String? type; @override Widget build(BuildContext context) { - final List<_MeasureWidgetContext> templates = - chartState._renderingDetails.legendWidgetContext; - templates.add(_MeasureWidgetContext( + final List templates = + stateProperties.renderingDetails.legendWidgetContext; + templates.add(MeasureWidgetContext( widget: currentWidget, key: currentKey, context: context, @@ -561,14 +751,14 @@ class _MeasureWidgetSize extends StatelessWidget { } /// To return legend template toggled state -bool _legendToggleTemplateState( - _MeasureWidgetContext currentItem, dynamic _chartState, String checkType) { +bool legendToggleTemplateState(MeasureWidgetContext currentItem, + StateProperties stateProperties, String checkType) { bool needSelect = false; - final List<_MeasureWidgetContext> legendToggles = - _chartState._renderingDetails.legendToggleTemplateStates; + final List legendToggles = + stateProperties.renderingDetails.legendToggleTemplateStates; if (legendToggles.isNotEmpty) { for (int i = 0; i < legendToggles.length; i++) { - final _MeasureWidgetContext item = legendToggles[i]; + final MeasureWidgetContext item = legendToggles[i]; if (currentItem.seriesIndex == item.seriesIndex && currentItem.pointIndex == item.pointIndex) { if (checkType != 'isSelect') { @@ -589,13 +779,14 @@ bool _legendToggleTemplateState( } /// To add legend toggle states in legend toggles list -void _legendToggleState(_LegendRenderContext currentItem, dynamic _chartState) { +void legendToggleState( + LegendRenderContext currentItem, StateProperties stateProperties) { bool needSelect = false; - final List<_LegendRenderContext> legendToggles = - _chartState._renderingDetails.legendToggleStates; + final List legendToggles = + stateProperties.renderingDetails.legendToggleStates; if (legendToggles.isNotEmpty) { for (int i = 0; i < legendToggles.length; i++) { - final _LegendRenderContext item = legendToggles[i]; + final LegendRenderContext item = legendToggles[i]; if (currentItem.seriesIndex == item.seriesIndex) { needSelect = true; legendToggles.removeAt(i); @@ -610,18 +801,19 @@ void _legendToggleState(_LegendRenderContext currentItem, dynamic _chartState) { } /// To add cartesian legend toggle states -void _cartesianLegendToggleState( - _LegendRenderContext currentItem, dynamic _chartState) { +void cartesianLegendToggleState( + LegendRenderContext currentItem, CartesianStateProperties stateProperties) { bool needSelect = false; - final List<_LegendRenderContext> legendToggles = - _chartState._renderingDetails.legendToggleStates; + final List legendToggles = + stateProperties.renderingDetails.legendToggleStates; if (currentItem.trendline == null || - _chartState._chartSeries.visibleSeriesRenderers[currentItem.seriesIndex] - ._visible == + SeriesHelper.getSeriesRendererDetails(stateProperties + .chartSeries.visibleSeriesRenderers[currentItem.seriesIndex]) + .visible! == true) { if (legendToggles.isNotEmpty) { for (int i = 0; i < legendToggles.length; i++) { - final _LegendRenderContext item = legendToggles[i]; + final LegendRenderContext item = legendToggles[i]; if (currentItem.trendline != null && currentItem.text == item.text && (currentItem.seriesIndex == item.seriesIndex && @@ -640,20 +832,25 @@ void _cartesianLegendToggleState( } if (!needSelect) { if (!(currentItem.seriesRenderer is TechnicalIndicators - ? !currentItem.indicatorRenderer!._visible! - : currentItem.seriesRenderer._visible == false && - _chartState._isTrendlineToggled == false)) { + ? !currentItem.indicatorRenderer!.visible! + : SeriesHelper.getSeriesRendererDetails( + currentItem.seriesRenderer.renderer) + .visible == + false && + stateProperties.isTrendlineToggled == false)) { needSelect = false; - final CartesianSeriesRenderer seriesRenderer = _chartState - ._chartSeries.visibleSeriesRenderers[currentItem.seriesIndex]; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails(stateProperties + .chartSeries.visibleSeriesRenderers[currentItem.seriesIndex]); if (currentItem.trendlineIndex != null) { - seriesRenderer._minimumX = 1 / 0; - seriesRenderer._minimumY = 1 / 0; - seriesRenderer._maximumX = -1 / 0; - seriesRenderer._maximumY = -1 / 0; + seriesRendererDetails.minimumX = 1 / 0; + seriesRendererDetails.minimumY = 1 / 0; + seriesRendererDetails.maximumX = -1 / 0; + seriesRendererDetails.maximumY = -1 / 0; } - _chartState._chartSeries - .visibleSeriesRenderers[currentItem.seriesIndex] = seriesRenderer; + stateProperties + .chartSeries.visibleSeriesRenderers[currentItem.seriesIndex] = + seriesRendererDetails.renderer; if (!legendToggles.contains(currentItem)) { legendToggles.add(currentItem); } @@ -663,7 +860,7 @@ void _cartesianLegendToggleState( } /// For checking whether elements collide -bool _findingCollision(Rect rect, List regions, [Rect? pathRect]) { +bool findingCollision(Rect rect, List regions, [Rect? pathRect]) { bool isCollide = false; if (pathRect != null && (pathRect.left < rect.left && @@ -687,8 +884,45 @@ bool _findingCollision(Rect rect, List regions, [Rect? pathRect]) { return isCollide; } +/// To find the labels are intersect. +bool isOverlap(Rect currentRect, Rect rect) { + return currentRect.left < rect.left + rect.width && + currentRect.left + currentRect.width > rect.left && + currentRect.top < (rect.top + rect.height) && + (currentRect.height + currentRect.top) > rect.top; +} + +/// To trim the text by given width +String getTrimmedText(String text, num labelsExtent, TextStyle labelStyle, + [ChartAxisRenderer? axisRenderer]) { + String label = text; + ChartAxisRendererDetails? axisRendererDetails; + axisRendererDetails = axisRenderer != null + ? AxisHelper.getAxisRendererDetails(axisRenderer) + : null; + num size = axisRenderer != null + ? measureText(text, axisRendererDetails!.axis.labelStyle, + axisRendererDetails.labelRotation) + .width + : measureText(label, labelStyle).width; + if (size > labelsExtent) { + final int textLength = text.length; + for (int i = textLength - 1; i >= 0; --i) { + label = text.substring(0, i) + '...'; + size = axisRenderer != null + ? measureText(label, labelStyle, axisRendererDetails!.labelRotation) + .width + : measureText(label, labelStyle).width; + if (size <= labelsExtent) { + return label == '...' ? '' : label; + } + } + } + return label == '...' ? '' : label; +} + /// To get equivalent value for the percentage -num _getValueByPercentage(num value1, num value2) { +num getValueByPercentage(num value1, num value2) { return value1.isNegative ? (num.tryParse('-' + (num.tryParse(value1.toString().replaceAll(RegExp('-'), ''))! % @@ -697,12 +931,13 @@ num _getValueByPercentage(num value1, num value2) { : (value1 % value2); } -Widget _renderChartTitle(dynamic _chartState) { +/// Method to render the chart title +Widget renderChartTitle(StateProperties stateProperties) { Widget titleWidget; - final dynamic widget = _chartState._chart; + final dynamic widget = stateProperties.chart; if (widget.title.text != null && widget.title.text.isNotEmpty == true) { final SfChartThemeData chartTheme = - _chartState._renderingDetails.chartTheme; + stateProperties.renderingDetails.chartTheme; final Color color = widget.title.textStyle.color ?? chartTheme.titleTextColor; final double fontSize = widget.title.textStyle.fontSize; @@ -745,27 +980,27 @@ Widget _renderChartTitle(dynamic _chartState) { } /// To get the legend template widgets -List _bindLegendTemplateWidgets(dynamic widgetState) { +List bindLegendTemplateWidgets(dynamic stateProperties) { Widget legendWidget; - final dynamic widget = widgetState._chart; + final dynamic widget = stateProperties.chart; final List templates = []; - widgetState._renderingDetails.chartWidgets = []; + stateProperties.renderingDetails.chartWidgets = []; if (widget.legend.isVisible == true && widget.legend.legendItemBuilder != null) { for (int i = 0; - i < widgetState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { final dynamic seriesRenderer = - widgetState._chartSeries.visibleSeriesRenderers[i]; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { + stateProperties.chartSeries.visibleSeriesRenderers[i]; + for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { legendWidget = widget.legend.legendItemBuilder( - seriesRenderer._renderPoints[j].x, + seriesRenderer.renderPoints[j].x, seriesRenderer, - seriesRenderer._renderPoints[j], + seriesRenderer.renderPoints[j], j); - templates.add(_MeasureWidgetSize( - chartState: widgetState, + templates.add(MeasureWidgetSize( + stateProperties: stateProperties, type: 'Legend', seriesIndex: i, pointIndex: j, @@ -779,7 +1014,7 @@ List _bindLegendTemplateWidgets(dynamic widgetState) { } /// To check whether indexes are valid -bool _validIndex(int? _pointIndex, int? _seriesIndex, dynamic chart) { +bool validIndex(int? _pointIndex, int? _seriesIndex, dynamic chart) { return _seriesIndex != null && _pointIndex != null && _seriesIndex >= 0 && @@ -788,8 +1023,8 @@ bool _validIndex(int? _pointIndex, int? _seriesIndex, dynamic chart) { _pointIndex < chart.series[_seriesIndex].dataSource.length; } -//this method removes the given listener from the animation controller and then dsiposes it. -void _disposeAnimationController( +/// This method removes the given listener from the animation controller and then dsiposes it. +void disposeAnimationController( AnimationController? animationController, VoidCallback listener) { if (animationController != null) { animationController.removeListener(listener); @@ -798,16 +1033,18 @@ void _disposeAnimationController( } } -void _calculatePointSeriesIndex( - dynamic chart, dynamic _chartState, Offset? position, - [_Region? pointRegion, ActivationMode? activationMode]) { +/// Method to calculate the series point index +void calculatePointSeriesIndex( + dynamic chart, dynamic stateProperties, Offset? position, + [Region? pointRegion, ActivationMode? activationMode]) { if (chart is SfCartesianChart) { for (int i = 0; - i < _chartState._chartSeries.visibleSeriesRenderers.length; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; i++) { - final CartesianSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[i]; - final String _seriesType = seriesRenderer._seriesType; + final SeriesRendererDetails seriesRendererDetails = + SeriesHelper.getSeriesRendererDetails( + stateProperties.chartSeries.visibleSeriesRenderers[i]); + final String _seriesType = seriesRendererDetails.seriesType; int? pointIndex; final double padding = (_seriesType == 'bubble') || (_seriesType == 'scatter') || @@ -821,7 +1058,7 @@ void _calculatePointSeriesIndex( : 15; /// Regional padding to detect smooth touch - seriesRenderer._regionalData! + seriesRendererDetails.regionalData! .forEach((dynamic regionRect, dynamic values) { final Rect region = regionRect[0]; final double left = region.left - padding; @@ -834,33 +1071,36 @@ void _calculatePointSeriesIndex( } }); - if (pointIndex != null) { - if ((seriesRenderer._series.onPointTap != null || - seriesRenderer._series.onPointDoubleTap != null || - seriesRenderer._series.onPointLongPress != null) && + if (pointIndex != null && + !seriesRendererDetails + .stateProperties.renderingDetails.isLegendToggled) { + if ((seriesRendererDetails.series.onPointTap != null || + seriesRendererDetails.series.onPointDoubleTap != null || + seriesRendererDetails.series.onPointLongPress != null) && activationMode != null) { ChartPointDetails pointInteractionDetails; pointInteractionDetails = ChartPointDetails( i, pointIndex!, - seriesRenderer._dataPoints, - seriesRenderer - ._visibleDataPoints![pointIndex!].overallDataPointIndex); + seriesRendererDetails.dataPoints, + seriesRendererDetails + .visibleDataPoints![pointIndex!].overallDataPointIndex); activationMode == ActivationMode.singleTap - ? seriesRenderer._series.onPointTap!(pointInteractionDetails) + ? seriesRendererDetails + .series.onPointTap!(pointInteractionDetails) : activationMode == ActivationMode.doubleTap - ? seriesRenderer - ._series.onPointDoubleTap!(pointInteractionDetails) - : seriesRenderer - ._series.onPointLongPress!(pointInteractionDetails); + ? seriesRendererDetails + .series.onPointDoubleTap!(pointInteractionDetails) + : seriesRendererDetails + .series.onPointLongPress!(pointInteractionDetails); } else { PointTapArgs pointTapArgs; pointTapArgs = PointTapArgs( i, pointIndex!, - seriesRenderer._dataPoints, - seriesRenderer - ._visibleDataPoints![pointIndex!].overallDataPointIndex); + seriesRendererDetails.dataPoints, + seriesRendererDetails + .visibleDataPoints![pointIndex!].overallDataPointIndex); chart.onPointTapped!(pointTapArgs); } } @@ -875,8 +1115,8 @@ void _calculatePointSeriesIndex( pointInteractionDetails = ChartPointDetails( pointRegion?.seriesIndex, pointRegion?.pointIndex, - _chartState - ._chartSeries.visibleSeriesRenderers[seriesIndex]._dataPoints, + stateProperties + .chartSeries.visibleSeriesRenderers[seriesIndex].dataPoints, pointRegion?.pointIndex); activationMode == ActivationMode.singleTap ? chart.series[seriesIndex].onPointTap!(pointInteractionDetails) @@ -890,17 +1130,17 @@ void _calculatePointSeriesIndex( pointTapArgs = PointTapArgs( pointRegion?.seriesIndex, pointRegion?.pointIndex, - _chartState - ._chartSeries.visibleSeriesRenderers[seriesIndex]._dataPoints, + stateProperties + .chartSeries.visibleSeriesRenderers[seriesIndex].dataPoints, pointRegion?.pointIndex); chart.onPointTapped!(pointTapArgs); } } else { int? index; const int seriesIndex = 0; - for (int i = 0; i < _chartState._renderPoints!.length; i++) { - if (_chartState._renderPoints![i].region != null && - _chartState._renderPoints![i].region!.contains(position) == true) { + for (int i = 0; i < stateProperties.renderPoints!.length; i++) { + if (stateProperties.renderPoints![i].region != null && + stateProperties.renderPoints![i].region!.contains(position) == true) { index = i; break; } @@ -912,7 +1152,7 @@ void _calculatePointSeriesIndex( activationMode != null) { ChartPointDetails pointInteractionDetails; pointInteractionDetails = ChartPointDetails( - seriesIndex, index, _chartState._dataPoints, index); + seriesIndex, index, stateProperties.dataPoints, index); activationMode == ActivationMode.singleTap ? chart.series.onPointTap!(pointInteractionDetails) : activationMode == ActivationMode.doubleTap @@ -921,9 +1161,57 @@ void _calculatePointSeriesIndex( } else { PointTapArgs pointTapArgs; pointTapArgs = - PointTapArgs(seriesIndex, index, _chartState._dataPoints, index); + PointTapArgs(seriesIndex, index, stateProperties.dataPoints, index); chart.onPointTapped!(pointTapArgs); } } } } + +/// Point to pixel +/// Used dynamic as the seriesRenderer can either be funnel or pyramid type series renderer. +Offset pyramidFunnelPointToPixel( + PointInfo point, dynamic seriesRenderer) { + Offset location; + if (point.region == null) { + final dynamic x = point.x; + final num y = point.y!; + for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { + if (seriesRenderer.dataPoints[i].x == x && + seriesRenderer.dataPoints[i].y == y) { + point = seriesRenderer.dataPoints[i]; + } + } + } + if (seriesRenderer.series.dataLabelSettings.isVisible == false) { + point.symbolLocation = Offset(point.region!.left + point.region!.width / 2, + point.region!.top + point.region!.height / 2); + } + location = point.symbolLocation; + location = Offset(location.dx, location.dy); + return location; +} + +/// Pixel to point +PointInfo pyramidFunnelPixelToPoint( + Offset position, dynamic seriesRenderer) { + final dynamic chartState = seriesRenderer.stateProperties; + const int seriesIndex = 0; + + late int? pointIndex; + bool isPoint; + for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { + if (seriesRenderer.renderPoints![j].isVisible == true) { + isPoint = isPointInPolygon( + seriesRenderer.renderPoints![j].pathRegion, position); + if (isPoint) { + pointIndex = j; + break; + } + } + } + final PointInfo chartPoint = chartState + .chartSeries.visibleSeriesRenderers[seriesIndex].renderPoints[pointIndex]; + + return chartPoint; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/common/utils/typedef.dart b/packages/syncfusion_flutter_charts/lib/src/common/utils/typedef.dart index 115562952..64394caef 100644 --- a/packages/syncfusion_flutter_charts/lib/src/common/utils/typedef.dart +++ b/packages/syncfusion_flutter_charts/lib/src/common/utils/typedef.dart @@ -1,4 +1,14 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import '../../chart/chart_series/series.dart'; +import '../../chart/chart_series/xy_data_series.dart'; +import '../../chart/common/trackball_marker_settings.dart'; +import '../../chart/utils/enum.dart'; +import '../../circular_chart/renderer/circular_series_controller.dart'; +import '../../funnel_chart/renderer/funnel_series.dart'; +import '../../pyramid_chart/renderer/series_controller.dart'; +import '../event_args.dart'; +import '../series/chart_series.dart'; /// typedef belongs SfCartesianChart @@ -30,7 +40,7 @@ typedef ChartLegendRenderCallback = void Function( /// Returns the Trendline args typedef ChartTrendlineRenderCallback = void Function( - TrendlineRenderArgs trendlineRenderArgs); + TrendlineRenderParams trendlineRenderParams); ///Returns the TrackballArgs. typedef ChartTrackballCallback = void Function(TrackballArgs trackballArgs); @@ -169,7 +179,7 @@ typedef CircularShaderCallback = Shader Function( typedef CircularSeriesRendererCreatedCallback = void Function( CircularSeriesController controller); -/// typedef belongs to SfFunnelChart +// typedef belongs to SfFunnelChart /// Returns the LegendRenderArgs. typedef FunnelLegendRenderCallback = void Function( @@ -196,7 +206,7 @@ typedef FunnelTouchInteractionCallback = void Function( typedef FunnelSeriesRendererCreatedCallback = void Function( FunnelSeriesController controller); -/// typedef belongs to SfPyramidChart +// typedef belongs to SfPyramidChart /// Returns the LegendRenderArgs. typedef PyramidLegendRenderCallback = void Function( @@ -222,3 +232,10 @@ typedef PyramidTouchInteractionCallback = void Function( /// Called when the pyramid series is created typedef PyramidSeriesRendererCreatedCallback = void Function( PyramidSeriesController controller); + +/// Callback definition for error bar event. +typedef ChartErrorBarRenderCallback = void Function( + ErrorBarRenderDetails errorBarRenderDetails); + +//// Callback definition for cartesian shader events. +typedef CartesianShaderCallback = Shader Function(ShaderDetails shaderDetails); diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart index 164f4427f..9145a9675 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_base.dart @@ -1,4 +1,30 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../common/common.dart'; +import '../../common/legend/legend.dart'; +import '../../common/legend/renderer.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../../common/utils/typedef.dart'; +import '../base/funnel_plot_area.dart'; +import '../base/funnel_state_properties.dart'; +import '../base/series_base.dart'; +import '../renderer/funnel_series.dart'; +import '../renderer/renderer_extension.dart'; ///Renders the funnel chart /// @@ -8,6 +34,8 @@ part of charts; /// The number of users at each stage of the process are indicated from the funnel's width as it narrows /// /// To render a funnel chart, create an instance of FunnelSeries, and add it to the series property of [SfFunnelChart]. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} //ignore:must_be_immutable class SfFunnelChart extends StatefulWidget { /// Creating an argument constructor of SfFunnelChart class. @@ -196,7 +224,7 @@ class SfFunnelChart extends StatefulWidget { /// Occurs when the data label is rendered /// - /// Here we can get get the datalabel render arguments and customise the datalabel parameters. + /// Here we can get get the data label render arguments and customize the data label parameters. final FunnelDataLabelRenderCallback? onDataLabelRender; /// Occurs when the legend is tapped ,using this event the legend tap arguments can be customized. @@ -390,39 +418,7 @@ class SfFunnelChart extends StatefulWidget { /// class SfFunnelChartState extends State with TickerProviderStateMixin { - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfFunnelChart get _chart => widget; - - /// Specifies the funnel data label renderer - _FunnelDataLabelRenderer? _renderDataLabel; - - /// Specifies the tooltip point index - int? _tooltipPointIndex; - - /// Specifies the series type - late String _seriesType; - - /// Specifies the data points - late List> _dataPoints; - - /// Specifies the render points - late List> _renderPoints; - - /// Specifies the funnel series - late _FunnelSeries _chartSeries; - - /// Specifies the funnel plot area - late _FunnelPlotArea _funnelplotArea; - - /// Specifies the chart rendering details - late _RenderingDetails _renderingDetails; - - // ignore: unused_element - bool get _animationCompleted { - return _renderingDetails.animationController.status != - AnimationStatus.forward; - } + late FunnelStateProperties _stateProperties; /// Called when this object is inserted into the tree. /// @@ -437,8 +433,8 @@ class SfFunnelChartState extends State @override void initState() { - _renderingDetails = _RenderingDetails(); - _renderingDetails.didSizeChange = false; + _stateProperties = FunnelStateProperties( + renderingDetails: RenderingDetails(), chartState: this); _initializeDefaultValues(); // Create the series renderer while initial rendering // _createAndUpdateSeriesRenderer(); @@ -456,7 +452,7 @@ class SfFunnelChartState extends State @override void didChangeDependencies() { - _renderingDetails.chartTheme = SfChartTheme.of(context); + _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -480,11 +476,14 @@ class SfFunnelChartState extends State _createAndUpdateSeriesRenderer(oldWidget); super.didUpdateWidget(oldWidget); - if (_renderingDetails.tooltipBehaviorRenderer._chartTooltipState != null) { - _renderingDetails.tooltipBehaviorRenderer._show = false; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + if (tooltipRenderingDetails.chartTooltipState != null) { + tooltipRenderingDetails.show = false; } - _renderingDetails.isLegendToggled = false; - _renderingDetails.widgetNeedUpdate = true; + _stateProperties.renderingDetails.isLegendToggled = false; + _stateProperties.renderingDetails.widgetNeedUpdate = true; } /// Describes the part of the user interface represented by this widget. @@ -500,13 +499,15 @@ class SfFunnelChartState extends State @override Widget build(BuildContext context) { - _renderingDetails.oldDeviceOrientation = - _renderingDetails.oldDeviceOrientation == null + _stateProperties.renderingDetails.oldDeviceOrientation = + _stateProperties.renderingDetails.oldDeviceOrientation == null ? MediaQuery.of(context).orientation - : _renderingDetails.deviceOrientation; - _renderingDetails.deviceOrientation = MediaQuery.of(context).orientation; + : _stateProperties.renderingDetails.deviceOrientation; + _stateProperties.renderingDetails.deviceOrientation = + MediaQuery.of(context).orientation; + _stateProperties.isTooltipOrientationChanged = false; return RepaintBoundary( - child: _ChartContainer( + child: ChartContainer( child: GestureDetector( child: Container( decoration: BoxDecoration( @@ -521,7 +522,7 @@ class SfFunnelChartState extends State width: widget.borderWidth)), child: Column( children: [ - _renderChartTitle(this), + renderChartTitle(_stateProperties), _renderChartElements() ], ))))); @@ -531,9 +532,9 @@ class SfFunnelChartState extends State /// /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this - /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// point. This stage of the life cycle is terminal: there is no way to remount a [State] object that has been disposed. /// - /// Subclasses should override this method to release any resources retained by this object. + /// Sub classes should override this method to release any resources retained by this object. /// /// * In [dispose], unsubscribe from the object. /// @@ -541,8 +542,9 @@ class SfFunnelChartState extends State @override void dispose() { - _disposeAnimationController( - _renderingDetails.animationController, _repaintChartElements); + disposeAnimationController( + _stateProperties.renderingDetails.animationController, + _repaintChartElements); super.dispose(); } @@ -613,27 +615,34 @@ class SfFunnelChartState extends State return image; } - /// To initialise chart default values + /// To initialize chart default values void _initializeDefaultValues() { - _chartSeries = _FunnelSeries(this); - _renderingDetails.chartLegend = _ChartLegend(this); - _funnelplotArea = _FunnelPlotArea(chartState: this); - _renderingDetails.initialRender = true; - _renderingDetails.annotationController = AnimationController(vsync: this); - _renderingDetails.seriesRepaintNotifier = ValueNotifier(0); - _renderingDetails.legendToggleStates = <_LegendRenderContext>[]; - _renderingDetails.legendToggleTemplateStates = <_MeasureWidgetContext>[]; - _renderingDetails.explodedPoints = []; - _renderingDetails.animateCompleted = false; - _renderingDetails.isLegendToggled = false; - _renderingDetails.widgetNeedUpdate = false; - _renderingDetails.dataLabelTemplateRegions = []; - _renderingDetails.selectionData = []; - _renderingDetails.legendWidgetContext = <_MeasureWidgetContext>[]; - _renderingDetails.animationController = AnimationController(vsync: this) - ..addListener(_repaintChartElements); - _renderingDetails.tooltipBehaviorRenderer = TooltipBehaviorRenderer(this); - _renderingDetails.legendRenderer = LegendRenderer(widget.legend); + _stateProperties.chartSeries = FunnelChartBase(_stateProperties); + _stateProperties.renderingDetails.chartLegend = + ChartLegend(_stateProperties); + _stateProperties.renderingDetails.initialRender = true; + _stateProperties.renderingDetails.annotationController = + AnimationController(vsync: this); + _stateProperties.renderingDetails.seriesRepaintNotifier = + ValueNotifier(0); + _stateProperties.renderingDetails.legendToggleStates = + []; + _stateProperties.renderingDetails.legendToggleTemplateStates = + []; + _stateProperties.renderingDetails.explodedPoints = []; + _stateProperties.renderingDetails.animateCompleted = false; + _stateProperties.renderingDetails.isLegendToggled = false; + _stateProperties.renderingDetails.widgetNeedUpdate = false; + _stateProperties.renderingDetails.dataLabelTemplateRegions = []; + _stateProperties.renderingDetails.selectionData = []; + _stateProperties.renderingDetails.legendWidgetContext = + []; + _stateProperties.renderingDetails.animationController = + AnimationController(vsync: this)..addListener(_repaintChartElements); + _stateProperties.renderingDetails.tooltipBehaviorRenderer = + TooltipBehaviorRenderer(_stateProperties); + _stateProperties.renderingDetails.legendRenderer = + LegendRenderer(widget.legend); } // In this method, create and update the series renderer for each series // @@ -643,38 +652,41 @@ class SfFunnelChartState extends State final FunnelSeriesRenderer? oldSeriesRenderer = // ignore: unnecessary_null_comparison oldWidget != null && oldWidget.series != null - ? _chartSeries.visibleSeriesRenderers[0] + ? _stateProperties.chartSeries.visibleSeriesRenderers[0] : null; FunnelSeries series; series = widget.series; // Create and update the series list here - FunnelSeriesRenderer seriesRenderers; + FunnelSeriesRendererExtension seriesRenderers; if (oldSeriesRenderer != null && - _isSameSeries(oldWidget!.series, series)) { - seriesRenderers = oldSeriesRenderer; + isSameSeries(oldWidget!.series, series)) { + seriesRenderers = oldSeriesRenderer as FunnelSeriesRendererExtension; } else { - seriesRenderers = series.createRenderer(series); - if (seriesRenderers._controller == null && + final FunnelSeriesRenderer renderer = series.createRenderer(series); + seriesRenderers = renderer is FunnelSeriesRendererExtension + ? renderer + : FunnelSeriesRendererExtension(); + if (seriesRenderers.controller == null && series.onRendererCreated != null) { - seriesRenderers._controller = FunnelSeriesController(seriesRenderers); - series.onRendererCreated!(seriesRenderers._controller!); + seriesRenderers.controller = FunnelSeriesController(seriesRenderers); + series.onRendererCreated!(seriesRenderers.controller!); } } - seriesRenderers._series = series; - seriesRenderers._isSelectionEnable = series.selectionBehavior.enable; - seriesRenderers._chartState = this; - _chartSeries.visibleSeriesRenderers + seriesRenderers.series = series; + seriesRenderers.isSelectionEnable = series.selectionBehavior.enable; + seriesRenderers.stateProperties = _stateProperties; + _stateProperties.chartSeries.visibleSeriesRenderers ..clear() ..add(seriesRenderers); } } void _repaintChartElements() { - _renderingDetails.seriesRepaintNotifier.value++; + _stateProperties.renderingDetails.seriesRepaintNotifier.value++; } /// To render chart elements @@ -682,25 +694,34 @@ class SfFunnelChartState extends State return Expanded(child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { Widget element; - _renderingDetails.prevSize = - _renderingDetails.prevSize ?? constraints.biggest; - _renderingDetails.didSizeChange = - _renderingDetails.prevSize != constraints.biggest; - _renderingDetails.prevSize = constraints.biggest; if (widget.series.dataSource != null) { _initialize(constraints); - _chartSeries._findVisibleSeries(); - _chartSeries._processDataPoints(_chartSeries.visibleSeriesRenderers[0]); - final List legendTemplates = _bindLegendTemplateWidgets(this); + _stateProperties.renderingDetails.prevSize = + _stateProperties.renderingDetails.prevSize ?? constraints.biggest; + _stateProperties.renderingDetails.didSizeChange = + _stateProperties.renderingDetails.prevSize != constraints.biggest; + _stateProperties.renderingDetails.prevSize = constraints.biggest; + final PointInfo tooltipPoint = + _getChartPoints(_stateProperties); + SchedulerBinding.instance!.addPostFrameCallback((_) { + _validateStateMaintenance(_stateProperties, tooltipPoint); + }); + _stateProperties.chartSeries.findVisibleSeries(); + _stateProperties.chartSeries.processDataPoints( + _stateProperties.chartSeries.visibleSeriesRenderers[0]); + final List legendTemplates = + bindLegendTemplateWidgets(_stateProperties); if (legendTemplates.isNotEmpty && - _renderingDetails.legendWidgetContext.isEmpty) { + _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); } else { - _renderingDetails.chartLegend - ._calculateLegendBounds(_renderingDetails.chartLegend.chartSize); - element = _getElements( - this, _FunnelPlotArea(chartState: this), constraints)!; + _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( + _stateProperties.renderingDetails.chartLegend.chartSize); + _stateProperties.funnelplotArea = + FunnelPlotArea(stateProperties: _stateProperties); + element = getElements( + _stateProperties, _stateProperties.funnelplotArea, constraints)!; } } else { element = Container(); @@ -711,11 +732,14 @@ class SfFunnelChartState extends State /// To refresh chart elements void _refresh() { - if (_renderingDetails.legendWidgetContext.isNotEmpty) { - _MeasureWidgetContext templateContext; + if (_stateProperties.renderingDetails.legendWidgetContext.isNotEmpty) { + MeasureWidgetContext templateContext; RenderBox renderBox; - for (int i = 0; i < _renderingDetails.legendWidgetContext.length; i++) { - templateContext = _renderingDetails.legendWidgetContext[i]; + for (int i = 0; + i < _stateProperties.renderingDetails.legendWidgetContext.length; + i++) { + templateContext = + _stateProperties.renderingDetails.legendWidgetContext[i]; renderBox = templateContext.context!.findRenderObject() as RenderBox; templateContext.size = renderBox.size; } @@ -725,631 +749,92 @@ class SfFunnelChartState extends State } } - /// To redraw chart elements - // ignore:unused_element - void _redraw() { - _renderingDetails.initialRender = false; - if (_renderingDetails.tooltipBehaviorRenderer._chartTooltipState != null) { - _renderingDetails.tooltipBehaviorRenderer._show = false; - } - setState(() { - /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. - }); - } - /// To initialize chart container void _initialize(BoxConstraints constraints) { - _renderingDetails.chartWidgets = []; + _stateProperties.renderingDetails.chartWidgets = []; final num width = constraints.maxWidth; final num height = constraints.maxHeight; final EdgeInsets margin = widget.margin; - _renderingDetails.legendRenderer._legendPosition = + final bool isMobilePlatform = + defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS; + _stateProperties.renderingDetails.legendRenderer.legendPosition = widget.legend.position == LegendPosition.auto - ? (height > width ? LegendPosition.bottom : LegendPosition.right) + ? (height > width + ? isMobilePlatform + ? LegendPosition.top + : LegendPosition.bottom + : LegendPosition.right) : widget.legend.position; - _renderingDetails.chartLegend.chartSize = Size( + _stateProperties.renderingDetails.chartLegend.chartSize = Size( width - margin.left - margin.right, height - margin.top - margin.bottom); } -} -// ignore: must_be_immutable -class _FunnelPlotArea extends StatelessWidget { - // ignore: prefer_const_constructors_in_immutables - _FunnelPlotArea({required this.chartState}); - final SfFunnelChartState chartState; - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfFunnelChart get chart => chartState._chart; - late FunnelSeriesRenderer seriesRenderer; - late RenderBox renderBox; - _Region? pointRegion; - late TapDownDetails tapDownDetails; - Offset? doubleTapPosition; - final bool _enableMouseHover = kIsWeb; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Container( - child: MouseRegion( - // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. - // Issue: https://github.com/flutter/flutter/issues/68690 - onHover: (PointerEvent event) => - _enableMouseHover ? _onHover(event) : null, - onExit: (PointerEvent event) { - chartState._renderingDetails.tooltipBehaviorRenderer - ._isHovering = false; - }, - child: Stack(textDirection: TextDirection.ltr, children: [ - _initializeChart(constraints, context), - Listener( - onPointerUp: (PointerUpEvent event) => _onTapUp(event), - onPointerDown: (PointerDownEvent event) => - _onTapDown(event), - onPointerMove: (PointerMoveEvent event) => - _performPointerMove(event), - child: GestureDetector( - onLongPress: _onLongPress, - onDoubleTap: _onDoubleTap, - onTapUp: (TapUpDetails details) { - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(details.globalPosition); - if (chart.onPointTapped != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex(chart, seriesRenderer, - chartState._renderingDetails.tapPosition!); - } - if (chart.series.onPointTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, - seriesRenderer, - chartState._renderingDetails.tapPosition!, - null, - ActivationMode.singleTap); - } - }, - child: Container( - decoration: - const BoxDecoration(color: Colors.transparent), - ))) - ]))); - }); - } - - /// To intialize chart elements - Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - _calculateContainerSize(constraints); - return GestureDetector( - child: Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: _renderWidgets(constraints, context))); - } - - /// To calculate size of chart - void _calculateContainerSize(BoxConstraints constraints) { - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - chartState._renderingDetails.chartContainerRect = - Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); - final EdgeInsets margin = chart.margin; - chartState._renderingDetails.chartAreaRect = Rect.fromLTWH( - margin.left, - margin.top, - width - margin.right - margin.left, - height - margin.top - margin.bottom); - } - - Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _bindSeriesWidgets(); - _calculatePathRegion(); - _findTemplates(chartState); - _renderTemplates(chartState); - _bindTooltipWidgets(constraints); - renderBox = context.findRenderObject() as RenderBox; - chartState._funnelplotArea = this; - return Container( - child: Stack( - textDirection: TextDirection.ltr, - children: chartState._renderingDetails.chartWidgets!)); - } - - /// To calculate region path for rendering funnel chart - void _calculatePathRegion() { - if (chartState._chartSeries.visibleSeriesRenderers.isNotEmpty) { - final FunnelSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[0]; - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - if (seriesRenderer._renderPoints[i].isVisible) { - chartState._chartSeries._calculateFunnelPathRegion(i, seriesRenderer); - } + /// This will return tooltip chart point + PointInfo _getChartPoints(FunnelStateProperties _stateProperties) { + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + + PointInfo tooltipChartPoint = PointInfo(null, null); + + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable == true && + _stateProperties.isTooltipHidden == false) { + tooltipChartPoint = pyramidFunnelPixelToPoint( + tooltipRenderingDetails.showLocation!, + _stateProperties.chartSeries.visibleSeriesRenderers[0]); } } + return tooltipChartPoint; } - /// To bind series widgets in chart - void _bindSeriesWidgets() { - CustomPainter seriesPainter; - Animation? seriesAnimation; - FunnelSeries series; - SelectionBehaviorRenderer selectionBehaviorRenderer; - dynamic selectionBehavior; - for (int i = 0; - i < chartState._chartSeries.visibleSeriesRenderers.length; - i++) { - seriesRenderer = chartState._chartSeries.visibleSeriesRenderers[i]; - series = seriesRenderer._series; - series.selectionBehavior._chartState = chartState; - chartState._chartSeries._initializeSeriesProperties(seriesRenderer); - selectionBehavior = - seriesRenderer._selectionBehavior = series.selectionBehavior; - selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer = - SelectionBehaviorRenderer(selectionBehavior, chart, chartState); - selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer?.chart = chart; - selectionBehaviorRenderer._selectionRenderer?.seriesRenderer = - seriesRenderer; - if (series.initialSelectedDataIndexes.isNotEmpty) { - for (int index = 0; - index < series.initialSelectedDataIndexes.length; - index++) { - chartState._renderingDetails.selectionData - .add(series.initialSelectedDataIndexes[index]); - } - } - - if (series.animationDuration > 0 && - !chartState._renderingDetails.didSizeChange && - (chartState._renderingDetails.oldDeviceOrientation == - chartState._renderingDetails.deviceOrientation) && - ((!chartState._renderingDetails.widgetNeedUpdate && - chartState._renderingDetails.initialRender!) || - chartState._renderingDetails.isLegendToggled)) { - chartState._renderingDetails.animationController.duration = - Duration(milliseconds: series.animationDuration.toInt()); - seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: chartState._renderingDetails.animationController, - curve: const Interval(0.1, 0.8, curve: Curves.linear), - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - chartState._renderingDetails.animateCompleted = true; - if (chartState._renderDataLabel != null) { - chartState._renderDataLabel!.state!.render(); - } - if (chartState._renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - chartState._renderingDetails.chartTemplate!.state != - null) { - chartState._renderingDetails.chartTemplate!.state - .templateRender(); - } - } - })); - chartState._renderingDetails.chartElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: chartState._renderingDetails.animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - chartState._renderingDetails.animationController.forward(from: 0.0); - } else { - chartState._renderingDetails.animateCompleted = true; - if (chartState._renderDataLabel?.state != null) { - chartState._renderDataLabel?.state!.render(); - } - } - seriesRenderer._repaintNotifier = - chartState._renderingDetails.seriesRepaintNotifier; - seriesPainter = _FunnelChartPainter( - chartState: chartState, - seriesIndex: i, - isRepaint: seriesRenderer._needsRepaint, - animationController: chartState._renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: chartState._renderingDetails.seriesRepaintNotifier); - chartState._renderingDetails.chartWidgets! - .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); - chartState._renderDataLabel = _FunnelDataLabelRenderer( - key: GlobalKey(), - chartState: chartState, - //ignore: avoid_bool_literals_in_conditional_expressions - show: !chartState._renderingDetails.widgetNeedUpdate - ? chartState._renderingDetails.animationController.status == - AnimationStatus.completed || - chartState._renderingDetails.animationController.duration == - null - : true); - chartState._renderingDetails.chartWidgets! - .add(chartState._renderDataLabel!); - } - } - - /// To bind tooltip widgets to chart - void _bindTooltipWidgets(BoxConstraints constraints) { - chart.tooltipBehavior._chartState = chartState; - final SfChartThemeData _chartTheme = - chartState._renderingDetails.chartTheme; - final TooltipBehavior tooltip = chart.tooltipBehavior; - if (chart.tooltipBehavior.enable) { - chartState._renderingDetails.tooltipBehaviorRenderer._prevTooltipValue = - chartState._renderingDetails.tooltipBehaviorRenderer - ._currentTooltipValue = null; - chartState._renderingDetails.tooltipBehaviorRenderer._chartTooltip = - SfTooltip( - color: tooltip.color ?? _chartTheme.tooltipColor, - key: GlobalKey(), - textStyle: tooltip.textStyle, - animationDuration: tooltip.animationDuration, - animationCurve: - const Interval(0.1, 0.8, curve: Curves.easeOutBack), - enable: tooltip.enable, - opacity: tooltip.opacity, - borderColor: tooltip.borderColor, - borderWidth: tooltip.borderWidth, - duration: tooltip.duration.toInt(), - shouldAlwaysShow: tooltip.shouldAlwaysShow, - elevation: tooltip.elevation, - canShowMarker: tooltip.canShowMarker, - textAlignment: tooltip.textAlignment, - decimalPlaces: tooltip.decimalPlaces, - labelColor: - tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, - header: tooltip.header, - format: tooltip.format, - shadowColor: tooltip.shadowColor, - onTooltipRender: chart.onTooltipRender != null - ? chartState._renderingDetails.tooltipBehaviorRenderer - ._tooltipRenderingEvent - : null); - chartState._renderingDetails.chartWidgets!.add( - chartState._renderingDetails.tooltipBehaviorRenderer._chartTooltip!); - } - } - - /// To perform pointer down event - void _onTapDown(PointerDownEvent event) { - // renderBox = context.findRenderObject(); - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = false; - chartState._renderingDetails.currentActive = null; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool isPoint = false; - const int seriesIndex = 0; - int? pointIndex; - final FunnelSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - ChartTouchInteractionArgs touchArgs; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - if (chart.onDataLabelRender != null) { - seriesRenderer._dataPoints[j].labelRenderEvent = false; - } - if (seriesRenderer._renderPoints[j].isVisible && !isPoint) { - isPoint = _isPointInPolygon(seriesRenderer._renderPoints[j].pathRegion, - chartState._renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - if (chart.onDataLabelRender == null) { - break; + /// Here for orientation change/browser resize, the logic in this method will get executed + void _validateStateMaintenance(FunnelStateProperties _stateProperties, + PointInfo tooltipChartPoint) { + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable == true && + _stateProperties.isTooltipHidden == false) { + _stateProperties.isTooltipOrientationChanged = true; + late PointInfo point; + late int index; + for (int i = 0; + i < + _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints.length; + i++) { + if (_stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i].x == + tooltipChartPoint.x && + _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i].y == + tooltipChartPoint.y) { + index = i; + point = _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i]; } } - } - } - doubleTapPosition = chartState._renderingDetails.tapPosition!; - // ignore: unnecessary_null_comparison - if (chartState._renderingDetails.tapPosition != null && isPoint) { - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex!, - seriesRenderer._series, - seriesRenderer._renderPoints[pointIndex], - ); - } else { - //hides the tooltip if the point of interaction is outside funnel region of the chart - if (chart.tooltipBehavior.builder != null) { - chartState._renderingDetails.tooltipBehaviorRenderer._show = false; - chartState._renderingDetails.tooltipBehaviorRenderer - ._hideTooltipTemplate(); - } - } - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown!(touchArgs); - } - } - - /// To perform pointer move event - void _performPointerMove(PointerMoveEvent event) { - ChartTouchInteractionArgs touchArgs; - final Offset position = renderBox.globalToLocal(event.position); - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = position; - chart.onChartTouchInteractionMove!(touchArgs); - } - } - - /// To perform double tap touch interactions - void _onDoubleTap() { - const int seriesIndex = 0; - if (doubleTapPosition != null && - chartState._renderingDetails.currentActive != null) { - if (chart.series.onPointDoubleTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, - seriesRenderer, - chartState._renderingDetails.tapPosition!, - null, - ActivationMode.doubleTap); - chartState._renderingDetails.tapPosition = null; - } - final int? pointIndex = - chartState._renderingDetails.currentActive!.pointIndex; - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._renderPoints[pointIndex!]); - if (chartState._renderingDetails.currentActive != null) { - if (chartState._renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.doubleTap) { - chartState._chartSeries._pointExplode(pointIndex); - final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; - final _FunnelDataLabelRendererState _funnelDataLabelRendererState = - key.currentState as _FunnelDataLabelRendererState; - _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - chartState._chartSeries - ._seriesPointSelection(pointIndex, ActivationMode.doubleTap); - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { - if (chart.tooltipBehavior.builder != null) { - _showFunnelTooltipTemplate(); - } else { - chartState._renderingDetails.tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition!.dx.toDouble(), - doubleTapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform long press touch interactions - void _onLongPress() { - const int seriesIndex = 0; - if (chartState._renderingDetails.tapPosition != null && - chartState._renderingDetails.currentActive != null) { - if (chart.series.onPointLongPress != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, - seriesRenderer, - chartState._renderingDetails.tapPosition!, - null, - ActivationMode.longPress); - chartState._renderingDetails.tapPosition = null; - } - final int pointIndex = - chartState._renderingDetails.currentActive!.pointIndex!; - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._renderPoints[pointIndex], - pointRegion); - chartState._chartSeries - ._seriesPointSelection(pointIndex, ActivationMode.longPress); - if (chartState._renderingDetails.currentActive != null) { - if (chartState._renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.longPress) { - chartState._chartSeries._pointExplode(pointIndex); - final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; - final _FunnelDataLabelRendererState _funnelDataLabelRendererState = - key.currentState as _FunnelDataLabelRendererState; - _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.longPress) { - if (chart.tooltipBehavior.builder != null) { - _showFunnelTooltipTemplate(); + final Offset tooltipPosition = pyramidFunnelPointToPixel( + point, _stateProperties.chartSeries.visibleSeriesRenderers[0]); + if (_stateProperties.chart.tooltipBehavior.builder != null) { + _stateProperties.funnelplotArea.showFunnelTooltipTemplate(index); } else { - chartState._renderingDetails.tooltipBehaviorRenderer.onLongPress( - chartState._renderingDetails.tapPosition!.dx.toDouble(), - chartState._renderingDetails.tapPosition!.dy.toDouble()); + tooltipRenderingDetails.internalShowByPixel( + tooltipPosition.dx, tooltipPosition.dy); } } } } - - /// To perform pointer up event - void _onTapUp(PointerUpEvent event) { - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = false; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - ChartTouchInteractionArgs touchArgs; - // ignore: unnecessary_null_comparison - if (chart.onDataLabelTapped != null && seriesRenderer != null) { - _triggerFunnelDataLabelEvent(chart, seriesRenderer, chartState, - chartState._renderingDetails.tapPosition!); - } - if (chartState._renderingDetails.tapPosition != null) { - if (chartState._renderingDetails.currentActive != null && - chartState._renderingDetails.currentActive!.series != null && - chartState._renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.singleTap) { - chartState._chartSeries._pointExplode( - chartState._renderingDetails.currentActive!.pointIndex!); - final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; - final _FunnelDataLabelRendererState _funnelDataLabelRendererState = - key.currentState as _FunnelDataLabelRendererState; - _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - if (chartState._renderingDetails.tapPosition != null && - chartState._renderingDetails.currentActive != null) { - chartState._chartSeries._seriesPointSelection( - chartState._renderingDetails.currentActive!.pointIndex!, - ActivationMode.singleTap); - } - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap && - chartState._renderingDetails.currentActive != null && - chartState._renderingDetails.currentActive!.series != null) { - if (chart.tooltipBehavior.builder != null) { - _showFunnelTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - chartState._renderingDetails.tooltipBehaviorRenderer - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); - } - } - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp!(touchArgs); - } - } - if (chart.series.onPointTap == null && - chart.series.onPointDoubleTap == null && - chart.series.onPointLongPress == null) { - chartState._renderingDetails.tapPosition = null; - } - } - - /// To perform event on mouse hover - void _onHover(PointerEvent event) { - chartState._renderingDetails.currentActive = null; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool? isPoint; - const int seriesIndex = 0; - int? pointIndex; - final FunnelSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - if (seriesRenderer._renderPoints[j].isVisible) { - isPoint = _isPointInPolygon(seriesRenderer._renderPoints[j].pathRegion, - chartState._renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - break; - } - } - } - if (chartState._renderingDetails.tapPosition != null && isPoint!) { - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex!, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._renderPoints[pointIndex], - ); - } else { - //hides the tooltip if the point of interaction is outside funnel region of the chart - chartState._renderingDetails.tooltipBehaviorRenderer._hide(); - } - if (chartState._renderingDetails.tapPosition != null) { - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chartState._renderingDetails.currentActive != null && - chartState._renderingDetails.currentActive!.series != null) { - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = true; - if (chart.tooltipBehavior.builder != null) { - _showFunnelTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - chartState._renderingDetails.tooltipBehaviorRenderer - .onEnter(position.dx.toDouble(), position.dy.toDouble()); - } - } else { - chartState._renderingDetails.tooltipBehaviorRenderer._prevTooltipValue = - null; - chartState._renderingDetails.tooltipBehaviorRenderer - ._currentTooltipValue = null; - } - } - chartState._renderingDetails.tapPosition = null; - } - - /// This method gets executed for showing tooltip when builder is provided in behavior - void _showFunnelTooltipTemplate([int? pointIndex]) { - if (!chartState._renderingDetails.tooltipBehaviorRenderer._isHovering) { - //assingning null for the previous and current tooltip values in case of touch interaction - chartState._renderingDetails.tooltipBehaviorRenderer._prevTooltipValue = - null; - chartState._renderingDetails.tooltipBehaviorRenderer - ._currentTooltipValue = null; - } - final FunnelSeries chartSeries = - chartState._renderingDetails.currentActive?.series ?? chart.series; - final PointInfo point = pointIndex == null - ? chartState._renderingDetails.currentActive?.point - : chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; - final Offset location = chart.tooltipBehavior.tooltipPosition == - TooltipPosition.pointer && - !chartState._chartSeries.visibleSeriesRenderers[0]._series.explode - ? chartState._renderingDetails.tapPosition! - : point.symbolLocation; - bool isPoint = false; - for (int j = 0; j < seriesRenderer._renderPoints.length; j++) { - if (seriesRenderer._renderPoints[j].isVisible) { - isPoint = _isPointInPolygon( - seriesRenderer._renderPoints[j].pathRegion, location); - if (isPoint) { - pointIndex = j; - break; - } - } - } - // ignore: unnecessary_null_comparison - if (location != null && isPoint && (chartSeries.enableTooltip)) { - chartState._renderingDetails.tooltipBehaviorRenderer._showLocation = - location; - chartState._renderingDetails.tooltipBehaviorRenderer._chartTooltipState! - .boundaryRect = - chartState._renderingDetails.tooltipBehaviorRenderer._tooltipBounds = - chartState._renderingDetails.chartContainerRect; - // tooltipTemplate.rect = Rect.fromLTWH(location.dx, location.dy, 0, 0); - chartState._renderingDetails.tooltipBehaviorRenderer._tooltipTemplate = - chart.tooltipBehavior.builder!( - chartSeries.dataSource![pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!], - point, - chartSeries, - 0, - pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!); - if (chartState._renderingDetails.tooltipBehaviorRenderer._isHovering) { - //assingning values for the previous and current tooltip values on mouse hover - chartState._renderingDetails.tooltipBehaviorRenderer._prevTooltipValue = - chartState - ._renderingDetails.tooltipBehaviorRenderer._currentTooltipValue; - chartState._renderingDetails.tooltipBehaviorRenderer - ._currentTooltipValue = - TooltipValue( - 0, - pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!); - } else { - chartState._renderingDetails.tooltipBehaviorRenderer - ._hideTooltipTemplate(); - } - chartState._renderingDetails.tooltipBehaviorRenderer._show = true; - chartState._renderingDetails.tooltipBehaviorRenderer._performTooltip(); - } - } } diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_plot_area.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_plot_area.dart new file mode 100644 index 000000000..d354b8815 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_plot_area.dart @@ -0,0 +1,665 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/tooltip_internal.dart'; + +import '../../chart/user_interaction/selection_renderer.dart'; +import '../../chart/utils/enum.dart'; +import '../../circular_chart/renderer/common.dart'; +import '../../common/event_args.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/helper.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../../pyramid_chart/utils/helper.dart'; +import '../base/funnel_base.dart'; +import '../base/funnel_state_properties.dart'; +import '../renderer/data_label_renderer.dart'; +import '../renderer/funnel_chart_painter.dart'; +import '../renderer/funnel_series.dart'; +import '../renderer/renderer_extension.dart'; + +/// Represents the funnel plot area +// ignore: must_be_immutable +class FunnelPlotArea extends StatelessWidget { + /// Creates an instance of funnel plot area + // ignore: prefer_const_constructors_in_immutables + FunnelPlotArea({required this.stateProperties}); + + /// Specifies the value of funnel state properties + final FunnelStateProperties stateProperties; + + /// Gets the chart widget from the stateProperties + SfFunnelChart get chart => stateProperties.chart; + + /// Specifies the value of funnel series renderer + late FunnelSeriesRendererExtension seriesRenderer; + + /// Holds the render box value + late RenderBox renderBox; + + /// Holds the value of point region + Region? pointRegion; + + /// Holds the value of tap down details + late TapDownDetails tapDownDetails; + + /// Specifies the double tap position + Offset? doubleTapPosition; + final bool _enableMouseHover = kIsWeb; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + child: MouseRegion( + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerEvent event) => + _enableMouseHover ? _onHover(event) : null, + onExit: (PointerEvent event) { + TooltipHelper.getRenderingDetails(stateProperties + .renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; + }, + child: Stack(textDirection: TextDirection.ltr, children: [ + _initializeChart(constraints, context), + Listener( + onPointerUp: (PointerUpEvent event) => _onTapUp(event), + onPointerDown: (PointerDownEvent event) => + _onTapDown(event), + onPointerMove: (PointerMoveEvent event) => + _performPointerMove(event), + child: GestureDetector( + onLongPress: _onLongPress, + onDoubleTap: _onDoubleTap, + onTapUp: (TapUpDetails details) { + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(details.globalPosition); + if (chart.onPointTapped != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex(chart, seriesRenderer, + stateProperties.renderingDetails.tapPosition!); + } + if (chart.series.onPointTap != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex( + chart, + seriesRenderer, + stateProperties.renderingDetails.tapPosition!, + null, + ActivationMode.singleTap); + } + }, + child: Container( + decoration: + const BoxDecoration(color: Colors.transparent), + ))) + ]))); + }); + } + + /// To initialize chart elements + Widget _initializeChart(BoxConstraints constraints, BuildContext context) { + _calculateContainerSize(constraints); + return GestureDetector( + child: Container( + decoration: const BoxDecoration(color: Colors.transparent), + child: _renderWidgets(constraints, context))); + } + + /// To calculate size of chart + void _calculateContainerSize(BoxConstraints constraints) { + final num width = constraints.maxWidth; + final num height = constraints.maxHeight; + stateProperties.renderingDetails.chartContainerRect = + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); + final EdgeInsets margin = chart.margin; + stateProperties.renderingDetails.chartAreaRect = Rect.fromLTWH( + margin.left, + margin.top, + width - margin.right - margin.left, + height - margin.top - margin.bottom); + } + + Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { + _bindSeriesWidgets(); + _calculatePathRegion(); + findTemplates(stateProperties); + renderTemplates(stateProperties); + _bindTooltipWidgets(constraints); + renderBox = context.findRenderObject() as RenderBox; + stateProperties.funnelplotArea = this; + return Container( + child: Stack( + textDirection: TextDirection.ltr, + children: stateProperties.renderingDetails.chartWidgets!)); + } + + /// To calculate region path for rendering funnel chart + void _calculatePathRegion() { + if (stateProperties.chartSeries.visibleSeriesRenderers.isNotEmpty) { + final FunnelSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + for (int i = 0; i < seriesRenderer.renderPoints.length; i++) { + if (seriesRenderer.renderPoints[i].isVisible) { + stateProperties.chartSeries + .calculateFunnelPathRegion(i, seriesRenderer); + } + } + } + } + + /// To bind series widgets in chart + void _bindSeriesWidgets() { + CustomPainter seriesPainter; + Animation? seriesAnimation; + FunnelSeries series; + SelectionBehaviorRenderer selectionBehaviorRenderer; + dynamic selectionBehavior; + for (int i = 0; + i < stateProperties.chartSeries.visibleSeriesRenderers.length; + i++) { + seriesRenderer = stateProperties.chartSeries.visibleSeriesRenderers[i]; + series = seriesRenderer.series; + stateProperties.chartSeries.initializeSeriesProperties(seriesRenderer); + selectionBehavior = + seriesRenderer.selectionBehavior = series.selectionBehavior; + selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer = + SelectionBehaviorRenderer(selectionBehavior, chart, stateProperties); + SelectionHelper.setSelectionBehaviorRenderer( + series.selectionBehavior, selectionBehaviorRenderer); + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); + selectionDetails.selectionRenderer ??= SelectionRenderer(); + selectionDetails.selectionRenderer?.chart = chart; + selectionDetails.selectionRenderer!.stateProperties = stateProperties; + selectionDetails.selectionRenderer?.seriesRendererDetails = + seriesRenderer; + if (series.initialSelectedDataIndexes.isNotEmpty) { + for (int index = 0; + index < series.initialSelectedDataIndexes.length; + index++) { + stateProperties.renderingDetails.selectionData + .add(series.initialSelectedDataIndexes[index]); + } + } + + if (series.animationDuration > 0 && + !stateProperties.renderingDetails.didSizeChange && + (stateProperties.renderingDetails.oldDeviceOrientation == + stateProperties.renderingDetails.deviceOrientation) && + ((!stateProperties.renderingDetails.widgetNeedUpdate && + stateProperties.renderingDetails.initialRender!) || + stateProperties.renderingDetails.isLegendToggled)) { + final int totalAnimationDuration = + series.animationDuration.toInt() + series.animationDelay.toInt(); + stateProperties.renderingDetails.animationController.duration = + Duration(milliseconds: totalAnimationDuration); + const double maxSeriesInterval = 0.8; + double minSeriesInterval = 0.1; + minSeriesInterval = series.animationDelay.toInt() / + totalAnimationDuration * + (maxSeriesInterval - minSeriesInterval) + + minSeriesInterval; + seriesAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: stateProperties.renderingDetails.animationController, + curve: Interval(minSeriesInterval, maxSeriesInterval, + curve: Curves.linear), + )..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + stateProperties.renderingDetails.animateCompleted = true; + if (stateProperties.renderDataLabel != null) { + stateProperties.renderDataLabel!.state!.render(); + } + if (stateProperties.renderingDetails.chartTemplate != null && + // ignore: unnecessary_null_comparison + stateProperties.renderingDetails.chartTemplate!.state != + null) { + stateProperties.renderingDetails.chartTemplate!.state + .templateRender(); + } + } + })); + stateProperties.renderingDetails.chartElementAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: stateProperties.renderingDetails.animationController, + curve: const Interval(0.85, 1.0, curve: Curves.decelerate), + )); + stateProperties.renderingDetails.animationController.forward(from: 0.0); + } else { + stateProperties.renderingDetails.animateCompleted = true; + if (stateProperties.renderDataLabel?.state != null) { + stateProperties.renderDataLabel?.state!.render(); + } + } + seriesRenderer.repaintNotifier = + stateProperties.renderingDetails.seriesRepaintNotifier; + seriesPainter = FunnelChartPainter( + stateProperties: stateProperties, + seriesIndex: i, + isRepaint: seriesRenderer.needsRepaint, + animationController: + stateProperties.renderingDetails.animationController, + seriesAnimation: seriesAnimation, + notifier: stateProperties.renderingDetails.seriesRepaintNotifier); + stateProperties.renderingDetails.chartWidgets! + .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); + stateProperties.renderDataLabel = FunnelDataLabelRenderer( + key: GlobalKey(), + stateProperties: stateProperties, + //ignore: avoid_bool_literals_in_conditional_expressions + show: !stateProperties.renderingDetails.widgetNeedUpdate + ? stateProperties.renderingDetails.animationController.status == + AnimationStatus.completed || + stateProperties + .renderingDetails.animationController.duration == + null + : true); + stateProperties.renderingDetails.chartWidgets! + .add(stateProperties.renderDataLabel!); + } + } + + /// To bind tooltip widgets to chart + void _bindTooltipWidgets(BoxConstraints constraints) { + TooltipHelper.setStateProperties(chart.tooltipBehavior, stateProperties); + final SfChartThemeData _chartTheme = + stateProperties.renderingDetails.chartTheme; + final TooltipBehavior tooltip = chart.tooltipBehavior; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + if (chart.tooltipBehavior.enable) { + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue = null; + tooltipRenderingDetails.chartTooltip = SfTooltip( + color: tooltip.color ?? _chartTheme.tooltipColor, + key: GlobalKey(), + textStyle: tooltip.textStyle, + animationDuration: tooltip.animationDuration, + animationCurve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), + enable: tooltip.enable, + opacity: tooltip.opacity, + borderColor: tooltip.borderColor, + borderWidth: tooltip.borderWidth, + duration: tooltip.duration.toInt(), + shouldAlwaysShow: tooltip.shouldAlwaysShow, + elevation: tooltip.elevation, + canShowMarker: tooltip.canShowMarker, + textAlignment: tooltip.textAlignment, + decimalPlaces: tooltip.decimalPlaces, + labelColor: tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, + header: tooltip.header, + format: tooltip.format, + shadowColor: tooltip.shadowColor, + onTooltipRender: chart.onTooltipRender != null + ? tooltipRenderingDetails.tooltipRenderingEvent + : null); + stateProperties.renderingDetails.chartWidgets! + .add(tooltipRenderingDetails.chartTooltip!); + } + } + + /// To perform pointer down event + void _onTapDown(PointerDownEvent event) { + // renderBox = context.findRenderObject(); + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.isHovering = false; + stateProperties.renderingDetails.currentActive = null; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + bool isPoint = false; + const int seriesIndex = 0; + int? pointIndex; + final FunnelSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; + ChartTouchInteractionArgs touchArgs; + for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { + if (chart.onDataLabelRender != null) { + seriesRenderer.dataPoints[j].labelRenderEvent = false; + } + if (seriesRenderer.renderPoints[j].isVisible && !isPoint) { + isPoint = isPointInPolygon(seriesRenderer.renderPoints[j].pathRegion, + stateProperties.renderingDetails.tapPosition!); + if (isPoint) { + pointIndex = j; + if (chart.onDataLabelRender == null) { + break; + } + } + } + } + doubleTapPosition = stateProperties.renderingDetails.tapPosition!; + // ignore: unnecessary_null_comparison + if (stateProperties.renderingDetails.tapPosition != null && isPoint) { + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex!, + seriesRenderer.series, + seriesRenderer.renderPoints[pointIndex], + ); + } else { + //hides the tooltip if the point of interaction is outside funnel region of the chart + if (chart.tooltipBehavior.builder != null) { + tooltipRenderingDetails.show = false; + tooltipRenderingDetails.hideTooltipTemplate(); + } + } + if (chart.onChartTouchInteractionDown != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionDown!(touchArgs); + } + } + + /// To perform pointer move event + void _performPointerMove(PointerMoveEvent event) { + ChartTouchInteractionArgs touchArgs; + final Offset position = renderBox.globalToLocal(event.position); + if (chart.onChartTouchInteractionMove != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = position; + chart.onChartTouchInteractionMove!(touchArgs); + } + } + + /// To perform double tap touch interactions + void _onDoubleTap() { + const int seriesIndex = 0; + if (doubleTapPosition != null && + stateProperties.renderingDetails.currentActive != null) { + if (chart.series.onPointDoubleTap != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex( + chart, + seriesRenderer, + stateProperties.renderingDetails.tapPosition!, + null, + ActivationMode.doubleTap); + stateProperties.renderingDetails.tapPosition = null; + } + final int? pointIndex = + stateProperties.renderingDetails.currentActive!.pointIndex; + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex, + stateProperties + .chartSeries.visibleSeriesRenderers[seriesIndex].series, + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .renderPoints[pointIndex!]); + if (stateProperties.renderingDetails.currentActive != null) { + if (stateProperties + .renderingDetails.currentActive!.series.explodeGesture == + ActivationMode.doubleTap) { + stateProperties.chartSeries.pointExplode(pointIndex); + final GlobalKey key = + stateProperties.renderDataLabel!.key as GlobalKey; + final FunnelDataLabelRendererState _funnelDataLabelRendererState = + key.currentState as FunnelDataLabelRendererState; + _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; + } + } + stateProperties.chartSeries + .seriesPointSelection(pointIndex, ActivationMode.doubleTap); + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { + if (chart.tooltipBehavior.builder != null) { + showFunnelTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer.onDoubleTap( + doubleTapPosition!.dx.toDouble(), + doubleTapPosition!.dy.toDouble()); + } + } + } + } + + /// To perform long press touch interactions + void _onLongPress() { + const int seriesIndex = 0; + if (stateProperties.renderingDetails.tapPosition != null && + stateProperties.renderingDetails.currentActive != null) { + if (chart.series.onPointLongPress != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex( + chart, + seriesRenderer, + stateProperties.renderingDetails.tapPosition!, + null, + ActivationMode.longPress); + stateProperties.renderingDetails.tapPosition = null; + } + final int pointIndex = + stateProperties.renderingDetails.currentActive!.pointIndex!; + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex, + stateProperties + .chartSeries.visibleSeriesRenderers[seriesIndex].series, + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .renderPoints[pointIndex], + pointRegion); + stateProperties.chartSeries + .seriesPointSelection(pointIndex, ActivationMode.longPress); + if (stateProperties.renderingDetails.currentActive != null) { + if (stateProperties + .renderingDetails.currentActive!.series.explodeGesture == + ActivationMode.longPress) { + stateProperties.chartSeries.pointExplode(pointIndex); + final GlobalKey key = + stateProperties.renderDataLabel!.key as GlobalKey; + final FunnelDataLabelRendererState _funnelDataLabelRendererState = + key.currentState as FunnelDataLabelRendererState; + _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; + } + } + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.longPress) { + if (chart.tooltipBehavior.builder != null) { + showFunnelTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer.onLongPress( + stateProperties.renderingDetails.tapPosition!.dx.toDouble(), + stateProperties.renderingDetails.tapPosition!.dy.toDouble()); + } + } + } + } + + /// To perform pointer up event + void _onTapUp(PointerUpEvent event) { + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + ChartTouchInteractionArgs touchArgs; + // ignore: unnecessary_null_comparison + if (chart.onDataLabelTapped != null && seriesRenderer != null) { + triggerFunnelDataLabelEvent( + chart, + seriesRenderer, + stateProperties.chartState, + stateProperties.renderingDetails.tapPosition!); + } + if (stateProperties.renderingDetails.tapPosition != null) { + if (stateProperties.renderingDetails.currentActive != null && + stateProperties.renderingDetails.currentActive!.series != null && + stateProperties + .renderingDetails.currentActive!.series.explodeGesture == + ActivationMode.singleTap) { + stateProperties.chartSeries.pointExplode( + stateProperties.renderingDetails.currentActive!.pointIndex!); + final GlobalKey key = stateProperties.renderDataLabel!.key as GlobalKey; + final FunnelDataLabelRendererState _funnelDataLabelRendererState = + key.currentState as FunnelDataLabelRendererState; + _funnelDataLabelRendererState.dataLabelRepaintNotifier.value++; + } + if (stateProperties.renderingDetails.tapPosition != null && + stateProperties.renderingDetails.currentActive != null) { + stateProperties.chartSeries.seriesPointSelection( + stateProperties.renderingDetails.currentActive!.pointIndex!, + ActivationMode.singleTap); + } + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.singleTap && + stateProperties.renderingDetails.currentActive != null && + stateProperties.renderingDetails.currentActive!.series != null) { + if (chart.tooltipBehavior.builder != null) { + showFunnelTooltipTemplate(); + } else { + final Offset position = renderBox.globalToLocal(event.position); + stateProperties.renderingDetails.tooltipBehaviorRenderer + .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); + } + } + if (chart.onChartTouchInteractionUp != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionUp!(touchArgs); + } + } + if (chart.series.onPointTap == null && + chart.series.onPointDoubleTap == null && + chart.series.onPointLongPress == null) { + stateProperties.renderingDetails.tapPosition = null; + } + } + + /// To perform event on mouse hover + void _onHover(PointerEvent event) { + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + stateProperties.renderingDetails.currentActive = null; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + bool? isPoint; + const int seriesIndex = 0; + int? pointIndex; + final FunnelSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; + for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { + if (seriesRenderer.renderPoints[j].isVisible) { + isPoint = isPointInPolygon(seriesRenderer.renderPoints[j].pathRegion, + stateProperties.renderingDetails.tapPosition!); + if (isPoint) { + pointIndex = j; + break; + } + } + } + if (stateProperties.renderingDetails.tapPosition != null && isPoint!) { + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex!, + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex].series, + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .renderPoints[pointIndex], + ); + } else { + //hides the tooltip if the point of interaction is outside funnel region of the chart + tooltipRenderingDetails.hide(); + } + if (stateProperties.renderingDetails.tapPosition != null) { + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + stateProperties.renderingDetails.currentActive != null && + stateProperties.renderingDetails.currentActive!.series != null) { + tooltipRenderingDetails.isHovering = true; + if (chart.tooltipBehavior.builder != null) { + showFunnelTooltipTemplate(); + } else { + final Offset position = renderBox.globalToLocal(event.position); + stateProperties.renderingDetails.tooltipBehaviorRenderer + .onEnter(position.dx.toDouble(), position.dy.toDouble()); + } + } else { + tooltipRenderingDetails.prevTooltipValue = null; + tooltipRenderingDetails.currentTooltipValue = null; + } + } + stateProperties.renderingDetails.tapPosition = null; + } + + /// This method gets executed for showing tooltip when builder is provided in behavior + void showFunnelTooltipTemplate([int? pointIndex]) { + stateProperties.isTooltipHidden = false; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + if (tooltipRenderingDetails.isHovering == false) { + //assigning null for the previous and current tooltip values in case of touch interaction + tooltipRenderingDetails.prevTooltipValue = null; + tooltipRenderingDetails.currentTooltipValue = null; + } + final FunnelSeries chartSeries = + stateProperties.renderingDetails.currentActive?.series ?? chart.series; + final PointInfo point = pointIndex == null + ? stateProperties.renderingDetails.currentActive?.point + : stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; + final Offset location = + chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && + !stateProperties + .chartSeries.visibleSeriesRenderers[0].series.explode + ? stateProperties.renderingDetails.tapPosition! + : point.symbolLocation; + bool isPoint = false; + for (int j = 0; j < seriesRenderer.renderPoints.length; j++) { + if (seriesRenderer.renderPoints[j].isVisible) { + isPoint = isPointInPolygon( + seriesRenderer.renderPoints[j].pathRegion, location); + if (isPoint) { + pointIndex = j; + break; + } + } + } + // ignore: unnecessary_null_comparison + if (location != null && isPoint && (chartSeries.enableTooltip)) { + tooltipRenderingDetails.showLocation = location; + tooltipRenderingDetails.chartTooltipState!.boundaryRect = + tooltipRenderingDetails.tooltipBounds = + stateProperties.renderingDetails.chartContainerRect; + // tooltipTemplate.rect = Rect.fromLTWH(location.dx, location.dy, 0, 0); + tooltipRenderingDetails.tooltipTemplate = chart.tooltipBehavior.builder!( + chartSeries.dataSource![pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!], + point, + chartSeries, + 0, + pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!); + if (tooltipRenderingDetails.isHovering == true) { + //assigning values for the previous and current tooltip values on mouse hover + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue; + tooltipRenderingDetails.currentTooltipValue = TooltipValue( + 0, + pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!); + } else { + tooltipRenderingDetails.hideTooltipTemplate(); + } + tooltipRenderingDetails.show = true; + tooltipRenderingDetails.performTooltip(); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_state_properties.dart new file mode 100644 index 000000000..c69ad8530 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/funnel_state_properties.dart @@ -0,0 +1,85 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import '../../common/rendering_details.dart'; +import '../../common/state_properties.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../base/funnel_base.dart'; +import '../base/funnel_plot_area.dart'; +import '../base/series_base.dart'; +import '../renderer/data_label_renderer.dart'; + +/// Specifies the funnel state properties +class FunnelStateProperties extends StateProperties { + /// Creates an instance of funnel chart properties + FunnelStateProperties( + {required this.renderingDetails, required this.chartState}) + : super(renderingDetails, chartState) { + renderingDetails.didSizeChange = false; + } + + /// Specifies the funnel chart + @override + SfFunnelChart get chart => chartState.widget; + + /// Specifies the funnel chart state + @override + final SfFunnelChartState chartState; + + /// Specifies the rendering details value + @override + final RenderingDetails renderingDetails; + + /// Specifies the funnel data label renderer + FunnelDataLabelRenderer? renderDataLabel; + + /// Specifies the tooltip point index + int? tooltipPointIndex; + + /// Specifies the series type + late String seriesType; + + /// Specifies the data points + late List> dataPoints; + + /// Specifies the render points + late List> renderPoints; + + /// Specifies the funnel series + late FunnelChartBase chartSeries; + + /// Specifies the funnel plot area + late FunnelPlotArea funnelplotArea; + + /// To redraw chart elements + void redraw() { + renderingDetails.initialRender = false; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + renderingDetails.tooltipBehaviorRenderer); + if (tooltipRenderingDetails.chartTooltipState != null) { + tooltipRenderingDetails.show = false; + } + // ignore: invalid_use_of_protected_member + chartState.setState(() { + /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. + }); + } + + /// Method when called, once animation completed + bool get animationCompleted { + return renderingDetails.animationController.status != + AnimationStatus.forward; + } + + /// Tooltip timer + Timer? tooltipTimer; + + /// To check the tooltip orientation changes. + bool isTooltipOrientationChanged = false; + + /// To check if tooltip has been hidden or not. + bool isTooltipHidden = false; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart index 1aa0e4423..b0d374ad1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/base/series_base.dart @@ -1,26 +1,49 @@ -part of charts; - -class _FunnelSeries { - _FunnelSeries(this._chartState); - - final SfFunnelChartState _chartState; - - late _FunnelSeriesBase currentSeries; - - List visibleSeriesRenderers = []; +import 'dart:math'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/utils/enum.dart'; +import '../../circular_chart/renderer/common.dart'; +import '../../circular_chart/utils/helper.dart'; +import '../../common/common.dart'; +import '../../common/event_args.dart'; +import '../../common/legend/renderer.dart'; +import '../../common/utils/typedef.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../../pyramid_chart/utils/helper.dart'; +import '../base/funnel_base.dart'; +import '../base/funnel_state_properties.dart'; +import '../renderer/funnel_series.dart'; +import '../renderer/renderer_extension.dart'; +import '../renderer/series_base.dart'; + +/// Represents the funnel series base +class FunnelChartBase { + /// Creates an instance for funnel series base + FunnelChartBase(this.stateProperties); + + /// Specifies the funnel chart state + final FunnelStateProperties stateProperties; + + /// Specifies the current series + late FunnelSeriesBase currentSeries; + + /// Specifies the list of visible series renderer + List visibleSeriesRenderers = + []; SelectionArgs? _selectionArgs; /// To find the visible series - void _findVisibleSeries() { - _chartState._chartSeries.visibleSeriesRenderers[0]._dataPoints = + void findVisibleSeries() { + stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints = >[]; //Considered the first series, since in triangular series one series will be considered for rendering - final FunnelSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - currentSeries = seriesRenderer._series; + final FunnelSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + currentSeries = seriesRenderer.series; //Setting series type - seriesRenderer._seriesType = 'funnel'; + seriesRenderer.seriesType = 'funnel'; final ChartIndexedValueMapper? xValue = currentSeries.xValueMapper; final ChartIndexedValueMapper? yValue = currentSeries.yValueMapper; @@ -28,7 +51,7 @@ class _FunnelSeries { pointIndex < currentSeries.dataSource!.length; pointIndex++) { if (xValue!(pointIndex) != null) { - seriesRenderer._dataPoints + seriesRenderer.dataPoints .add(PointInfo(xValue(pointIndex), yValue!(pointIndex))); } } @@ -38,18 +61,19 @@ class _FunnelSeries { } /// To calculate empty point values for null values in chart - void _calculateFunnelEmptyPoints(FunnelSeriesRenderer seriesRenderer) { - for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { - if (seriesRenderer._dataPoints[i].y == null) { - seriesRenderer._series.calculateEmptyPointValue( - i, seriesRenderer._dataPoints[i], seriesRenderer); + void _calculateFunnelEmptyPoints( + FunnelSeriesRendererExtension seriesRenderer) { + for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { + if (seriesRenderer.dataPoints[i].y == null) { + seriesRenderer.series.calculateEmptyPointValue( + i, seriesRenderer.dataPoints[i], seriesRenderer); } } } /// To process the data points for series render. - void _processDataPoints(FunnelSeriesRenderer seriesRenderer) { - currentSeries = seriesRenderer._series; + void processDataPoints(FunnelSeriesRendererExtension seriesRenderer) { + currentSeries = seriesRenderer.series; _calculateFunnelEmptyPoints(seriesRenderer); _calculateVisiblePoints(seriesRenderer); _setPointStyle(seriesRenderer); @@ -57,30 +81,30 @@ class _FunnelSeries { } /// To calculate the visible points in the series - void _calculateVisiblePoints(FunnelSeriesRenderer seriesRenderer) { - final List> points = seriesRenderer._dataPoints; - seriesRenderer._renderPoints = >[]; + void _calculateVisiblePoints(FunnelSeriesRendererExtension seriesRenderer) { + final List> points = seriesRenderer.dataPoints; + seriesRenderer.renderPoints = >[]; for (int i = 0; i < points.length; i++) { if (points[i].isVisible) { - seriesRenderer._renderPoints.add(points[i]); + seriesRenderer.renderPoints.add(points[i]); } } } /// To set point style properties - void _setPointStyle(FunnelSeriesRenderer seriesRenderer) { - currentSeries = seriesRenderer._series; - final List palette = _chartState._chart.palette; + void _setPointStyle(FunnelSeriesRendererExtension seriesRenderer) { + currentSeries = seriesRenderer.series; + final List palette = stateProperties.chart.palette; final ChartIndexedValueMapper? pointColor = currentSeries.pointColorMapper; final EmptyPointSettings empty = currentSeries.emptyPointSettings; final ChartIndexedValueMapper? textMapping = currentSeries.textFieldMapper; - final List> points = seriesRenderer._renderPoints; + final List> points = seriesRenderer.renderPoints; PointInfo currentPoint; - List<_MeasureWidgetContext> legendToggles; - _MeasureWidgetContext item; - _LegendRenderContext legendRenderContext; + List legendToggles; + MeasureWidgetContext item; + LegendRenderContext legendRenderContext; for (int i = 0; i < points.length; i++) { currentPoint = points[i]; // ignore: unnecessary_null_comparison @@ -107,9 +131,9 @@ class _FunnelSeries { ? textMapping(i) ?? currentPoint.y!.toString() : currentPoint.y!.toString()); - if (_chartState._chart.legend.legendItemBuilder != null) { + if (stateProperties.chart.legend.legendItemBuilder != null) { legendToggles = - _chartState._renderingDetails.legendToggleTemplateStates; + stateProperties.renderingDetails.legendToggleTemplateStates; if (legendToggles.isNotEmpty) { for (int j = 0; j < legendToggles.length; j++) { item = legendToggles[j]; @@ -120,12 +144,12 @@ class _FunnelSeries { } } } else { - if (_chartState._renderingDetails.legendToggleStates.isNotEmpty) { + if (stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { for (int j = 0; - j < _chartState._renderingDetails.legendToggleStates.length; + j < stateProperties.renderingDetails.legendToggleStates.length; j++) { legendRenderContext = - _chartState._renderingDetails.legendToggleStates[j]; + stateProperties.renderingDetails.legendToggleStates[j]; if (i == legendRenderContext.seriesIndex) { currentPoint.isVisible = false; break; @@ -137,45 +161,46 @@ class _FunnelSeries { } /// To find the sum of data points - void _findSumOfPoints(FunnelSeriesRenderer seriesRenderer) { - seriesRenderer._sumOfPoints = 0; - for (final PointInfo point in seriesRenderer._renderPoints) { + void _findSumOfPoints(FunnelSeriesRendererExtension seriesRenderer) { + seriesRenderer.sumOfPoints = 0; + for (final PointInfo point in seriesRenderer.renderPoints) { if (point.isVisible) { - seriesRenderer._sumOfPoints += point.y!.abs(); + seriesRenderer.sumOfPoints += point.y!.abs(); } } } - /// To initialise series properties - void _initializeSeriesProperties(FunnelSeriesRenderer seriesRenderer) { - final Rect chartAreaRect = _chartState._renderingDetails.chartAreaRect; - final FunnelSeries series = seriesRenderer._series; - final bool reverse = seriesRenderer._seriesType == 'pyramid'; - seriesRenderer._triangleSize = Size( - _percentToValue(series.width, chartAreaRect.width)!.toDouble(), - _percentToValue(series.height, chartAreaRect.height)!.toDouble()); - seriesRenderer._neckSize = Size( - _percentToValue(series.neckWidth, chartAreaRect.width)!.toDouble(), - _percentToValue(series.neckHeight, chartAreaRect.height)!.toDouble()); - seriesRenderer._explodeDistance = - _percentToValue(series.explodeOffset, chartAreaRect.width)!; + /// To initialize series properties + void initializeSeriesProperties( + FunnelSeriesRendererExtension seriesRenderer) { + final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; + final FunnelSeries series = seriesRenderer.series; + final bool reverse = seriesRenderer.seriesType == 'pyramid'; + seriesRenderer.triangleSize = Size( + percentToValue(series.width, chartAreaRect.width)!.toDouble(), + percentToValue(series.height, chartAreaRect.height)!.toDouble()); + seriesRenderer.neckSize = Size( + percentToValue(series.neckWidth, chartAreaRect.width)!.toDouble(), + percentToValue(series.neckHeight, chartAreaRect.height)!.toDouble()); + seriesRenderer.explodeDistance = + percentToValue(series.explodeOffset, chartAreaRect.width)!; _initializeSizeRatio(seriesRenderer, reverse); } - /// To initialise size ratio for the funnel - void _initializeSizeRatio(FunnelSeriesRenderer seriesRenderer, + /// To initialize size ratio for the funnel + void _initializeSizeRatio(FunnelSeriesRendererExtension seriesRenderer, [bool? reverse]) { - final List> points = seriesRenderer._renderPoints; + final List> points = seriesRenderer.renderPoints; double y; assert( // ignore: unnecessary_null_comparison - !(seriesRenderer._series.gapRatio != null) || - seriesRenderer._series.gapRatio >= 0 && - seriesRenderer._series.gapRatio <= 1, + !(seriesRenderer.series.gapRatio != null) || + seriesRenderer.series.gapRatio >= 0 && + seriesRenderer.series.gapRatio <= 1, 'The gap ratio for the funnel chart must be between 0 and 1.'); - final double gapRatio = min(max(seriesRenderer._series.gapRatio, 0), 1); + final double gapRatio = min(max(seriesRenderer.series.gapRatio, 0), 1); final double coEff = - 1 / (seriesRenderer._sumOfPoints * (1 + gapRatio / (1 - gapRatio))); + 1 / (seriesRenderer.sumOfPoints * (1 + gapRatio / (1 - gapRatio))); final double spacing = gapRatio / (points.length - 1); y = 0; int index; @@ -205,59 +230,58 @@ class _FunnelSeries { } /// To perform point explode - void _pointExplode(int pointIndex) { + void pointExplode(int pointIndex) { bool existExplodedRegion = false; - final FunnelSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - final SfFunnelChartState chartState = _chartState; - final PointInfo point = seriesRenderer._renderPoints[pointIndex]; - if (seriesRenderer._series.explode) { - if (chartState._renderingDetails.explodedPoints.isNotEmpty) { + final FunnelSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + final PointInfo point = seriesRenderer.renderPoints[pointIndex]; + if (seriesRenderer.series.explode) { + if (stateProperties.renderingDetails.explodedPoints.isNotEmpty) { existExplodedRegion = true; final int previousIndex = - chartState._renderingDetails.explodedPoints[0]; - seriesRenderer._renderPoints[previousIndex].explodeDistance = 0; + stateProperties.renderingDetails.explodedPoints[0]; + seriesRenderer.renderPoints[previousIndex].explodeDistance = 0; point.explodeDistance = - previousIndex == pointIndex ? 0 : seriesRenderer._explodeDistance; - chartState._renderingDetails.explodedPoints[0] = pointIndex; + previousIndex == pointIndex ? 0 : seriesRenderer.explodeDistance; + stateProperties.renderingDetails.explodedPoints[0] = pointIndex; if (previousIndex == pointIndex) { - chartState._renderingDetails.explodedPoints = []; + stateProperties.renderingDetails.explodedPoints = []; } - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; } if (!existExplodedRegion) { - point.explodeDistance = seriesRenderer._explodeDistance; - chartState._renderingDetails.explodedPoints.add(pointIndex); - chartState._renderingDetails.seriesRepaintNotifier.value++; + point.explodeDistance = seriesRenderer.explodeDistance; + stateProperties.renderingDetails.explodedPoints.add(pointIndex); + stateProperties.renderingDetails.seriesRepaintNotifier.value++; } - _calculateFunnelPathRegion(pointIndex, seriesRenderer); + calculateFunnelPathRegion(pointIndex, seriesRenderer); } } - /// To calculate Path for the segemnt regions - void _calculateFunnelPathRegion( - int pointIndex, FunnelSeriesRenderer seriesRenderer) { + /// To calculate Path for the segment regions + void calculateFunnelPathRegion( + int pointIndex, FunnelSeriesRendererExtension seriesRenderer) { num lineWidth, topRadius, bottomRadius, endTop, endBottom, top, bottom; num? minRadius, bottomY; num endMin = 0; - final Size area = seriesRenderer._triangleSize; + final Size area = seriesRenderer.triangleSize; const num offset = 0; final PointInfo currentPoint = - seriesRenderer._renderPoints[pointIndex]; + seriesRenderer.renderPoints[pointIndex]; currentPoint.pathRegion = []; - final Rect rect = _chartState._renderingDetails.chartContainerRect; + final Rect rect = stateProperties.renderingDetails.chartContainerRect; //ignore: prefer_if_null_operators final num extraSpace = (currentPoint.explodeDistance != null ? currentPoint.explodeDistance! - : _isNeedExplode(pointIndex, currentSeries, _chartState) - ? seriesRenderer._explodeDistance + : isNeedExplode(pointIndex, currentSeries, stateProperties) + ? seriesRenderer.explodeDistance : 0) + - (rect.width - seriesRenderer._triangleSize.width) / 2; + (rect.width - seriesRenderer.triangleSize.width) / 2; final num emptySpaceAtLeft = extraSpace + rect.left; final num seriesTop = rect.top + (rect.height - area.height) / 2; top = currentPoint.yRatio * area.height; bottom = top + currentPoint.heightRatio * area.height; - final Size neckSize = seriesRenderer._neckSize; + final Size neckSize = seriesRenderer.neckSize; lineWidth = neckSize.width + (area.width - neckSize.width) * ((area.height - neckSize.height - top) / @@ -298,7 +322,7 @@ class _FunnelSeries { bottom ]; _addPathToCurrentPoint(currentPoint, values, minRadius, bottomY); - _calculatePathSegment(seriesRenderer._seriesType, currentPoint); + _calculatePathSegment(seriesRenderer.seriesType, currentPoint); } void _addPathToCurrentPoint(PointInfo currentPoint, List values, @@ -342,11 +366,11 @@ class _FunnelSeries { } /// To calculate the funnel segments and render path - void _calculateFunnelSegments( - Canvas canvas, int pointIndex, FunnelSeriesRenderer seriesRenderer) { - _calculateFunnelPathRegion(pointIndex, seriesRenderer); + void calculateFunnelSegments(Canvas canvas, int pointIndex, + FunnelSeriesRendererExtension seriesRenderer) { + calculateFunnelPathRegion(pointIndex, seriesRenderer); final PointInfo currentPoint = - seriesRenderer._renderPoints[pointIndex]; + seriesRenderer.renderPoints[pointIndex]; final Path path = Path(); path.moveTo(currentPoint.pathRegion[0].dx, currentPoint.pathRegion[0].dy); path.lineTo(currentPoint.pathRegion[1].dx, currentPoint.pathRegion[1].dy); @@ -355,18 +379,18 @@ class _FunnelSeries { path.lineTo(currentPoint.pathRegion[4].dx, currentPoint.pathRegion[4].dy); path.lineTo(currentPoint.pathRegion[5].dx, currentPoint.pathRegion[5].dy); path.close(); - if (pointIndex == seriesRenderer._renderPoints.length - 1) { - seriesRenderer._maximumDataLabelRegion = path.getBounds(); + if (pointIndex == seriesRenderer.renderPoints.length - 1) { + seriesRenderer.maximumDataLabelRegion = path.getBounds(); } _segmentPaint(canvas, path, pointIndex, seriesRenderer); } /// To paint the funnel segments void _segmentPaint(Canvas canvas, Path path, int pointIndex, - FunnelSeriesRenderer seriesRenderer) { - final PointInfo point = seriesRenderer._renderPoints[pointIndex]; - final _StyleOptions? style = - _getPointStyle(pointIndex, seriesRenderer, _chartState, point); + FunnelSeriesRendererExtension seriesRenderer) { + final PointInfo point = seriesRenderer.renderPoints[pointIndex]; + final StyleOptions? style = _getPointStyle( + pointIndex, seriesRenderer, stateProperties.chartState, point); final Color fillColor = style != null && style.fill != null ? style.fill! : point.fill; @@ -383,11 +407,11 @@ class _FunnelSeries { ? style.opacity!.toDouble() : currentSeries.opacity.toDouble(); - _drawPath( + drawPath( canvas, - _StyleOptions( + StyleOptions( fill: fillColor, - strokeWidth: _chartState._renderingDetails.animateCompleted + strokeWidth: stateProperties.renderingDetails.animateCompleted ? strokeWidth : 0, strokeColor: strokeColor, @@ -396,26 +420,27 @@ class _FunnelSeries { } /// To add selection points to selection list - void _seriesPointSelection(int pointIndex, ActivationMode mode) { + void seriesPointSelection(int pointIndex, ActivationMode mode) { bool isPointAlreadySelected = false; - final SfFunnelChart chart = _chartState._chart; - final FunnelSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - final List selectionData = _chartState._renderingDetails.selectionData; + final SfFunnelChart chart = stateProperties.chart; + final FunnelSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + final List selectionData = + stateProperties.renderingDetails.selectionData; int? currentSelectedIndex; const int seriesIndex = 0; - if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { + if (seriesRenderer.isSelectionEnable && mode == chart.selectionGesture) { if (selectionData.isNotEmpty) { if (!chart.enableMultiSelection && - _chartState._renderingDetails.selectionData.isNotEmpty && - _chartState._renderingDetails.selectionData.length > 1) { - if (_chartState._renderingDetails.selectionData + stateProperties.renderingDetails.selectionData.isNotEmpty && + stateProperties.renderingDetails.selectionData.length > 1) { + if (stateProperties.renderingDetails.selectionData .contains(pointIndex)) { currentSelectedIndex = pointIndex; } - _chartState._renderingDetails.selectionData.clear(); + stateProperties.renderingDetails.selectionData.clear(); if (currentSelectedIndex != null) { - _chartState._renderingDetails.selectionData.add(pointIndex); + stateProperties.renderingDetails.selectionData.add(pointIndex); } } @@ -425,21 +450,21 @@ class _FunnelSeries { if (!chart.enableMultiSelection) { isPointAlreadySelected = selectionData.length == 1 && pointIndex == selectionIndex; - if (seriesRenderer._selectionBehavior.toggleSelection == true || + if (seriesRenderer.selectionBehavior.toggleSelection == true || !isPointAlreadySelected) { selectionData.removeAt(i); } - _chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); } } else if (pointIndex == selectionIndex) { - if (seriesRenderer._selectionBehavior.toggleSelection == true) { + if (seriesRenderer.selectionBehavior.toggleSelection == true) { selectionData.removeAt(i); } isPointAlreadySelected = true; - _chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); @@ -450,7 +475,7 @@ class _FunnelSeries { } if (!isPointAlreadySelected) { selectionData.add(pointIndex); - _chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!( _getSelectionEventArgs(seriesRenderer, seriesIndex, pointIndex)); @@ -460,21 +485,22 @@ class _FunnelSeries { } /// To return style options for the point on selection - _StyleOptions? _getPointStyle( + StyleOptions? _getPointStyle( int currentPointIndex, - FunnelSeriesRenderer seriesRenderer, + FunnelSeriesRendererExtension seriesRenderer, SfFunnelChartState _chartState, PointInfo point) { - _StyleOptions? pointStyle; - final dynamic selection = seriesRenderer._series.selectionBehavior; - final List selectionData = _chartState._renderingDetails.selectionData; + StyleOptions? pointStyle; + final dynamic selection = seriesRenderer.series.selectionBehavior; + final List selectionData = + stateProperties.renderingDetails.selectionData; if (selection.enable == true) { if (selectionData.isNotEmpty) { int selectionIndex; for (int i = 0; i < selectionData.length; i++) { selectionIndex = selectionData[i]; if (currentPointIndex == selectionIndex) { - pointStyle = _StyleOptions( + pointStyle = StyleOptions( fill: _selectionArgs != null ? _selectionArgs!.selectedColor : selection.selectedColor, @@ -487,7 +513,7 @@ class _FunnelSeries { opacity: selection.selectedOpacity); break; } else if (i == selectionData.length - 1) { - pointStyle = _StyleOptions( + pointStyle = StyleOptions( fill: _selectionArgs != null ? _selectionArgs!.unselectedColor : selection.unselectedColor, @@ -507,10 +533,12 @@ class _FunnelSeries { /// To perform selection event and return selectionArgs SelectionArgs _getSelectionEventArgs( - FunnelSeriesRenderer seriesRenderer, int seriesIndex, int pointIndex) { - final SfFunnelChart chart = seriesRenderer._chartState._chart; + FunnelSeriesRendererExtension seriesRenderer, + int seriesIndex, + int pointIndex) { + final SfFunnelChart chart = seriesRenderer.stateProperties.chart; if (pointIndex < chart.series.dataSource!.length) { - final dynamic selectionBehavior = seriesRenderer._selectionBehavior; + final dynamic selectionBehavior = seriesRenderer.selectionBehavior; _selectionArgs = SelectionArgs( seriesRenderer: seriesRenderer, seriesIndex: seriesIndex, diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart index ee06a4faf..80b4c56c1 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/data_label_renderer.dart @@ -1,24 +1,53 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; + +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../circular_chart/renderer/circular_chart_annotation.dart'; +import '../../circular_chart/renderer/data_label_renderer.dart'; +import '../../circular_chart/utils/enum.dart'; +import '../../circular_chart/utils/helper.dart'; +import '../../common/event_args.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../../pyramid_chart/utils/helper.dart'; +import '../base/funnel_base.dart'; +import '../base/funnel_state_properties.dart'; +import 'funnel_series.dart'; +import 'renderer_extension.dart'; + +/// Represents the data label renderer of funnel chart // ignore: must_be_immutable -class _FunnelDataLabelRenderer extends StatefulWidget { +class FunnelDataLabelRenderer extends StatefulWidget { + /// Creates an instance for funnel data label renderer // ignore: prefer_const_constructors_in_immutables - _FunnelDataLabelRenderer( - {required Key key, required this.chartState, required this.show}) + FunnelDataLabelRenderer( + {required Key key, required this.stateProperties, required this.show}) : super(key: key); - final SfFunnelChartState chartState; + /// Creates the instance of funnel chart state + final FunnelStateProperties stateProperties; + /// Specifies whether to show data label renderer bool show; - _FunnelDataLabelRendererState? state; + /// Specifies the state of funnel data label renderer + FunnelDataLabelRendererState? state; @override - State createState() => _FunnelDataLabelRendererState(); + State createState() => FunnelDataLabelRendererState(); } -class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> +/// Represents the data label renderer state +class FunnelDataLabelRendererState extends State with SingleTickerProviderStateMixin { + /// Specifies the animation controller list late List animationControllersList; /// Animation controller for series @@ -40,7 +69,7 @@ class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> widget.state = this; animationController.duration = Duration( milliseconds: - widget.chartState._renderingDetails.initialRender! ? 500 : 0); + widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -53,7 +82,7 @@ class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> child: RepaintBoundary( child: CustomPaint( painter: _FunnelDataLabelPainter( - chartState: widget.chartState, + stateProperties: widget.stateProperties, animation: dataLabelAnimation, state: this, notifier: dataLabelRepaintNotifier, @@ -62,14 +91,16 @@ class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> @override void dispose() { - _disposeAnimationController(animationController, repaintDataLabelElements); + disposeAnimationController(animationController, repaintDataLabelElements); super.dispose(); } + /// Method to repaint the data label element void repaintDataLabelElements() { dataLabelRepaintNotifier.value++; } + /// Method to render the widget void render() { setState(() { widget.show = true; @@ -79,16 +110,16 @@ class _FunnelDataLabelRendererState extends State<_FunnelDataLabelRenderer> class _FunnelDataLabelPainter extends CustomPainter { _FunnelDataLabelPainter( - {required this.chartState, + {required this.stateProperties, required this.state, required this.animationController, required this.animation, required ValueNotifier notifier}) : super(repaint: notifier); - final SfFunnelChartState chartState; + final FunnelStateProperties stateProperties; - final _FunnelDataLabelRendererState state; + final FunnelDataLabelRendererState state; final AnimationController animationController; @@ -97,16 +128,16 @@ class _FunnelDataLabelPainter extends CustomPainter { /// To paint funnel data label @override void paint(Canvas canvas, Size size) { - final FunnelSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[0]; - final FunnelSeries series = seriesRenderer._series; + final FunnelSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + final FunnelSeries series = seriesRenderer.series; // ignore: unnecessary_null_comparison if (series.dataLabelSettings != null && series.dataLabelSettings.isVisible) { - seriesRenderer._dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer._series.dataLabelSettings); + seriesRenderer.dataLabelSettingsRenderer = + DataLabelSettingsRenderer(seriesRenderer.series.dataLabelSettings); _renderFunnelDataLabel( - seriesRenderer, canvas, chartState, animation, series); + seriesRenderer, canvas, stateProperties, animation, series); } } @@ -116,13 +147,13 @@ class _FunnelDataLabelPainter extends CustomPainter { /// To render funnel data label void _renderFunnelDataLabel( - FunnelSeriesRenderer seriesRenderer, + FunnelSeriesRendererExtension seriesRenderer, Canvas canvas, - SfFunnelChartState chartState, + FunnelStateProperties stateProperties, Animation? animation, FunnelSeries series) { PointInfo point; - final SfFunnelChart chart = chartState._chart; + final SfFunnelChart chart = stateProperties.chart; final DataLabelSettings dataLabel = series.dataLabelSettings; String? label; final double animateOpacity = animation != null ? animation.value : 1; @@ -132,34 +163,32 @@ void _renderFunnelDataLabel( DataLabelSettingsRenderer dataLabelSettingsRenderer; Size textSize; for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints.length; + pointIndex < seriesRenderer.renderPoints.length; pointIndex++) { - dataLabelSettingsRenderer = seriesRenderer._dataLabelSettingsRenderer; - point = seriesRenderer._renderPoints[pointIndex]; + dataLabelSettingsRenderer = seriesRenderer.dataLabelSettingsRenderer; + point = seriesRenderer.renderPoints[pointIndex]; if (point.isVisible && (point.y != 0 || dataLabel.showZeroValue)) { label = point.text; dataLabelStyle = dataLabel.textStyle; - dataLabelSettingsRenderer._color = - seriesRenderer._series.dataLabelSettings.color; + dataLabelSettingsRenderer.color = + seriesRenderer.series.dataLabelSettings.color; if (chart.onDataLabelRender != null && - !seriesRenderer._renderPoints[pointIndex].labelRenderEvent) { + !seriesRenderer.renderPoints[pointIndex].labelRenderEvent) { dataLabelArgs = DataLabelRenderArgs(seriesRenderer, - seriesRenderer._renderPoints, pointIndex, pointIndex); + seriesRenderer.renderPoints, pointIndex, pointIndex); dataLabelArgs.text = label!; dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = dataLabelSettingsRenderer._color; + dataLabelArgs.color = dataLabelSettingsRenderer.color; chart.onDataLabelRender!(dataLabelArgs); label = point.text = dataLabelArgs.text; dataLabelStyle = dataLabelArgs.textStyle; pointIndex = dataLabelArgs.pointIndex!; - dataLabelSettingsRenderer._color = dataLabelArgs.color; - if (animation!.status == AnimationStatus.completed) { - seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; - } + dataLabelSettingsRenderer.color = dataLabelArgs.color; + seriesRenderer.dataPoints[pointIndex].labelRenderEvent = true; } dataLabelStyle = chart.onDataLabelRender == null - ? _getDataLabelTextStyle( - seriesRenderer, point, chartState, animateOpacity) + ? getDataLabelTextStyle( + seriesRenderer, point, stateProperties, animateOpacity) : dataLabelStyle; textSize = measureText(label!, dataLabelStyle); @@ -170,7 +199,7 @@ void _renderFunnelDataLabel( dataLabel, point, textSize, - chartState, + stateProperties, canvas, renderDataLabelRegions, pointIndex, @@ -180,8 +209,8 @@ void _renderFunnelDataLabel( dataLabelStyle); } else { point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = _getDataLabelTextStyle( - seriesRenderer, point, chartState, animateOpacity); + dataLabelStyle = getDataLabelTextStyle( + seriesRenderer, point, stateProperties, animateOpacity); _renderOutsideFunnelDataLabel( canvas, label, @@ -189,7 +218,7 @@ void _renderFunnelDataLabel( textSize, pointIndex, seriesRenderer, - chartState, + stateProperties, dataLabelStyle, renderDataLabelRegions, animateOpacity); @@ -204,15 +233,15 @@ void _setFunnelInsideLabelPosition( DataLabelSettings dataLabel, PointInfo point, Size textSize, - SfFunnelChartState chartState, + FunnelStateProperties stateProperties, Canvas canvas, List renderDataLabelRegions, int pointIndex, String? label, - FunnelSeriesRenderer seriesRenderer, + FunnelSeriesRendererExtension seriesRenderer, double animateOpacity, TextStyle dataLabelStyle) { - final SfFunnelChart chart = chartState._chart; + final SfFunnelChart chart = stateProperties.chart; final num angle = dataLabel.angle; Offset labelLocation; final SmartLabelMode smartLabelMode = chart.smartLabelMode; @@ -231,12 +260,12 @@ void _setFunnelInsideLabelPosition( textSize.width + (2 * labelPadding), textSize.height + (2 * labelPadding)); final bool isDataLabelCollide = - _findingCollision(point.labelRect!, renderDataLabelRegions, point.region); + findingCollision(point.labelRect!, renderDataLabelRegions, point.region); if (isDataLabelCollide && smartLabelMode == SmartLabelMode.shift) { point.saturationRegionOutside = true; point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = _getDataLabelTextStyle( - seriesRenderer, point, chartState, animateOpacity); + dataLabelStyle = getDataLabelTextStyle( + seriesRenderer, point, stateProperties, animateOpacity); _renderOutsideFunnelDataLabel( canvas, label!, @@ -244,7 +273,7 @@ void _setFunnelInsideLabelPosition( textSize, pointIndex, seriesRenderer, - chartState, + stateProperties, dataLabelStyle, renderDataLabelRegions, animateOpacity); @@ -261,7 +290,7 @@ void _setFunnelInsideLabelPosition( seriesRenderer, point, pointIndex, - chartState, + stateProperties, dataLabelStyle, renderDataLabelRegions, animateOpacity); @@ -275,41 +304,41 @@ void _renderOutsideFunnelDataLabel( PointInfo point, Size textSize, int pointIndex, - FunnelSeriesRenderer seriesRenderer, - SfFunnelChartState _chartState, + FunnelSeriesRendererExtension seriesRenderer, + FunnelStateProperties stateProperties, TextStyle textStyle, List renderDataLabelRegions, double animateOpacity) { Path connectorPath; Rect? rect; Offset labelLocation; - // Maximum available space for rendering datalabel. + // Maximum available space for rendering data label. const int maximumAvailableWidth = 22; - final EdgeInsets margin = seriesRenderer._series.dataLabelSettings.margin; + final EdgeInsets margin = seriesRenderer.series.dataLabelSettings.margin; final ConnectorLineSettings connector = - seriesRenderer._series.dataLabelSettings.connectorLineSettings; + seriesRenderer.series.dataLabelSettings.connectorLineSettings; const num regionPadding = 10; connectorPath = Path(); - final num connectorLength = _percentToValue(connector.length ?? '0%', - _chartState._renderingDetails.chartAreaRect.width / 2)! + - seriesRenderer._maximumDataLabelRegion.width / 2 - + final num connectorLength = percentToValue(connector.length ?? '0%', + stateProperties.renderingDetails.chartAreaRect.width / 2)! + + seriesRenderer.maximumDataLabelRegion.width / 2 - regionPadding; final List regions = - seriesRenderer._renderPoints[pointIndex].pathRegion; + seriesRenderer.renderPoints[pointIndex].pathRegion; final Offset startPoint = Offset( (regions[1].dx + regions[2].dx) / 2, (regions[1].dy + regions[2].dy) / 2); if (textSize.width > maximumAvailableWidth) { label = label!.substring(0, 2) + '..'; textSize = measureText(label, textStyle); } - final double dx = seriesRenderer._renderPoints[pointIndex].symbolLocation.dx + + final double dx = seriesRenderer.renderPoints[pointIndex].symbolLocation.dx + connectorLength; final Offset endPoint = Offset( (dx + textSize.width + margin.left + margin.right) > - _chartState._renderingDetails.chartAreaRect.right + stateProperties.renderingDetails.chartAreaRect.right ? dx - - (_percentToValue(seriesRenderer._series.explodeOffset, - _chartState._renderingDetails.chartAreaRect.width)!) + (percentToValue(seriesRenderer.series.explodeOffset, + stateProperties.renderingDetails.chartAreaRect.width)!) : dx, (regions[1].dy + regions[2].dy) / 2); connectorPath.moveTo(startPoint.dx, startPoint.dy); @@ -317,15 +346,15 @@ void _renderOutsideFunnelDataLabel( connectorPath.lineTo(endPoint.dx, endPoint.dy); } point.dataLabelPosition = Position.right; - rect = _getDataLabelRect(point.dataLabelPosition!, connector.type, margin, + rect = getDataLabelRect(point.dataLabelPosition!, connector.type, margin, connectorPath, endPoint, textSize); if (rect != null) { - final Rect containerRect = _chartState._renderingDetails.chartAreaRect; + final Rect containerRect = stateProperties.renderingDetails.chartAreaRect; point.labelRect = rect; labelLocation = Offset(rect.left + margin.left, rect.top + rect.height / 2 - textSize.height / 2); - if (seriesRenderer._series.dataLabelSettings.builder == null) { + if (seriesRenderer.series.dataLabelSettings.builder == null) { Rect? lastRenderedLabelRegion; if (renderDataLabelRegions.isNotEmpty) { lastRenderedLabelRegion = @@ -345,7 +374,7 @@ void _renderOutsideFunnelDataLabel( seriesRenderer, point, pointIndex, - _chartState, + stateProperties, textStyle, renderDataLabelRegions, animateOpacity); @@ -385,7 +414,7 @@ void _renderOutsideFunnelDataLabel( seriesRenderer, point, pointIndex, - _chartState, + stateProperties, textStyle, renderDataLabelRegions, animateOpacity); @@ -413,17 +442,17 @@ void _drawFunnelLabel( String? label, Path? connectorPath, Canvas canvas, - FunnelSeriesRenderer seriesRenderer, + FunnelSeriesRendererExtension seriesRenderer, PointInfo point, int pointIndex, - SfFunnelChartState _chartState, + FunnelStateProperties stateProperties, TextStyle textStyle, List renderDataLabelRegions, double animateOpacity) { Paint rectPaint; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; + seriesRenderer.dataLabelSettingsRenderer; final ConnectorLineSettings connector = dataLabel.connectorLineSettings; if (connectorPath != null) { canvas.drawPath( @@ -433,7 +462,7 @@ void _drawFunnelLabel( ? Colors.transparent : connector.color ?? point.fill.withOpacity( - !_chartState._renderingDetails.isLegendToggled + !stateProperties.renderingDetails.isLegendToggled ? animateOpacity : dataLabel.opacity) ..strokeWidth = connector.width @@ -442,22 +471,22 @@ void _drawFunnelLabel( if (dataLabel.builder == null) { final double strokeWidth = dataLabel.borderWidth; - final Color? labelFill = dataLabelSettingsRenderer._color ?? + final Color? labelFill = dataLabelSettingsRenderer.color ?? (dataLabel.useSeriesColor ? point.fill - : dataLabelSettingsRenderer._color); + : dataLabelSettingsRenderer.color); final Color? strokeColor = dataLabel.borderColor.withOpacity(dataLabel.opacity); // ignore: unnecessary_null_comparison if (strokeWidth != null && strokeWidth > 0) { rectPaint = Paint() ..color = strokeColor!.withOpacity( - !_chartState._renderingDetails.isLegendToggled + !stateProperties.renderingDetails.isLegendToggled ? animateOpacity : dataLabel.opacity) ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth; - _drawLabelRect( + drawLabelRect( rectPaint, Rect.fromLTRB( labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), @@ -465,10 +494,10 @@ void _drawFunnelLabel( canvas); } if (labelFill != null) { - _drawLabelRect( + drawLabelRect( Paint() ..color = labelFill - .withOpacity(!_chartState._renderingDetails.isLegendToggled + .withOpacity(!stateProperties.renderingDetails.isLegendToggled ? (animateOpacity - (1 - dataLabel.opacity)) < 0 ? 0 : animateOpacity - (1 - dataLabel.opacity) @@ -479,29 +508,30 @@ void _drawFunnelLabel( dataLabel.borderRadius, canvas); } - _drawText(canvas, label!, location, textStyle, dataLabel.angle); + drawText(canvas, label!, location, textStyle, dataLabel.angle); renderDataLabelRegions.add(labelRect); } } -void _triggerFunnelDataLabelEvent( +/// Method to trigger the funnel data label event +void triggerFunnelDataLabelEvent( SfFunnelChart chart, - FunnelSeriesRenderer seriesRenderer, + FunnelSeriesRendererExtension seriesRenderer, SfFunnelChartState chartState, Offset position) { const int seriesIndex = 0; PointInfo point; DataLabelSettings dataLabel; Offset labelLocation; - for (int index = 0; index < seriesRenderer._renderPoints.length; index++) { - point = seriesRenderer._renderPoints[index]; - dataLabel = seriesRenderer._series.dataLabelSettings; + for (int index = 0; index < seriesRenderer.renderPoints.length; index++) { + point = seriesRenderer.renderPoints[index]; + dataLabel = seriesRenderer.series.dataLabelSettings; labelLocation = point.symbolLocation; if (dataLabel.isVisible && - seriesRenderer._renderPoints[index].labelRect != null && - seriesRenderer._renderPoints[index].labelRect!.contains(position)) { + seriesRenderer.renderPoints[index].labelRect != null && + seriesRenderer.renderPoints[index].labelRect!.contains(position)) { position = Offset(labelLocation.dx, labelLocation.dy); - _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, index, + dataLabelTapEvent(chart, seriesRenderer.series.dataLabelSettings, index, point, position, seriesIndex); } } diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_chart_painter.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_chart_painter.dart new file mode 100644 index 000000000..22ffc1dc9 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_chart_painter.dart @@ -0,0 +1,84 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../pyramid_chart/utils/common.dart'; +import '../base/funnel_state_properties.dart'; +import 'renderer_extension.dart'; + +/// Represents the funnel chart painter +class FunnelChartPainter extends CustomPainter { + /// Creates an instance of funnel chart painter + FunnelChartPainter({ + required this.stateProperties, + required this.seriesIndex, + required this.isRepaint, + this.animationController, + this.seriesAnimation, + required ValueNotifier notifier, + }) : super(repaint: notifier); + + /// Specifies the value of funnel state properties + final FunnelStateProperties stateProperties; + + /// Holds the series index details + final int seriesIndex; + + /// Specifies whether to repaint the chart + final bool isRepaint; + + /// Specifies the value of animation controller + final AnimationController? animationController; + + /// Specifies the value of series animation + final Animation? seriesAnimation; + + /// Specifies the value of funnel series renderer extension + late FunnelSeriesRendererExtension seriesRenderer; + + /// Holds the point value + static late PointInfo point; + + @override + void paint(Canvas canvas, Size size) { + seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; + double animationFactor; + double factor; + double height; + for (int pointIndex = 0; + pointIndex < seriesRenderer.renderPoints.length; + pointIndex++) { + if (seriesRenderer.renderPoints[pointIndex].isVisible) { + animationFactor = seriesAnimation != null ? seriesAnimation!.value : 1; + if (seriesRenderer.series.animationDuration > 0 && + !stateProperties.renderingDetails.isLegendToggled) { + factor = (stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height) - + animationFactor * + (stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height); + height = stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height - + factor; + canvas.clipRect(Rect.fromLTRB( + 0, + stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height - + height, + stateProperties.renderingDetails.chartAreaRect.left + + stateProperties.renderingDetails.chartAreaRect.width, + stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height)); + } + stateProperties.chartSeries + .calculateFunnelSegments(canvas, pointIndex, seriesRenderer); + } + } + } + + @override + bool shouldRepaint(FunnelChartPainter oldDelegate) => true; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart index bbe571e78..74804669d 100644 --- a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/funnel_series.dart @@ -1,687 +1,22 @@ -part of charts; - -class _FunnelSeriesBase extends ChartSeries - implements TriangularChartEmptyPointBehavior { - _FunnelSeriesBase({ - this.key, - this.onCreateRenderer, - this.onRendererCreated, - this.onPointTap, - this.onPointDoubleTap, - this.onPointLongPress, - this.dataSource, - this.xValueMapper, - this.yValueMapper, - this.pointColorMapper, - this.textFieldMapper, - this.name, - this.explodeIndex, - String? neckWidth, - String? neckHeight, - String? height, - String? width, - double? gapRatio, - EmptyPointSettings? emptyPointSettings, - String? explodeOffset, - bool? explode, - ActivationMode? explodeGesture, - Color? borderColor, - double? borderWidth, - LegendIconType? legendIconType, - DataLabelSettings? dataLabelSettings, - double? animationDuration, - double? opacity, - SelectionBehavior? selectionBehavior, - List? initialSelectedDataIndexes, - }) : neckWidth = neckWidth ?? '20%', - neckHeight = neckHeight ?? '20%', - height = height ?? '80%', - width = width ?? '80%', - gapRatio = gapRatio ?? 0, - emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), - explodeOffset = explodeOffset ?? '10%', - explode = explode ?? false, - explodeGesture = explodeGesture ?? ActivationMode.singleTap, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0.0, - legendIconType = legendIconType ?? LegendIconType.seriesType, - dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), - animationDuration = animationDuration ?? 1500, - opacity = opacity ?? 1, - initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], - selectionBehavior = selectionBehavior ?? SelectionBehavior(), - super( - name: name, - dataSource: dataSource, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper) { - // _renderer = _FunnelSeriesRender(); - } - - ///A collection of data required for rendering the series. - /// - ///If no data source is specified, - ///empty chart will be rendered without series. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// )], - /// )); - ///} - @override - final List? dataSource; - - ///Maps the field name, which will be considered as x-values. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// )], - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? xValueMapper; - - ///Maps the field name, which will be considered as y-values. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// yValueMapper: (ChartData data, _) => data.yVal, - /// )], - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? yValueMapper; - - ///Name of the series. - /// - ///Defaults to '' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// name: 'Pyramid' - /// )); - ///} - ///``` - @override - final String? name; - - ///Neck height of funnel. - /// - ///Defaults to '20%' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// neckHeight: '10%' - /// )); - ///} - ///``` - final String neckHeight; - - ///Neck width of funnel. - /// - ///Defaults to '20%' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// neckWidth: '10%' - /// )); - ///} - ///``` - final String neckWidth; - - ///Height of the series. - /// - ///Defaults to '80%' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// height:'50%' - /// )); - ///} - ///``` - final String height; - - ///Width of the series. - /// - ///Defaults to '80%' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// width:'50%' - /// )); - ///} - ///``` - final String width; - - ///Gap ratio between the segments of pyramid. - /// - /// Ranges from 0 to 1 - /// - ///Defaults to 0. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// gapRatio: 0.3 - /// )); - ///} - ///``` - final double gapRatio; - - ///Customizes the empty data points in the series - /// - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// emptyPointSettings: EmptyPointSettings (color: Colors.red)) - /// )); - ///} - ///``` - @override - final EmptyPointSettings emptyPointSettings; - - ///Offset of exploded slice. - /// - /// The value ranges from 0% to 100%. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explodeOffset: '5%') - /// )); - ///} - ///``` - final String explodeOffset; - - ///Enables or disables the explode of slices on tap. - /// - ///Default to false. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explode: true) - /// )); - ///} - ///``` - final bool explode; - - ///Gesture for activating the explode. - /// - ///Explode can be activated in tap, double tap, - ///and long press. - /// - ///Defaults to ActivationMode.tap - /// - ///Also refer [ActivationMode] - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explode: true, - /// explodeGesture: ActivationMode.singleTap - /// ) - /// )); - ///} - ///``` - final ActivationMode explodeGesture; - - ///Border width of the data points in the series. - /// - ///Defaults to 0 - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// borderWidth: 2 - /// ) - /// )); - ///} - ///``` - @override - final double borderWidth; - - ///Border color of the data points in the series. - /// - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// borderColor: Colors.red - /// ) - /// )); - ///} - ///``` - @override - final Color borderColor; - - ///Shape of the legend icon. - /// - ///Any shape in the LegendIconType can be applied to this property. - ///By default, icon will be rendered based on the type of the series. - /// - /// - ///Also refer [LegendIconType] - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// legendIconType: LegendIconType.diamond, - /// ) - /// )); - ///} - ///``` - @override - final LegendIconType legendIconType; - - ///Enables the datalabel of the series - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true), - /// ) - /// )); - ///} - ///``` - @override - final DataLabelSettings dataLabelSettings; - - //Duration for animating the data points. - /// - ///Defaults to 1500 - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// animationDuration: 2000, - /// ) - /// )); - ///} - ///``` - @override - final double animationDuration; - - ///Maps the field name, which will be considered as data point color. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// pointColorMapper: (ChartData data, _) => data.pointColor, - /// ) - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointColor]); - /// final String xVal; - /// final int yVal; - /// final Color pointColor; - ///} - ///``` - @override - final ChartIndexedValueMapper? pointColorMapper; - - ///Maps the field name, which will be considered as text for data label. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// textFieldMapper: (ChartData data, _) => data.xVal, - /// ) - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointColor]); - /// final String xVal; - /// final int yVal; - /// final Color pointColor; - ///} - ///``` - final ChartIndexedValueMapper? textFieldMapper; - - ///Opacity of the series. - /// - /// The value ranges from 0 to 1. - /// - ///Defaults to 1 - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// opacity: 0.5, - /// ) - /// )); - ///} - ///``` - @override - final double opacity; - - ///Customizes the selection of series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// selectionBehavior: selectionBehavior( - /// selectedColor: Colors.red, - /// unselectedColor: Colors.grey - /// ), - /// ) - /// )); - ///} - ///``` - @override - final SelectionBehavior selectionBehavior; - - ///Index of the slice to explode it at the initial rendering. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// explodeIndex: 1, - /// explode: true - /// ) - /// )); - ///} - ///``` - final num? explodeIndex; - - /// List of data indexes initially selected - /// - /// Defaults to `null`. - ///```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Container( - /// child: SfFunnelChart( - /// initialSelectedDataIndexes: [IndexesModel(1, 0)] - /// ) - /// ) - /// ) - /// ); - /// } - List initialSelectedDataIndexes; - - ///Key to identify a series in a collection. - - /// - - ///On specifying [ValueKey] as the series [key], existing series index can be - /// changed in the series collection without losing its state. - - /// - - ///When a new series is added dynamically to the collection, existing series index will be changed. On that case, - - /// the existing series and its state will be linked based on its chart type and this key value. - - /// - - ///Defaults to `null`. - - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// key: const ValueKey('funnel_series_key'), - /// ), - /// )); - ///} - ///``` - final ValueKey? key; - - ///Used to create the renderer for custom series. - /// - ///This is applicable only when the custom series is defined in the sample - /// and for built-in series types, it is not applicable. - /// - ///Renderer created in this will hold the series state and - /// this should be created for each series. [onCreateRenderer] callback - /// function should return the renderer class and should not return null. - /// - ///Series state will be created only once per series and will not be created - ///again when we update the series. - /// - ///Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// onCreateRenderer:(FunnelSeries series){ - /// return CustomLinerSeriesRenderer(); - /// } - /// ), - /// )); - /// } - /// class CustomLinerSeriesRenderer extends FunnelSeriesRenderer { - /// // custom implementation here... - /// } - ///``` - final ChartSeriesRendererFactory? onCreateRenderer; - - ///Triggers when the series renderer is created. - - /// - - ///Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. - - /// - - ///Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfFunnelChart( - /// series: FunnelSeries( - /// onRendererCreated: (ChartSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ], - /// )); - ///} - ///``` - final FunnelSeriesRendererCreatedCallback? onRendererCreated; - - ///Called when tapped on the chart data point. - /// - ///The user can fetch the series index, point index, viewport point index and - /// data of the tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// )); - ///} - ///``` - final ChartPointInteractionCallback? onPointTap; - - ///Called when double tapped on the chart data point. - /// - ///The user can fetch the series index, point index, viewport point index and - /// data of the double-tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointDoubleTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// )); - ///} - ///``` - final ChartPointInteractionCallback? onPointDoubleTap; - - ///Called when long pressed on the chart data point. - /// - ///The user can fetch the series index, point index, viewport point index and - /// data of the long-pressed data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointLongPress: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// )); - ///} - ///``` - final ChartPointInteractionCallback? onPointLongPress; - - /// To calculate empty point values if null values are provided - @override - void calculateEmptyPointValue( - int pointIndex, dynamic currentPoint, dynamic seriesRenderer) { - final List> dataPoints = seriesRenderer._dataPoints; - final EmptyPointSettings empty = emptyPointSettings; - final int pointLength = dataPoints.length; - final PointInfo point = dataPoints[pointIndex]; - if (point.y == null) { - switch (empty.mode) { - case EmptyPointMode.average: - final num previous = - pointIndex - 1 >= 0 ? dataPoints[pointIndex - 1].y ?? 0 : 0; - final num next = pointIndex + 1 <= pointLength - 1 - ? dataPoints[pointIndex + 1].y ?? 0 - : 0; - point.y = (previous + next).abs() / 2; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.zero: - point.y = 0; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.drop: - case EmptyPointMode.gap: - point.isEmpty = true; - point.isVisible = false; - break; - } - } - } -} +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; + +import '../../chart/chart_series/series.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../base/funnel_base.dart'; +import '../base/funnel_state_properties.dart'; +import 'renderer_extension.dart'; +import 'series_base.dart'; /// Renders Funnel series. /// @@ -689,7 +24,9 @@ class _FunnelSeriesBase extends ChartSeries ///To render a funnel chart, create an instance of FunnelSeries, and add it to the series property of [SfFunnelChart]. /// /// Provides options to customize the [opacity], [borderWidth], [borderColor] and [pointColorMapper] of the funnel segments. -class FunnelSeries extends _FunnelSeriesBase { +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} +class FunnelSeries extends FunnelSeriesBase { /// Creating an argument constructor of FunnelSeries class. FunnelSeries({ ValueKey? key, @@ -713,6 +50,7 @@ class FunnelSeries extends _FunnelSeriesBase { EmptyPointSettings? emptyPointSettings, DataLabelSettings? dataLabelSettings, double? animationDuration, + double? animationDelay, double? opacity, Color? borderColor, double? borderWidth, @@ -753,6 +91,7 @@ class FunnelSeries extends _FunnelSeriesBase { borderColor: borderColor, borderWidth: borderWidth, animationDuration: animationDuration, + animationDelay: animationDelay, explode: explode, explodeIndex: explodeIndex, explodeGesture: explodeGesture, @@ -770,7 +109,7 @@ class FunnelSeries extends _FunnelSeriesBase { 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; } - return FunnelSeriesRenderer(); + return FunnelSeriesRendererExtension(); } @override @@ -804,6 +143,7 @@ class FunnelSeries extends _FunnelSeriesBase { other.emptyPointSettings == emptyPointSettings && other.dataLabelSettings == dataLabelSettings && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.opacity == opacity && other.borderColor == borderColor && other.borderWidth == borderWidth && @@ -840,6 +180,7 @@ class FunnelSeries extends _FunnelSeriesBase { emptyPointSettings, dataLabelSettings, animationDuration, + animationDelay, opacity, borderColor, borderWidth, @@ -854,89 +195,10 @@ class FunnelSeries extends _FunnelSeriesBase { } } -class _FunnelChartPainter extends CustomPainter { - _FunnelChartPainter({ - required this.chartState, - required this.seriesIndex, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : super(repaint: notifier); - final SfFunnelChartState chartState; - final int seriesIndex; - final bool isRepaint; - final AnimationController? animationController; - final Animation? seriesAnimation; - late FunnelSeriesRenderer seriesRenderer; - //ignore: unused_field - static late PointInfo point; - - @override - void paint(Canvas canvas, Size size) { - seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - double animationFactor; - double factor; - double height; - for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints.length; - pointIndex++) { - if (seriesRenderer._renderPoints[pointIndex].isVisible) { - animationFactor = seriesAnimation != null ? seriesAnimation!.value : 1; - if (seriesRenderer._series.animationDuration > 0 && - !chartState._renderingDetails.isLegendToggled) { - factor = (chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height) - - animationFactor * - (chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height); - height = chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height - - factor; - canvas.clipRect(Rect.fromLTRB( - 0, - chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height - - height, - chartState._renderingDetails.chartAreaRect.left + - chartState._renderingDetails.chartAreaRect.width, - chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height)); - } - chartState._chartSeries - ._calculateFunnelSegments(canvas, pointIndex, seriesRenderer); - } - } - } - - @override - bool shouldRepaint(_FunnelChartPainter oldDelegate) => true; -} - /// Creates series renderer for Funnel series class FunnelSeriesRenderer extends ChartSeriesRenderer { /// Calling the default constructor of FunnelSeriesRenderer class. FunnelSeriesRenderer(); - - late FunnelSeries _series; - //Internal variables - late String _seriesType; - late List> _dataPoints; - late List> _renderPoints; - late num _sumOfPoints; - late Size _triangleSize; - late Size _neckSize; - late num _explodeDistance; - late Rect _maximumDataLabelRegion; - FunnelSeriesController? _controller; - late SfFunnelChartState _chartState; - late ValueNotifier _repaintNotifier; - late DataLabelSettingsRenderer _dataLabelSettingsRenderer; - late SelectionBehaviorRenderer _selectionBehaviorRenderer; - late dynamic _selectionBehavior; - //ignore: prefer_final_fields - bool _isSelectionEnable = false; } ///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods @@ -958,7 +220,7 @@ class FunnelSeriesController { /// onRendererCreated: (FunnelSeriesController controller) { /// _chartSeriesController = controller; /// // prints series yAxisName - /// print(_chartSeriesController.seriesRenderer._series.yAxisName); + /// print(_chartSeriesController.seriesRenderer.seriesRendererDetails.series.yAxisName); /// }, /// ), /// )); @@ -1050,7 +312,9 @@ class FunnelSeriesController { /// add or update a data point in the given index void _addOrUpdateDataPoint(int index, bool needUpdate) { - final FunnelSeries series = seriesRenderer._series; + final FunnelSeriesRendererExtension renderer = + seriesRenderer as FunnelSeriesRendererExtension; + final FunnelSeries series = renderer.series; if (index >= 0 && series.dataSource!.length > index && series.dataSource![index] != null) { @@ -1060,14 +324,14 @@ class FunnelSeriesController { PointInfo(xValue!(index), yValue!(index)); if (currentPoint.x != null) { if (needUpdate) { - if (seriesRenderer._dataPoints.length > index) { - seriesRenderer._dataPoints[index] = currentPoint; + if (renderer.dataPoints.length > index) { + renderer.dataPoints[index] = currentPoint; } } else { - if (seriesRenderer._dataPoints.length == index) { - seriesRenderer._dataPoints.add(currentPoint); - } else if (seriesRenderer._dataPoints.length > index && index >= 0) { - seriesRenderer._dataPoints.insert(index, currentPoint); + if (renderer.dataPoints.length == index) { + renderer.dataPoints.add(currentPoint); + } else if (renderer.dataPoints.length > index && index >= 0) { + renderer.dataPoints.insert(index, currentPoint); } } } @@ -1076,7 +340,7 @@ class FunnelSeriesController { ///Remove list of points void _removeDataPointsList(List removedDataIndexes) { - ///Remove the redudant index from the list + ///Remove the redundant index from the list final List indexList = removedDataIndexes.toSet().toList(); indexList.sort((int b, int a) => a.compareTo(b)); int dataIndex; @@ -1088,28 +352,76 @@ class FunnelSeriesController { /// remove a data point in the given index void _removeDataPoint(int index) { - if (seriesRenderer._dataPoints.isNotEmpty && + final FunnelSeriesRendererExtension renderer = + seriesRenderer as FunnelSeriesRendererExtension; + if (renderer.dataPoints.isNotEmpty && index >= 0 && - index < seriesRenderer._dataPoints.length) { - seriesRenderer._dataPoints.removeAt(index); + index < renderer.dataPoints.length) { + renderer.dataPoints.removeAt(index); } } - /// After add/remove/update datapoints, recalculate the chart segments + /// After add/remove/update data points, recalculate the chart segments void _updateFunnelSeries() { - final SfFunnelChartState chartState = seriesRenderer._chartState; - chartState._chartSeries._processDataPoints(seriesRenderer); - chartState._chartSeries._initializeSeriesProperties(seriesRenderer); - seriesRenderer._repaintNotifier.value++; - if (seriesRenderer._series.dataLabelSettings.isVisible && - chartState._renderDataLabel != null) { - chartState._renderDataLabel!.state!.render(); + final FunnelSeriesRendererExtension renderer = + seriesRenderer as FunnelSeriesRendererExtension; + final FunnelStateProperties stateProperties = renderer.stateProperties; + stateProperties.chartSeries.processDataPoints(renderer); + stateProperties.chartSeries.initializeSeriesProperties(renderer); + renderer.repaintNotifier.value++; + if (renderer.series.dataLabelSettings.isVisible && + stateProperties.renderDataLabel != null) { + stateProperties.renderDataLabel!.state!.render(); } - if (seriesRenderer._series.dataLabelSettings.isVisible && - chartState._renderingDetails.chartTemplate != null && + if (renderer.series.dataLabelSettings.isVisible && + stateProperties.renderingDetails.chartTemplate != null && // ignore: unnecessary_null_comparison - chartState._renderingDetails.chartTemplate!.state != null) { - chartState._renderingDetails.chartTemplate!.state.templateRender(); + stateProperties.renderingDetails.chartTemplate!.state != null) { + stateProperties.renderingDetails.chartTemplate!.state.templateRender(); } } + + /// Converts chart data point value to logical pixel value. + /// + /// The [pointToPixel] method takes chart data point value as input and returns logical pixel value. + /// + /// _Note_: It returns the data point's center location value. + /// + /// late FunnelSeriesController seriesController; + /// SfFunnelChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// PointInfo chartPoint = seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// PointInfo chartPoint1 = seriesController.pixelToPoint(value); + /// }, + /// series: FunnelSeries( + /// dataSource: pieData, + /// onRendererCreated: (FunnelSeriesController funnelSeriesController) { + /// seriesController = FunnelSeriesController; + /// } + /// )); + // ignore: unused_element + Offset _pointToPixel(PointInfo point) { + return pyramidFunnelPointToPixel(point, seriesRenderer); + } + + /// Converts logical pixel value to the data point value. + /// + /// The [pixelToPoint] method takes logical pixel value as input and returns a chart data point. + /// + /// late FunnelSeriesController seriesController; + /// SfFunnelChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// PointInfo chartPoint = seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// }, + /// series: FunnelSeries( + /// dataSource: pieData, + /// onRendererCreated: (FunnelSeriesController funnelSeriesController) { + /// seriesController = FunnelSeriesController; + /// } + /// )); + PointInfo pixelToPoint(Offset position) { + return pyramidFunnelPixelToPoint(position, seriesRenderer); + } } diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/renderer_extension.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/renderer_extension.dart new file mode 100644 index 000000000..789843726 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/renderer_extension.dart @@ -0,0 +1,70 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../base/funnel_state_properties.dart'; +import 'funnel_series.dart'; + +/// Creates series renderer for Funnel series +class FunnelSeriesRendererExtension extends FunnelSeriesRenderer { + /// Calling the default constructor of FunnelSeriesRendererBase class. + FunnelSeriesRendererExtension() { + seriesType = 'funnel'; + } + + /// Specifies the funnel series + late FunnelSeries series; + + /// Specifies the funnel series + late String seriesType; + + /// Specifies the data points + late List> dataPoints; + + /// Specifies the render points + late List> renderPoints; + + /// Specifies the value of sum of points + late num sumOfPoints; + + /// Specifies the triangular size + late Size triangleSize; + + /// Specifies the funnel neck size + late Size neckSize; + + /// Specifies the distance explode + late num explodeDistance; + + /// Specifies the maximum data label region + late Rect maximumDataLabelRegion; + + /// Specifies the funnel series controller + FunnelSeriesController? controller; + + /// Specifies the funnel state properties + late FunnelStateProperties stateProperties; + + /// Specifies the repaint notifier + late ValueNotifier repaintNotifier; + + /// Specifies the data label setting renderer + late DataLabelSettingsRenderer dataLabelSettingsRenderer; + + /// Specifies the selection behavior renderer + late SelectionBehaviorRenderer selectionBehaviorRenderer; + + /// Specifies the value of selection behavior + late SelectionBehavior selectionBehavior; + + /// Specifies whether the selection is enables + bool isSelectionEnable = false; + + /// Specifies whether to repaint the chart + bool needsRepaint = true; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/series_base.dart new file mode 100644 index 000000000..a8804c5f4 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/funnel_chart/renderer/series_base.dart @@ -0,0 +1,722 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/chart_series/series.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../../pyramid_chart/utils/common.dart'; + +/// Represents the funnel series base +class FunnelSeriesBase extends ChartSeries + implements TriangularChartEmptyPointBehavior { + /// Creates an instance of funnel series base + FunnelSeriesBase({ + this.key, + this.onCreateRenderer, + this.onRendererCreated, + this.onPointTap, + this.onPointDoubleTap, + this.onPointLongPress, + this.dataSource, + this.xValueMapper, + this.yValueMapper, + this.pointColorMapper, + this.textFieldMapper, + this.name, + this.explodeIndex, + String? neckWidth, + String? neckHeight, + String? height, + String? width, + double? gapRatio, + EmptyPointSettings? emptyPointSettings, + String? explodeOffset, + bool? explode, + ActivationMode? explodeGesture, + Color? borderColor, + double? borderWidth, + LegendIconType? legendIconType, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + double? animationDelay, + double? opacity, + SelectionBehavior? selectionBehavior, + List? initialSelectedDataIndexes, + }) : neckWidth = neckWidth ?? '20%', + neckHeight = neckHeight ?? '20%', + height = height ?? '80%', + width = width ?? '80%', + gapRatio = gapRatio ?? 0, + emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), + explodeOffset = explodeOffset ?? '10%', + explode = explode ?? false, + explodeGesture = explodeGesture ?? ActivationMode.singleTap, + borderColor = borderColor ?? Colors.transparent, + borderWidth = borderWidth ?? 0.0, + legendIconType = legendIconType ?? LegendIconType.seriesType, + dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), + animationDuration = animationDuration ?? 1500, + animationDelay = animationDelay ?? 0, + opacity = opacity ?? 1, + initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], + selectionBehavior = selectionBehavior ?? SelectionBehavior(), + super( + name: name, + dataSource: dataSource, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper) { + // _renderer = _FunnelSeriesRender(); + } + + ///A collection of data required for rendering the series. + /// + ///If no data source is specified, + ///empty chart will be rendered without series. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// )], + /// )); + ///} + @override + final List? dataSource; + + ///Maps the field name, which will be considered as x-values. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// xValueMapper: (ChartData data, _) => data.xVal, + /// )], + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal); + /// final String xVal; + /// final int yVal; + ///} + ///``` + @override + final ChartIndexedValueMapper? xValueMapper; + + ///Maps the field name, which will be considered as y-values. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// yValueMapper: (ChartData data, _) => data.yVal, + /// )], + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal); + /// final String xVal; + /// final int yVal; + ///} + ///``` + @override + final ChartIndexedValueMapper? yValueMapper; + + ///Name of the series. + /// + ///Defaults to '' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// name: 'Pyramid' + /// )); + ///} + ///``` + @override + final String? name; + + ///Neck height of funnel. + /// + ///Defaults to '20%' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// neckHeight: '10%' + /// )); + ///} + ///``` + final String neckHeight; + + ///Neck width of funnel. + /// + ///Defaults to '20%' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// neckWidth: '10%' + /// )); + ///} + ///``` + final String neckWidth; + + ///Height of the series. + /// + ///Defaults to '80%' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// height:'50%' + /// )); + ///} + ///``` + final String height; + + ///Width of the series. + /// + ///Defaults to '80%' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// width:'50%' + /// )); + ///} + ///``` + final String width; + + ///Gap ratio between the segments of pyramid. + /// + /// Ranges from 0 to 1 + /// + ///Defaults to 0. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// gapRatio: 0.3 + /// )); + ///} + ///``` + final double gapRatio; + + ///Customizes the empty data points in the series + /// + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// emptyPointSettings: EmptyPointSettings (color: Colors.red)) + /// )); + ///} + ///``` + @override + final EmptyPointSettings emptyPointSettings; + + ///Offset of exploded slice. + /// + /// The value ranges from 0% to 100%. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explodeOffset: '5%') + /// )); + ///} + ///``` + final String explodeOffset; + + ///Enables or disables the explode of slices on tap. + /// + ///Default to false. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explode: true) + /// )); + ///} + ///``` + final bool explode; + + ///Gesture for activating the explode. + /// + ///Explode can be activated in tap, double tap, + ///and long press. + /// + ///Defaults to ActivationMode.tap + /// + ///Also refer [ActivationMode] + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explode: true, + /// explodeGesture: ActivationMode.singleTap + /// ) + /// )); + ///} + ///``` + final ActivationMode explodeGesture; + + ///Border width of the data points in the series. + /// + ///Defaults to 0 + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// borderWidth: 2 + /// ) + /// )); + ///} + ///``` + @override + final double borderWidth; + + ///Border color of the data points in the series. + /// + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// borderColor: Colors.red + /// ) + /// )); + ///} + ///``` + @override + final Color borderColor; + + ///Shape of the legend icon. + /// + ///Any shape in the LegendIconType can be applied to this property. + ///By default, icon will be rendered based on the type of the series. + /// + /// + ///Also refer [LegendIconType] + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// legendIconType: LegendIconType.diamond, + /// ) + /// )); + ///} + ///``` + @override + final LegendIconType legendIconType; + + ///Enables the data label of the series + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// dataLabelSettings: DataLabelSettings(isVisible: true), + /// ) + /// )); + ///} + ///``` + @override + final DataLabelSettings dataLabelSettings; + + //Duration for animating the data points. + /// + ///Defaults to 1500 + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// animationDuration: 2000, + /// ) + /// )); + ///} + ///``` + @override + final double animationDuration; + + /// Delay duration of the series animation.It takes a millisecond value as input. + /// By default, the series will get animated for the specified duration. + /// If animationDelay is specified, then the series will begin to animate + /// after the specified duration. + /// + /// Defaults to 0. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// animationDelay: 500, + /// ) + /// )); + ///} + ///``` + @override + final double animationDelay; + + ///Maps the field name, which will be considered as data point color. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// pointColorMapper: (ChartData data, _) => data.pointColor, + /// ) + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal, [this.pointColor]); + /// final String xVal; + /// final int yVal; + /// final Color pointColor; + ///} + ///``` + @override + final ChartIndexedValueMapper? pointColorMapper; + + ///Maps the field name, which will be considered as text for data label. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// textFieldMapper: (ChartData data, _) => data.xVal, + /// ) + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal, [this.pointColor]); + /// final String xVal; + /// final int yVal; + /// final Color pointColor; + ///} + ///``` + final ChartIndexedValueMapper? textFieldMapper; + + ///Opacity of the series. + /// + /// The value ranges from 0 to 1. + /// + ///Defaults to 1 + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// opacity: 0.5, + /// ) + /// )); + ///} + ///``` + @override + final double opacity; + + ///Customizes the selection of series. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// selectionBehavior: selectionBehavior( + /// selectedColor: Colors.red, + /// unselectedColor: Colors.grey + /// ), + /// ) + /// )); + ///} + ///``` + @override + final SelectionBehavior selectionBehavior; + + ///Index of the slice to explode it at the initial rendering. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// explodeIndex: 1, + /// explode: true + /// ) + /// )); + ///} + ///``` + final num? explodeIndex; + + /// List of data indexes initially selected + /// + /// Defaults to `null`. + ///```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// child: SfFunnelChart( + /// initialSelectedDataIndexes: [IndexesModel(1, 0)] + /// ) + /// ) + /// ) + /// ); + /// } + List initialSelectedDataIndexes; + + ///Key to identify a series in a collection. + + /// + + ///On specifying [ValueKey] as the series [key], existing series index can be + /// changed in the series collection without losing its state. + + /// + + ///When a new series is added dynamically to the collection, existing series index will be changed. On that case, + + /// the existing series and its state will be linked based on its chart type and this key value. + + /// + + ///Defaults to `null`. + + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// key: const ValueKey('funnel_series_key'), + /// ), + /// )); + ///} + ///``` + final ValueKey? key; + + ///Used to create the renderer for custom series. + /// + ///This is applicable only when the custom series is defined in the sample + /// and for built-in series types, it is not applicable. + /// + ///Renderer created in this will hold the series state and + /// this should be created for each series. [onCreateRenderer] callback + /// function should return the renderer class and should not return null. + /// + ///Series state will be created only once per series and will not be created + ///again when we update the series. + /// + ///Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// onCreateRenderer:(FunnelSeries series){ + /// return CustomLinerSeriesRenderer(); + /// } + /// ), + /// )); + /// } + /// class CustomLinerSeriesRenderer extends FunnelSeriesRenderer { + /// // custom implementation here... + /// } + ///``` + final ChartSeriesRendererFactory? onCreateRenderer; + + ///Triggers when the series renderer is created. + + /// + + ///Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. + + /// + + ///Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfFunnelChart( + /// series: FunnelSeries( + /// onRendererCreated: (ChartSeriesController controller) { + /// _chartSeriesController = controller; + /// }, + /// ], + /// )); + ///} + ///``` + final FunnelSeriesRendererCreatedCallback? onRendererCreated; + + ///Called when tapped on the chart data point. + /// + ///The user can fetch the series index, point index, view port point index and + /// data of the tapped data point. + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointTap: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// )); + ///} + ///``` + final ChartPointInteractionCallback? onPointTap; + + ///Called when double tapped on the chart data point. + /// + ///The user can fetch the series index, point index, view port point index and + /// data of the double-tapped data point. + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointDoubleTap: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// )); + ///} + ///``` + final ChartPointInteractionCallback? onPointDoubleTap; + + ///Called when long pressed on the chart data point. + /// + ///The user can fetch the series index, point index, view port point index and + /// data of the long-pressed data point. + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointLongPress: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// )); + ///} + ///``` + final ChartPointInteractionCallback? onPointLongPress; + + /// To calculate empty point values if null values are provided + @override + void calculateEmptyPointValue( + int pointIndex, dynamic currentPoint, dynamic seriesRenderer) { + final List> dataPoints = seriesRenderer.dataPoints; + final EmptyPointSettings empty = emptyPointSettings; + final int pointLength = dataPoints.length; + final PointInfo point = dataPoints[pointIndex]; + if (point.y == null) { + switch (empty.mode) { + case EmptyPointMode.average: + final num previous = + pointIndex - 1 >= 0 ? dataPoints[pointIndex - 1].y ?? 0 : 0; + final num next = pointIndex + 1 <= pointLength - 1 + ? dataPoints[pointIndex + 1].y ?? 0 + : 0; + point.y = (previous + next).abs() / 2; + point.isVisible = true; + point.isEmpty = true; + break; + case EmptyPointMode.zero: + point.y = 0; + point.isVisible = true; + point.isEmpty = true; + break; + case EmptyPointMode.drop: + case EmptyPointMode.gap: + point.isEmpty = true; + point.isVisible = false; + break; + } + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/chart_base.dart similarity index 57% rename from packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart rename to packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/chart_base.dart index 41dd6b1fc..5485cec17 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/series_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/chart_base.dart @@ -1,34 +1,59 @@ -part of charts; - -class _PyramidSeries { - _PyramidSeries(this._chartState); - - final SfPyramidChartState _chartState; - +import 'dart:math'; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/utils/enum.dart'; +import '../../circular_chart/renderer/common.dart'; +import '../../circular_chart/utils/helper.dart'; +import '../../common/common.dart'; +import '../../common/event_args.dart'; +import '../../common/legend/renderer.dart'; + +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../renderer/pyramid_series.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/common.dart'; +import '../utils/helper.dart'; +import 'pyramid_base.dart'; +import 'pyramid_state_properties.dart'; + +/// Represents the pyramid series base +class PyramidChartBase { + /// Creates an instance of pyramid chart base + PyramidChartBase(this.stateProperties); + + /// Specifies the pyramid state properties + final PyramidStateProperties stateProperties; + + /// Specifies the current pyramid series late PyramidSeries currentSeries; - List visibleSeriesRenderers = - []; + /// Specifies the list of visible series renderer + List visibleSeriesRenderers = + []; + + /// Specifies the selection args SelectionArgs? _selectionArgs; /// To find the visible series - void _findVisibleSeries() { - _chartState._chartSeries.visibleSeriesRenderers[0]._dataPoints = + void findVisibleSeries() { + stateProperties.chartSeries.visibleSeriesRenderers[0].dataPoints = >[]; //Considered the first series, since in triangular series one series will be considered for rendering - final PyramidSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - currentSeries = seriesRenderer._series; - //Setting seriestype - seriesRenderer._seriesType = 'pyramid'; + final PyramidSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + currentSeries = seriesRenderer.series; + //Setting series type + seriesRenderer.seriesType = 'pyramid'; final ChartIndexedValueMapper? xValue = currentSeries.xValueMapper; final ChartIndexedValueMapper? yValue = currentSeries.yValueMapper; for (int pointIndex = 0; pointIndex < currentSeries.dataSource!.length; pointIndex++) { if (xValue!(pointIndex) != null) { - seriesRenderer._dataPoints + seriesRenderer.dataPoints .add(PointInfo(xValue(pointIndex), yValue!(pointIndex))); } } @@ -38,19 +63,21 @@ class _PyramidSeries { } /// To calculate empty point values if null values are provided - void _calculatePyramidEmptyPoints(PyramidSeriesRenderer seriesRenderer) { - for (int i = 0; i < seriesRenderer._dataPoints.length; i++) { - if (seriesRenderer._dataPoints[i].y == null) { - seriesRenderer._series.calculateEmptyPointValue( - i, seriesRenderer._dataPoints[i], seriesRenderer); + void _calculatePyramidEmptyPoints( + PyramidSeriesRendererExtension seriesRenderer) { + for (int i = 0; i < seriesRenderer.dataPoints.length; i++) { + if (seriesRenderer.dataPoints[i].y == null) { + seriesRenderer.series.calculateEmptyPointValue( + i, seriesRenderer.dataPoints[i], seriesRenderer); } } } /// To process the data points for series render - void _processDataPoints() { - for (final PyramidSeriesRenderer seriesRenderer in visibleSeriesRenderers) { - currentSeries = seriesRenderer._series; + void processDataPoints() { + for (final PyramidSeriesRendererExtension seriesRenderer + in visibleSeriesRenderers) { + currentSeries = seriesRenderer.series; _calculatePyramidEmptyPoints(seriesRenderer); _calculateVisiblePoints(seriesRenderer); _setPointStyle(seriesRenderer); @@ -59,30 +86,30 @@ class _PyramidSeries { } /// To calculate the visible points in a series - void _calculateVisiblePoints(PyramidSeriesRenderer seriesRenderer) { - final List> points = seriesRenderer._dataPoints; - seriesRenderer._renderPoints = >[]; + void _calculateVisiblePoints(PyramidSeriesRendererExtension seriesRenderer) { + final List> points = seriesRenderer.dataPoints; + seriesRenderer.renderPoints = >[]; for (int i = 0; i < points.length; i++) { if (points[i].isVisible) { - seriesRenderer._renderPoints!.add(points[i]); + seriesRenderer.renderPoints!.add(points[i]); } } } /// To set style properties for current point - void _setPointStyle(PyramidSeriesRenderer seriesRenderer) { - currentSeries = seriesRenderer._series; - final List palette = _chartState._chart.palette; + void _setPointStyle(PyramidSeriesRendererExtension seriesRenderer) { + currentSeries = seriesRenderer.series; + final List palette = stateProperties.chart.palette; final ChartIndexedValueMapper? pointColor = currentSeries.pointColorMapper; final EmptyPointSettings empty = currentSeries.emptyPointSettings; final ChartIndexedValueMapper? textMapping = currentSeries.textFieldMapper; - final List> points = seriesRenderer._renderPoints!; + final List> points = seriesRenderer.renderPoints!; PointInfo currentPoint; - List<_MeasureWidgetContext> legendToggles; - _MeasureWidgetContext item; - _LegendRenderContext legendRenderContext; + List legendToggles; + MeasureWidgetContext item; + LegendRenderContext legendRenderContext; for (int i = 0; i < points.length; i++) { currentPoint = points[i]; currentPoint.fill = currentPoint.isEmpty && @@ -107,9 +134,9 @@ class _PyramidSeries { ? textMapping(i) ?? currentPoint.y.toString() : currentPoint.y.toString()); - if (_chartState._chart.legend.legendItemBuilder != null) { + if (stateProperties.chart.legend.legendItemBuilder != null) { legendToggles = - _chartState._renderingDetails.legendToggleTemplateStates; + stateProperties.renderingDetails.legendToggleTemplateStates; if (legendToggles.isNotEmpty) { for (int j = 0; j < legendToggles.length; j++) { item = legendToggles[j]; @@ -120,12 +147,12 @@ class _PyramidSeries { } } } else { - if (_chartState._renderingDetails.legendToggleStates.isNotEmpty) { + if (stateProperties.renderingDetails.legendToggleStates.isNotEmpty) { for (int j = 0; - j < _chartState._renderingDetails.legendToggleStates.length; + j < stateProperties.renderingDetails.legendToggleStates.length; j++) { legendRenderContext = - _chartState._renderingDetails.legendToggleStates[j]; + stateProperties.renderingDetails.legendToggleStates[j]; if (i == legendRenderContext.seriesIndex) { currentPoint.isVisible = false; break; @@ -137,25 +164,26 @@ class _PyramidSeries { } /// To find the sum of points - void _findSumOfPoints(PyramidSeriesRenderer seriesRenderer) { - seriesRenderer._sumOfPoints = 0; - for (final PointInfo point in seriesRenderer._renderPoints!) { + void _findSumOfPoints(PyramidSeriesRendererExtension seriesRenderer) { + seriesRenderer.sumOfPoints = 0; + for (final PointInfo point in seriesRenderer.renderPoints!) { if (point.isVisible) { - seriesRenderer._sumOfPoints += point.y!.abs(); + seriesRenderer.sumOfPoints += point.y!.abs(); } } } - /// To initialise the series properties in chart - void _initializeSeriesProperties(PyramidSeriesRenderer seriesRenderer) { - final PyramidSeries series = seriesRenderer._series; - final Rect chartAreaRect = _chartState._renderingDetails.chartAreaRect; - final bool reverse = seriesRenderer._seriesType == 'pyramid'; - seriesRenderer._triangleSize = Size( - _percentToValue(series.width, chartAreaRect.width)!.toDouble(), - _percentToValue(series.height, chartAreaRect.height)!.toDouble()); - seriesRenderer._explodeDistance = - _percentToValue(series.explodeOffset, chartAreaRect.width)!; + /// To initialize the series properties in chart + void initializeSeriesProperties( + PyramidSeriesRendererExtension seriesRenderer) { + final PyramidSeries series = seriesRenderer.series; + final Rect chartAreaRect = stateProperties.renderingDetails.chartAreaRect; + final bool reverse = seriesRenderer.seriesType == 'pyramid'; + seriesRenderer.triangleSize = Size( + percentToValue(series.width, chartAreaRect.width)!.toDouble(), + percentToValue(series.height, chartAreaRect.height)!.toDouble()); + seriesRenderer.explodeDistance = + percentToValue(series.explodeOffset, chartAreaRect.width)!; if (series.pyramidMode == PyramidMode.linear) { _initializeSizeRatio(seriesRenderer, reverse); } else { @@ -163,33 +191,34 @@ class _PyramidSeries { } } - /// To intialize the surface size ratio in chart - void _initializeSurfaceSizeRatio(PyramidSeriesRenderer seriesRenderer) { - final num count = seriesRenderer._renderPoints!.length; - final num sumOfValues = seriesRenderer._sumOfPoints; + /// To initialize the surface size ratio in chart + void _initializeSurfaceSizeRatio( + PyramidSeriesRendererExtension seriesRenderer) { + final num count = seriesRenderer.renderPoints!.length; + final num sumOfValues = seriesRenderer.sumOfPoints; List y; List height; y = []; height = []; - final num gapRatio = min(max(seriesRenderer._series.gapRatio, 0), 1); + final num gapRatio = min(max(seriesRenderer.series.gapRatio, 0), 1); final num gapHeight = gapRatio / (count - 1); final num preSum = _getSurfaceHeight(0, sumOfValues); num currY = 0; PointInfo point; for (int i = 0; i < count; i++) { - point = seriesRenderer._renderPoints![i]; + point = seriesRenderer.renderPoints![i]; if (point.isVisible) { y.add(currY); height.add(_getSurfaceHeight(currY, point.y!.abs())); currY += height[i] + gapHeight * preSum; } } - final num coef = 1 / (currY - gapHeight * preSum); + final num coeff = 1 / (currY - gapHeight * preSum); for (int i = 0; i < count; i++) { - point = seriesRenderer._renderPoints![i]; + point = seriesRenderer.renderPoints![i]; if (point.isVisible) { - point.yRatio = coef * y[i]; - point.heightRatio = coef * height[i]; + point.yRatio = coeff * y[i]; + point.heightRatio = coeff * height[i]; } } } @@ -212,20 +241,20 @@ class _PyramidSeries { return 0; } - /// To initialise size ratio for the pyramid - void _initializeSizeRatio(PyramidSeriesRenderer seriesRenderer, + /// To initialize size ratio for the pyramid + void _initializeSizeRatio(PyramidSeriesRendererExtension seriesRenderer, [bool? reverse]) { - final List> points = seriesRenderer._renderPoints!; + final List> points = seriesRenderer.renderPoints!; double y; assert( // ignore: unnecessary_null_comparison - !(seriesRenderer._series.gapRatio != null) || - seriesRenderer._series.gapRatio >= 0 && - seriesRenderer._series.gapRatio <= 1, + !(seriesRenderer.series.gapRatio != null) || + seriesRenderer.series.gapRatio >= 0 && + seriesRenderer.series.gapRatio <= 1, 'The gap ratio for the pyramid chart must be between 0 and 1.'); - final double gapRatio = min(max(seriesRenderer._series.gapRatio, 0), 1); + final double gapRatio = min(max(seriesRenderer.series.gapRatio, 0), 1); final double coEff = - 1 / (seriesRenderer._sumOfPoints * (1 + gapRatio / (1 - gapRatio))); + 1 / (seriesRenderer.sumOfPoints * (1 + gapRatio / (1 - gapRatio))); final double spacing = gapRatio / (points.length - 1); y = 0; int index; @@ -242,53 +271,52 @@ class _PyramidSeries { } /// To explode current point index - void _pointExplode(int pointIndex) { + void pointExplode(int pointIndex) { bool existExplodedRegion = false; - final PyramidSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - final SfPyramidChartState chartState = _chartState; - final PointInfo point = seriesRenderer._renderPoints![pointIndex]; - if (seriesRenderer._series.explode) { - if (chartState._renderingDetails.explodedPoints.isNotEmpty) { + final PyramidSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; + final PointInfo point = seriesRenderer.renderPoints![pointIndex]; + if (seriesRenderer.series.explode) { + if (stateProperties.renderingDetails.explodedPoints.isNotEmpty) { existExplodedRegion = true; final int previousIndex = - chartState._renderingDetails.explodedPoints[0]; - seriesRenderer._renderPoints![previousIndex].explodeDistance = 0; + stateProperties.renderingDetails.explodedPoints[0]; + seriesRenderer.renderPoints![previousIndex].explodeDistance = 0; point.explodeDistance = - previousIndex == pointIndex ? 0 : seriesRenderer._explodeDistance; - chartState._renderingDetails.explodedPoints[0] = pointIndex; + previousIndex == pointIndex ? 0 : seriesRenderer.explodeDistance; + stateProperties.renderingDetails.explodedPoints[0] = pointIndex; if (previousIndex == pointIndex) { - chartState._renderingDetails.explodedPoints = []; + stateProperties.renderingDetails.explodedPoints = []; } - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; } if (!existExplodedRegion) { - point.explodeDistance = seriesRenderer._explodeDistance; - chartState._renderingDetails.explodedPoints.add(pointIndex); - chartState._renderingDetails.seriesRepaintNotifier.value++; + point.explodeDistance = seriesRenderer.explodeDistance; + stateProperties.renderingDetails.explodedPoints.add(pointIndex); + stateProperties.renderingDetails.seriesRepaintNotifier.value++; } - _calculatePathRegion(pointIndex, seriesRenderer); + calculatePathRegion(pointIndex, seriesRenderer); } } /// To calculate region path for rendering chart - void _calculatePathRegion( - int pointIndex, PyramidSeriesRenderer seriesRenderer) { + void calculatePathRegion( + int pointIndex, PyramidSeriesRendererExtension seriesRenderer) { final PointInfo currentPoint = - seriesRenderer._renderPoints![pointIndex]; + seriesRenderer.renderPoints![pointIndex]; currentPoint.pathRegion = []; - final SfPyramidChartState chartState = _chartState; - final Size area = seriesRenderer._triangleSize; - final Rect rect = chartState._renderingDetails.chartContainerRect; + final Size area = seriesRenderer.triangleSize; + final Rect rect = stateProperties.renderingDetails.chartContainerRect; final num seriesTop = rect.top + (rect.height - area.height) / 2; const num offset = 0; + // ignore: prefer_if_null_operators final num extraSpace = (currentPoint.explodeDistance != null ? currentPoint.explodeDistance! - : _isNeedExplode(pointIndex, currentSeries, _chartState) - ? seriesRenderer._explodeDistance + : isNeedExplode(pointIndex, currentSeries, stateProperties) + ? seriesRenderer.explodeDistance : 0) + - (rect.width - seriesRenderer._triangleSize.width) / 2; + (rect.width - seriesRenderer.triangleSize.width) / 2; final num emptySpaceAtLeft = extraSpace + rect.left; num top = currentPoint.yRatio; num bottom = currentPoint.yRatio + currentPoint.heightRatio; @@ -309,15 +337,15 @@ class _PyramidSeries { currentPoint.pathRegion.add(Offset(line2X.toDouble(), line2Y.toDouble())); currentPoint.pathRegion.add(Offset(line3X.toDouble(), line3Y.toDouble())); currentPoint.pathRegion.add(Offset(line4X.toDouble(), line4Y.toDouble())); - _calculatePathSegment(seriesRenderer._seriesType, currentPoint); + _calculatePathSegment(seriesRenderer.seriesType, currentPoint); } /// To calculate pyramid segments - void _calculatePyramidSegments( - Canvas canvas, int pointIndex, PyramidSeriesRenderer seriesRenderer) { - _calculatePathRegion(pointIndex, seriesRenderer); + void calculatePyramidSegments(Canvas canvas, int pointIndex, + PyramidSeriesRendererExtension seriesRenderer) { + calculatePathRegion(pointIndex, seriesRenderer); final PointInfo currentPoint = - seriesRenderer._renderPoints![pointIndex]; + seriesRenderer.renderPoints![pointIndex]; final Path path = Path(); path.moveTo(currentPoint.pathRegion[0].dx, currentPoint.pathRegion[0].dy); path.lineTo(currentPoint.pathRegion[1].dx, currentPoint.pathRegion[0].dy); @@ -325,18 +353,18 @@ class _PyramidSeries { path.lineTo(currentPoint.pathRegion[2].dx, currentPoint.pathRegion[2].dy); path.lineTo(currentPoint.pathRegion[3].dx, currentPoint.pathRegion[3].dy); path.close(); - if (pointIndex == seriesRenderer._renderPoints!.length - 1) { - seriesRenderer._maximumDataLabelRegion = path.getBounds(); + if (pointIndex == seriesRenderer.renderPoints!.length - 1) { + seriesRenderer.maximumDataLabelRegion = path.getBounds(); } _segmentPaint(canvas, path, pointIndex, seriesRenderer); } /// To paint the funnel segments void _segmentPaint(Canvas canvas, Path path, int pointIndex, - PyramidSeriesRenderer seriesRenderer) { - final PointInfo point = seriesRenderer._renderPoints![pointIndex]; - final _StyleOptions? style = - _getPointStyle(pointIndex, seriesRenderer, _chartState._chart, point); + PyramidSeriesRendererExtension seriesRenderer) { + final PointInfo point = seriesRenderer.renderPoints![pointIndex]; + final StyleOptions? style = _getPointStyle( + pointIndex, seriesRenderer, stateProperties.chart, point); final Color fillColor = style != null && style.fill != null ? style.fill! : point.fill; @@ -353,11 +381,11 @@ class _PyramidSeries { ? style.opacity! : currentSeries.opacity; - _drawPath( + drawPath( canvas, - _StyleOptions( + StyleOptions( fill: fillColor, - strokeWidth: _chartState._renderingDetails.animateCompleted + strokeWidth: stateProperties.renderingDetails.animateCompleted ? strokeWidth : 0, strokeColor: strokeColor, @@ -379,53 +407,52 @@ class _PyramidSeries { } /// To add selection points to selection list - void _seriesPointSelection(int pointIndex, ActivationMode mode) { + void seriesPointSelection(int pointIndex, ActivationMode mode) { bool isPointAlreadySelected = false; - final SfPyramidChart chart = _chartState._chart; - final PyramidSeriesRenderer seriesRenderer = - _chartState._chartSeries.visibleSeriesRenderers[0]; - final SfPyramidChartState chartState = _chartState; + final SfPyramidChart chart = stateProperties.chart; + final PyramidSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; int? currentSelectedIndex; const int seriesIndex = 0; - if (seriesRenderer._isSelectionEnable && mode == chart.selectionGesture) { - if (chartState._renderingDetails.selectionData.isNotEmpty) { + if (seriesRenderer.isSelectionEnable && mode == chart.selectionGesture) { + if (stateProperties.renderingDetails.selectionData.isNotEmpty) { if (!chart.enableMultiSelection && - _chartState._renderingDetails.selectionData.isNotEmpty && - _chartState._renderingDetails.selectionData.length > 1) { - if (_chartState._renderingDetails.selectionData + stateProperties.renderingDetails.selectionData.isNotEmpty && + stateProperties.renderingDetails.selectionData.length > 1) { + if (stateProperties.renderingDetails.selectionData .contains(pointIndex)) { currentSelectedIndex = pointIndex; } - _chartState._renderingDetails.selectionData.clear(); + stateProperties.renderingDetails.selectionData.clear(); if (currentSelectedIndex != null) { - _chartState._renderingDetails.selectionData.add(pointIndex); + stateProperties.renderingDetails.selectionData.add(pointIndex); } } int selectionIndex; for (int i = 0; - i < chartState._renderingDetails.selectionData.length; + i < stateProperties.renderingDetails.selectionData.length; i++) { - selectionIndex = chartState._renderingDetails.selectionData[i]; + selectionIndex = stateProperties.renderingDetails.selectionData[i]; if (!chart.enableMultiSelection) { isPointAlreadySelected = - chartState._renderingDetails.selectionData.length == 1 && + stateProperties.renderingDetails.selectionData.length == 1 && pointIndex == selectionIndex; - if (seriesRenderer._selectionBehavior.toggleSelection == true || + if (seriesRenderer.selectionBehavior.toggleSelection == true || !isPointAlreadySelected) { - chartState._renderingDetails.selectionData.removeAt(i); + stateProperties.renderingDetails.selectionData.removeAt(i); } - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); } } else if (pointIndex == selectionIndex) { - if (seriesRenderer._selectionBehavior.toggleSelection == true) { - chartState._renderingDetails.selectionData.removeAt(i); + if (seriesRenderer.selectionBehavior.toggleSelection == true) { + stateProperties.renderingDetails.selectionData.removeAt(i); } isPointAlreadySelected = true; - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!(_getSelectionEventArgs( seriesRenderer, seriesIndex, selectionIndex)); @@ -435,8 +462,8 @@ class _PyramidSeries { } } if (!isPointAlreadySelected) { - chartState._renderingDetails.selectionData.add(pointIndex); - chartState._renderingDetails.seriesRepaintNotifier.value++; + stateProperties.renderingDetails.selectionData.add(pointIndex); + stateProperties.renderingDetails.seriesRepaintNotifier.value++; if (chart.onSelectionChanged != null) { chart.onSelectionChanged!( _getSelectionEventArgs(seriesRenderer, seriesIndex, pointIndex)); @@ -446,22 +473,22 @@ class _PyramidSeries { } /// To return style options for the point on selection - _StyleOptions? _getPointStyle( + StyleOptions? _getPointStyle( int currentPointIndex, - PyramidSeriesRenderer seriesRenderer, + PyramidSeriesRendererExtension seriesRenderer, SfPyramidChart chart, PointInfo point) { - _StyleOptions? pointStyle; - final dynamic selection = seriesRenderer._series.selectionBehavior; + StyleOptions? pointStyle; + final dynamic selection = seriesRenderer.series.selectionBehavior; if (selection.enable == true) { - if (_chartState._renderingDetails.selectionData.isNotEmpty) { + if (stateProperties.renderingDetails.selectionData.isNotEmpty) { int selectionIndex; for (int i = 0; - i < _chartState._renderingDetails.selectionData.length; + i < stateProperties.renderingDetails.selectionData.length; i++) { - selectionIndex = _chartState._renderingDetails.selectionData[i]; + selectionIndex = stateProperties.renderingDetails.selectionData[i]; if (currentPointIndex == selectionIndex) { - pointStyle = _StyleOptions( + pointStyle = StyleOptions( fill: _selectionArgs != null ? _selectionArgs!.selectedColor : selection!.selectedColor, @@ -474,8 +501,8 @@ class _PyramidSeries { opacity: selection.selectedOpacity); break; } else if (i == - _chartState._renderingDetails.selectionData.length - 1) { - pointStyle = _StyleOptions( + stateProperties.renderingDetails.selectionData.length - 1) { + pointStyle = StyleOptions( fill: _selectionArgs != null ? _selectionArgs!.unselectedColor : selection.unselectedColor, @@ -495,14 +522,16 @@ class _PyramidSeries { /// To perform selection event and return selectionArgs SelectionArgs _getSelectionEventArgs( - PyramidSeriesRenderer seriesRenderer, int seriesIndex, int pointIndex) { - final SfPyramidChart chart = seriesRenderer._chartState._chart; + PyramidSeriesRendererExtension seriesRenderer, + int seriesIndex, + int pointIndex) { + final SfPyramidChart chart = seriesRenderer.stateProperties.chart; // ignore: unnecessary_null_comparison if (seriesRenderer != null && //ignore: unnecessary_null_comparison chart.series != null && pointIndex < chart.series.dataSource!.length) { - final dynamic selectionBehavior = seriesRenderer._selectionBehavior; + final dynamic selectionBehavior = seriesRenderer.selectionBehavior; _selectionArgs = SelectionArgs( seriesRenderer: seriesRenderer, seriesIndex: seriesIndex, diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart index 59d51671f..9ed3ff737 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_base.dart @@ -1,4 +1,31 @@ -part of charts; +import 'dart:async'; +import 'dart:ui'; +import 'dart:ui' as dart_ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../common/common.dart'; +import '../../common/legend/legend.dart'; +import '../../common/legend/renderer.dart'; +import '../../common/rendering_details.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../../common/utils/typedef.dart'; +import '../renderer/pyramid_series.dart'; +import '../renderer/renderer_extension.dart'; +import '../renderer/series_controller.dart'; +import 'chart_base.dart'; +import 'pyramid_plot_area.dart'; +import 'pyramid_state_properties.dart'; ///Renders the pyramid chart /// @@ -6,6 +33,8 @@ part of charts; /// ///Properties such as opacity, [borderWidth], [borderColor], pointColorMapper ///are used to customize the appearance of a pyramid segment. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} //ignore:must_be_immutable class SfPyramidChart extends StatefulWidget { /// Creating an argument constructor of SfPyramidChart class. @@ -191,13 +220,13 @@ class SfPyramidChart extends StatefulWidget { /// Here,you can get the tooltip arguments and customize the arguments. final PyramidTooltipCallback? onTooltipRender; - /// Occurs when the datalabel is rendered,Here datalabel arguments can be customized. + /// Occurs when the data label is rendered,Here data label arguments can be customized. final PyramidDataLabelRenderCallback? onDataLabelRender; /// Occurs when the legend is tapped,the arguments can be used to customize the legend arguments final ChartLegendTapCallback? onLegendTapped; - /// Smart labelmode to avoid the overlapping of labels. + /// Smart label mode to avoid the overlapping of labels. final SmartLabelMode smartLabelMode; ///Data points or series can be selected while performing interaction on the chart. @@ -383,27 +412,8 @@ class SfPyramidChart extends StatefulWidget { /// class SfPyramidChartState extends State with TickerProviderStateMixin { - _PyramidDataLabelRenderer? _renderDataLabel; - int? _tooltipPointIndex; - //Internal variables - late String _seriesType; - late List> _dataPoints; - List>? _renderPoints; - late _PyramidSeries _chartSeries; - late _PyramidPlotArea _chartPlotArea; - - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfPyramidChart get _chart => widget; - - /// Specifies the chart rendering details - late _RenderingDetails _renderingDetails; - - // ignore: unused_element - bool get _animationCompleted { - return _renderingDetails.animationController.status != - AnimationStatus.forward; - } + /// Specifies the pyramid state properties + late PyramidStateProperties _stateProperties; /// Called when this object is inserted into the tree. /// @@ -418,8 +428,9 @@ class SfPyramidChartState extends State @override void initState() { - _renderingDetails = _RenderingDetails(); - _renderingDetails.didSizeChange = false; + _stateProperties = PyramidStateProperties( + renderingDetails: RenderingDetails(), chartState: this); + _initializeDefaultValues(); //Update and maintain the series state, when we update the series in the series collection // _createAndUpdateSeriesRenderer(); @@ -437,7 +448,7 @@ class SfPyramidChartState extends State @override void didChangeDependencies() { - _renderingDetails.chartTheme = SfChartTheme.of(context); + _stateProperties.renderingDetails.chartTheme = SfChartTheme.of(context); super.didChangeDependencies(); } @@ -458,11 +469,14 @@ class SfPyramidChartState extends State void didUpdateWidget(SfPyramidChart oldWidget) { //Update and maintain the series state, when we update the series in the series collection // _createAndUpdateSeriesRenderer(oldWidget); - if (_renderingDetails.tooltipBehaviorRenderer._chartTooltipState != null) { - _renderingDetails.tooltipBehaviorRenderer._show = false; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + _stateProperties.renderingDetails.tooltipBehaviorRenderer); + if (tooltipRenderingDetails.chartTooltipState != null) { + tooltipRenderingDetails.show = false; } super.didUpdateWidget(oldWidget); - _renderingDetails.widgetNeedUpdate = true; + _stateProperties.renderingDetails.widgetNeedUpdate = true; } /// Describes the part of the user interface represented by this widget. @@ -478,13 +492,16 @@ class SfPyramidChartState extends State @override Widget build(BuildContext context) { - _renderingDetails.oldDeviceOrientation = - _renderingDetails.oldDeviceOrientation == null + _stateProperties.renderingDetails.oldDeviceOrientation = + _stateProperties.renderingDetails.oldDeviceOrientation == null ? MediaQuery.of(context).orientation - : _renderingDetails.deviceOrientation; - _renderingDetails.deviceOrientation = MediaQuery.of(context).orientation; + : _stateProperties.renderingDetails.deviceOrientation; + _stateProperties.renderingDetails.deviceOrientation = + MediaQuery.of(context).orientation; + _stateProperties.isTooltipOrientationChanged = false; + return RepaintBoundary( - child: _ChartContainer( + child: ChartContainer( child: GestureDetector( child: Container( decoration: BoxDecoration( @@ -499,7 +516,7 @@ class SfPyramidChartState extends State width: widget.borderWidth)), child: Column( children: [ - _renderChartTitle(this), + renderChartTitle(_stateProperties), _renderChartElements() ], ))))); @@ -509,9 +526,9 @@ class SfPyramidChartState extends State /// /// The framework calls this method when this [State] object will never build again. After the framework calls [dispose], /// the [State] object is considered unmounted and the [mounted] property is false. It is an error to call [setState] at this - /// point. This stage of the lifecycle is terminal: there is no way to remount a [State] object that has been disposed. + /// point. This stage of the life cycle is terminal: there is no way to remount a [State] object that has been disposed. /// - /// Subclasses should override this method to release any resources retained by this object. + /// Sub classes should override this method to release any resources retained by this object. /// /// * In [dispose], unsubscribe from the object. /// @@ -519,8 +536,9 @@ class SfPyramidChartState extends State @override void dispose() { - _disposeAnimationController( - _renderingDetails.animationController, _repaintChartElements); + disposeAnimationController( + _stateProperties.renderingDetails.animationController, + _repaintChartElements); super.dispose(); } @@ -591,27 +609,32 @@ class SfPyramidChartState extends State return image; } - /// To intialize default chart elements value + /// To initialize default chart elements value void _initializeDefaultValues() { - _chartSeries = _PyramidSeries(this); - _renderingDetails.chartLegend = _ChartLegend(this); - _chartPlotArea = _PyramidPlotArea(chartState: this); - _renderingDetails.initialRender = true; - _renderingDetails.annotationController = AnimationController(vsync: this); - _renderingDetails.seriesRepaintNotifier = ValueNotifier(0); - _renderingDetails.legendToggleStates = <_LegendRenderContext>[]; - _renderingDetails.legendToggleTemplateStates = <_MeasureWidgetContext>[]; - _renderingDetails.explodedPoints = []; - _renderingDetails.animateCompleted = false; - _renderingDetails.isLegendToggled = false; - _renderingDetails.widgetNeedUpdate = false; - _renderingDetails.legendWidgetContext = <_MeasureWidgetContext>[]; - _renderingDetails.dataLabelTemplateRegions = []; - _renderingDetails.selectionData = []; - _renderingDetails.animationController = AnimationController(vsync: this) - ..addListener(_repaintChartElements); - _renderingDetails.tooltipBehaviorRenderer = TooltipBehaviorRenderer(this); - _renderingDetails.legendRenderer = LegendRenderer(widget.legend); + _stateProperties.chartSeries = PyramidChartBase(_stateProperties); + _stateProperties.renderingDetails.chartLegend = + ChartLegend(_stateProperties); + _stateProperties.renderingDetails.initialRender = true; + _stateProperties.renderingDetails.seriesRepaintNotifier = + ValueNotifier(0); + _stateProperties.renderingDetails.legendToggleStates = + []; + _stateProperties.renderingDetails.legendToggleTemplateStates = + []; + _stateProperties.renderingDetails.explodedPoints = []; + _stateProperties.renderingDetails.animateCompleted = false; + _stateProperties.renderingDetails.isLegendToggled = false; + _stateProperties.renderingDetails.widgetNeedUpdate = false; + _stateProperties.renderingDetails.legendWidgetContext = + []; + _stateProperties.renderingDetails.dataLabelTemplateRegions = []; + _stateProperties.renderingDetails.selectionData = []; + _stateProperties.renderingDetails.animationController = + AnimationController(vsync: this)..addListener(_repaintChartElements); + _stateProperties.renderingDetails.tooltipBehaviorRenderer = + TooltipBehaviorRenderer(_stateProperties); + _stateProperties.renderingDetails.legendRenderer = + LegendRenderer(widget.legend); } // In this method, create and update the series renderer for each series // @@ -621,38 +644,41 @@ class SfPyramidChartState extends State final PyramidSeriesRenderer? oldSeriesRenderer = // ignore: unnecessary_null_comparison oldWidget != null && oldWidget.series != null - ? _chartSeries.visibleSeriesRenderers[0] + ? _stateProperties.chartSeries.visibleSeriesRenderers[0] : null; PyramidSeries series; series = widget.series; // Create and update the series list here - PyramidSeriesRenderer seriesRenderers; + PyramidSeriesRendererExtension seriesRenderers; if (oldSeriesRenderer != null && - _isSameSeries(oldWidget!.series, series)) { - seriesRenderers = oldSeriesRenderer; + isSameSeries(oldWidget!.series, series)) { + seriesRenderers = oldSeriesRenderer as PyramidSeriesRendererExtension; } else { - seriesRenderers = series.createRenderer(series); - if (seriesRenderers._controller == null && + final PyramidSeriesRenderer renderer = series.createRenderer(series); + seriesRenderers = renderer is PyramidSeriesRendererExtension + ? renderer + : PyramidSeriesRendererExtension(); + + if (seriesRenderers.controller == null && series.onRendererCreated != null) { - seriesRenderers._controller = - PyramidSeriesController(seriesRenderers); - series.onRendererCreated!(seriesRenderers._controller!); + seriesRenderers.controller = PyramidSeriesController(seriesRenderers); + series.onRendererCreated!(seriesRenderers.controller!); } } - seriesRenderers._series = series; - seriesRenderers._isSelectionEnable = series.selectionBehavior.enable; - seriesRenderers._chartState = this; - _chartSeries.visibleSeriesRenderers + seriesRenderers.series = series; + seriesRenderers.isSelectionEnable = series.selectionBehavior.enable; + seriesRenderers.stateProperties = _stateProperties; + _stateProperties.chartSeries.visibleSeriesRenderers ..clear() ..add(seriesRenderers); } } void _repaintChartElements() { - _renderingDetails.seriesRepaintNotifier.value++; + _stateProperties.renderingDetails.seriesRepaintNotifier.value++; } /// To render chart elements @@ -660,26 +686,34 @@ class SfPyramidChartState extends State return Expanded(child: LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { Widget element; - _renderingDetails.prevSize = - _renderingDetails.prevSize ?? constraints.biggest; - _renderingDetails.didSizeChange = - _renderingDetails.prevSize != constraints.biggest; - _renderingDetails.prevSize = constraints.biggest; if (widget.series.dataSource != null) { _initialize(constraints); - - _chartSeries._findVisibleSeries(); - _chartSeries._processDataPoints(); - final List legendTemplates = _bindLegendTemplateWidgets(this); + _stateProperties.renderingDetails.prevSize = + _stateProperties.renderingDetails.prevSize ?? constraints.biggest; + _stateProperties.renderingDetails.didSizeChange = + _stateProperties.renderingDetails.prevSize != constraints.biggest; + _stateProperties.renderingDetails.prevSize = constraints.biggest; + + final PointInfo tooltipPoint = + _getChartPoints(_stateProperties); + SchedulerBinding.instance!.addPostFrameCallback((_) { + _validateStateMaintenance(_stateProperties, tooltipPoint); + }); + _stateProperties.chartSeries.findVisibleSeries(); + _stateProperties.chartSeries.processDataPoints(); + final List legendTemplates = + bindLegendTemplateWidgets(_stateProperties); if (legendTemplates.isNotEmpty && - _renderingDetails.legendWidgetContext.isEmpty) { + _stateProperties.renderingDetails.legendWidgetContext.isEmpty) { element = Container(child: Stack(children: legendTemplates)); SchedulerBinding.instance!.addPostFrameCallback((_) => _refresh()); } else { - _renderingDetails.chartLegend - ._calculateLegendBounds(_renderingDetails.chartLegend.chartSize); - element = _getElements( - this, _PyramidPlotArea(chartState: this), constraints)!; + _stateProperties.renderingDetails.chartLegend.calculateLegendBounds( + _stateProperties.renderingDetails.chartLegend.chartSize); + _stateProperties.chartPlotArea = + PyramidPlotArea(stateProperties: _stateProperties); + element = getElements( + _stateProperties, _stateProperties.chartPlotArea, constraints)!; } } else { element = Container(); @@ -689,10 +723,10 @@ class SfPyramidChartState extends State } void _refresh() { - final List<_MeasureWidgetContext> legendWidgetContexts = - _renderingDetails.legendWidgetContext; + final List legendWidgetContexts = + _stateProperties.renderingDetails.legendWidgetContext; if (legendWidgetContexts.isNotEmpty) { - _MeasureWidgetContext templateContext; + MeasureWidgetContext templateContext; RenderBox renderBox; for (int i = 0; i < legendWidgetContexts.length; i++) { templateContext = legendWidgetContexts[i]; @@ -705,652 +739,96 @@ class SfPyramidChartState extends State } } - // ignore:unused_element - void _redraw() { - _renderingDetails.initialRender = false; - if (_renderingDetails.tooltipBehaviorRenderer._chartTooltipState != null) { - _renderingDetails.tooltipBehaviorRenderer._show = false; - } - setState(() { - /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. - }); - } - - /// To intialize chart container area + /// To initialize chart container area void _initialize(BoxConstraints constraints) { - _renderingDetails.chartWidgets = []; + _stateProperties.renderingDetails.chartWidgets = []; final num width = constraints.maxWidth; final num height = constraints.maxHeight; final EdgeInsets margin = widget.margin; - final LegendRenderer legendRenderer = _renderingDetails.legendRenderer; - legendRenderer._legendPosition = + final bool isMobilePlatform = + defaultTargetPlatform == TargetPlatform.android || + defaultTargetPlatform == TargetPlatform.iOS; + final LegendRenderer legendRenderer = + _stateProperties.renderingDetails.legendRenderer; + legendRenderer.legendPosition = widget.legend.position == LegendPosition.auto - ? (height > width ? LegendPosition.bottom : LegendPosition.right) + ? (height > width + ? isMobilePlatform + ? LegendPosition.top + : LegendPosition.bottom + : LegendPosition.right) : widget.legend.position; - _renderingDetails.chartLegend.chartSize = Size( + _stateProperties.renderingDetails.chartLegend.chartSize = Size( width - margin.left - margin.right, height - margin.top - margin.bottom); } -} - -// ignore: must_be_immutable -class _PyramidPlotArea extends StatelessWidget { - // ignore: prefer_const_constructors_in_immutables - _PyramidPlotArea({required this.chartState}); - final SfPyramidChartState chartState; - //Here, we are using get keyword inorder to get the proper & updated instance of chart widget - //When we initialize chart widget as a property to other classes like _ChartSeries, the chart widget is not updated properly and by using get we can rectify this. - SfPyramidChart get chart => chartState._chart; - late PyramidSeriesRenderer seriesRenderer; - late RenderBox renderBox; - _Region? pointRegion; - late TapDownDetails tapDownDetails; - Offset? doubleTapPosition; - final bool _enableMouseHover = kIsWeb; - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return Container( - child: MouseRegion( - // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. - // Issue: https://github.com/flutter/flutter/issues/68690 - onHover: (PointerEvent event) => - _enableMouseHover ? _onHover(event) : null, - onExit: (PointerEvent event) { - chartState._renderingDetails.tooltipBehaviorRenderer - ._isHovering = false; - }, - child: Stack(textDirection: TextDirection.ltr, children: [ - _initializeChart(constraints, context), - Listener( - onPointerUp: (PointerUpEvent event) => _onTapUp(event), - onPointerDown: (PointerDownEvent event) => _onTapDown(event), - onPointerMove: (PointerMoveEvent event) => - _performPointerMove(event), - child: GestureDetector( - onLongPress: _onLongPress, - onDoubleTap: _onDoubleTap, - onTapUp: (TapUpDetails details) { - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(details.globalPosition); - if (chart.onPointTapped != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex(chart, seriesRenderer, - chartState._renderingDetails.tapPosition!); - } - if (chart.series.onPointTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, - seriesRenderer, - chartState._renderingDetails.tapPosition!, - null, - ActivationMode.singleTap); - } - }, - child: Container( - height: constraints.maxHeight, - width: constraints.maxWidth, - decoration: - const BoxDecoration(color: Colors.transparent), - )), - ), - ]))); - }); - } - - /// To initialize chart - Widget _initializeChart(BoxConstraints constraints, BuildContext context) { - _calculateContainerSize(constraints); - return GestureDetector( - child: Container( - decoration: const BoxDecoration(color: Colors.transparent), - child: _renderWidgets(constraints, context))); - } - - /// To calculate chart plot area - void _calculateContainerSize(BoxConstraints constraints) { - final num width = constraints.maxWidth; - final num height = constraints.maxHeight; - chartState._renderingDetails.chartContainerRect = - Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); - final EdgeInsets margin = chart.margin; - chartState._renderingDetails.chartAreaRect = Rect.fromLTWH( - margin.left, - margin.top, - width - margin.right - margin.left, - height - margin.top - margin.bottom); - } - Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { - _bindSeriesWidgets(); - _calculatePathRegion(); - _findTemplates(chartState); - _renderTemplates(chartState); - _bindTooltipWidgets(constraints); - renderBox = context.findRenderObject() as RenderBox; - chartState._chartPlotArea = this; - return Container( - child: Stack( - textDirection: TextDirection.ltr, - children: chartState._renderingDetails.chartWidgets!)); - } - - /// To calculate region path of pyramid - void _calculatePathRegion() { - final List visibleSeriesRenderers = - chartState._chartSeries.visibleSeriesRenderers; - if (visibleSeriesRenderers.isNotEmpty) { - seriesRenderer = visibleSeriesRenderers[0]; - for (int i = 0; i < seriesRenderer._renderPoints!.length; i++) { - if (seriesRenderer._renderPoints![i].isVisible) { - chartState._chartSeries._calculatePathRegion(i, seriesRenderer); - } - } - } - } - - /// To bind series widget together - void _bindSeriesWidgets() { - late CustomPainter seriesPainter; - Animation? seriesAnimation; - PyramidSeries series; - final List visibleSeriesRenderers = - chartState._chartSeries.visibleSeriesRenderers; - SelectionBehaviorRenderer selectionBehaviorRenderer; - dynamic selectionBehavior; - for (int i = 0; i < visibleSeriesRenderers.length; i++) { - seriesRenderer = visibleSeriesRenderers[i]; - series = seriesRenderer._series; - series.selectionBehavior._chartState = chartState; - chartState._chartSeries._initializeSeriesProperties(seriesRenderer); - selectionBehavior = - seriesRenderer._selectionBehavior = series.selectionBehavior; - selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer = - SelectionBehaviorRenderer(selectionBehavior, chart, chartState); - selectionBehaviorRenderer = seriesRenderer._selectionBehaviorRenderer; - selectionBehaviorRenderer._selectionRenderer ??= _SelectionRenderer(); - selectionBehaviorRenderer._selectionRenderer!.chart = chart; - selectionBehaviorRenderer._selectionRenderer!.seriesRenderer = - seriesRenderer; - if (series.initialSelectedDataIndexes.isNotEmpty) { - for (int index = 0; - index < series.initialSelectedDataIndexes.length; - index++) { - chartState._renderingDetails.selectionData - .add(series.initialSelectedDataIndexes[index]); - } - } - if (series.animationDuration > 0 && - !chartState._renderingDetails.didSizeChange && - (chartState._renderingDetails.deviceOrientation == - chartState._renderingDetails.oldDeviceOrientation) && - ((!chartState._renderingDetails.widgetNeedUpdate && - chartState._renderingDetails.initialRender!) || - chartState._renderingDetails.isLegendToggled)) { - chartState._renderingDetails.animationController.duration = - Duration(milliseconds: series.animationDuration.toInt()); - seriesAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: chartState._renderingDetails.animationController, - curve: const Interval(0.1, 0.8, curve: Curves.linear), - )..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) { - chartState._renderingDetails.animateCompleted = true; - if (chartState._renderDataLabel != null) { - chartState._renderDataLabel!.state?.render(); - } - if (chartState._renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - chartState._renderingDetails.chartTemplate!.state != - null) { - chartState._renderingDetails.chartTemplate!.state - .templateRender(); - } - } - })); - chartState._renderingDetails.chartElementAnimation = - Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( - parent: chartState._renderingDetails.animationController, - curve: const Interval(0.85, 1.0, curve: Curves.decelerate), - )); - chartState._renderingDetails.animationController.forward(from: 0.0); - } else { - chartState._renderingDetails.animateCompleted = true; - if (chartState._renderDataLabel != null) { - chartState._renderDataLabel!.state?.render(); - } - } - seriesRenderer._repaintNotifier = - chartState._renderingDetails.seriesRepaintNotifier; - if (seriesRenderer._seriesType == 'pyramid') { - seriesPainter = _PyramidChartPainter( - chartState: chartState, - seriesIndex: i, - isRepaint: seriesRenderer._needsRepaint, - animationController: - chartState._renderingDetails.animationController, - seriesAnimation: seriesAnimation, - notifier: chartState._renderingDetails.seriesRepaintNotifier); - } - chartState._renderingDetails.chartWidgets! - .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); - chartState._renderDataLabel = _PyramidDataLabelRenderer( - key: GlobalKey(), - chartState: chartState, - //ignore: avoid_bool_literals_in_conditional_expressions - show: !chartState._renderingDetails.widgetNeedUpdate - ? chartState._renderingDetails.animationController.status == - AnimationStatus.completed || - chartState._renderingDetails.animationController.duration == - null - : true); - chartState._renderingDetails.chartWidgets! - .add(chartState._renderDataLabel!); - } - } - - /// To bind tooltip widgets - void _bindTooltipWidgets(BoxConstraints constraints) { - final TooltipBehavior tooltip = chart.tooltipBehavior; + /// This will return tooltip chart point + PointInfo _getChartPoints(PyramidStateProperties _stateProperties) { final TooltipBehaviorRenderer tooltipBehaviorRenderer = - chartState._renderingDetails.tooltipBehaviorRenderer; - tooltip._chartState = chartState; - if (tooltip.enable) { - final SfChartThemeData _chartTheme = - chartState._renderingDetails.chartTheme; - tooltipBehaviorRenderer._prevTooltipValue = - tooltipBehaviorRenderer._currentTooltipValue = null; - chartState._renderingDetails.tooltipBehaviorRenderer._chartTooltip = - SfTooltip( - color: tooltip.color ?? _chartTheme.tooltipColor, - key: GlobalKey(), - textStyle: tooltip.textStyle, - animationDuration: tooltip.animationDuration, - animationCurve: - const Interval(0.1, 0.8, curve: Curves.easeOutBack), - enable: tooltip.enable, - opacity: tooltip.opacity, - borderColor: tooltip.borderColor, - borderWidth: tooltip.borderWidth, - duration: tooltip.duration.toInt(), - shouldAlwaysShow: tooltip.shouldAlwaysShow, - elevation: tooltip.elevation, - canShowMarker: tooltip.canShowMarker, - textAlignment: tooltip.textAlignment, - decimalPlaces: tooltip.decimalPlaces, - labelColor: - tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, - header: tooltip.header, - format: tooltip.format, - shadowColor: tooltip.shadowColor, - onTooltipRender: chart.onTooltipRender != null - ? chartState._renderingDetails.tooltipBehaviorRenderer - ._tooltipRenderingEvent - : null); - chartState._renderingDetails.chartWidgets! - .add(tooltipBehaviorRenderer._chartTooltip!); - } - } - - /// To perform pointer down event - void _onTapDown(PointerDownEvent event) { - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = false; - //renderBox = context.findRenderObject(); - chartState._renderingDetails.currentActive = null; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool isPoint = false; - const int seriesIndex = 0; - late int pointIndex; - final List visibleSeriesRenderers = - chartState._chartSeries.visibleSeriesRenderers; - final PyramidSeriesRenderer seriesRenderer = - visibleSeriesRenderers[seriesIndex]; - ChartTouchInteractionArgs touchArgs; - for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { - if (chart.onDataLabelRender != null) { - seriesRenderer._dataPoints[j].labelRenderEvent = false; - } - if (seriesRenderer._renderPoints![j].isVisible && !isPoint) { - isPoint = _isPointInPolygon(seriesRenderer._renderPoints![j].pathRegion, - chartState._renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - if (chart.onDataLabelRender == null) { - break; - } - } - } - } - doubleTapPosition = chartState._renderingDetails.tapPosition; - if (chartState._renderingDetails.tapPosition != null && isPoint) { - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex, - visibleSeriesRenderers[seriesIndex]._series, - visibleSeriesRenderers[seriesIndex]._renderPoints![pointIndex], - ); - } else { - //hides the tooltip if the point of interaction is outside pyramid region of the chart - chartState._renderingDetails.tooltipBehaviorRenderer._show = false; - chartState._renderingDetails.tooltipBehaviorRenderer - ._hideTooltipTemplate(); - } - if (chart.onChartTouchInteractionDown != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionDown!(touchArgs); - } - } - - /// To perform pointer move event - void _performPointerMove(PointerMoveEvent event) { - ChartTouchInteractionArgs touchArgs; - final Offset position = renderBox.globalToLocal(event.position); - if (chart.onChartTouchInteractionMove != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = position; - chart.onChartTouchInteractionMove!(touchArgs); - } - } - - /// To perform double tap touch interactions - void _onDoubleTap() { - const int seriesIndex = 0; - if (doubleTapPosition != null && - chartState._renderingDetails.currentActive != null) { - if (chart.series.onPointDoubleTap != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, - seriesRenderer, - chartState._renderingDetails.tapPosition!, - null, - ActivationMode.doubleTap); - chartState._renderingDetails.tapPosition = null; - } - final int pointIndex = - chartState._renderingDetails.currentActive!.pointIndex!; - final List visibleSeriesRenderers = - chartState._chartSeries.visibleSeriesRenderers; - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex, - visibleSeriesRenderers[seriesIndex]._series, - visibleSeriesRenderers[seriesIndex]._renderPoints![pointIndex]); - if (chartState._renderingDetails.currentActive != null) { - if (chartState._renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.doubleTap) { - chartState._chartSeries._pointExplode(pointIndex); - final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; - final _PyramidDataLabelRendererState _pyramidDataLabelRendererState = - key.currentState as _PyramidDataLabelRendererState; - _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - chartState._chartSeries - ._seriesPointSelection(pointIndex, ActivationMode.doubleTap); - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { - if (chart.tooltipBehavior.builder != null) { - _showPyramidTooltipTemplate(); - } else { - chartState._renderingDetails.tooltipBehaviorRenderer.onDoubleTap( - doubleTapPosition!.dx.toDouble(), - doubleTapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform long press touch interactions - void _onLongPress() { - const int seriesIndex = 0; - if (chartState._renderingDetails.tapPosition != null && - chartState._renderingDetails.currentActive != null) { - if (chart.series.onPointLongPress != null && - // ignore: unnecessary_null_comparison - seriesRenderer != null) { - _calculatePointSeriesIndex( - chart, - seriesRenderer, - chartState._renderingDetails.tapPosition!, - null, - ActivationMode.longPress); - chartState._renderingDetails.tapPosition = null; - } - final List visibleSeriesRenderers = - chartState._chartSeries.visibleSeriesRenderers; - final int pointIndex = - chartState._renderingDetails.currentActive!.pointIndex!; - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex, - visibleSeriesRenderers[seriesIndex]._series, - visibleSeriesRenderers[seriesIndex]._renderPoints![pointIndex], - pointRegion); - chartState._chartSeries - ._seriesPointSelection(pointIndex, ActivationMode.longPress); - if (chartState._renderingDetails.currentActive != null) { - if (chartState._renderingDetails.currentActive!.series.explodeGesture == - ActivationMode.longPress) { - chartState._chartSeries._pointExplode(pointIndex); - final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; - final _PyramidDataLabelRendererState _pyramidDataLabelRendererState = - key.currentState as _PyramidDataLabelRendererState; - _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - } - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.longPress) { - if (chart.tooltipBehavior.builder != null) { - _showPyramidTooltipTemplate(); - } else { - chartState._renderingDetails.tooltipBehaviorRenderer.onLongPress( - chartState._renderingDetails.tapPosition!.dx.toDouble(), - chartState._renderingDetails.tapPosition!.dy.toDouble()); - } - } - } - } - - /// To perform pointer up event - void _onTapUp(PointerUpEvent event) { - chartState._renderingDetails.tooltipBehaviorRenderer._isHovering = false; - bool isPoint = false; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { - if (seriesRenderer._renderPoints![j].isVisible) { - isPoint = _isPointInPolygon(seriesRenderer._renderPoints![j].pathRegion, - chartState._renderingDetails.tapPosition!); - if (isPoint) { - break; - } + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + + PointInfo tooltipPoint = PointInfo(null, null); + + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable == true && + _stateProperties.isTooltipHidden == false) { + tooltipPoint = pyramidFunnelPixelToPoint( + tooltipRenderingDetails.showLocation!, + _stateProperties.chartSeries.visibleSeriesRenderers[0]); } } - final _ChartInteraction? currentActive = - isPoint ? chartState._renderingDetails.currentActive! : null; - ChartTouchInteractionArgs touchArgs; - if (currentActive != null) { - // ignore: unnecessary_null_comparison - if (chart.onDataLabelTapped != null && seriesRenderer != null) { - _triggerPyramidDataLabelEvent(chart, seriesRenderer, chartState, - chartState._renderingDetails.tapPosition!); - } - if (chartState._renderingDetails.tapPosition != null && - chartState._renderingDetails.currentActive != null) { - if (currentActive.series != null && - currentActive.series.explodeGesture == ActivationMode.singleTap) { - chartState._chartSeries._pointExplode(currentActive.pointIndex!); - final GlobalKey key = chartState._renderDataLabel!.key as GlobalKey; - final _PyramidDataLabelRendererState _pyramidDataLabelRendererState = - key.currentState as _PyramidDataLabelRendererState; - _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; - } - - if (chartState - ._chartSeries.visibleSeriesRenderers[0]._isSelectionEnable) { - chartState._chartSeries._seriesPointSelection( - currentActive.pointIndex!, ActivationMode.singleTap); - } - if (chart.tooltipBehavior.enable && - chartState._renderingDetails.animateCompleted && - chart.tooltipBehavior.activationMode == ActivationMode.singleTap && - currentActive.series != null) { - if (chart.tooltipBehavior.builder != null) { - _showPyramidTooltipTemplate(); - } else { - final Offset position = renderBox.globalToLocal(event.position); - chartState._renderingDetails.tooltipBehaviorRenderer - .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); - } - } - } - } - if (chart.onChartTouchInteractionUp != null) { - touchArgs = ChartTouchInteractionArgs(); - touchArgs.position = renderBox.globalToLocal(event.position); - chart.onChartTouchInteractionUp!(touchArgs); - } - if (chart.series.onPointTap == null && - chart.series.onPointDoubleTap == null && - chart.series.onPointLongPress == null) { - chartState._renderingDetails.tapPosition = null; - } + return tooltipPoint; } - /// To perform event on mouse hover - void _onHover(PointerEvent event) { - chartState._renderingDetails.currentActive = null; - chartState._renderingDetails.tapPosition = - renderBox.globalToLocal(event.position); - bool? isPoint; - const int seriesIndex = 0; - int? pointIndex; - final PyramidSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - final TooltipBehavior tooltip = chart.tooltipBehavior; + /// Here for orientation change/browser resize, the logic in this method will get executed + void _validateStateMaintenance(PyramidStateProperties _stateProperties, + PointInfo tooltipPoint) { final TooltipBehaviorRenderer tooltipBehaviorRenderer = - chartState._renderingDetails.tooltipBehaviorRenderer; - for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { - if (seriesRenderer._renderPoints![j].isVisible) { - isPoint = _isPointInPolygon(seriesRenderer._renderPoints![j].pathRegion, - chartState._renderingDetails.tapPosition!); - if (isPoint) { - pointIndex = j; - break; + _stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + if (_stateProperties.renderingDetails.oldDeviceOrientation != + _stateProperties.renderingDetails.deviceOrientation || + _stateProperties.renderingDetails.didSizeChange) { + if (tooltipRenderingDetails.showLocation != null && + _stateProperties.chart.tooltipBehavior.enable && + !_stateProperties.isTooltipHidden) { + _stateProperties.isTooltipOrientationChanged = true; + late PointInfo point; + late int index; + for (int i = 0; + i < + _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints.length; + i++) { + if (_stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i].x == + tooltipPoint.x && + _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i].y == + tooltipPoint.y) { + index = i; + point = _stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[i]; + } } - } - } - if (chartState._renderingDetails.tapPosition != null && - isPoint != null && - isPoint) { - chartState._renderingDetails.currentActive = _ChartInteraction( - seriesIndex, - pointIndex!, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]._series, - chartState._chartSeries.visibleSeriesRenderers[seriesIndex] - ._renderPoints![pointIndex], - ); - } else if (tooltip.builder != null) { - tooltipBehaviorRenderer._hide(); - } - if (chartState._renderingDetails.tapPosition != null) { - if (tooltip.enable && - chartState._renderingDetails.currentActive != null && - chartState._renderingDetails.currentActive!.series != null) { - tooltipBehaviorRenderer._isHovering = true; - if (tooltip.builder != null && - chartState._renderingDetails.animateCompleted) { - _showPyramidTooltipTemplate(); + final Offset tooltipPosition = pyramidFunnelPointToPixel( + point, _stateProperties.chartSeries.visibleSeriesRenderers[0]); + if (_stateProperties.chart.tooltipBehavior.builder != null) { + _stateProperties.chartPlotArea.showPyramidTooltipTemplate(index); } else { - final Offset position = renderBox.globalToLocal(event.position); - tooltipBehaviorRenderer.onEnter( - position.dx.toDouble(), position.dy.toDouble()); + tooltipRenderingDetails.internalShowByPixel( + tooltipPosition.dx, tooltipPosition.dy); } - } else { - tooltipBehaviorRenderer._prevTooltipValue = null; - tooltipBehaviorRenderer._currentTooltipValue = null; - tooltipBehaviorRenderer._hide(); - } - } - chartState._renderingDetails.tapPosition = null; - } - - /// This method gets executed for showing tooltip when builder is provided in behavior - void _showPyramidTooltipTemplate([int? pointIndex]) { - final TooltipBehavior tooltip = chart.tooltipBehavior; - final TooltipBehaviorRenderer tooltipBehaviorRenderer = - chartState._renderingDetails.tooltipBehaviorRenderer; - - if (!tooltipBehaviorRenderer._isHovering) { - //assingning null for the previous and current tooltip values in case of touch interaction - tooltipBehaviorRenderer._prevTooltipValue = null; - tooltipBehaviorRenderer._currentTooltipValue = null; - } - final PyramidSeries chartSeries = - chartState._renderingDetails.currentActive?.series ?? chart.series; - final PointInfo point = pointIndex == null - ? chartState._renderingDetails.currentActive?.point - : chartState - ._chartSeries.visibleSeriesRenderers[0]._dataPoints[pointIndex]; - final Offset? location = chart.tooltipBehavior.tooltipPosition == - TooltipPosition.pointer && - !chartState._chartSeries.visibleSeriesRenderers[0]._series.explode - ? chartState._renderingDetails.tapPosition! - : point.symbolLocation; - bool isPoint = false; - for (int j = 0; j < seriesRenderer._renderPoints!.length; j++) { - if (seriesRenderer._renderPoints![j].isVisible) { - isPoint = _isPointInPolygon( - seriesRenderer._renderPoints![j].pathRegion, location!); - if (isPoint) { - pointIndex = j; - break; - } - } - } - if (location != null && isPoint && (chartSeries.enableTooltip)) { - tooltipBehaviorRenderer._showLocation = location; - chartState._renderingDetails.tooltipBehaviorRenderer._chartTooltipState! - .boundaryRect = - chartState._renderingDetails.tooltipBehaviorRenderer._tooltipBounds = - chartState._renderingDetails.chartContainerRect; - tooltipBehaviorRenderer._tooltipTemplate = tooltip.builder!( - chartSeries.dataSource![pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!], - point, - chartSeries, - chartState._renderingDetails.currentActive?.seriesIndex ?? 0, - pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!); - if (tooltipBehaviorRenderer._isHovering) { - //assingning values for the previous and current tooltip values on mouse hover - tooltipBehaviorRenderer._prevTooltipValue = - tooltipBehaviorRenderer._currentTooltipValue; - tooltipBehaviorRenderer._currentTooltipValue = TooltipValue( - 0, - pointIndex ?? - chartState._renderingDetails.currentActive!.pointIndex!); - } else { - chartState._renderingDetails.tooltipBehaviorRenderer - ._hideTooltipTemplate(); } - tooltipBehaviorRenderer._show = true; - tooltipBehaviorRenderer._performTooltip(); - tooltipBehaviorRenderer._chartTooltipState! - .hide(hideDelay: tooltip.duration.toInt()); } } } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_plot_area.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_plot_area.dart new file mode 100644 index 000000000..c147d3ec4 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_plot_area.dart @@ -0,0 +1,689 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_core/tooltip_internal.dart'; + +import '../../chart/user_interaction/selection_renderer.dart'; +import '../../chart/utils/enum.dart'; +import '../../circular_chart/renderer/common.dart'; +import '../../common/event_args.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../../common/utils/helper.dart'; +import '../../pyramid_chart/utils/common.dart'; +import '../../pyramid_chart/utils/helper.dart'; +import '../renderer/data_label_renderer.dart'; +import '../renderer/pyramid_chart_painter.dart'; +import '../renderer/pyramid_series.dart'; +import '../renderer/renderer_extension.dart'; +import 'pyramid_base.dart'; +import 'pyramid_state_properties.dart'; + +/// Represents the pyramid plot areas +// ignore: must_be_immutable +class PyramidPlotArea extends StatelessWidget { + /// Creates an instance of pyramid plot area + // ignore: prefer_const_constructors_in_immutables + PyramidPlotArea({required this.stateProperties}); + + /// Creates the pyramid state properties + final PyramidStateProperties stateProperties; + + ///Here, we are using get keyword in order to get the proper & updated instance of chart widget + ///When we initialize chart widget as a property to other classes like ChartSeries, the chart widget is not updated properly and by using get we can rectify this. + SfPyramidChart get chart => stateProperties.chart; + + /// Represents the pyramid series renderer + late PyramidSeriesRendererExtension seriesRenderer; + + /// Represents the value of render box + late RenderBox renderBox; + + /// Represents the value of point region + Region? pointRegion; + + /// Represents the value of tap down details + late TapDownDetails tapDownDetails; + + /// Represents the series animation + Animation? seriesAnimation; + + /// Represents the value of double tap position + Offset? doubleTapPosition; + final bool _enableMouseHover = kIsWeb; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return Container( + child: MouseRegion( + // Using the _enableMouseHover property, prevented mouse hover function in mobile platforms. The mouse hover event should not be triggered for mobile platforms and logged an issue regarding this to the Flutter team. + // Issue: https://github.com/flutter/flutter/issues/68690 + onHover: (PointerEvent event) => + _enableMouseHover ? _onHover(event) : null, + onExit: (PointerEvent event) { + TooltipHelper.getRenderingDetails(stateProperties + .renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; + }, + child: Stack(textDirection: TextDirection.ltr, children: [ + _initializeChart(constraints, context), + Listener( + onPointerUp: (PointerUpEvent event) => _onTapUp(event), + onPointerDown: (PointerDownEvent event) => _onTapDown(event), + onPointerMove: (PointerMoveEvent event) => + _performPointerMove(event), + child: GestureDetector( + onLongPress: _onLongPress, + onDoubleTap: _onDoubleTap, + onTapUp: (TapUpDetails details) { + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(details.globalPosition); + if (chart.onPointTapped != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex(chart, seriesRenderer, + stateProperties.renderingDetails.tapPosition!); + } + if (chart.series.onPointTap != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex( + chart, + seriesRenderer, + stateProperties.renderingDetails.tapPosition!, + null, + ActivationMode.singleTap); + } + }, + child: Container( + height: constraints.maxHeight, + width: constraints.maxWidth, + decoration: + const BoxDecoration(color: Colors.transparent), + )), + ), + ]))); + }); + } + + /// To initialize chart + Widget _initializeChart(BoxConstraints constraints, BuildContext context) { + _calculateContainerSize(constraints); + return GestureDetector( + child: Container( + decoration: const BoxDecoration(color: Colors.transparent), + child: _renderWidgets(constraints, context))); + } + + /// To calculate chart plot area + void _calculateContainerSize(BoxConstraints constraints) { + final num width = constraints.maxWidth; + final num height = constraints.maxHeight; + stateProperties.renderingDetails.chartContainerRect = + Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()); + final EdgeInsets margin = chart.margin; + stateProperties.renderingDetails.chartAreaRect = Rect.fromLTWH( + margin.left, + margin.top, + width - margin.right - margin.left, + height - margin.top - margin.bottom); + } + + Widget _renderWidgets(BoxConstraints constraints, BuildContext context) { + _bindSeriesWidgets(); + _calculatePathRegion(); + findTemplates(stateProperties); + renderTemplates(stateProperties); + _bindTooltipWidgets(constraints); + renderBox = context.findRenderObject() as RenderBox; + stateProperties.chartPlotArea = this; + return Container( + child: Stack( + textDirection: TextDirection.ltr, + children: stateProperties.renderingDetails.chartWidgets!)); + } + + /// To calculate region path of pyramid + void _calculatePathRegion() { + final List visibleSeriesRenderers = + stateProperties.chartSeries.visibleSeriesRenderers; + if (visibleSeriesRenderers.isNotEmpty) { + seriesRenderer = visibleSeriesRenderers[0]; + for (int i = 0; i < seriesRenderer.renderPoints!.length; i++) { + if (seriesRenderer.renderPoints![i].isVisible) { + stateProperties.chartSeries.calculatePathRegion(i, seriesRenderer); + } + } + } + } + + /// To bind series widget together + void _bindSeriesWidgets() { + late CustomPainter seriesPainter; + PyramidSeries series; + final List visibleSeriesRenderers = + stateProperties.chartSeries.visibleSeriesRenderers; + SelectionBehaviorRenderer selectionBehaviorRenderer; + dynamic selectionBehavior; + for (int i = 0; i < visibleSeriesRenderers.length; i++) { + seriesRenderer = visibleSeriesRenderers[i]; + series = seriesRenderer.series; + stateProperties.chartSeries.initializeSeriesProperties(seriesRenderer); + selectionBehavior = + seriesRenderer.selectionBehavior = series.selectionBehavior; + selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer = + SelectionBehaviorRenderer(selectionBehavior, chart, stateProperties); + SelectionHelper.setSelectionBehaviorRenderer( + series.selectionBehavior, selectionBehaviorRenderer); + selectionBehaviorRenderer = seriesRenderer.selectionBehaviorRenderer; + final SelectionDetails selectionDetails = + SelectionHelper.getRenderingDetails(selectionBehaviorRenderer); + selectionDetails.selectionRenderer ??= SelectionRenderer(); + selectionDetails.selectionRenderer!.chart = chart; + selectionDetails.selectionRenderer!.seriesRendererDetails = + seriesRenderer; + if (series.initialSelectedDataIndexes.isNotEmpty) { + for (int index = 0; + index < series.initialSelectedDataIndexes.length; + index++) { + stateProperties.renderingDetails.selectionData + .add(series.initialSelectedDataIndexes[index]); + } + } + if (series.animationDuration > 0 && + !stateProperties.renderingDetails.didSizeChange && + (stateProperties.renderingDetails.deviceOrientation == + stateProperties.renderingDetails.oldDeviceOrientation) && + ((!stateProperties.renderingDetails.widgetNeedUpdate && + stateProperties.renderingDetails.initialRender!) || + stateProperties.renderingDetails.isLegendToggled)) { + final int totalAnimationDuration = + series.animationDuration.toInt() + series.animationDelay.toInt(); + stateProperties.renderingDetails.animationController.duration = + Duration(milliseconds: totalAnimationDuration); + const double maxSeriesInterval = 0.8; + double minSeriesInterval = 0.1; + minSeriesInterval = series.animationDelay.toInt() / + totalAnimationDuration * + (maxSeriesInterval - minSeriesInterval) + + minSeriesInterval; + seriesAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: stateProperties.renderingDetails.animationController, + curve: Interval(minSeriesInterval, maxSeriesInterval, + curve: Curves.linear), + )..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.completed) { + stateProperties.renderingDetails.animateCompleted = true; + if (stateProperties.renderDataLabel != null) { + stateProperties.renderDataLabel!.state?.render(); + } + if (stateProperties.renderingDetails.chartTemplate != null && + // ignore: unnecessary_null_comparison + stateProperties.renderingDetails.chartTemplate!.state != + null) { + stateProperties.renderingDetails.chartTemplate!.state + .templateRender(); + } + } + })); + stateProperties.renderingDetails.chartElementAnimation = + Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( + parent: stateProperties.renderingDetails.animationController, + curve: const Interval(0.85, 1.0, curve: Curves.decelerate), + )); + stateProperties.renderingDetails.animationController.forward(from: 0.0); + } else { + stateProperties.renderingDetails.animateCompleted = true; + if (stateProperties.renderDataLabel != null) { + stateProperties.renderDataLabel!.state?.render(); + } + } + seriesRenderer.repaintNotifier = + stateProperties.renderingDetails.seriesRepaintNotifier; + if (seriesRenderer.seriesType == 'pyramid') { + seriesPainter = PyramidChartPainter( + stateProperties: stateProperties, + seriesIndex: i, + isRepaint: seriesRenderer.needsRepaint, + animationController: + stateProperties.renderingDetails.animationController, + seriesAnimation: seriesAnimation, + notifier: stateProperties.renderingDetails.seriesRepaintNotifier); + } + stateProperties.renderingDetails.chartWidgets! + .add(RepaintBoundary(child: CustomPaint(painter: seriesPainter))); + stateProperties.renderDataLabel = PyramidDataLabelRenderer( + key: GlobalKey(), + stateProperties: stateProperties, + //ignore: avoid_bool_literals_in_conditional_expressions + show: !stateProperties.renderingDetails.widgetNeedUpdate + ? stateProperties.renderingDetails.animationController.status == + AnimationStatus.completed || + stateProperties + .renderingDetails.animationController.duration == + null + : true); + stateProperties.renderingDetails.chartWidgets! + .add(stateProperties.renderDataLabel!); + } + } + + /// To bind tooltip widgets + void _bindTooltipWidgets(BoxConstraints constraints) { + final TooltipBehavior tooltip = chart.tooltipBehavior; + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + stateProperties.renderingDetails.tooltipBehaviorRenderer; + TooltipHelper.setStateProperties(chart.tooltipBehavior, stateProperties); + if (tooltip.enable) { + final SfChartThemeData _chartTheme = + stateProperties.renderingDetails.chartTheme; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails(tooltipBehaviorRenderer); + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue = null; + tooltipRenderingDetails.chartTooltip = SfTooltip( + color: tooltip.color ?? _chartTheme.tooltipColor, + key: GlobalKey(), + textStyle: tooltip.textStyle, + animationDuration: tooltip.animationDuration, + animationCurve: const Interval(0.1, 0.8, curve: Curves.easeOutBack), + enable: tooltip.enable, + opacity: tooltip.opacity, + borderColor: tooltip.borderColor, + borderWidth: tooltip.borderWidth, + duration: tooltip.duration.toInt(), + shouldAlwaysShow: tooltip.shouldAlwaysShow, + elevation: tooltip.elevation, + canShowMarker: tooltip.canShowMarker, + textAlignment: tooltip.textAlignment, + decimalPlaces: tooltip.decimalPlaces, + labelColor: tooltip.textStyle.color ?? _chartTheme.tooltipLabelColor, + header: tooltip.header, + format: tooltip.format, + shadowColor: tooltip.shadowColor, + onTooltipRender: chart.onTooltipRender != null + ? tooltipRenderingDetails.tooltipRenderingEvent + : null); + stateProperties.renderingDetails.chartWidgets! + .add(tooltipRenderingDetails.chartTooltip!); + } + } + + /// To perform pointer down event + void _onTapDown(PointerDownEvent event) { + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + tooltipRenderingDetails.isHovering = false; + //renderBox = context.findRenderObject(); + stateProperties.renderingDetails.currentActive = null; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + bool isPoint = false; + const int seriesIndex = 0; + late int pointIndex; + final List visibleSeriesRenderers = + stateProperties.chartSeries.visibleSeriesRenderers; + final PyramidSeriesRendererExtension seriesRenderer = + visibleSeriesRenderers[seriesIndex]; + ChartTouchInteractionArgs touchArgs; + for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { + if (chart.onDataLabelRender != null) { + seriesRenderer.dataPoints[j].labelRenderEvent = false; + } + if (seriesRenderer.renderPoints![j].isVisible && !isPoint) { + isPoint = isPointInPolygon(seriesRenderer.renderPoints![j].pathRegion, + stateProperties.renderingDetails.tapPosition!); + if (isPoint) { + pointIndex = j; + if (chart.onDataLabelRender == null) { + break; + } + } + } + } + doubleTapPosition = stateProperties.renderingDetails.tapPosition; + if (stateProperties.renderingDetails.tapPosition != null && isPoint) { + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex, + visibleSeriesRenderers[seriesIndex].series, + visibleSeriesRenderers[seriesIndex].renderPoints![pointIndex], + ); + } else { + //hides the tooltip if the point of interaction is outside pyramid region of the chart + tooltipRenderingDetails.show = false; + tooltipRenderingDetails.hideTooltipTemplate(); + } + if (chart.onChartTouchInteractionDown != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionDown!(touchArgs); + } + } + + /// To perform pointer move event + void _performPointerMove(PointerMoveEvent event) { + ChartTouchInteractionArgs touchArgs; + final Offset position = renderBox.globalToLocal(event.position); + if (chart.onChartTouchInteractionMove != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = position; + chart.onChartTouchInteractionMove!(touchArgs); + } + } + + /// To perform double tap touch interactions + void _onDoubleTap() { + const int seriesIndex = 0; + if (doubleTapPosition != null && + stateProperties.renderingDetails.currentActive != null) { + if (chart.series.onPointDoubleTap != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex( + chart, + seriesRenderer, + stateProperties.renderingDetails.tapPosition!, + null, + ActivationMode.doubleTap); + stateProperties.renderingDetails.tapPosition = null; + } + final int pointIndex = + stateProperties.renderingDetails.currentActive!.pointIndex!; + final List visibleSeriesRenderers = + stateProperties.chartSeries.visibleSeriesRenderers; + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex, + visibleSeriesRenderers[seriesIndex].series, + visibleSeriesRenderers[seriesIndex].renderPoints![pointIndex]); + if (stateProperties.renderingDetails.currentActive != null) { + if (stateProperties + .renderingDetails.currentActive!.series.explodeGesture == + ActivationMode.doubleTap) { + stateProperties.chartSeries.pointExplode(pointIndex); + final GlobalKey key = + stateProperties.renderDataLabel!.key as GlobalKey; + final PyramidDataLabelRendererState _pyramidDataLabelRendererState = + key.currentState as PyramidDataLabelRendererState; + _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; + } + } + stateProperties.chartSeries + .seriesPointSelection(pointIndex, ActivationMode.doubleTap); + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.doubleTap) { + if (chart.tooltipBehavior.builder != null) { + showPyramidTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer.onDoubleTap( + doubleTapPosition!.dx.toDouble(), + doubleTapPosition!.dy.toDouble()); + } + } + } + } + + /// To perform long press touch interactions + void _onLongPress() { + const int seriesIndex = 0; + if (stateProperties.renderingDetails.tapPosition != null && + stateProperties.renderingDetails.currentActive != null) { + if (chart.series.onPointLongPress != null && + // ignore: unnecessary_null_comparison + seriesRenderer != null) { + calculatePointSeriesIndex( + chart, + seriesRenderer, + stateProperties.renderingDetails.tapPosition!, + null, + ActivationMode.longPress); + stateProperties.renderingDetails.tapPosition = null; + } + final List visibleSeriesRenderers = + stateProperties.chartSeries.visibleSeriesRenderers; + final int pointIndex = + stateProperties.renderingDetails.currentActive!.pointIndex!; + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex, + visibleSeriesRenderers[seriesIndex].series, + visibleSeriesRenderers[seriesIndex].renderPoints![pointIndex], + pointRegion); + stateProperties.chartSeries + .seriesPointSelection(pointIndex, ActivationMode.longPress); + if (stateProperties.renderingDetails.currentActive != null) { + if (stateProperties + .renderingDetails.currentActive!.series.explodeGesture == + ActivationMode.longPress) { + stateProperties.chartSeries.pointExplode(pointIndex); + final GlobalKey key = + stateProperties.renderDataLabel!.key as GlobalKey; + final PyramidDataLabelRendererState _pyramidDataLabelRendererState = + key.currentState as PyramidDataLabelRendererState; + _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; + } + } + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.longPress) { + if (chart.tooltipBehavior.builder != null) { + showPyramidTooltipTemplate(); + } else { + stateProperties.renderingDetails.tooltipBehaviorRenderer.onLongPress( + stateProperties.renderingDetails.tapPosition!.dx.toDouble(), + stateProperties.renderingDetails.tapPosition!.dy.toDouble()); + } + } + } + } + + /// To perform pointer up event + void _onTapUp(PointerUpEvent event) { + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer) + .isHovering = false; + bool isPoint = false; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { + if (seriesRenderer.renderPoints![j].isVisible) { + isPoint = isPointInPolygon(seriesRenderer.renderPoints![j].pathRegion, + stateProperties.renderingDetails.tapPosition!); + if (isPoint) { + break; + } + } + } + final ChartInteraction? currentActive = isPoint + ? stateProperties.renderingDetails.currentActive != null + ? stateProperties.renderingDetails.currentActive! + : null + : null; + ChartTouchInteractionArgs touchArgs; + if (currentActive != null) { + // ignore: unnecessary_null_comparison + if (chart.onDataLabelTapped != null && seriesRenderer != null) { + triggerPyramidDataLabelEvent(chart, seriesRenderer, stateProperties, + stateProperties.renderingDetails.tapPosition!); + } + if (stateProperties.renderingDetails.tapPosition != null && + stateProperties.renderingDetails.currentActive != null) { + if (currentActive.series != null && + currentActive.series.explodeGesture == ActivationMode.singleTap) { + stateProperties.chartSeries.pointExplode(currentActive.pointIndex!); + final GlobalKey key = + stateProperties.renderDataLabel!.key as GlobalKey; + final PyramidDataLabelRendererState _pyramidDataLabelRendererState = + key.currentState as PyramidDataLabelRendererState; + _pyramidDataLabelRendererState.dataLabelRepaintNotifier.value++; + } + + if (stateProperties + .chartSeries.visibleSeriesRenderers[0].isSelectionEnable) { + stateProperties.chartSeries.seriesPointSelection( + currentActive.pointIndex!, ActivationMode.singleTap); + } + + if (chart.tooltipBehavior.enable && + stateProperties.renderingDetails.animateCompleted && + chart.tooltipBehavior.activationMode == ActivationMode.singleTap && + currentActive.series != null) { + if (chart.tooltipBehavior.builder != null) { + showPyramidTooltipTemplate(); + } else { + final Offset position = renderBox.globalToLocal(event.position); + stateProperties.renderingDetails.tooltipBehaviorRenderer + .onTouchUp(position.dx.toDouble(), position.dy.toDouble()); + } + } + } + } + if (chart.onChartTouchInteractionUp != null) { + touchArgs = ChartTouchInteractionArgs(); + touchArgs.position = renderBox.globalToLocal(event.position); + chart.onChartTouchInteractionUp!(touchArgs); + } + if (chart.series.onPointTap == null && + chart.series.onPointDoubleTap == null && + chart.series.onPointLongPress == null) { + stateProperties.renderingDetails.tapPosition = null; + } + } + + /// To perform event on mouse hover + void _onHover(PointerEvent event) { + stateProperties.renderingDetails.currentActive = null; + stateProperties.renderingDetails.tapPosition = + renderBox.globalToLocal(event.position); + bool? isPoint; + const int seriesIndex = 0; + int? pointIndex; + final PyramidSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; + final TooltipBehavior tooltip = chart.tooltipBehavior; + final TooltipBehaviorRenderer tooltipBehaviorRenderer = + stateProperties.renderingDetails.tooltipBehaviorRenderer; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { + if (seriesRenderer.renderPoints![j].isVisible) { + isPoint = isPointInPolygon(seriesRenderer.renderPoints![j].pathRegion, + stateProperties.renderingDetails.tapPosition!); + if (isPoint) { + pointIndex = j; + break; + } + } + } + if (stateProperties.renderingDetails.tapPosition != null && + isPoint != null && + isPoint) { + stateProperties.renderingDetails.currentActive = ChartInteraction( + seriesIndex, + pointIndex!, + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex].series, + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex] + .renderPoints![pointIndex], + ); + } else if (tooltip.builder != null) { + tooltipRenderingDetails.hide(); + } + if (stateProperties.renderingDetails.tapPosition != null) { + if (tooltip.enable && + stateProperties.renderingDetails.currentActive != null && + stateProperties.renderingDetails.currentActive!.series != null) { + tooltipRenderingDetails.isHovering = true; + if (tooltip.builder != null && + stateProperties.renderingDetails.animateCompleted) { + showPyramidTooltipTemplate(); + } else { + final Offset position = renderBox.globalToLocal(event.position); + tooltipBehaviorRenderer.onEnter( + position.dx.toDouble(), position.dy.toDouble()); + } + } else { + tooltipRenderingDetails.prevTooltipValue = null; + tooltipRenderingDetails.currentTooltipValue = null; + tooltipRenderingDetails.hide(); + } + } + stateProperties.renderingDetails.tapPosition = null; + } + + /// This method gets executed for showing tooltip when builder is provided in behavior + void showPyramidTooltipTemplate([int? pointIndex]) { + stateProperties.isTooltipHidden = false; + final TooltipBehavior tooltip = chart.tooltipBehavior; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + stateProperties.renderingDetails.tooltipBehaviorRenderer); + if (!tooltipRenderingDetails.isHovering) { + //assigning null for the previous and current tooltip values in case of touch interaction + tooltipRenderingDetails.prevTooltipValue = null; + tooltipRenderingDetails.currentTooltipValue = null; + } + final PyramidSeries chartSeries = + stateProperties.renderingDetails.currentActive?.series ?? chart.series; + final PointInfo point = pointIndex == null + ? stateProperties.renderingDetails.currentActive?.point + : stateProperties + .chartSeries.visibleSeriesRenderers[0].dataPoints[pointIndex]; + final Offset? location = + chart.tooltipBehavior.tooltipPosition == TooltipPosition.pointer && + !stateProperties + .chartSeries.visibleSeriesRenderers[0].series.explode + ? stateProperties.renderingDetails.tapPosition! + : point.symbolLocation; + bool isPoint = false; + for (int j = 0; j < seriesRenderer.renderPoints!.length; j++) { + if (seriesRenderer.renderPoints![j].isVisible == true) { + isPoint = isPointInPolygon( + seriesRenderer.renderPoints![j].pathRegion, location!); + if (isPoint) { + pointIndex = j; + break; + } + } + } + if (location != null && isPoint && (chartSeries.enableTooltip)) { + tooltipRenderingDetails.showLocation = location; + tooltipRenderingDetails.chartTooltipState!.boundaryRect = + tooltipRenderingDetails.tooltipBounds = + stateProperties.renderingDetails.chartContainerRect; + tooltipRenderingDetails.tooltipTemplate = tooltip.builder!( + chartSeries.dataSource![pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!], + point, + chartSeries, + stateProperties.renderingDetails.currentActive?.seriesIndex ?? 0, + pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!); + if (tooltipRenderingDetails.isHovering == true) { + //assigning values for the previous and current tooltip values on mouse hover + tooltipRenderingDetails.prevTooltipValue = + tooltipRenderingDetails.currentTooltipValue; + tooltipRenderingDetails.currentTooltipValue = TooltipValue( + 0, + pointIndex ?? + stateProperties.renderingDetails.currentActive!.pointIndex!); + } else { + tooltipRenderingDetails.hideTooltipTemplate(); + } + tooltipRenderingDetails.show = true; + tooltipRenderingDetails.performTooltip(); + tooltipRenderingDetails.chartTooltipState! + .hide(hideDelay: tooltip.duration.toInt()); + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_state_properties.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_state_properties.dart new file mode 100644 index 000000000..70da0fb9e --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/base/pyramid_state_properties.dart @@ -0,0 +1,86 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'package:syncfusion_flutter_charts/src/common/user_interaction/tooltip_rendering_details.dart'; +import '../../common/rendering_details.dart'; +import '../../common/state_properties.dart'; +import '../../common/user_interaction/tooltip.dart'; +import '../base/pyramid_plot_area.dart'; +import '../renderer/data_label_renderer.dart'; +import '../utils/common.dart'; +import 'chart_base.dart'; +import 'pyramid_base.dart'; + +/// Represents the pyramid state properties +class PyramidStateProperties extends StateProperties { + /// Creates an instance of pyramid state properties + PyramidStateProperties( + {required this.renderingDetails, required this.chartState}) + : super(renderingDetails, chartState) { + renderingDetails.didSizeChange = false; + } + + /// Specifies the pyramid chart + @override + SfPyramidChart get chart => chartState.widget; + + /// Specifies the pyramid chart state + @override + final SfPyramidChartState chartState; + + /// Specifies the rendering details value + @override + final RenderingDetails renderingDetails; + + /// Specifies the pyramid data label renderer + PyramidDataLabelRenderer? renderDataLabel; + + /// Specifies the tooltip point index value + int? tooltipPointIndex; + + /// Specifies the series type + late String seriesType; + + /// Specifies the list of data points + late List> dataPoints; + + /// Specifies the list of render points + List>? renderPoints; + + /// Specifies the pyramid chart base + late PyramidChartBase chartSeries; + + /// Specifies the pyramid plot area + late PyramidPlotArea chartPlotArea; + + /// Method called when animation is completed + bool get animationCompleted { + return renderingDetails.animationController.status != + AnimationStatus.forward; + } + + /// Method to redraw the chart + void redraw() { + renderingDetails.initialRender = false; + final TooltipRenderingDetails tooltipRenderingDetails = + TooltipHelper.getRenderingDetails( + renderingDetails.tooltipBehaviorRenderer); + if (tooltipRenderingDetails.chartTooltipState != null) { + tooltipRenderingDetails.show = false; + } + + // ignore: invalid_use_of_protected_member + chartState.setState(() { + /// The chart will be rebuilding again, When we do the legend toggle, zoom/pan the chart. + }); + } + + /// Tooltip timer + Timer? tooltipTimer; + + /// To check the tooltip orientation changes. + bool isTooltipOrientationChanged = false; + + /// To check if tooltip has been hidden or not. + bool isTooltipHidden = false; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart index 7992ddbf9..4b9987d1a 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/data_label_renderer.dart @@ -1,26 +1,43 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../common/utils/helper.dart'; +import '../../pyramid_chart/utils/helper.dart'; +import '../base/pyramid_state_properties.dart'; +import 'renderer_extension.dart'; + +/// Represnts the data lable renderer of pyramid chart // ignore: must_be_immutable -class _PyramidDataLabelRenderer extends StatefulWidget { +class PyramidDataLabelRenderer extends StatefulWidget { + /// Creats an instance of pyramid data label renderer // ignore: prefer_const_constructors_in_immutables - _PyramidDataLabelRenderer( - {required Key key, required this.chartState, required this.show}) + PyramidDataLabelRenderer( + {required Key key, required this.stateProperties, required this.show}) : super(key: key); - final SfPyramidChartState chartState; + /// Represents the pyramid chart state + final PyramidStateProperties stateProperties; + /// Specifies whether to show the data label bool show; - _PyramidDataLabelRendererState? state; + /// Specifies the state instance of data label renderer + PyramidDataLabelRendererState? state; @override State createState() { - return _PyramidDataLabelRendererState(); + return PyramidDataLabelRendererState(); } } -class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> +/// Represents the state class of data label renderer state +class PyramidDataLabelRendererState extends State with SingleTickerProviderStateMixin { + /// Specifies the animation controller list late List animationControllersList; /// Animation controller for series @@ -42,7 +59,7 @@ class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> widget.state = this; animationController.duration = Duration( milliseconds: - widget.chartState._renderingDetails.initialRender! ? 500 : 0); + widget.stateProperties.renderingDetails.initialRender! ? 500 : 0); final Animation dataLabelAnimation = Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( parent: animationController, @@ -54,8 +71,8 @@ class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> : Container( child: RepaintBoundary( child: CustomPaint( - painter: _PyramidDataLabelPainter( - chartState: widget.chartState, + painter: PyramidDataLabelPainter( + stateProperties: widget.stateProperties, animation: dataLabelAnimation, state: this, notifier: dataLabelRepaintNotifier, @@ -64,14 +81,16 @@ class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> @override void dispose() { - _disposeAnimationController(animationController, repaintDataLabelElements); + disposeAnimationController(animationController, repaintDataLabelElements); super.dispose(); } + /// Method to repaint the data label element void repaintDataLabelElements() { dataLabelRepaintNotifier.value++; } + /// Method to render the widget void render() { setState(() { widget.show = true; @@ -79,419 +98,43 @@ class _PyramidDataLabelRendererState extends State<_PyramidDataLabelRenderer> } } -class _PyramidDataLabelPainter extends CustomPainter { - _PyramidDataLabelPainter( - {required this.chartState, +/// Represents the pyramid data label painter +class PyramidDataLabelPainter extends CustomPainter { + /// Creates an instance of pyramid data label painter + PyramidDataLabelPainter( + {required this.stateProperties, required this.state, required this.animationController, required this.animation, required ValueNotifier notifier}) : super(repaint: notifier); - final SfPyramidChartState chartState; + /// Represents the pyramid state properties + final PyramidStateProperties stateProperties; - final _PyramidDataLabelRendererState state; + /// Represents the data label renderer state + final PyramidDataLabelRendererState state; + /// Represents the animation controller final AnimationController animationController; + /// Specifies the series animation final Animation animation; @override void paint(Canvas canvas, Size size) { - final PyramidSeriesRenderer seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[0]; + final PyramidSeriesRendererExtension seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[0]; // ignore: unnecessary_null_comparison - if (seriesRenderer._series.dataLabelSettings != null && - seriesRenderer._series.dataLabelSettings.isVisible) { - seriesRenderer._dataLabelSettingsRenderer = - DataLabelSettingsRenderer(seriesRenderer._series.dataLabelSettings); - _renderPyramidDataLabel(seriesRenderer, canvas, chartState, animation); + if (seriesRenderer.series.dataLabelSettings != null && + seriesRenderer.series.dataLabelSettings.isVisible == true) { + seriesRenderer.dataLabelSettingsRenderer = + DataLabelSettingsRenderer(seriesRenderer.series.dataLabelSettings); + renderPyramidDataLabel( + seriesRenderer, canvas, stateProperties, animation); } } @override - bool shouldRepaint(_PyramidDataLabelPainter oldDelegate) => true; -} - -/// To render pyramid data labels -void _renderPyramidDataLabel( - PyramidSeriesRenderer seriesRenderer, - Canvas canvas, - SfPyramidChartState chartState, - Animation animation) { - PointInfo point; - final SfPyramidChart chart = chartState._chart; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; - String? label; - // ignore: unnecessary_null_comparison - final double animateOpacity = animation != null ? animation.value : 1; - DataLabelRenderArgs dataLabelArgs; - TextStyle dataLabelStyle; - final List renderDataLabelRegions = []; - Size textSize; - for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints!.length; - pointIndex++) { - point = seriesRenderer._renderPoints![pointIndex]; - if (point.isVisible && (point.y != 0 || dataLabel.showZeroValue)) { - label = point.text; - dataLabelStyle = dataLabel.textStyle; - dataLabelSettingsRenderer._color = - seriesRenderer._series.dataLabelSettings.color; - if (chart.onDataLabelRender != null && - !seriesRenderer._renderPoints![pointIndex].labelRenderEvent) { - dataLabelArgs = DataLabelRenderArgs(seriesRenderer, - seriesRenderer._renderPoints, pointIndex, pointIndex); - dataLabelArgs.text = label!; - dataLabelArgs.textStyle = dataLabelStyle; - dataLabelArgs.color = dataLabelSettingsRenderer._color; - chart.onDataLabelRender!(dataLabelArgs); - label = point.text = dataLabelArgs.text; - dataLabelStyle = dataLabelArgs.textStyle; - pointIndex = dataLabelArgs.pointIndex!; - dataLabelSettingsRenderer._color = dataLabelArgs.color; - if (animation.status == AnimationStatus.completed) { - seriesRenderer._dataPoints[pointIndex].labelRenderEvent = true; - } - } - dataLabelStyle = chart.onDataLabelRender == null - ? _getDataLabelTextStyle( - seriesRenderer, point, chartState, animateOpacity) - : dataLabelStyle; - textSize = measureText(label!, dataLabelStyle); - - ///Label check after event - if (label != '') { - if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { - _setPyramidInsideLabelPosition( - dataLabel, - point, - textSize, - chartState, - canvas, - renderDataLabelRegions, - pointIndex, - label, - seriesRenderer, - animateOpacity, - dataLabelStyle); - } else { - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = _getDataLabelTextStyle( - seriesRenderer, point, chartState, animateOpacity); - _renderOutsidePyramidDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - chartState, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } -} - -/// To calculate pyramid inside label position -void _setPyramidInsideLabelPosition( - DataLabelSettings dataLabel, - PointInfo point, - Size textSize, - SfPyramidChartState chartState, - Canvas canvas, - List renderDataLabelRegions, - int pointIndex, - String label, - PyramidSeriesRenderer seriesRenderer, - double animateOpacity, - TextStyle dataLabelStyle) { - final num angle = dataLabel.angle; - Offset labelLocation; - final SmartLabelMode smartLabelMode = chartState._chart.smartLabelMode; - const int labelPadding = 2; - labelLocation = point.symbolLocation; - labelLocation = Offset( - labelLocation.dx - - (textSize.width / 2) + - (angle == 0 ? 0 : textSize.width / 2), - labelLocation.dy - - (textSize.height / 2) + - (angle == 0 ? 0 : textSize.height / 2)); - point.labelRect = Rect.fromLTWH( - labelLocation.dx - labelPadding, - labelLocation.dy - labelPadding, - textSize.width + (2 * labelPadding), - textSize.height + (2 * labelPadding)); - final bool isDataLabelCollide = - _findingCollision(point.labelRect!, renderDataLabelRegions, point.region); - if (isDataLabelCollide && smartLabelMode == SmartLabelMode.shift) { - point.saturationRegionOutside = true; - point.renderPosition = ChartDataLabelPosition.outside; - dataLabelStyle = _getDataLabelTextStyle( - seriesRenderer, point, chartState, animateOpacity); - _renderOutsidePyramidDataLabel( - canvas, - label, - point, - textSize, - pointIndex, - seriesRenderer, - chartState, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } else if (smartLabelMode == SmartLabelMode.none || - (!isDataLabelCollide && smartLabelMode == SmartLabelMode.shift) || - (!isDataLabelCollide && smartLabelMode == SmartLabelMode.hide)) { - point.renderPosition = ChartDataLabelPosition.inside; - _drawPyramidLabel( - point.labelRect!, - labelLocation, - label, - null, - canvas, - seriesRenderer, - point, - pointIndex, - chartState, - dataLabelStyle, - renderDataLabelRegions, - animateOpacity); - } -} - -/// To render outside pyramid data label -void _renderOutsidePyramidDataLabel( - Canvas canvas, - String label, - PointInfo point, - Size textSize, - int pointIndex, - PyramidSeriesRenderer seriesRenderer, - SfPyramidChartState _chartState, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Path connectorPath; - Rect? rect; - Offset labelLocation; - final EdgeInsets margin = seriesRenderer._series.dataLabelSettings.margin; - final ConnectorLineSettings connector = - seriesRenderer._series.dataLabelSettings.connectorLineSettings; - const num regionPadding = 12; - connectorPath = Path(); - final num connectorLength = _percentToValue(connector.length ?? '0%', - _chartState._renderingDetails.chartAreaRect.width / 2)! + - seriesRenderer._maximumDataLabelRegion.width / 2 - - regionPadding; - final Offset startPoint = Offset( - seriesRenderer._renderPoints![pointIndex].region!.right, - seriesRenderer._renderPoints![pointIndex].region!.top + - seriesRenderer._renderPoints![pointIndex].region!.height / 2); - final double dx = - seriesRenderer._renderPoints![pointIndex].symbolLocation.dx + - connectorLength; - final Offset endPoint = Offset( - (dx + textSize.width + margin.left + margin.right > - _chartState._renderingDetails.chartAreaRect.right) - ? dx - - (_percentToValue(seriesRenderer._series.explodeOffset, - _chartState._renderingDetails.chartAreaRect.width)!) - : dx, - seriesRenderer._renderPoints![pointIndex].symbolLocation.dy); - connectorPath.moveTo(startPoint.dx, startPoint.dy); - if (connector.type == ConnectorType.line) { - connectorPath.lineTo(endPoint.dx, endPoint.dy); - } - point.dataLabelPosition = Position.right; - rect = _getDataLabelRect(point.dataLabelPosition!, connector.type, margin, - connectorPath, endPoint, textSize); - if (rect != null) { - point.labelRect = rect; - labelLocation = Offset(rect.left + margin.left, - rect.top + rect.height / 2 - textSize.height / 2); - final Rect containerRect = _chartState._renderingDetails.chartAreaRect; - if (seriesRenderer._series.dataLabelSettings.builder == null) { - Rect? lastRenderedLabelRegion; - if (renderDataLabelRegions.isNotEmpty) { - lastRenderedLabelRegion = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - } - if (!_isPyramidLabelIntersect(rect, lastRenderedLabelRegion) && - (rect.left > containerRect.left && - rect.left + rect.width < - containerRect.left + containerRect.width) && - rect.top > containerRect.top && - rect.top + rect.height < containerRect.top + containerRect.height) { - _drawPyramidLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - _chartState, - textStyle, - renderDataLabelRegions, - animateOpacity); - } else { - if (pointIndex != 0) { - const num connectorLinePadding = 15; - const num padding = 2; - final Rect previousRenderedRect = - renderDataLabelRegions[renderDataLabelRegions.length - 1]; - rect = Rect.fromLTWH(rect.left, previousRenderedRect.bottom + padding, - rect.width, rect.height); - labelLocation = Offset( - rect.left + margin.left, - previousRenderedRect.bottom + - padding + - rect.height / 2 - - textSize.height / 2); - connectorPath = Path(); - connectorPath.moveTo(startPoint.dx, startPoint.dy); - connectorPath.lineTo( - rect.left - connectorLinePadding, rect.top + rect.height / 2); - connectorPath.lineTo(rect.left, rect.top + rect.height / 2); - } - if (rect.bottom < _chartState._renderingDetails.chartAreaRect.bottom) { - _drawPyramidLabel( - rect, - labelLocation, - label, - connectorPath, - canvas, - seriesRenderer, - point, - pointIndex, - _chartState, - textStyle, - renderDataLabelRegions, - animateOpacity); - } - } - } - } -} - -/// To check whether labels intesect -bool _isPyramidLabelIntersect(Rect rect, Rect? previousRect) { - bool isIntersect = false; - const num padding = 2; - if (previousRect != null && (rect.top - padding) < previousRect.bottom) { - isIntersect = true; - } - return isIntersect; -} - -/// To draw pyramid data label -void _drawPyramidLabel( - Rect labelRect, - Offset location, - String? label, - Path? connectorPath, - Canvas canvas, - PyramidSeriesRenderer seriesRenderer, - PointInfo point, - int pointIndex, - SfPyramidChartState chartState, - TextStyle textStyle, - List renderDataLabelRegions, - double animateOpacity) { - Paint rectPaint; - final DataLabelSettings dataLabel = seriesRenderer._series.dataLabelSettings; - final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; - final ConnectorLineSettings connector = dataLabel.connectorLineSettings; - if (connectorPath != null) { - canvas.drawPath( - connectorPath, - Paint() - ..color = connector.width <= 0 - ? Colors.transparent - : connector.color ?? - point.fill.withOpacity( - !chartState._renderingDetails.isLegendToggled - ? animateOpacity - : dataLabel.opacity) - ..strokeWidth = connector.width - ..style = PaintingStyle.stroke); - } - - if (dataLabel.builder == null) { - final double strokeWidth = dataLabel.borderWidth; - final Color? labelFill = dataLabelSettingsRenderer._color ?? - (dataLabel.useSeriesColor - ? point.fill - : dataLabelSettingsRenderer._color); - final Color? strokeColor = - dataLabel.borderColor.withOpacity(dataLabel.opacity); - // ignore: unnecessary_null_comparison - if (strokeWidth != null && strokeWidth > 0) { - rectPaint = Paint() - ..color = strokeColor!.withOpacity( - !chartState._renderingDetails.isLegendToggled - ? animateOpacity - : dataLabel.opacity) - ..style = PaintingStyle.stroke - ..strokeWidth = strokeWidth; - _drawLabelRect( - rectPaint, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - if (labelFill != null) { - _drawLabelRect( - Paint() - ..color = labelFill - .withOpacity(!chartState._renderingDetails.isLegendToggled - ? (animateOpacity - (1 - dataLabel.opacity)) < 0 - ? 0 - : animateOpacity - (1 - dataLabel.opacity) - : dataLabel.opacity) - ..style = PaintingStyle.fill, - Rect.fromLTRB( - labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), - dataLabel.borderRadius, - canvas); - } - _drawText(canvas, label!, location, textStyle, dataLabel.angle); - renderDataLabelRegions.add(labelRect); - } -} - -void _triggerPyramidDataLabelEvent( - SfPyramidChart chart, - PyramidSeriesRenderer seriesRenderer, - SfPyramidChartState chartState, - Offset position) { - const int seriesIndex = 0; - DataLabelSettings dataLabel; - PointInfo point; - Offset labelLocation; - for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints!.length; - pointIndex++) { - dataLabel = seriesRenderer._series.dataLabelSettings; - point = seriesRenderer._renderPoints![pointIndex]; - labelLocation = point.symbolLocation; - if (dataLabel.isVisible && - seriesRenderer._renderPoints![pointIndex].labelRect != null && - seriesRenderer._renderPoints![pointIndex].labelRect! - .contains(position)) { - position = Offset(labelLocation.dx, labelLocation.dy); - _dataLabelTapEvent(chart, seriesRenderer._series.dataLabelSettings, - pointIndex, point, position, seriesIndex); - } - } + bool shouldRepaint(PyramidDataLabelPainter oldDelegate) => true; } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_chart_painter.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_chart_painter.dart new file mode 100644 index 000000000..646f2a561 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_chart_painter.dart @@ -0,0 +1,83 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../base/pyramid_state_properties.dart'; +import '../utils/common.dart'; +import 'renderer_extension.dart'; + +/// Represents the pyramid chart painter +class PyramidChartPainter extends CustomPainter { + /// Creates an instance of pyramid chart painter + PyramidChartPainter({ + required this.stateProperties, + required this.seriesIndex, + required this.isRepaint, + this.animationController, + this.seriesAnimation, + required ValueNotifier notifier, + }) : super(repaint: notifier); + + /// Specifies the pyramid state properties + final PyramidStateProperties stateProperties; + + /// Specifies the series index value + final int seriesIndex; + + /// Specifies whether to repaint the series + final bool isRepaint; + + /// Specifies the animation controller of series + final AnimationController? animationController; + + /// Specifies the pyramid series animation + final Animation? seriesAnimation; + + /// Specifies the pyramid series renderer + late PyramidSeriesRendererExtension seriesRenderer; + + /// Specifies the point info + static late PointInfo point; + + @override + void paint(Canvas canvas, Size size) { + seriesRenderer = + stateProperties.chartSeries.visibleSeriesRenderers[seriesIndex]; + + double animationFactor, factor, height; + for (int pointIndex = 0; + pointIndex < seriesRenderer.renderPoints!.length; + pointIndex++) { + if (seriesRenderer.renderPoints![pointIndex].isVisible) { + animationFactor = seriesAnimation != null ? seriesAnimation!.value : 1; + if (seriesRenderer.series.animationDuration > 0 && + !stateProperties.renderingDetails.isLegendToggled) { + factor = (stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height) - + animationFactor * + (stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height); + height = stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height - + factor; + canvas.clipRect(Rect.fromLTRB( + 0, + stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height - + height, + stateProperties.renderingDetails.chartAreaRect.left + + stateProperties.renderingDetails.chartAreaRect.width, + stateProperties.renderingDetails.chartAreaRect.top + + stateProperties.renderingDetails.chartAreaRect.height)); + } + stateProperties.chartSeries + .calculatePyramidSegments(canvas, pointIndex, seriesRenderer); + } + } + } + + @override + bool shouldRepaint(PyramidChartPainter oldDelegate) => true; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart index 1efc700c3..187439157 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/pyramid_series.dart @@ -1,659 +1,18 @@ -part of charts; - -class _PyramidSeriesBase extends ChartSeries - implements TriangularChartEmptyPointBehavior { - _PyramidSeriesBase({ - this.key, - this.onCreateRenderer, - this.onRendererCreated, - this.onPointTap, - this.onPointDoubleTap, - this.onPointLongPress, - this.dataSource, - this.xValueMapper, - this.yValueMapper, - this.pointColorMapper, - this.textFieldMapper, - this.name, - this.explodeIndex, - String? height, - String? width, - PyramidMode? pyramidMode, - double? gapRatio, - EmptyPointSettings? emptyPointSettings, - String? explodeOffset, - bool? explode, - ActivationMode? explodeGesture, - Color? borderColor, - double? borderWidth, - LegendIconType? legendIconType, - DataLabelSettings? dataLabelSettings, - double? animationDuration, - double? opacity, - SelectionBehavior? selectionBehavior, - List? initialSelectedDataIndexes, - }) : height = height ?? '80%', - width = width ?? '80%', - pyramidMode = pyramidMode ?? PyramidMode.linear, - gapRatio = gapRatio ?? 0, - emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), - explodeOffset = explodeOffset ?? '10%', - explode = explode ?? false, - explodeGesture = explodeGesture ?? ActivationMode.singleTap, - borderColor = borderColor ?? Colors.transparent, - borderWidth = borderWidth ?? 0.0, - legendIconType = legendIconType ?? LegendIconType.seriesType, - dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), - animationDuration = animationDuration ?? 1500, - opacity = opacity ?? 1, - initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], - selectionBehavior = selectionBehavior ?? SelectionBehavior(), - super( - name: name, - dataSource: dataSource, - xValueMapper: xValueMapper, - yValueMapper: yValueMapper) { - // _renderer = _PyramidSeriesRender(); - } - - ///A collection of data required for rendering the series. If no data source is specified, - ///empty chart will be rendered without series. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// )], - /// )); - ///} - @override - final List? dataSource; - - ///Maps the field name, which will be considered as x-values. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// xValueMapper: (ChartData data, _) => data.xVal, - /// )], - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? xValueMapper; - - ///Maps the field name, which will be considered as y-values. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: [ - /// ChartData('USA', 10), - /// ChartData('China', 11), - /// ChartData('Russia', 9), - /// ChartData('Germany', 10), - /// ], - /// yValueMapper: (ChartData data, _) => data.yVal, - /// )], - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal); - /// final String xVal; - /// final int yVal; - ///} - ///``` - @override - final ChartIndexedValueMapper? yValueMapper; - - ///Name of the series. - /// - ///Defaults to '' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// name: 'Pyramid' - /// )); - ///} - ///``` - @override - final String? name; - - ///Height of the series. - /// - ///Defaults to '80%' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// height:'50%' - /// )); - ///} - ///``` - final String height; - - ///Width of the series. - /// - ///Defaults to '80%' - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// width:'50%' - /// )); - ///} - ///``` - final String width; - - ///Specifies the rendering type of pyramid. - /// - ///Defaults to PyramidMode.linear - /// - ///Also refer [PyramidMode] - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// pyramidMode:PyramidMode.surface - /// )); - ///} - ///``` - final PyramidMode pyramidMode; - - ///Gap ratio between the segments of pyramid. Ranges from 0 to 1 - /// - ///Defaults to 0. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// gapRatio: 0.3 - /// )); - ///} - ///``` - final double gapRatio; - - ///Customizes the empty data points in the series - /// - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// emptyPointSettings: EmptyPointSettings (color: Colors.red)) - /// )); - ///} - ///``` - @override - final EmptyPointSettings emptyPointSettings; - - ///Offset of exploded slice. The value ranges from 0% to 100%. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explodeOffset: '5%') - /// )); - ///} - ///``` - final String explodeOffset; - - ///Enables or disables the explode of slices on tap. - /// - ///Default to false. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explode: true) - /// )); - ///} - ///``` - final bool explode; - - ///Gesture for activating the explode. Explode can be activated in tap, double tap, - ///and long press. - /// - ///Defaults to ActivationMode.tap - /// - ///Also refer [ActivationMode] - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explode: true, - /// explodeGesture: ActivationMode.singleTap - /// ) - /// )); - ///} - ///``` - final ActivationMode explodeGesture; - - ///Border width of the data points in the series. - /// - ///Defaults to 0 - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// borderWidth: 2 - /// ) - /// )); - ///} - ///``` - @override - final double borderWidth; - - ///Border color of the data points in the series. - /// - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// borderColor: Colors.red - /// ) - /// )); - ///} - ///``` - @override - final Color borderColor; - - ///Shape of the legend icon. Any shape in the LegendIconType can be applied to this property. - ///By default, icon will be rendered based on the type of the series. - /// - /// - ///Also refer [LegendIconType] - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// legendIconType: LegendIconType.diamond, - /// ) - /// )); - ///} - ///``` - @override - final LegendIconType legendIconType; - - ///Enables the dataLabel of the series - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataLabelSettings: DataLabelSettings(isVisible: true), - /// ) - /// )); - ///} - ///``` - @override - final DataLabelSettings dataLabelSettings; - - ///Duration for animating the data points. - /// - ///Defaults to 1500 - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// animationDuration: 2000, - /// ) - /// )); - ///} - ///``` - @override - final double animationDuration; - - ///Maps the field name, which will be considered as data point color. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// pointColorMapper: (ChartData data, _) => data.pointColor, - /// ) - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointColor]); - /// final String xVal; - /// final int yVal; - /// final Color pointColor; - ///} - ///``` - @override - final ChartIndexedValueMapper? pointColorMapper; - - ///Maps the field name, which will be considered as text for data label. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// textFieldMapper: (ChartData data, _) => data.xVal, - /// ) - /// )); - ///} - ///class ChartData { - /// ChartData(this.xVal, this.yVal, [this.pointColor]); - /// final String xVal; - /// final int yVal; - /// final Color pointColor; - ///} - ///``` - final ChartIndexedValueMapper? textFieldMapper; - - ///Opacity of the series. The value ranges from 0 to 1. - /// - ///Defaults to 1 - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// opacity: 0.5, - /// ) - /// )); - ///} - ///``` - @override - final double opacity; - - ///Customizes the selection of series. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// selectionBehavior: SelectionBehavior( - /// selectedColor: Colors.red, - /// unselectedColor: Colors.grey - /// ), - /// ) - /// )); - ///} - ///``` - @override - final SelectionBehavior selectionBehavior; - - ///Index of the slice to explode it at the initial rendering. - /// - ///Defaults to null - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// explodeIndex: 1, - /// explode: true - /// ) - /// )); - ///} - ///``` - final num? explodeIndex; - - /// List of data indexes initially selected - /// - /// Defaults to `null`. - ///```dart - /// Widget build(BuildContext context) { - /// return Scaffold( - /// body: Center( - /// child: Container( - /// child: SfPyramidChart( - /// initialSelectedDataIndexes: [IndexesModel(1, 0)] - /// ) - /// ) - /// ) - /// ); - /// } - List initialSelectedDataIndexes; - - ///Key to identify a series in a collection. - - /// - - ///On specifying [ValueKey] as the series [key], existing series index - ///can be changed in the series collection without losing its state. - - /// - - ///When a new series is added dynamically to the collection, - ///existing series index will be changed. On that case, - - /// the existing series and its state will be linked based on its chart type and this key value. - - /// - - ///Defaults to `null`. - - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// key: const ValueKey('line_series_key'), - /// ), - /// )); - ///} - ///``` - final ValueKey? key; - - ///Used to create the renderer for custom series. - /// - ///This is applicable only when the custom series is defined in the sample - /// and for built-in series types, it is not applicable. - /// - ///Renderer created in this will hold the series state and - /// this should be created for each series. [onCreateRenderer] callback - /// function should return the renderer class and should not return null. - /// - ///Series state will be created only once per series and will not be created - ///again when we update the series. - /// - ///Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// onCreateRenderer:(PyramidSeries series){ - /// return CustomLinerSeriesRenderer(); - /// } - /// ), - /// )); - /// } - /// class CustomLinerSeriesRenderer extends PyramidSeriesRenderer { - /// // custom implementation here... - /// } - ///``` - final ChartSeriesRendererFactory? onCreateRenderer; - - ///Triggers when the series renderer is created. - - /// - - ///Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. - - /// - - ///Defaults to `null`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// onRendererCreated: (PyramidSeriesController controller) { - /// _chartSeriesController = controller; - /// }, - /// ], - /// )); - ///} - ///``` - final PyramidSeriesRendererCreatedCallback? onRendererCreated; - - ///Called when tapped on the chart data point. - /// - ///The user can fetch the series index, point index, viewport point index and - /// data of the tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// )); - ///} - ///``` - final ChartPointInteractionCallback? onPointTap; - - ///Called when double tapped on the chart data point. - /// - ///The user can fetch the series index, point index, viewport point index and - /// data of the double-tapped data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointDoubleTap: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// )); - ///} - ///``` - final ChartPointInteractionCallback? onPointDoubleTap; - - ///Called when long pressed on the chart data point. - /// - ///The user can fetch the series index, point index, viewport point index and - /// data of the long-pressed data point. - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfCartesianChart( - /// series: >[ - /// LineSeries( - /// onPointLongPress: (ChartPointDetails details) { - /// print(details.pointIndex); - /// }, - /// ), - /// ], - /// )); - ///} - ///``` - final ChartPointInteractionCallback? onPointLongPress; - - @override - void calculateEmptyPointValue( - int pointIndex, dynamic currentPoint, dynamic seriesRenderer) { - final List> dataPoints = seriesRenderer._dataPoints; - final EmptyPointSettings empty = emptyPointSettings; - final int pointLength = dataPoints.length; - final PointInfo point = dataPoints[pointIndex]; - if (point.y == null) { - switch (empty.mode) { - case EmptyPointMode.average: - final num previous = - pointIndex - 1 >= 0 ? dataPoints[pointIndex - 1].y ?? 0 : 0; - final num next = pointIndex + 1 <= pointLength - 1 - ? dataPoints[pointIndex + 1].y ?? 0 - : 0; - point.y = (previous + next).abs() / 2; - point.isVisible = true; - point.isEmpty = true; - break; - case EmptyPointMode.zero: - point.y = 0; - point.isVisible = true; - point.isEmpty = true; - break; - default: - point.isEmpty = true; - point.isVisible = false; - break; - } - } - } -} +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import 'renderer_extension.dart'; +import 'series_base.dart'; +import 'series_controller.dart'; /// Renders the pyramid series /// @@ -662,8 +21,10 @@ class _PyramidSeriesBase extends ChartSeries /// /// Provides the property of color, [opacity], border color and border width for customizing the appearance. /// +/// {@youtube 560 315 https://www.youtube.com/watch?v=t3Dczqj8-10} +/// @immutable -class PyramidSeries extends _PyramidSeriesBase { +class PyramidSeries extends PyramidSeriesBase { /// Creating an argument constructor of PyramidSeries class. PyramidSeries({ ValueKey? key, @@ -686,6 +47,7 @@ class PyramidSeries extends _PyramidSeriesBase { EmptyPointSettings? emptyPointSettings, DataLabelSettings? dataLabelSettings, double? animationDuration, + double? animationDelay, double? opacity, Color? borderColor, double? borderWidth, @@ -723,6 +85,7 @@ class PyramidSeries extends _PyramidSeriesBase { borderColor: borderColor, borderWidth: borderWidth, animationDuration: animationDuration, + animationDelay: animationDelay, explode: explode, explodeIndex: explodeIndex, explodeOffset: explodeOffset, @@ -741,7 +104,7 @@ class PyramidSeries extends _PyramidSeriesBase { 'This onCreateRenderer callback function should return value as extends from ChartSeriesRenderer class and should not be return value as null'); return seriesRenderer; } - return PyramidSeriesRenderer(); + return PyramidSeriesRendererExtension(); } @override @@ -773,6 +136,7 @@ class PyramidSeries extends _PyramidSeriesBase { other.emptyPointSettings == emptyPointSettings && other.dataLabelSettings == dataLabelSettings && other.animationDuration == animationDuration && + other.animationDelay == animationDelay && other.opacity == opacity && other.borderColor == borderColor && other.borderWidth == borderWidth && @@ -807,6 +171,7 @@ class PyramidSeries extends _PyramidSeriesBase { emptyPointSettings, dataLabelSettings, animationDuration, + animationDelay, opacity, borderColor, borderWidth, @@ -820,263 +185,3 @@ class PyramidSeries extends _PyramidSeriesBase { return hashList(values); } } - -class _PyramidChartPainter extends CustomPainter { - _PyramidChartPainter({ - required this.chartState, - required this.seriesIndex, - required this.isRepaint, - this.animationController, - this.seriesAnimation, - required ValueNotifier notifier, - }) : super(repaint: notifier); - final SfPyramidChartState chartState; - final int seriesIndex; - final bool isRepaint; - final AnimationController? animationController; - final Animation? seriesAnimation; - late PyramidSeriesRenderer seriesRenderer; - //ignore: unused_field - static late PointInfo point; - - @override - void paint(Canvas canvas, Size size) { - seriesRenderer = - chartState._chartSeries.visibleSeriesRenderers[seriesIndex]; - - double animationFactor, factor, height; - for (int pointIndex = 0; - pointIndex < seriesRenderer._renderPoints!.length; - pointIndex++) { - if (seriesRenderer._renderPoints![pointIndex].isVisible) { - animationFactor = seriesAnimation != null ? seriesAnimation!.value : 1; - if (seriesRenderer._series.animationDuration > 0 && - !chartState._renderingDetails.isLegendToggled) { - factor = (chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height) - - animationFactor * - (chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height); - height = chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height - - factor; - canvas.clipRect(Rect.fromLTRB( - 0, - chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height - - height, - chartState._renderingDetails.chartAreaRect.left + - chartState._renderingDetails.chartAreaRect.width, - chartState._renderingDetails.chartAreaRect.top + - chartState._renderingDetails.chartAreaRect.height)); - } - chartState._chartSeries - ._calculatePyramidSegments(canvas, pointIndex, seriesRenderer); - } - } - } - - @override - bool shouldRepaint(_PyramidChartPainter oldDelegate) => true; -} - -/// Creates series renderer for Pyramid series -class PyramidSeriesRenderer extends ChartSeriesRenderer { - /// Calling the default constructor of PyramidSeriesRenderer class. - PyramidSeriesRenderer(); - - late PyramidSeries _series; - //Internal variables - late String _seriesType; - late List> _dataPoints; - List>? _renderPoints; - late num _sumOfPoints; - late Size _triangleSize; - late num _explodeDistance; - late Rect _maximumDataLabelRegion; - PyramidSeriesController? _controller; - late SfPyramidChartState _chartState; - - /// Repaint notifier for series - late ValueNotifier _repaintNotifier; - late DataLabelSettingsRenderer _dataLabelSettingsRenderer; - late SelectionBehaviorRenderer _selectionBehaviorRenderer; - late dynamic _selectionBehavior; - // ignore: prefer_final_fields - bool _isSelectionEnable = false; -} - -///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods -///in this before we must get the ChartSeriesController onRendererCreated event. -class PyramidSeriesController { - /// Creating an argument constructor of PyramidSeriesController class. - PyramidSeriesController(this.seriesRenderer); - - ///Used to access the series properties. - /// - ///Defaults to `null` - /// - ///```dart - ///Widget build(BuildContext context) { - /// ChartSeriesController _chartSeriesController; - /// return Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// onRendererCreated: (PyramidSeriesController controller) { - /// _chartSeriesController = controller; - /// // prints series yAxisName - /// print(_chartSeriesController.seriesRenderer._series.yAxisName); - /// }, - /// ), - /// )); - ///} - ///``` - final PyramidSeriesRenderer seriesRenderer; - - ///Used to process only the newly added, updated and removed data points in a series, - /// instead of processing all the data points. - /// - ///To re-render the chart with modified data points, setState() will be called. - /// This will render the process and render the chart from scratch. - /// Thus, the app’s performance will be degraded on continuous update. - /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. - /// Chart will process only that point and skip various steps like bounds calculation, - /// old data points processing, etc. Thus, this will improve the app’s performance. - /// - ///The following are the arguments of this method. - /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. - /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. - /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. - /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. - /// * removedDataIndex – `int` type – Index of removed data point in the existing series. - /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. - /// - ///Returns `void`. - /// - ///```dart - ///Widget build(BuildContext context) { - /// PyramidSeriesController _pyramidSeriesController; - /// return Column( - /// children: [ - /// Container( - /// child: SfPyramidChart( - /// series: PyramidSeries( - /// dataSource: chartData, - /// onRendererCreated: (PyramidSeriesController controller) { - /// _pyramidSeriesController = controller; - /// }, - /// ), - /// )), - /// Container( - /// child: RaisedButton( - /// onPressed: () { - /// chartData.removeAt(0); - /// chartData.add(ChartData(3,23)); - /// _pyramidSeriesController.updateDataSource( - /// addedDataIndexes: [chartData.length -1], - /// removedDataIndexes: [0], - /// ); - /// }) - /// )] - /// ); - /// } - ///``` - void updateDataSource( - {List? addedDataIndexes, - List? removedDataIndexes, - List? updatedDataIndexes, - int? addedDataIndex, - int? removedDataIndex, - int? updatedDataIndex}) { - if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { - _removeDataPointsList(removedDataIndexes); - } else if (removedDataIndex != null) { - _removeDataPoint(removedDataIndex); - } - if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(addedDataIndexes, false); - } else if (addedDataIndex != null) { - _addOrUpdateDataPoint(addedDataIndex, false); - } - if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { - _addOrUpdateDataPoints(updatedDataIndexes, true); - } else if (updatedDataIndex != null) { - _addOrUpdateDataPoint(updatedDataIndex, true); - } - _updatePyramidSeries(); - } - - /// Add or update the data points on dynamic series update - void _addOrUpdateDataPoints(List indexes, bool needUpdate) { - int dataIndex; - for (int i = 0; i < indexes.length; i++) { - dataIndex = indexes[i]; - _addOrUpdateDataPoint(dataIndex, needUpdate); - } - } - - /// add or update a data point in the given index - void _addOrUpdateDataPoint(int index, bool needUpdate) { - final PyramidSeries series = seriesRenderer._series; - if (index >= 0 && - series.dataSource!.length > index && - series.dataSource![index] != null) { - final ChartIndexedValueMapper? xValue = series.xValueMapper; - final ChartIndexedValueMapper? yValue = series.yValueMapper; - final PointInfo _currentPoint = - PointInfo(xValue!(index), yValue!(index)); - if (_currentPoint.x != null) { - if (needUpdate) { - if (seriesRenderer._dataPoints.length > index) { - seriesRenderer._dataPoints[index] = _currentPoint; - } - } else { - if (seriesRenderer._dataPoints.length == index) { - seriesRenderer._dataPoints.add(_currentPoint); - } else if (seriesRenderer._dataPoints.length > index && index >= 0) { - seriesRenderer._dataPoints.insert(index, _currentPoint); - } - } - } - } - } - - ///Remove list of points - void _removeDataPointsList(List removedDataIndexes) { - ///Remove the redudant index from the list - final List indexList = removedDataIndexes.toSet().toList(); - indexList.sort((int b, int a) => a.compareTo(b)); - int dataIndex; - for (int i = 0; i < indexList.length; i++) { - dataIndex = indexList[i]; - _removeDataPoint(dataIndex); - } - } - - /// remove a data point in the given index - void _removeDataPoint(int index) { - if (seriesRenderer._dataPoints.isNotEmpty && - index >= 0 && - index < seriesRenderer._dataPoints.length) { - seriesRenderer._dataPoints.removeAt(index); - } - } - - /// After add/remove/update datapoints, recalculate the chart angle and positions - void _updatePyramidSeries() { - final SfPyramidChartState _chartState = seriesRenderer._chartState; - _chartState._chartSeries._processDataPoints(); - _chartState._chartSeries._initializeSeriesProperties(seriesRenderer); - seriesRenderer._repaintNotifier.value++; - if (seriesRenderer._series.dataLabelSettings.isVisible && - _chartState._renderDataLabel != null) { - _chartState._renderDataLabel!.state?.render(); - } - if (seriesRenderer._series.dataLabelSettings.isVisible && - _chartState._renderingDetails.chartTemplate != null && - // ignore: unnecessary_null_comparison - _chartState._renderingDetails.chartTemplate!.state != null) { - _chartState._renderingDetails.chartTemplate!.state.templateRender(); - } - } -} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/renderer_extension.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/renderer_extension.dart new file mode 100644 index 000000000..c38a1b3e8 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/renderer_extension.dart @@ -0,0 +1,69 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../base/pyramid_state_properties.dart'; +import '../utils/common.dart'; +import 'pyramid_series.dart'; +import 'series_controller.dart'; + +/// Creates series renderer for Pyramid series +class PyramidSeriesRendererExtension extends PyramidSeriesRenderer { + /// Calling the default constructor of PyramidSeriesRenderer class. + PyramidSeriesRendererExtension() { + seriesType = 'pyramid'; + } + + /// Specifies the pyramid series + late PyramidSeries series; + + /// Specifies the series type + late String seriesType; + + /// Represents the list of data points + late List> dataPoints; + + /// Represents the list of render points + List>? renderPoints; + + /// Gets or sets the value of sum of points + late num sumOfPoints; + + /// Specifies the triangle size value + late Size triangleSize; + + /// Specifies the explode distance value + late num explodeDistance; + + /// Specifies the maximum data label region + late Rect maximumDataLabelRegion; + + /// Specifies the value of series controller + PyramidSeriesController? controller; + + /// Specifies the state properties + late PyramidStateProperties stateProperties; + + /// Specifies the repaint notifier of the series + late ValueNotifier repaintNotifier; + + /// Represents the data label setting renderer + late DataLabelSettingsRenderer dataLabelSettingsRenderer; + + /// Represents the selection behavior renderer + late SelectionBehaviorRenderer selectionBehaviorRenderer; + + /// Specifies the selection behavior + late SelectionBehavior selectionBehavior; + + /// Specifies whether the selection is enabled + // ignore: prefer_final_fields + bool isSelectionEnable = false; + + /// Specifies whether to repaint the chart + bool needsRepaint = true; +} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_base.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_base.dart new file mode 100644 index 000000000..9a532e9cc --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_base.dart @@ -0,0 +1,693 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../common/common.dart'; +import '../../common/series/chart_series.dart'; +import '../../common/user_interaction/selection_behavior.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/typedef.dart'; +import '../utils/common.dart'; + +/// Represents the class of pyramid series base +class PyramidSeriesBase extends ChartSeries + implements TriangularChartEmptyPointBehavior { + /// Creates an instance of pyramid series base + PyramidSeriesBase({ + this.key, + this.onCreateRenderer, + this.onRendererCreated, + this.onPointTap, + this.onPointDoubleTap, + this.onPointLongPress, + this.dataSource, + this.xValueMapper, + this.yValueMapper, + this.pointColorMapper, + this.textFieldMapper, + this.name, + this.explodeIndex, + String? height, + String? width, + PyramidMode? pyramidMode, + double? gapRatio, + EmptyPointSettings? emptyPointSettings, + String? explodeOffset, + bool? explode, + ActivationMode? explodeGesture, + Color? borderColor, + double? borderWidth, + LegendIconType? legendIconType, + DataLabelSettings? dataLabelSettings, + double? animationDuration, + double? animationDelay, + double? opacity, + SelectionBehavior? selectionBehavior, + List? initialSelectedDataIndexes, + }) : height = height ?? '80%', + width = width ?? '80%', + pyramidMode = pyramidMode ?? PyramidMode.linear, + gapRatio = gapRatio ?? 0, + emptyPointSettings = emptyPointSettings ?? EmptyPointSettings(), + explodeOffset = explodeOffset ?? '10%', + explode = explode ?? false, + explodeGesture = explodeGesture ?? ActivationMode.singleTap, + borderColor = borderColor ?? Colors.transparent, + borderWidth = borderWidth ?? 0.0, + legendIconType = legendIconType ?? LegendIconType.seriesType, + dataLabelSettings = dataLabelSettings ?? const DataLabelSettings(), + animationDuration = animationDuration ?? 1500, + animationDelay = animationDelay ?? 0, + opacity = opacity ?? 1, + initialSelectedDataIndexes = initialSelectedDataIndexes ?? [], + selectionBehavior = selectionBehavior ?? SelectionBehavior(), + super( + name: name, + dataSource: dataSource, + xValueMapper: xValueMapper, + yValueMapper: yValueMapper) { + // _renderer = _PyramidSeriesRender(); + } + + ///A collection of data required for rendering the series. If no data source is specified, + ///empty chart will be rendered without series. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// )], + /// )); + ///} + @override + final List? dataSource; + + ///Maps the field name, which will be considered as x-values. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// xValueMapper: (ChartData data, _) => data.xVal, + /// )], + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal); + /// final String xVal; + /// final int yVal; + ///} + ///``` + @override + final ChartIndexedValueMapper? xValueMapper; + + ///Maps the field name, which will be considered as y-values. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// dataSource: [ + /// ChartData('USA', 10), + /// ChartData('China', 11), + /// ChartData('Russia', 9), + /// ChartData('Germany', 10), + /// ], + /// yValueMapper: (ChartData data, _) => data.yVal, + /// )], + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal); + /// final String xVal; + /// final int yVal; + ///} + ///``` + @override + final ChartIndexedValueMapper? yValueMapper; + + ///Name of the series. + /// + ///Defaults to '' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// name: 'Pyramid' + /// )); + ///} + ///``` + @override + final String? name; + + ///Height of the series. + /// + ///Defaults to '80%' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// height:'50%' + /// )); + ///} + ///``` + final String height; + + ///Width of the series. + /// + ///Defaults to '80%' + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// width:'50%' + /// )); + ///} + ///``` + final String width; + + ///Specifies the rendering type of pyramid. + /// + ///Defaults to PyramidMode.linear + /// + ///Also refer [PyramidMode] + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// pyramidMode:PyramidMode.surface + /// )); + ///} + ///``` + final PyramidMode pyramidMode; + + ///Gap ratio between the segments of pyramid. Ranges from 0 to 1 + /// + ///Defaults to 0. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// gapRatio: 0.3 + /// )); + ///} + ///``` + final double gapRatio; + + ///Customizes the empty data points in the series + /// + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// emptyPointSettings: EmptyPointSettings (color: Colors.red)) + /// )); + ///} + ///``` + @override + final EmptyPointSettings emptyPointSettings; + + ///Offset of exploded slice. The value ranges from 0% to 100%. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explodeOffset: '5%') + /// )); + ///} + ///``` + final String explodeOffset; + + ///Enables or disables the explode of slices on tap. + /// + ///Default to false. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explode: true) + /// )); + ///} + ///``` + final bool explode; + + ///Gesture for activating the explode. Explode can be activated in tap, double tap, + ///and long press. + /// + ///Defaults to ActivationMode.tap + /// + ///Also refer [ActivationMode] + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explode: true, + /// explodeGesture: ActivationMode.singleTap + /// ) + /// )); + ///} + ///``` + final ActivationMode explodeGesture; + + ///Border width of the data points in the series. + /// + ///Defaults to 0 + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// borderWidth: 2 + /// ) + /// )); + ///} + ///``` + @override + final double borderWidth; + + ///Border color of the data points in the series. + /// + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// borderColor: Colors.red + /// ) + /// )); + ///} + ///``` + @override + final Color borderColor; + + ///Shape of the legend icon. Any shape in the LegendIconType can be applied to this property. + ///By default, icon will be rendered based on the type of the series. + /// + /// + ///Also refer [LegendIconType] + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// legendIconType: LegendIconType.diamond, + /// ) + /// )); + ///} + ///``` + @override + final LegendIconType legendIconType; + + ///Enables the dataLabel of the series + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// dataLabelSettings: DataLabelSettings(isVisible: true), + /// ) + /// )); + ///} + ///``` + @override + final DataLabelSettings dataLabelSettings; + + ///Duration for animating the data points. + /// + ///Defaults to 1500 + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// animationDuration: 2000, + /// ) + /// )); + ///} + ///``` + @override + final double animationDuration; + + /// Delay duration of the series animation.It takes a millisecond value as input. + /// By default, the series will get animated for the specified duration. + /// If animationDelay is specified, then the series will begin to animate + /// after the specified duration. + /// + /// Defaults to 0. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// animationDelay: 2000, + /// ) + /// )); + ///} + ///``` + @override + final double animationDelay; + + ///Maps the field name, which will be considered as data point color. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// pointColorMapper: (ChartData data, _) => data.pointColor, + /// ) + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal, [this.pointColor]); + /// final String xVal; + /// final int yVal; + /// final Color pointColor; + ///} + ///``` + @override + final ChartIndexedValueMapper? pointColorMapper; + + ///Maps the field name, which will be considered as text for data label. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// textFieldMapper: (ChartData data, _) => data.xVal, + /// ) + /// )); + ///} + ///class ChartData { + /// ChartData(this.xVal, this.yVal, [this.pointColor]); + /// final String xVal; + /// final int yVal; + /// final Color pointColor; + ///} + ///``` + final ChartIndexedValueMapper? textFieldMapper; + + ///Opacity of the series. The value ranges from 0 to 1. + /// + ///Defaults to 1 + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// opacity: 0.5, + /// ) + /// )); + ///} + ///``` + @override + final double opacity; + + ///Customizes the selection of series. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// selectionBehavior: SelectionBehavior( + /// selectedColor: Colors.red, + /// unselectedColor: Colors.grey + /// ), + /// ) + /// )); + ///} + ///``` + @override + final SelectionBehavior selectionBehavior; + + ///Index of the slice to explode it at the initial rendering. + /// + ///Defaults to null + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// explodeIndex: 1, + /// explode: true + /// ) + /// )); + ///} + ///``` + final num? explodeIndex; + + /// List of data indexes initially selected + /// + /// Defaults to `null`. + ///```dart + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// child: SfPyramidChart( + /// initialSelectedDataIndexes: [IndexesModel(1, 0)] + /// ) + /// ) + /// ) + /// ); + /// } + List initialSelectedDataIndexes; + + ///Key to identify a series in a collection. + + /// + + ///On specifying [ValueKey] as the series [key], existing series index + ///can be changed in the series collection without losing its state. + + /// + + ///When a new series is added dynamically to the collection, + ///existing series index will be changed. On that case, + + /// the existing series and its state will be linked based on its chart type and this key value. + + /// + + ///Defaults to `null`. + + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// key: const ValueKey('line_series_key'), + /// ), + /// )); + ///} + ///``` + final ValueKey? key; + + ///Used to create the renderer for custom series. + /// + ///This is applicable only when the custom series is defined in the sample + /// and for built-in series types, it is not applicable. + /// + ///Renderer created in this will hold the series state and + /// this should be created for each series. [onCreateRenderer] callback + /// function should return the renderer class and should not return null. + /// + ///Series state will be created only once per series and will not be created + ///again when we update the series. + /// + ///Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// onCreateRenderer:(PyramidSeries series){ + /// return CustomLinerSeriesRenderer(); + /// } + /// ), + /// )); + /// } + /// class CustomLinerSeriesRenderer extends PyramidSeriesRenderer { + /// // custom implementation here... + /// } + ///``` + final ChartSeriesRendererFactory? onCreateRenderer; + + ///Triggers when the series renderer is created. + + /// + + ///Using this callback, able to get the [ChartSeriesController] instance, which is used to access the public methods in the series. + + /// + + ///Defaults to `null`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// onRendererCreated: (PyramidSeriesController controller) { + /// _chartSeriesController = controller; + /// }, + /// ], + /// )); + ///} + ///``` + final PyramidSeriesRendererCreatedCallback? onRendererCreated; + + ///Called when tapped on the chart data point. + /// + ///The user can fetch the series index, point index, view port point index and + /// data of the tapped data point. + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointTap: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// )); + ///} + ///``` + final ChartPointInteractionCallback? onPointTap; + + ///Called when double tapped on the chart data point. + /// + ///The user can fetch the series index, point index, view port point index and + /// data of the double-tapped data point. + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointDoubleTap: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// )); + ///} + ///``` + final ChartPointInteractionCallback? onPointDoubleTap; + + ///Called when long pressed on the chart data point. + /// + ///The user can fetch the series index, point index, view port point index and + /// data of the long-pressed data point. + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfCartesianChart( + /// series: >[ + /// LineSeries( + /// onPointLongPress: (ChartPointDetails details) { + /// print(details.pointIndex); + /// }, + /// ), + /// ], + /// )); + ///} + ///``` + final ChartPointInteractionCallback? onPointLongPress; + + @override + void calculateEmptyPointValue( + int pointIndex, dynamic currentPoint, dynamic seriesRenderer) { + final List> dataPoints = seriesRenderer.dataPoints; + final EmptyPointSettings empty = emptyPointSettings; + final int pointLength = dataPoints.length; + final PointInfo point = dataPoints[pointIndex]; + if (point.y == null) { + switch (empty.mode) { + case EmptyPointMode.average: + final num previous = + pointIndex - 1 >= 0 ? dataPoints[pointIndex - 1].y ?? 0 : 0; + final num next = pointIndex + 1 <= pointLength - 1 + ? dataPoints[pointIndex + 1].y ?? 0 + : 0; + point.y = (previous + next).abs() / 2; + point.isVisible = true; + point.isEmpty = true; + break; + case EmptyPointMode.zero: + point.y = 0; + point.isVisible = true; + point.isEmpty = true; + break; + default: + point.isEmpty = true; + point.isVisible = false; + break; + } + } + } +} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_controller.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_controller.dart new file mode 100644 index 000000000..a9696a3f2 --- /dev/null +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/renderer/series_controller.dart @@ -0,0 +1,237 @@ +import 'dart:ui'; +import 'package:syncfusion_flutter_charts/src/common/utils/helper.dart'; +import '../../chart/chart_series/series.dart'; +import '../../common/utils/typedef.dart'; +import '../base/pyramid_state_properties.dart'; +import '../utils/common.dart'; +import 'pyramid_series.dart'; +import 'renderer_extension.dart'; + +///We can redraw the series with updating or creating new points by using this controller.If we need to access the redrawing methods +///in this before we must get the ChartSeriesController onRendererCreated event. +class PyramidSeriesController { + /// Creating an argument constructor of PyramidSeriesController class. + PyramidSeriesController(this.seriesRenderer); + + ///Used to access the series properties. + /// + ///Defaults to `null` + /// + ///```dart + ///Widget build(BuildContext context) { + /// ChartSeriesController _chartSeriesController; + /// return Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// onRendererCreated: (PyramidSeriesController controller) { + /// _chartSeriesController = controller; + /// // prints series yAxisName + /// print(_chartSeriesController.seriesRenderer.seriesRendererDetails.series.yAxisName); + /// }, + /// ), + /// )); + ///} + ///``` + final PyramidSeriesRenderer seriesRenderer; + + ///Used to process only the newly added, updated and removed data points in a series, + /// instead of processing all the data points. + /// + ///To re-render the chart with modified data points, setState() will be called. + /// This will render the process and render the chart from scratch. + /// Thus, the app’s performance will be degraded on continuous update. + /// To overcome this problem, [updateDataSource] method can be called by passing updated data points indexes. + /// Chart will process only that point and skip various steps like bounds calculation, + /// old data points processing, etc. Thus, this will improve the app’s performance. + /// + ///The following are the arguments of this method. + /// * addedDataIndexes – `List` type – Indexes of newly added data points in the existing series. + /// * removedDataIndexes – `List` type – Indexes of removed data points in the existing series. + /// * updatedDataIndexes – `List` type – Indexes of updated data points in the existing series. + /// * addedDataIndex – `int` type – Index of newly added data point in the existing series. + /// * removedDataIndex – `int` type – Index of removed data point in the existing series. + /// * updatedDataIndex – `int` type – Index of updated data point in the existing series. + /// + ///Returns `void`. + /// + ///```dart + ///Widget build(BuildContext context) { + /// PyramidSeriesController _pyramidSeriesController; + /// return Column( + /// children: [ + /// Container( + /// child: SfPyramidChart( + /// series: PyramidSeries( + /// dataSource: chartData, + /// onRendererCreated: (PyramidSeriesController controller) { + /// _pyramidSeriesController = controller; + /// }, + /// ), + /// )), + /// Container( + /// child: RaisedButton( + /// onPressed: () { + /// chartData.removeAt(0); + /// chartData.add(ChartData(3,23)); + /// _pyramidSeriesController.updateDataSource( + /// addedDataIndexes: [chartData.length -1], + /// removedDataIndexes: [0], + /// ); + /// }) + /// )] + /// ); + /// } + ///``` + void updateDataSource( + {List? addedDataIndexes, + List? removedDataIndexes, + List? updatedDataIndexes, + int? addedDataIndex, + int? removedDataIndex, + int? updatedDataIndex}) { + if (removedDataIndexes != null && removedDataIndexes.isNotEmpty) { + _removeDataPointsList(removedDataIndexes); + } else if (removedDataIndex != null) { + _removeDataPoint(removedDataIndex); + } + if (addedDataIndexes != null && addedDataIndexes.isNotEmpty) { + _addOrUpdateDataPoints(addedDataIndexes, false); + } else if (addedDataIndex != null) { + _addOrUpdateDataPoint(addedDataIndex, false); + } + if (updatedDataIndexes != null && updatedDataIndexes.isNotEmpty) { + _addOrUpdateDataPoints(updatedDataIndexes, true); + } else if (updatedDataIndex != null) { + _addOrUpdateDataPoint(updatedDataIndex, true); + } + _updatePyramidSeries(); + } + + /// Add or update the data points on dynamic series update + void _addOrUpdateDataPoints(List indexes, bool needUpdate) { + int dataIndex; + for (int i = 0; i < indexes.length; i++) { + dataIndex = indexes[i]; + _addOrUpdateDataPoint(dataIndex, needUpdate); + } + } + + /// add or update a data point in the given index + void _addOrUpdateDataPoint(int index, bool needUpdate) { + final PyramidSeriesRendererExtension renderer = + seriesRenderer as PyramidSeriesRendererExtension; + final PyramidSeries series = renderer.series; + if (index >= 0 && + series.dataSource!.length > index && + series.dataSource![index] != null) { + final ChartIndexedValueMapper? xValue = series.xValueMapper; + final ChartIndexedValueMapper? yValue = series.yValueMapper; + final PointInfo _currentPoint = + PointInfo(xValue!(index), yValue!(index)); + if (_currentPoint.x != null) { + if (needUpdate) { + if (renderer.dataPoints.length > index) { + renderer.dataPoints[index] = _currentPoint; + } + } else { + if (renderer.dataPoints.length == index) { + renderer.dataPoints.add(_currentPoint); + } else if (renderer.dataPoints.length > index && index >= 0) { + renderer.dataPoints.insert(index, _currentPoint); + } + } + } + } + } + + ///Remove list of points + void _removeDataPointsList(List removedDataIndexes) { + //Remove the redundant index from the list + final List indexList = removedDataIndexes.toSet().toList(); + indexList.sort((int b, int a) => a.compareTo(b)); + int dataIndex; + for (int i = 0; i < indexList.length; i++) { + dataIndex = indexList[i]; + _removeDataPoint(dataIndex); + } + } + + /// remove a data point in the given index + void _removeDataPoint(int index) { + final PyramidSeriesRendererExtension renderer = + seriesRenderer as PyramidSeriesRendererExtension; + if (renderer.dataPoints.isNotEmpty && + index >= 0 && + index < renderer.dataPoints.length) { + renderer.dataPoints.removeAt(index); + } + } + + /// After add/remove/update data points, recalculate the chart angle and positions + void _updatePyramidSeries() { + final PyramidSeriesRendererExtension renderer = + seriesRenderer as PyramidSeriesRendererExtension; + final PyramidStateProperties stateProperties = renderer.stateProperties; + stateProperties.chartSeries.processDataPoints(); + stateProperties.chartSeries.initializeSeriesProperties(renderer); + renderer.repaintNotifier.value++; + if (renderer.series.dataLabelSettings.isVisible && + stateProperties.renderDataLabel != null) { + stateProperties.renderDataLabel!.state?.render(); + } + if (renderer.series.dataLabelSettings.isVisible && + stateProperties.renderingDetails.chartTemplate != null && + // ignore: unnecessary_null_comparison + stateProperties.renderingDetails.chartTemplate!.state != null) { + stateProperties.renderingDetails.chartTemplate!.state.templateRender(); + } + } + + /// Converts chart data point value to logical pixel value. + /// + /// The [pointToPixel] method takes chart data point value as input and returns logical pixel value. + /// + /// _Note_: It returns the data point's center location value. + /// + /// late PyramidSeriesController pyramidSeriesController; + /// SfPyramidChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// PointInfo chartPoint1 = seriesController.pixelToPoint(value); + /// }, + /// series: PyramidSeries( + /// onRendererCreated: (PyramidSeriesController seriesController) { + /// pyramidSeriesController = seriesController; + /// }), + /// ); + // ignore: unused_element + Offset _pointToPixel(PointInfo point) { + return pyramidFunnelPointToPixel(point, seriesRenderer); + } + + /// Converts logical pixel value to the data point value. + /// + /// The [pixelToPoint] method takes logical pixel value as input and returns a chart data point. + /// + /// late PyramidSeriesController pyramidSeriesController; + /// SfPyramidChart( + /// onChartTouchInteractionDown: (ChartTouchInteractionArgs args) { + /// ChartPoint chartPoint = seriesController.pixelToPoint(args.position); + /// Offset value = seriesController.pointToPixel(chartPoint); + /// }, + /// series: PyramidSeries( + /// onRendererCreated: (PyramidSeriesController seriesController) { + /// pyramidSeriesController = seriesController; + /// }), + /// ); + PointInfo pixelToPoint(Offset position) { + return pyramidFunnelPixelToPoint(position, seriesRenderer); + } +} + +/// Creates series renderer for Pyramid series +class PyramidSeriesRenderer extends ChartSeriesRenderer { + /// Calling the default constructor of PyramidSeriesRenderer class. + PyramidSeriesRenderer(); +} diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart index 0af5119ab..50f69d5c8 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/common.dart @@ -1,4 +1,8 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import '../../chart/utils/enum.dart'; +import '../../circular_chart/utils/enum.dart'; /// This is similar to the point of the Cartesian chart. class PointInfo { @@ -84,10 +88,4 @@ class PointInfo { /// To execute OnDataLabelRender event or not. // ignore: prefer_final_fields bool labelRenderEvent = false; - - /// Stores the tooltip label text. - String? _tooltipLabelText; - - /// Stores the tooltip header text. - String? _tooltipHeaderText; } diff --git a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart index 38fb1326e..26e610c94 100644 --- a/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/pyramid_chart/utils/helper.dart @@ -1,7 +1,27 @@ -part of charts; +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import '../../chart/common/data_label.dart'; +import '../../chart/utils/enum.dart'; +import '../../chart/utils/helper.dart'; +import '../../circular_chart/renderer/circular_chart_annotation.dart'; +import '../../circular_chart/renderer/data_label_renderer.dart'; +import '../../circular_chart/utils/enum.dart'; +import '../../circular_chart/utils/helper.dart'; +import '../../common/event_args.dart'; +import '../../common/state_properties.dart'; +import '../../common/template/rendering.dart'; +import '../../common/utils/enum.dart'; +import '../../common/utils/helper.dart'; +import '../base/pyramid_base.dart'; +import '../base/pyramid_state_properties.dart'; +import '../renderer/renderer_extension.dart'; +import '../utils/common.dart'; /// Method for checking if point is within polygon -bool _isPointInPolygon(List polygon, Offset point) { +bool isPointInPolygon(List polygon, Offset point) { bool p = false; int i = -1; final int l = polygon.length; @@ -21,25 +41,25 @@ bool _isPointInPolygon(List polygon, Offset point) { } /// To add chart templates -void _findTemplates(dynamic _chartState) { +void findTemplates(dynamic _stateProperties) { Offset labelLocation; const num lineLength = 10; PointInfo point; Widget labelWidget; - _chartState._renderingDetails.dataLabelTemplateRegions = []; - _chartState._renderingDetails.templates = <_ChartTemplateInfo>[]; + _stateProperties.renderingDetails.dataLabelTemplateRegions = []; + _stateProperties.renderingDetails.templates = []; dynamic series; dynamic seriesRenderer; ChartAlignment labelAlign; for (int k = 0; - k < _chartState._chartSeries.visibleSeriesRenderers.length; + k < _stateProperties.chartSeries.visibleSeriesRenderers.length; k++) { - seriesRenderer = _chartState._chartSeries.visibleSeriesRenderers[k]; - series = seriesRenderer._series; + seriesRenderer = _stateProperties.chartSeries.visibleSeriesRenderers[k]; + series = seriesRenderer.series; if (series.dataLabelSettings.isVisible == true && series.dataLabelSettings.builder != null) { - for (int i = 0; i < seriesRenderer._renderPoints.length; i++) { - point = seriesRenderer._renderPoints[i]; + for (int i = 0; i < seriesRenderer.renderPoints.length; i++) { + point = seriesRenderer.renderPoints[i]; if (point.isVisible) { labelWidget = series.dataLabelSettings .builder(series.dataSource[i], point, series, i, k); @@ -58,13 +78,13 @@ void _findTemplates(dynamic _chartState) { ? ChartAlignment.far : ChartAlignment.near; } - _chartState._renderingDetails.templates.add(_ChartTemplateInfo( + _stateProperties.renderingDetails.templates.add(ChartTemplateInfo( key: GlobalKey(), templateType: 'DataLabel', pointIndex: i, seriesIndex: k, needMeasure: true, - clipRect: _chartState._renderingDetails.chartAreaRect, + clipRect: _stateProperties.renderingDetails.chartAreaRect, animationDuration: 500, widget: labelWidget, horizontalAlignment: labelAlign, @@ -77,58 +97,60 @@ void _findTemplates(dynamic _chartState) { } /// To render a template -void _renderTemplates(dynamic _chartState) { - if (_chartState._renderingDetails.templates.isNotEmpty == true) { - _ChartTemplateInfo chartTemplateInfo; - for (int i = 0; i < _chartState._renderingDetails.templates.length; i++) { - chartTemplateInfo = _chartState._renderingDetails.templates[i]; +void renderTemplates(StateProperties stateProperties) { + if (stateProperties.renderingDetails.templates.isNotEmpty == true) { + ChartTemplateInfo chartTemplateInfo; + for (int i = 0; + i < stateProperties.renderingDetails.templates.length; + i++) { + chartTemplateInfo = stateProperties.renderingDetails.templates[i]; chartTemplateInfo.animationDuration = - _chartState._renderingDetails.initialRender == false + stateProperties.renderingDetails.initialRender == false ? 0 : chartTemplateInfo.animationDuration; } - _chartState._renderingDetails.chartTemplate = _ChartTemplate( - templates: _chartState._renderingDetails.templates, - render: _chartState._renderingDetails.animateCompleted, - chartState: _chartState); - _chartState._renderingDetails.chartWidgets - .add(_chartState._renderingDetails.chartTemplate); + stateProperties.renderingDetails.chartTemplate = ChartTemplate( + templates: stateProperties.renderingDetails.templates, + render: stateProperties.renderingDetails.animateCompleted, + stateProperties: stateProperties); + stateProperties.renderingDetails.chartWidgets! + .add(stateProperties.renderingDetails.chartTemplate!); } } ///To get pyramid series data label saturation color -Color _getPyramidFunnelColor(PointInfo currentPoint, - dynamic seriesRenderer, dynamic _chartState) { +Color getPyramidFunnelColor(PointInfo currentPoint, + dynamic seriesRenderer, dynamic _stateProperties) { Color color; - final dynamic series = seriesRenderer._series; + final dynamic series = seriesRenderer.series; final DataLabelSettings dataLabel = series.dataLabelSettings; final DataLabelSettingsRenderer dataLabelSettingsRenderer = - seriesRenderer._dataLabelSettingsRenderer; + seriesRenderer.dataLabelSettingsRenderer; color = (currentPoint.renderPosition == null || currentPoint.renderPosition == ChartDataLabelPosition.inside && !currentPoint.saturationRegionOutside) - ? _innerColor(dataLabelSettingsRenderer._color, currentPoint.fill, - _chartState._renderingDetails.chartTheme) - : _outerColor( - dataLabelSettingsRenderer._color, + ? innerColor(dataLabelSettingsRenderer.color, currentPoint.fill, + _stateProperties.renderingDetails.chartTheme) + : outerColor( + dataLabelSettingsRenderer.color, dataLabel.useSeriesColor ? currentPoint.fill - : (_chartState._chart.backgroundColor != null - ? _chartState - ._renderingDetails.chartTheme.plotAreaBackgroundColor + : (_stateProperties.chart.backgroundColor != null + ? _stateProperties + .renderingDetails.chartTheme.plotAreaBackgroundColor : null), - _chartState._renderingDetails.chartTheme); + _stateProperties.renderingDetails.chartTheme); - return _getSaturationColor(color); + return getSaturationColor(color); } ///To get inner data label color -Color _innerColor( +Color innerColor( Color? dataLabelColor, Color? pointColor, SfChartThemeData theme) => dataLabelColor ?? pointColor ?? Colors.black; ///To get outer data label color -Color _outerColor(Color? dataLabelColor, Color? backgroundColor, +Color outerColor(Color? dataLabelColor, Color? backgroundColor, SfChartThemeData theme) => // ignore: prefer_if_null_operators dataLabelColor != null @@ -141,13 +163,13 @@ Color _outerColor(Color? dataLabelColor, Color? backgroundColor, : Colors.black; ///To get outer data label text style -TextStyle _getDataLabelTextStyle( - dynamic seriesRenderer, PointInfo point, dynamic chartState, +TextStyle getDataLabelTextStyle( + dynamic seriesRenderer, PointInfo point, dynamic stateProperties, [double? animateOpacity]) { - final dynamic series = seriesRenderer._series; + final dynamic series = seriesRenderer.series; final DataLabelSettings dataLabel = series.dataLabelSettings; final Color fontColor = dataLabel.textStyle.color ?? - _getPyramidFunnelColor(point, seriesRenderer, chartState); + getPyramidFunnelColor(point, seriesRenderer, stateProperties); final TextStyle textStyle = TextStyle( color: fontColor.withOpacity(animateOpacity ?? 1), fontSize: dataLabel.textStyle.fontSize, @@ -175,25 +197,25 @@ TextStyle _getDataLabelTextStyle( } /// To check the point explosion -bool _isNeedExplode(int pointIndex, dynamic series, dynamic _chartState) { +bool isNeedExplode(int pointIndex, dynamic series, dynamic stateProperties) { bool isNeedExplode = false; if (series.explode == true) { - if (_chartState._renderingDetails.initialRender == true) { + if (stateProperties.renderingDetails.initialRender == true) { if (pointIndex == series.explodeIndex) { - _chartState._renderingDetails.explodedPoints.add(pointIndex); + stateProperties.renderingDetails.explodedPoints.add(pointIndex); isNeedExplode = true; } - } else if (_chartState._renderingDetails.widgetNeedUpdate == true || - _chartState._renderingDetails.isLegendToggled == true) { + } else if (stateProperties.renderingDetails.widgetNeedUpdate == true || + stateProperties.renderingDetails.isLegendToggled == true) { isNeedExplode = - _chartState._renderingDetails.explodedPoints.contains(pointIndex); + stateProperties.renderingDetails.explodedPoints.contains(pointIndex); } } return isNeedExplode; } /// To return data label rect calculation method based on position -Rect? _getDataLabelRect(Position position, ConnectorType connectorType, +Rect? getDataLabelRect(Position position, ConnectorType connectorType, EdgeInsets margin, Path connectorPath, Offset endPoint, Size textSize) { Rect? rect; const int lineLength = 10; @@ -230,3 +252,385 @@ Rect? _getDataLabelRect(Position position, ConnectorType connectorType, } return rect; } + +/// To render pyramid data labels +void renderPyramidDataLabel( + PyramidSeriesRendererExtension seriesRenderer, + Canvas canvas, + PyramidStateProperties stateProperties, + Animation animation) { + PointInfo point; + final SfPyramidChart chart = stateProperties.chart; + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; + final DataLabelSettingsRenderer dataLabelSettingsRenderer = + seriesRenderer.dataLabelSettingsRenderer; + String? label; + // ignore: unnecessary_null_comparison + final double animateOpacity = animation != null ? animation.value : 1; + DataLabelRenderArgs dataLabelArgs; + TextStyle dataLabelStyle; + final List renderDataLabelRegions = []; + Size textSize; + for (int pointIndex = 0; + pointIndex < seriesRenderer.renderPoints!.length; + pointIndex++) { + point = seriesRenderer.renderPoints![pointIndex]; + if (point.isVisible && (point.y != 0 || dataLabel.showZeroValue)) { + label = point.text; + dataLabelStyle = dataLabel.textStyle; + dataLabelSettingsRenderer.color = + seriesRenderer.series.dataLabelSettings.color; + if (chart.onDataLabelRender != null && + !seriesRenderer.renderPoints![pointIndex].labelRenderEvent) { + dataLabelArgs = DataLabelRenderArgs(seriesRenderer, + seriesRenderer.renderPoints, pointIndex, pointIndex); + dataLabelArgs.text = label!; + dataLabelArgs.textStyle = dataLabelStyle; + dataLabelArgs.color = dataLabelSettingsRenderer.color; + chart.onDataLabelRender!(dataLabelArgs); + label = point.text = dataLabelArgs.text; + dataLabelStyle = dataLabelArgs.textStyle; + pointIndex = dataLabelArgs.pointIndex!; + dataLabelSettingsRenderer.color = dataLabelArgs.color; + seriesRenderer.dataPoints[pointIndex].labelRenderEvent = true; + } + dataLabelStyle = chart.onDataLabelRender == null + ? getDataLabelTextStyle( + seriesRenderer, point, stateProperties, animateOpacity) + : dataLabelStyle; + textSize = measureText(label!, dataLabelStyle); + + // Label check after event + if (label != '') { + if (dataLabel.labelPosition == ChartDataLabelPosition.inside) { + _setPyramidInsideLabelPosition( + dataLabel, + point, + textSize, + stateProperties, + canvas, + renderDataLabelRegions, + pointIndex, + label, + seriesRenderer, + animateOpacity, + dataLabelStyle); + } else { + point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = getDataLabelTextStyle( + seriesRenderer, point, stateProperties, animateOpacity); + _renderOutsidePyramidDataLabel( + canvas, + label, + point, + textSize, + pointIndex, + seriesRenderer, + stateProperties, + dataLabelStyle, + renderDataLabelRegions, + animateOpacity); + } + } + } + } +} + +/// To calculate pyramid inside label position +void _setPyramidInsideLabelPosition( + DataLabelSettings dataLabel, + PointInfo point, + Size textSize, + PyramidStateProperties stateProperties, + Canvas canvas, + List renderDataLabelRegions, + int pointIndex, + String label, + PyramidSeriesRendererExtension seriesRenderer, + double animateOpacity, + TextStyle dataLabelStyle) { + final num angle = dataLabel.angle; + Offset labelLocation; + final SmartLabelMode smartLabelMode = stateProperties.chart.smartLabelMode; + const int labelPadding = 2; + labelLocation = point.symbolLocation; + labelLocation = Offset( + labelLocation.dx - + (textSize.width / 2) + + (angle == 0 ? 0 : textSize.width / 2), + labelLocation.dy - + (textSize.height / 2) + + (angle == 0 ? 0 : textSize.height / 2)); + point.labelRect = Rect.fromLTWH( + labelLocation.dx - labelPadding, + labelLocation.dy - labelPadding, + textSize.width + (2 * labelPadding), + textSize.height + (2 * labelPadding)); + final bool isDataLabelCollide = + findingCollision(point.labelRect!, renderDataLabelRegions, point.region); + if (isDataLabelCollide && smartLabelMode == SmartLabelMode.shift) { + point.saturationRegionOutside = true; + point.renderPosition = ChartDataLabelPosition.outside; + dataLabelStyle = getDataLabelTextStyle( + seriesRenderer, point, stateProperties, animateOpacity); + _renderOutsidePyramidDataLabel( + canvas, + label, + point, + textSize, + pointIndex, + seriesRenderer, + stateProperties, + dataLabelStyle, + renderDataLabelRegions, + animateOpacity); + } else if (smartLabelMode == SmartLabelMode.none || + (!isDataLabelCollide && smartLabelMode == SmartLabelMode.shift) || + (!isDataLabelCollide && smartLabelMode == SmartLabelMode.hide)) { + point.renderPosition = ChartDataLabelPosition.inside; + _drawPyramidLabel( + point.labelRect!, + labelLocation, + label, + null, + canvas, + seriesRenderer, + point, + pointIndex, + stateProperties, + dataLabelStyle, + renderDataLabelRegions, + animateOpacity); + } +} + +/// To render outside pyramid data label +void _renderOutsidePyramidDataLabel( + Canvas canvas, + String label, + PointInfo point, + Size textSize, + int pointIndex, + PyramidSeriesRendererExtension seriesRenderer, + PyramidStateProperties stateProperties, + TextStyle textStyle, + List renderDataLabelRegions, + double animateOpacity) { + Path connectorPath; + Rect? rect; + Offset labelLocation; + final EdgeInsets margin = seriesRenderer.series.dataLabelSettings.margin; + final ConnectorLineSettings connector = + seriesRenderer.series.dataLabelSettings.connectorLineSettings; + const num regionPadding = 12; + connectorPath = Path(); + final num connectorLength = percentToValue(connector.length ?? '0%', + stateProperties.renderingDetails.chartAreaRect.width / 2)! + + seriesRenderer.maximumDataLabelRegion.width / 2 - + regionPadding; + final Offset startPoint = Offset( + seriesRenderer.renderPoints![pointIndex].region!.right, + seriesRenderer.renderPoints![pointIndex].region!.top + + seriesRenderer.renderPoints![pointIndex].region!.height / 2); + final double dx = seriesRenderer.renderPoints![pointIndex].symbolLocation.dx + + connectorLength; + final Offset endPoint = Offset( + (dx + textSize.width + margin.left + margin.right > + stateProperties.renderingDetails.chartAreaRect.right) + ? dx - + (percentToValue(seriesRenderer.series.explodeOffset, + stateProperties.renderingDetails.chartAreaRect.width)!) + : dx, + seriesRenderer.renderPoints![pointIndex].symbolLocation.dy); + connectorPath.moveTo(startPoint.dx, startPoint.dy); + if (connector.type == ConnectorType.line) { + connectorPath.lineTo(endPoint.dx, endPoint.dy); + } + point.dataLabelPosition = Position.right; + rect = getDataLabelRect(point.dataLabelPosition!, connector.type, margin, + connectorPath, endPoint, textSize); + if (rect != null) { + point.labelRect = rect; + labelLocation = Offset(rect.left + margin.left, + rect.top + rect.height / 2 - textSize.height / 2); + final Rect containerRect = stateProperties.renderingDetails.chartAreaRect; + if (seriesRenderer.series.dataLabelSettings.builder == null) { + Rect? lastRenderedLabelRegion; + if (renderDataLabelRegions.isNotEmpty) { + lastRenderedLabelRegion = + renderDataLabelRegions[renderDataLabelRegions.length - 1]; + } + if (!_isPyramidLabelIntersect(rect, lastRenderedLabelRegion) && + (rect.left > containerRect.left && + rect.left + rect.width < + containerRect.left + containerRect.width) && + rect.top > containerRect.top && + rect.top + rect.height < containerRect.top + containerRect.height) { + _drawPyramidLabel( + rect, + labelLocation, + label, + connectorPath, + canvas, + seriesRenderer, + point, + pointIndex, + stateProperties, + textStyle, + renderDataLabelRegions, + animateOpacity); + } else { + if (pointIndex != 0) { + const num connectorLinePadding = 15; + const num padding = 2; + final Rect previousRenderedRect = + renderDataLabelRegions[renderDataLabelRegions.length - 1]; + rect = Rect.fromLTWH(rect.left, previousRenderedRect.bottom + padding, + rect.width, rect.height); + labelLocation = Offset( + rect.left + margin.left, + previousRenderedRect.bottom + + padding + + rect.height / 2 - + textSize.height / 2); + connectorPath = Path(); + connectorPath.moveTo(startPoint.dx, startPoint.dy); + connectorPath.lineTo( + rect.left - connectorLinePadding, rect.top + rect.height / 2); + connectorPath.lineTo(rect.left, rect.top + rect.height / 2); + } + if (rect.bottom < + stateProperties.renderingDetails.chartAreaRect.bottom) { + _drawPyramidLabel( + rect, + labelLocation, + label, + connectorPath, + canvas, + seriesRenderer, + point, + pointIndex, + stateProperties, + textStyle, + renderDataLabelRegions, + animateOpacity); + } + } + } + } +} + +/// To check whether labels intersect +bool _isPyramidLabelIntersect(Rect rect, Rect? previousRect) { + bool isIntersect = false; + const num padding = 2; + if (previousRect != null && (rect.top - padding) < previousRect.bottom) { + isIntersect = true; + } + return isIntersect; +} + +/// To draw pyramid data label +void _drawPyramidLabel( + Rect labelRect, + Offset location, + String? label, + Path? connectorPath, + Canvas canvas, + PyramidSeriesRendererExtension seriesRenderer, + PointInfo point, + int pointIndex, + PyramidStateProperties stateProperties, + TextStyle textStyle, + List renderDataLabelRegions, + double animateOpacity) { + Paint rectPaint; + final DataLabelSettings dataLabel = seriesRenderer.series.dataLabelSettings; + final DataLabelSettingsRenderer dataLabelSettingsRenderer = + seriesRenderer.dataLabelSettingsRenderer; + final ConnectorLineSettings connector = dataLabel.connectorLineSettings; + if (connectorPath != null) { + canvas.drawPath( + connectorPath, + Paint() + ..color = connector.width <= 0 + ? Colors.transparent + : connector.color ?? + point.fill.withOpacity( + !stateProperties.renderingDetails.isLegendToggled + ? animateOpacity + : dataLabel.opacity) + ..strokeWidth = connector.width + ..style = PaintingStyle.stroke); + } + + if (dataLabel.builder == null) { + final double strokeWidth = dataLabel.borderWidth; + final Color? labelFill = dataLabelSettingsRenderer.color ?? + (dataLabel.useSeriesColor + ? point.fill + : dataLabelSettingsRenderer.color); + final Color? strokeColor = + dataLabel.borderColor.withOpacity(dataLabel.opacity); + // ignore: unnecessary_null_comparison + if (strokeWidth != null && strokeWidth > 0) { + rectPaint = Paint() + ..color = strokeColor!.withOpacity( + !stateProperties.renderingDetails.isLegendToggled + ? animateOpacity + : dataLabel.opacity) + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth; + drawLabelRect( + rectPaint, + Rect.fromLTRB( + labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), + dataLabel.borderRadius, + canvas); + } + if (labelFill != null) { + drawLabelRect( + Paint() + ..color = labelFill + .withOpacity(!stateProperties.renderingDetails.isLegendToggled + ? (animateOpacity - (1 - dataLabel.opacity)) < 0 + ? 0 + : animateOpacity - (1 - dataLabel.opacity) + : dataLabel.opacity) + ..style = PaintingStyle.fill, + Rect.fromLTRB( + labelRect.left, labelRect.top, labelRect.right, labelRect.bottom), + dataLabel.borderRadius, + canvas); + } + drawText(canvas, label!, location, textStyle, dataLabel.angle); + renderDataLabelRegions.add(labelRect); + } +} + +/// method to trigger the pyramid data label event +void triggerPyramidDataLabelEvent( + SfPyramidChart chart, + PyramidSeriesRendererExtension seriesRenderer, + PyramidStateProperties chartState, + Offset position) { + const int seriesIndex = 0; + DataLabelSettings dataLabel; + PointInfo point; + Offset labelLocation; + for (int pointIndex = 0; + pointIndex < seriesRenderer.renderPoints!.length; + pointIndex++) { + dataLabel = seriesRenderer.series.dataLabelSettings; + point = seriesRenderer.renderPoints![pointIndex]; + labelLocation = point.symbolLocation; + if (dataLabel.isVisible && + seriesRenderer.renderPoints![pointIndex].labelRect != null && + seriesRenderer.renderPoints![pointIndex].labelRect! + .contains(position)) { + position = Offset(labelLocation.dx, labelLocation.dy); + dataLabelTapEvent(chart, seriesRenderer.series.dataLabelSettings, + pointIndex, point, position, seriesIndex); + } + } +} 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 ba5ccc50a..9c4263a07 100644 --- a/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart +++ b/packages/syncfusion_flutter_charts/lib/src/sparkline/utils/helper.dart @@ -1,9 +1,11 @@ import 'dart:math' as math; import 'dart:ui'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:syncfusion_flutter_core/core.dart'; -import 'package:flutter/foundation.dart'; + import '../marker.dart'; import 'enum.dart'; diff --git a/packages/syncfusion_flutter_charts/pubspec.yaml b/packages/syncfusion_flutter_charts/pubspec.yaml index 1d43786fd..9de67787c 100644 --- a/packages/syncfusion_flutter_charts/pubspec.yaml +++ b/packages/syncfusion_flutter_charts/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: flutter: sdk: flutter intl: ^0.17.0 - vector_math: ^2.1.0 + vector_math: ">=2.1.0 <=3.0.0" syncfusion_flutter_core: path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_core/lib/interactive_scroll_viewer_internal.dart b/packages/syncfusion_flutter_core/lib/interactive_scroll_viewer_internal.dart new file mode 100644 index 000000000..9c6c3d1ed --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/interactive_scroll_viewer_internal.dart @@ -0,0 +1,7 @@ +///This library is for internal use only +library interactive_scroll_viewer_internal; + +import 'dart:ui'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +part 'src/widgets/interactive_scroll_viewer.dart'; diff --git a/packages/syncfusion_flutter_core/lib/src/legend/legend.dart b/packages/syncfusion_flutter_core/lib/src/legend/legend.dart index 6a115e3c2..d72a90a8e 100644 --- a/packages/syncfusion_flutter_core/lib/src/legend/legend.dart +++ b/packages/syncfusion_flutter_core/lib/src/legend/legend.dart @@ -6,8 +6,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; - import '../utils/shape_helper.dart'; +import '../utils/shape_helper.dart' as shape_helper; /// Callback which returns toggled indices and teh current toggled index. typedef ToggledIndicesChangedCallback = void Function( @@ -45,6 +45,10 @@ enum LegendOverflowMode { /// It will wrap and place the remaining legend items to next line. wrap, + /// It will wrap and place the remaining legend items to next + /// line with scrolling. + wrapScroll, + /// Exceeding items will be clipped. none, } @@ -135,13 +139,18 @@ class ItemRendererDetails { /// Represents the class of items in legends. class LegendItem { /// Creates a [LegendItem]. - const LegendItem({ - required this.text, - this.color, - this.shader, - this.imageProvider, - this.iconType, - }) : assert(color != null || shader != null || imageProvider != null); + const LegendItem( + {required this.text, + this.color, + this.shader, + this.imageProvider, + this.iconType, + this.iconStrokeWidth, + this.overlayMarkerType, + this.degree, + this.endAngle, + this.startAngle}) + : assert(color != null || shader != null || imageProvider != null); /// Specifies the text of the legend. final String text; @@ -157,6 +166,21 @@ class LegendItem { /// Specifies the type of the icon. final ShapeMarkerType? iconType; + + /// Specifies the stroke width of the icon. + final double? iconStrokeWidth; + + /// Specifies the overlay marker for cartesian line type icon. + final ShapeMarkerType? overlayMarkerType; + + /// Specifies the start angle for radial bar icon. + final double? startAngle; + + /// Specifies the degree for radial bar icon. + final double? degree; + + /// Specifies the end angle for radial bar icon. + final double? endAngle; } /// Represents the class for legends. @@ -172,14 +196,17 @@ class SfLegend extends StatefulWidget { this.overflowMode = LegendOverflowMode.wrap, this.spacing = 5.0, this.itemSpacing = 10.0, + this.itemRunSpacing, this.iconSize = const Size(8.0, 8.0), this.iconBorder, this.direction, + this.scrollDirection, this.width, this.height, this.alignment = LegendAlignment.center, this.offset, this.padding = const EdgeInsets.all(10.0), + this.margin, this.textStyle, this.iconType = ShapeMarkerType.circle, this.imageProvider, @@ -218,11 +245,16 @@ class SfLegend extends StatefulWidget { this.border, this.offset, this.padding, + this.margin, this.position = LegendPosition.top, this.overflowMode = LegendOverflowMode.wrap, this.itemSpacing = 10.0, + this.itemRunSpacing, this.spacing = 5.0, this.direction, + this.scrollDirection, + this.width, + this.height, this.alignment = LegendAlignment.center, this.toggledItemColor, this.onToggledIndicesChanged, @@ -238,8 +270,6 @@ class SfLegend extends StatefulWidget { iconBorder = null, segmentSize = null, labelsPlacement = null, - width = null, - height = null, edgeLabelsPlacement = null, labelOverflow = null, segmentPaintingStyle = null, @@ -264,8 +294,10 @@ class SfLegend extends StatefulWidget { this.overflowMode = LegendOverflowMode.scroll, this.itemSpacing = 2.0, this.direction, + this.scrollDirection, this.offset, this.padding = const EdgeInsets.all(10.0), + this.margin, this.textStyle, this.segmentSize, this.labelsPlacement, @@ -286,6 +318,7 @@ class SfLegend extends StatefulWidget { imageProvider = null, iconSize = Size.zero, iconBorder = null, + itemRunSpacing = null, spacing = 0.0, itemBuilder = null, itemCount = 0, @@ -325,6 +358,9 @@ class SfLegend extends StatefulWidget { /// Specifies the space between the each legend items. final double itemSpacing; + /// Specifies the cross axis run spacing for the wrapped elements. + final double? itemRunSpacing; + /// Specifies the shape of the legend icon. final ShapeMarkerType? iconType; @@ -340,6 +376,9 @@ class SfLegend extends StatefulWidget { /// Arranges the legend items in either horizontal or vertical direction. final Axis? direction; + /// Scroll the legend items in either horizontal or vertical direction. + final Axis? scrollDirection; + /// Specifies the alignment of legend. final LegendAlignment? alignment; @@ -352,9 +391,12 @@ class SfLegend extends StatefulWidget { /// Places the legend in custom position. final Offset? offset; - /// Sets the padding around the legend. + /// Empty space inside the decoration. final EdgeInsetsGeometry? padding; + /// Empty space outside the decoration. + final EdgeInsetsGeometry? margin; + /// Customizes the legend item's text style. final TextStyle? textStyle; @@ -398,7 +440,7 @@ class SfLegend extends StatefulWidget { /// Specifies the toggle item's color. Applicable for vector builder. final Color? toggledItemColor; - /// Avoid the legend rendering is its size is greater than its child. + /// Avoid the legend rendering if its size is greater than its child. final bool isComplex; /// Returns a widget for the given value. @@ -543,6 +585,7 @@ class _SfLegendState extends State { itemBuilder: widget.itemBuilder, itemCount: widget.itemCount, itemSpacing: widget.itemSpacing, + itemRunSpacing: widget.itemRunSpacing, overflowMode: widget.overflowMode, onItemRenderer: widget.onItemRenderer, onToggledIndicesChanged: widget.onToggledIndicesChanged, @@ -596,12 +639,27 @@ class _SfLegendState extends State { if (widget.title == null) { if (widget.overflowMode == LegendOverflowMode.scroll) { current = SingleChildScrollView( - scrollDirection: widget.position == LegendPosition.top || - widget.position == LegendPosition.bottom - ? Axis.horizontal - : Axis.vertical, + scrollDirection: widget.scrollDirection ?? + (widget.position == LegendPosition.top || + widget.position == LegendPosition.bottom + ? Axis.horizontal + : Axis.vertical), + child: current, + ); + } else if (widget.overflowMode == LegendOverflowMode.wrapScroll) { + current = SingleChildScrollView( + scrollDirection: widget.direction == Axis.horizontal + ? Axis.vertical + : Axis.horizontal, child: current, ); + } else if (widget.overflowMode == LegendOverflowMode.none) { + current = SingleChildScrollView( + scrollDirection: widget.scrollDirection == Axis.horizontal + ? Axis.horizontal + : Axis.vertical, + physics: const NeverScrollableScrollPhysics(), + child: current); } } else { if (widget.position == LegendPosition.top || @@ -613,10 +671,37 @@ class _SfLegendState extends State { children: [ widget.title!, if (widget.overflowMode == LegendOverflowMode.scroll) - SingleChildScrollView( - scrollDirection: Axis.horizontal, child: current) + (widget.width != null || widget.height != null) + ? Expanded( + child: SingleChildScrollView( + scrollDirection: + widget.scrollDirection ?? Axis.horizontal, + child: current), + ) + : SingleChildScrollView( + scrollDirection: + widget.scrollDirection ?? Axis.horizontal, + child: current) + else if (widget.overflowMode == LegendOverflowMode.wrapScroll) + Expanded( + child: SingleChildScrollView( + scrollDirection: widget.direction == Axis.horizontal + ? Axis.vertical + : Axis.horizontal, + child: current), + ) + else if (widget.overflowMode == LegendOverflowMode.none) + Expanded( + child: SingleChildScrollView( + scrollDirection: widget.direction == Axis.horizontal + ? Axis.horizontal + : Axis.vertical, + physics: const NeverScrollableScrollPhysics(), + child: current)) else - current + (widget.width != null || widget.height != null) + ? Expanded(child: current) + : current ], ); } else { @@ -628,8 +713,23 @@ class _SfLegendState extends State { fit: FlexFit.loose, child: widget.overflowMode == LegendOverflowMode.scroll ? SingleChildScrollView( - scrollDirection: Axis.vertical, child: current) - : current, + scrollDirection: widget.scrollDirection ?? Axis.vertical, + child: current) + : widget.overflowMode == LegendOverflowMode.wrapScroll + ? SingleChildScrollView( + scrollDirection: widget.direction == Axis.horizontal + ? Axis.vertical + : Axis.horizontal, + child: current) + : widget.overflowMode == LegendOverflowMode.none + ? SingleChildScrollView( + scrollDirection: + widget.direction == Axis.horizontal + ? Axis.vertical + : Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + child: current) + : current, ), ], ); @@ -649,14 +749,18 @@ class _SfLegendState extends State { ); } - if (widget.width != null && widget.height != null) { - current = Container( + if (widget.width != null || widget.height != null) { + current = SizedBox( width: widget.width, height: widget.height, child: current, ); } + if (widget.margin != null) { + current = Padding(padding: widget.margin!, child: current); + } + return current; } @@ -756,6 +860,7 @@ class _VectorLegend extends StatefulWidget { this.itemBuilder, this.itemCount, this.itemSpacing, + this.itemRunSpacing, this.overflowMode, this.onItemRenderer, this.onToggledIndicesChanged, @@ -819,6 +924,9 @@ class _VectorLegend extends StatefulWidget { /// Specifies the space between the each legend items. final double? itemSpacing; + /// Specifies the cross axis run spacing for the wrapped elements. + final double? itemRunSpacing; + /// Arranges the legend items in either horizontal or vertical direction. final Axis? direction; @@ -841,6 +949,7 @@ class _VectorLegendState extends State<_VectorLegend> text: item.text, textStyle: widget.textStyle, iconType: item.iconType ?? widget.iconType, + iconStrokeWidth: item.iconStrokeWidth, imageProvider: item.imageProvider ?? widget.imageProvider, shader: item.shader, iconSize: widget.iconSize, @@ -852,6 +961,10 @@ class _VectorLegendState extends State<_VectorLegend> toggledTextOpacity: widget.toggledTextOpacity, onToggledIndicesChanged: widget.onToggledIndicesChanged, onItemRenderer: widget.onItemRenderer, + overlayMarkerType: item.overlayMarkerType, + degree: item.degree, + startAngle: item.startAngle, + endAngle: item.endAngle, )); } } else if (widget.itemCount != null && @@ -886,13 +999,13 @@ class _VectorLegendState extends State<_VectorLegend> @override void dispose() { - widget.toggledIndices?.clear(); super.dispose(); } @override Widget build(BuildContext context) { final ThemeData themeData = Theme.of(context); + final Widget current = Wrap( direction: widget.direction ?? (widget.position == LegendPosition.top || @@ -900,23 +1013,12 @@ class _VectorLegendState extends State<_VectorLegend> ? Axis.horizontal : Axis.vertical), spacing: widget.itemSpacing!, - runSpacing: 6, + runSpacing: widget.itemRunSpacing ?? 6.0, runAlignment: WrapAlignment.center, alignment: WrapAlignment.start, children: _buildLegendItems(themeData), ); - if (widget.overflowMode == LegendOverflowMode.none) { - return SingleChildScrollView( - scrollDirection: widget.position == LegendPosition.top || - widget.position == LegendPosition.bottom - ? Axis.horizontal - : Axis.vertical, - physics: const NeverScrollableScrollPhysics(), - child: current, - ); - } - return current; } } @@ -930,6 +1032,7 @@ class _LegendItem extends StatefulWidget { this.text, this.textStyle, this.iconType, + this.iconStrokeWidth, this.imageProvider, this.shader, this.iconSize = Size.zero, @@ -941,6 +1044,10 @@ class _LegendItem extends StatefulWidget { required this.toggledIndices, required this.onToggledIndicesChanged, this.onItemRenderer, + this.overlayMarkerType, + this.degree, + this.startAngle, + this.endAngle, }); /// Specifies the item index. @@ -958,6 +1065,9 @@ class _LegendItem extends StatefulWidget { /// Specifies the shape of the legend icon. final ShapeMarkerType? iconType; + /// Specifies the stroke width of the legend icon. + final double? iconStrokeWidth; + /// Identifies an image. final ImageProvider? imageProvider; @@ -991,6 +1101,18 @@ class _LegendItem extends StatefulWidget { /// Called every time while rendering a legend item. final ItemRenderCallback? onItemRenderer; + /// Specifies the overlay marker for cartesian line type icon. + final ShapeMarkerType? overlayMarkerType; + + /// Specifies the start angle for radial bar icon. + final double? startAngle; + + /// Specifies the degree for radial bar icon. + final double? degree; + + /// Specifies the end angle for radial bar icon. + final double? endAngle; + @override _LegendItemState createState() => _LegendItemState(); } @@ -1012,12 +1134,16 @@ class _LegendItemState extends State<_LegendItem> Widget current = CustomPaint( size: widget.iconSize, painter: _LegendIconShape( - color: details.color, - iconType: details.iconType, - iconBorder: details.iconBorder, - image: snapshot.data, - shader: widget.shader, - ), + color: details.color, + iconType: details.iconType, + iconBorder: details.iconBorder, + iconStrokeWidth: widget.iconStrokeWidth, + image: snapshot.data, + shader: widget.shader, + overlayMarkerType: widget.overlayMarkerType, + degree: widget.degree, + startAngle: widget.startAngle, + endAngle: widget.endAngle), ); if (widget.shader != null && @@ -1042,15 +1168,14 @@ class _LegendItemState extends State<_LegendItem> void _handleTapUp(TapUpDetails details) { if (widget.toggledIndices != null) { - final List toggledIndices = List.from(widget.toggledIndices!); - if (!toggledIndices.contains(widget.index)) { - toggledIndices.add(widget.index); - _toggleAnimationController.forward(); + final List newToggledIndices = + List.from(widget.toggledIndices!); + if (!newToggledIndices.contains(widget.index)) { + newToggledIndices.add(widget.index); } else { - toggledIndices.remove(widget.index); - _toggleAnimationController.reverse(); + newToggledIndices.remove(widget.index); } - widget.onToggledIndicesChanged?.call(toggledIndices, widget.index); + widget.onToggledIndicesChanged?.call(newToggledIndices, widget.index); } } @@ -1060,7 +1185,7 @@ class _LegendItemState extends State<_LegendItem> return null; } - _completer ??= Completer(); + _completer = Completer(); _imageStream?.removeListener(imageStreamListener(_completer!)); _imageStream = widget.imageProvider!.resolve(const ImageConfiguration()); _imageStream!.addListener(imageStreamListener(_completer!)); @@ -1095,6 +1220,14 @@ class _LegendItemState extends State<_LegendItem> _iconColorTween = ColorTween(begin: begin, end: widget.toggledColor); _opacityTween = Tween(begin: 1.0, end: widget.toggledTextOpacity); + if (widget.toggledIndices != null) { + if (widget.toggledIndices!.contains(widget.index)) { + _toggleAnimationController.value = 1.0; + } else { + _toggleAnimationController.value = 0.0; + } + } + _obtainImage = _retrieveImageFromProvider(); super.initState(); } @@ -1155,14 +1288,21 @@ class _LegendItemState extends State<_LegendItem> } } } else { + final Color? referenceIconColor = + _iconColorTween.evaluate(_toggleAnimation); final ItemRendererDetails details = ItemRendererDetails( index: widget.index, text: widget.text!, - color: _iconColorTween.evaluate(_toggleAnimation), + color: referenceIconColor, iconType: widget.iconType!, iconBorder: widget.iconBorder, ); widget.onItemRenderer?.call(details); + if (referenceIconColor != null && + referenceIconColor != details.color) { + _iconColorTween.begin = details.color!; + details.color = _iconColorTween.evaluate(_toggleAnimation); + } current = Row( mainAxisSize: MainAxisSize.min, children: [ @@ -1200,13 +1340,17 @@ class _LegendItemState extends State<_LegendItem> /// Represents the class for rendering icon shape. class _LegendIconShape extends CustomPainter { /// Represents [LegendIconShape] - _LegendIconShape({ - required this.color, - required this.iconType, - this.iconBorder, - this.image, - this.shader, - }); + _LegendIconShape( + {required this.color, + required this.iconType, + this.iconBorder, + this.iconStrokeWidth, + this.image, + this.shader, + this.overlayMarkerType, + this.degree, + this.startAngle, + this.endAngle}); /// Specifies the color of the icon. final Color? color; @@ -1217,12 +1361,27 @@ class _LegendIconShape extends CustomPainter { /// Specifies the border of the icon. final BorderSide? iconBorder; + /// Specifies the stroke width of the icon. + final double? iconStrokeWidth; + /// Identifies an image. final ui.Image? image; /// Specifies the shader of the icon. final Shader? shader; + /// Specifies the overlay marker for cartesian line icon type icon. + final ShapeMarkerType? overlayMarkerType; + + /// Specifies the start angle for radial bar icon. + final double? startAngle; + + /// Specifies the degree for radial bar icon. + final double? degree; + + /// Specifies the end angle for radial bar icon. + final double? endAngle; + Paint _getFillPaint() { final Paint paint = Paint(); if (shader != null) { @@ -1234,18 +1393,32 @@ class _LegendIconShape extends CustomPainter { return paint; } + Paint? _getStrokePaint() { + if (iconStrokeWidth != null && color != null) { + return Paint() + ..style = PaintingStyle.stroke + ..color = color! + ..strokeWidth = iconStrokeWidth!; + } + + return iconBorder?.toPaint(); + } + @override void paint(Canvas canvas, Size size) { if (iconType == ShapeMarkerType.image && image != null) { paintImage(canvas: canvas, rect: Offset.zero & size, image: image!); } else { - ShapePainter.paint( - canvas: canvas, - rect: Offset.zero & size, - shapeType: iconType!, - paint: _getFillPaint(), - borderPaint: iconBorder?.toPaint(), - ); + shape_helper.paint( + canvas: canvas, + rect: Offset.zero & size, + shapeType: iconType!, + paint: _getFillPaint(), + borderPaint: _getStrokePaint(), + overlayMarkerType: overlayMarkerType, + degree: degree, + startAngle: startAngle, + endAngle: endAngle); } } diff --git a/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart b/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart index f07ef69f4..dd1837230 100644 --- a/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart +++ b/packages/syncfusion_flutter_core/lib/src/localizations/global_localizations.dart @@ -1,5 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; /// Defines the localized resource values used by the Syncfusion Widgets. /// @@ -64,6 +64,9 @@ abstract class SfLocalizations { /// date picker on header interaction. String get todayLabel; + /// Label that is displayed prefix to week number. + String get weeknumberLabel; + /// The header string for the first month of hirji calendar String get muharramLabel; @@ -252,35 +255,38 @@ class _DefaultLocalizations implements SfLocalizations { String get daySpanCountLabel => 'Day'; @override - String get allowedViewDayLabel => 'DAY'; + String get allowedViewDayLabel => ''; @override - String get allowedViewWeekLabel => 'WEEK'; + String get allowedViewWeekLabel => ''; @override - String get allowedViewWorkWeekLabel => 'WORK WEEK'; + String get allowedViewWorkWeekLabel => ''; @override - String get allowedViewMonthLabel => 'MONTH'; + String get allowedViewMonthLabel => ''; @override - String get allowedViewScheduleLabel => 'SCHEDULE'; + String get allowedViewScheduleLabel => ''; @override - String get allowedViewTimelineDayLabel => 'TIMELINE DAY'; + String get allowedViewTimelineDayLabel => ''; @override - String get allowedViewTimelineWeekLabel => 'TIMELINE WEEK'; + String get allowedViewTimelineWeekLabel => ''; @override - String get allowedViewTimelineWorkWeekLabel => 'TIMELINE WORK WEEK'; + String get allowedViewTimelineWorkWeekLabel => ''; @override - String get allowedViewTimelineMonthLabel => 'TIMELINE MONTH'; + String get allowedViewTimelineMonthLabel => ''; @override String get todayLabel => 'TODAY'; + @override + String get weeknumberLabel => 'Week'; + @override String get muharramLabel => 'Muharram'; 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 9bdbf6e1e..907dd6b49 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/calendar_theme.dart @@ -119,6 +119,7 @@ class SfCalendarThemeData with Diagnosticable { Color? todayHighlightColor, Color? viewHeaderBackgroundColor, Color? weekNumberBackgroundColor, + Color? allDayPanelColor, TextStyle? todayTextStyle, TextStyle? agendaDayTextStyle, TextStyle? agendaDateTextStyle, @@ -132,6 +133,7 @@ class SfCalendarThemeData with Diagnosticable { TextStyle? blackoutDatesTextStyle, TextStyle? displayNameTextStyle, TextStyle? weekNumberTextStyle, + TextStyle? timeIndicatorTextStyle, }) { brightness = brightness ?? Brightness.light; final bool isLight = brightness == Brightness.light; @@ -166,6 +168,7 @@ class SfCalendarThemeData with Diagnosticable { leadingDatesBackgroundColor ??= Colors.transparent; viewHeaderBackgroundColor ??= Colors.transparent; weekNumberBackgroundColor = Colors.grey.withOpacity(0.19); + allDayPanelColor ??= Colors.transparent; cellBorderColor ??= isLight ? Colors.black.withOpacity(0.16) : Colors.white30; todayTextStyle ??= isLight @@ -236,6 +239,11 @@ class SfCalendarThemeData with Diagnosticable { color: Colors.black87, fontSize: 13, fontFamily: 'Roboto') : const TextStyle( color: Colors.white, fontSize: 13, fontFamily: 'Roboto'); + timeIndicatorTextStyle ??= isLight + ? const TextStyle( + color: null, fontWeight: FontWeight.w500, fontSize: 10) + : const TextStyle( + color: null, fontWeight: FontWeight.w500, fontSize: 10); return SfCalendarThemeData.raw( brightness: brightness, @@ -254,6 +262,7 @@ class SfCalendarThemeData with Diagnosticable { todayBackgroundColor: todayBackgroundColor, trailingDatesBackgroundColor: trailingDatesBackgroundColor, leadingDatesBackgroundColor: leadingDatesBackgroundColor, + allDayPanelColor: allDayPanelColor, trailingDatesTextStyle: trailingDatesTextStyle, blackoutDatesTextStyle: blackoutDatesTextStyle, displayNameTextStyle: displayNameTextStyle, @@ -263,7 +272,8 @@ class SfCalendarThemeData with Diagnosticable { viewHeaderBackgroundColor: viewHeaderBackgroundColor, weekNumberBackgroundColor: weekNumberBackgroundColor, selectionBorderColor: selectionBorderColor, - weekNumberTextStyle: weekNumberTextStyle); + weekNumberTextStyle: weekNumberTextStyle, + timeIndicatorTextStyle: timeIndicatorTextStyle); } /// Create a [SfCalendarThemeData] given a set of exact values. @@ -298,7 +308,9 @@ class SfCalendarThemeData with Diagnosticable { required this.todayHighlightColor, required this.weekNumberBackgroundColor, required this.selectionBorderColor, - required this.weekNumberTextStyle}); + required this.weekNumberTextStyle, + required this.timeIndicatorTextStyle, + required this.allDayPanelColor}); /// The brightness of the overall theme of the /// application for the calendar widgets. @@ -867,76 +879,123 @@ class SfCalendarThemeData with Diagnosticable { /// ``` final TextStyle weekNumberTextStyle; + /// Specifies the text style for the timeIndicator text of dragging + /// appointment in calendar. + /// + /// ```dart + ///Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// calendarThemeData: SfCalendarThemeData( + /// timeIndicatorTextStyle: TextStyle(color: Colors.grey, + /// fontSize: 20), + /// ) + /// ), + /// child: SfCalendar(), + /// ), + /// ) + /// ); + ///} + /// ``` + final TextStyle timeIndicatorTextStyle; + + /// Specifies the background for all day panel. + /// + /// ```dart + ///Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(), + /// body: Center( + /// child: SfTheme( + /// data: SfThemeData( + /// calendarThemeData: SfCalendarThemeData( + /// allDayPanelColor: Colors.blue), + /// ) + /// ), + /// child: SfCalendar(), + /// ), + /// ) + /// ); + ///} + /// ``` + final Color? allDayPanelColor; + /// Creates a copy of this theme but with the given /// fields replaced with the new values. - SfCalendarThemeData copyWith({ - Brightness? brightness, - Color? backgroundColor, - TextStyle? headerTextStyle, - Color? headerBackgroundColor, - Color? agendaBackgroundColor, - Color? cellBorderColor, - TextStyle? viewHeaderDateTextStyle, - TextStyle? viewHeaderDayTextStyle, - TextStyle? agendaDayTextStyle, - TextStyle? agendaDateTextStyle, - TextStyle? timeTextStyle, - TextStyle? activeDatesTextStyle, - Color? activeDatesBackgroundColor, - Color? todayBackgroundColor, - Color? trailingDatesBackgroundColor, - Color? leadingDatesBackgroundColor, - TextStyle? trailingDatesTextStyle, - TextStyle? blackoutDatesTextStyle, - TextStyle? displayNameTextStyle, - TextStyle? leadingDatesTextStyle, - TextStyle? todayTextStyle, - TextStyle? weekNumberTextStyle, - Color? todayHighlightColor, - Color? viewHeaderBackgroundColor, - Color? weekNumberBackgroundColor, - Color? selectionBorderColor, - }) { + SfCalendarThemeData copyWith( + {Brightness? brightness, + Color? backgroundColor, + TextStyle? headerTextStyle, + Color? headerBackgroundColor, + Color? agendaBackgroundColor, + Color? cellBorderColor, + TextStyle? viewHeaderDateTextStyle, + TextStyle? viewHeaderDayTextStyle, + TextStyle? agendaDayTextStyle, + TextStyle? agendaDateTextStyle, + TextStyle? timeTextStyle, + TextStyle? activeDatesTextStyle, + Color? activeDatesBackgroundColor, + Color? todayBackgroundColor, + Color? trailingDatesBackgroundColor, + Color? leadingDatesBackgroundColor, + TextStyle? trailingDatesTextStyle, + TextStyle? blackoutDatesTextStyle, + TextStyle? displayNameTextStyle, + TextStyle? leadingDatesTextStyle, + TextStyle? todayTextStyle, + TextStyle? weekNumberTextStyle, + TextStyle? timeIndicatorTextStyle, + Color? todayHighlightColor, + Color? viewHeaderBackgroundColor, + Color? weekNumberBackgroundColor, + Color? selectionBorderColor, + Color? allDayPanelColor}) { return SfCalendarThemeData.raw( - brightness: brightness ?? this.brightness, - backgroundColor: backgroundColor ?? this.backgroundColor, - headerTextStyle: headerTextStyle ?? this.headerTextStyle, - headerBackgroundColor: - headerBackgroundColor ?? this.headerBackgroundColor, - agendaBackgroundColor: - agendaBackgroundColor ?? this.agendaBackgroundColor, - cellBorderColor: cellBorderColor ?? this.cellBorderColor, - viewHeaderDateTextStyle: - viewHeaderDateTextStyle ?? this.viewHeaderDateTextStyle, - viewHeaderDayTextStyle: - viewHeaderDayTextStyle ?? this.viewHeaderDayTextStyle, - agendaDayTextStyle: agendaDayTextStyle ?? this.agendaDayTextStyle, - agendaDateTextStyle: agendaDateTextStyle ?? this.agendaDateTextStyle, - timeTextStyle: timeTextStyle ?? this.timeTextStyle, - activeDatesTextStyle: activeDatesTextStyle ?? this.activeDatesTextStyle, - activeDatesBackgroundColor: - activeDatesBackgroundColor ?? this.activeDatesBackgroundColor, - todayBackgroundColor: todayBackgroundColor ?? this.todayBackgroundColor, - trailingDatesBackgroundColor: - trailingDatesBackgroundColor ?? this.trailingDatesBackgroundColor, - leadingDatesBackgroundColor: - leadingDatesBackgroundColor ?? this.leadingDatesBackgroundColor, - trailingDatesTextStyle: - trailingDatesTextStyle ?? this.trailingDatesTextStyle, - blackoutDatesTextStyle: - blackoutDatesTextStyle ?? this.blackoutDatesTextStyle, - displayNameTextStyle: displayNameTextStyle ?? this.displayNameTextStyle, - leadingDatesTextStyle: - leadingDatesTextStyle ?? this.leadingDatesTextStyle, - todayTextStyle: todayTextStyle ?? this.todayTextStyle, - weekNumberTextStyle: weekNumberTextStyle ?? this.weekNumberTextStyle, - todayHighlightColor: todayHighlightColor ?? this.todayHighlightColor, - viewHeaderBackgroundColor: - viewHeaderBackgroundColor ?? this.viewHeaderBackgroundColor, - weekNumberBackgroundColor: - weekNumberBackgroundColor ?? this.weekNumberBackgroundColor, - selectionBorderColor: selectionBorderColor ?? this.selectionBorderColor, - ); + brightness: brightness ?? this.brightness, + backgroundColor: backgroundColor ?? this.backgroundColor, + headerTextStyle: headerTextStyle ?? this.headerTextStyle, + headerBackgroundColor: + headerBackgroundColor ?? this.headerBackgroundColor, + agendaBackgroundColor: + agendaBackgroundColor ?? this.agendaBackgroundColor, + cellBorderColor: cellBorderColor ?? this.cellBorderColor, + viewHeaderDateTextStyle: + viewHeaderDateTextStyle ?? this.viewHeaderDateTextStyle, + viewHeaderDayTextStyle: + viewHeaderDayTextStyle ?? this.viewHeaderDayTextStyle, + agendaDayTextStyle: agendaDayTextStyle ?? this.agendaDayTextStyle, + agendaDateTextStyle: agendaDateTextStyle ?? this.agendaDateTextStyle, + timeTextStyle: timeTextStyle ?? this.timeTextStyle, + activeDatesTextStyle: activeDatesTextStyle ?? this.activeDatesTextStyle, + activeDatesBackgroundColor: + activeDatesBackgroundColor ?? this.activeDatesBackgroundColor, + todayBackgroundColor: todayBackgroundColor ?? this.todayBackgroundColor, + trailingDatesBackgroundColor: + trailingDatesBackgroundColor ?? this.trailingDatesBackgroundColor, + leadingDatesBackgroundColor: + leadingDatesBackgroundColor ?? this.leadingDatesBackgroundColor, + trailingDatesTextStyle: + trailingDatesTextStyle ?? this.trailingDatesTextStyle, + blackoutDatesTextStyle: + blackoutDatesTextStyle ?? this.blackoutDatesTextStyle, + displayNameTextStyle: displayNameTextStyle ?? this.displayNameTextStyle, + leadingDatesTextStyle: + leadingDatesTextStyle ?? this.leadingDatesTextStyle, + todayTextStyle: todayTextStyle ?? this.todayTextStyle, + weekNumberTextStyle: weekNumberTextStyle ?? this.weekNumberTextStyle, + todayHighlightColor: todayHighlightColor ?? this.todayHighlightColor, + timeIndicatorTextStyle: + timeIndicatorTextStyle ?? this.timeIndicatorTextStyle, + viewHeaderBackgroundColor: + viewHeaderBackgroundColor ?? this.viewHeaderBackgroundColor, + weekNumberBackgroundColor: + weekNumberBackgroundColor ?? this.weekNumberBackgroundColor, + selectionBorderColor: selectionBorderColor ?? this.selectionBorderColor, + allDayPanelColor: allDayPanelColor ?? this.allDayPanelColor); } /// Linearly interpolate between two themes. @@ -967,7 +1026,9 @@ class SfCalendarThemeData with Diagnosticable { viewHeaderBackgroundColor: Color.lerp( a.viewHeaderBackgroundColor, b.viewHeaderBackgroundColor, t), weekNumberBackgroundColor: Color.lerp( - a.weekNumberBackgroundColor, b.weekNumberBackgroundColor, t)); + a.weekNumberBackgroundColor, b.weekNumberBackgroundColor, t), + allDayPanelColor: + Color.lerp(a.allDayPanelColor, b.allDayPanelColor, t)); } @override @@ -1003,7 +1064,9 @@ class SfCalendarThemeData with Diagnosticable { other.todayHighlightColor == todayHighlightColor && other.viewHeaderBackgroundColor == viewHeaderBackgroundColor && other.weekNumberBackgroundColor == weekNumberBackgroundColor && - other.selectionBorderColor == selectionBorderColor; + other.selectionBorderColor == selectionBorderColor && + other.allDayPanelColor == allDayPanelColor && + other.timeIndicatorTextStyle == timeIndicatorTextStyle; } @override @@ -1033,6 +1096,8 @@ class SfCalendarThemeData with Diagnosticable { viewHeaderBackgroundColor, weekNumberBackgroundColor, selectionBorderColor, + allDayPanelColor, + timeIndicatorTextStyle, ]; return hashList(values); } @@ -1072,5 +1137,7 @@ class SfCalendarThemeData with Diagnosticable { defaultValue: defaultData.weekNumberBackgroundColor)); properties.add(ColorProperty('selectionBorderColor', selectionBorderColor, defaultValue: defaultData.selectionBorderColor)); + properties.add(ColorProperty('allDayPanelColor ', allDayPanelColor, + defaultValue: defaultData.allDayPanelColor)); } } 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 a1e178ed0..4b7b39323 100644 --- a/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart +++ b/packages/syncfusion_flutter_core/lib/src/theme/datagrid_theme.dart @@ -120,6 +120,8 @@ class SfDataGridThemeData with Diagnosticable { Color? headerColor, double? frozenPaneElevation, Color? rowHoverColor, + Color? columnResizeIndicatorColor, + double? columnResizeIndicatorStrokeWidth, TextStyle? rowHoverTextStyle, }) { brightness = brightness ?? Brightness.light; @@ -159,6 +161,11 @@ class SfDataGridThemeData with Diagnosticable { frozenPaneElevation ??= 5.0; + columnResizeIndicatorColor ??= + isLight ? Colors.blue[500]! : Colors.tealAccent[200]!; + + columnResizeIndicatorStrokeWidth ??= 2; + rowHoverColor ??= isLight ? const Color.fromRGBO(0, 0, 0, 0.08) : const Color.fromRGBO(255, 255, 255, 0.12); @@ -188,6 +195,8 @@ class SfDataGridThemeData with Diagnosticable { headerColor: headerColor, frozenPaneElevation: frozenPaneElevation, rowHoverColor: rowHoverColor, + columnResizeIndicatorColor: columnResizeIndicatorColor, + columnResizeIndicatorStrokeWidth: columnResizeIndicatorStrokeWidth, rowHoverTextStyle: rowHoverTextStyle, ); } @@ -211,6 +220,8 @@ class SfDataGridThemeData with Diagnosticable { required this.headerColor, required this.headerHoverColor, required this.frozenPaneElevation, + required this.columnResizeIndicatorColor, + required this.columnResizeIndicatorStrokeWidth, required this.rowHoverColor, required this.rowHoverTextStyle, }); @@ -360,6 +371,12 @@ class SfDataGridThemeData with Diagnosticable { /// Defaults to 5.0. The value is always non-negative. final double frozenPaneElevation; + /// The color of the line which indicates the column resizing. + final Color columnResizeIndicatorColor; + + /// The width of the line which indicates the column resizing. + final double columnResizeIndicatorStrokeWidth; + /// The color for the row when a pointer is hovering over it. final Color rowHoverColor; @@ -397,6 +414,10 @@ class SfDataGridThemeData with Diagnosticable { headerColor: headerColor ?? this.headerColor, headerHoverColor: headerHoverColor ?? this.headerHoverColor, frozenPaneElevation: frozenPaneElevation ?? this.frozenPaneElevation, + columnResizeIndicatorColor: + columnResizeIndicatorColor ?? this.columnResizeIndicatorColor, + columnResizeIndicatorStrokeWidth: columnResizeIndicatorStrokeWidth ?? + this.columnResizeIndicatorStrokeWidth, rowHoverColor: rowHoverColor ?? this.rowHoverColor, rowHoverTextStyle: rowHoverTextStyle ?? this.rowHoverTextStyle, ); @@ -425,6 +446,12 @@ class SfDataGridThemeData with Diagnosticable { frozenPaneElevation: lerpDouble(a.frozenPaneElevation, b.frozenPaneElevation, t), rowHoverColor: Color.lerp(a.rowHoverColor, b.rowHoverColor, t), + columnResizeIndicatorColor: Color.lerp( + a.columnResizeIndicatorColor, b.columnResizeIndicatorColor, t), + columnResizeIndicatorStrokeWidth: lerpDouble( + a.columnResizeIndicatorStrokeWidth, + b.columnResizeIndicatorStrokeWidth, + t), rowHoverTextStyle: TextStyle.lerp(a.rowHoverTextStyle, b.rowHoverTextStyle, t)); } @@ -451,6 +478,9 @@ class SfDataGridThemeData with Diagnosticable { other.headerColor == headerColor && other.frozenPaneElevation == frozenPaneElevation && other.rowHoverColor == rowHoverColor && + other.columnResizeIndicatorColor == columnResizeIndicatorColor && + other.columnResizeIndicatorStrokeWidth == + columnResizeIndicatorStrokeWidth && other.rowHoverTextStyle == rowHoverTextStyle; } @@ -468,6 +498,8 @@ class SfDataGridThemeData with Diagnosticable { headerColor, frozenPaneElevation, rowHoverColor, + columnResizeIndicatorColor, + columnResizeIndicatorStrokeWidth, rowHoverTextStyle, ]; return hashList(values); @@ -500,6 +532,12 @@ class SfDataGridThemeData with Diagnosticable { defaultValue: defaultData.headerColor)); properties.add(DoubleProperty('frozenPaneElevation', frozenPaneElevation, defaultValue: defaultData.frozenPaneElevation)); + properties.add(ColorProperty( + 'columnResizeIndicatorColor', columnResizeIndicatorColor, + defaultValue: defaultData.columnResizeIndicatorColor)); + properties.add(DoubleProperty( + 'columnResizeIndicatorStrokeWidth', columnResizeIndicatorStrokeWidth, + defaultValue: defaultData.columnResizeIndicatorStrokeWidth)); properties.add(ColorProperty('rowHoverColor', rowHoverColor, defaultValue: defaultData.rowHoverColor)); properties.add(DiagnosticsProperty( diff --git a/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart b/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart index f9ca53ab6..cd2671854 100644 --- a/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart +++ b/packages/syncfusion_flutter_core/lib/src/tooltip/tooltip.dart @@ -1156,14 +1156,12 @@ Path _getMarkerShapesPath( switch (markerType) { case DataMarkerType.circle: { - ShapeMaker.drawCircle( - path, position.dx, position.dy, size.width, size.height); + drawCircle(path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.rectangle: { - ShapeMaker.drawRectangle( - path, position.dx, position.dy, size.width, size.height); + drawRectangle(path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.image: @@ -1171,38 +1169,35 @@ Path _getMarkerShapesPath( break; case DataMarkerType.pentagon: { - ShapeMaker.drawPentagon( - path, position.dx, position.dy, size.width, size.height); + drawPentagon(path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.verticalLine: { - ShapeMaker.drawVerticalLine( + drawVerticalLine( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.invertedTriangle: { - ShapeMaker.drawInvertedTriangle( + drawInvertedTriangle( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.horizontalLine: { - ShapeMaker.drawHorizontalLine( + drawHorizontalLine( path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.diamond: { - ShapeMaker.drawDiamond( - path, position.dx, position.dy, size.width, size.height); + drawDiamond(path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.triangle: { - ShapeMaker.drawTriangle( - path, position.dx, position.dy, size.width, size.height); + drawTriangle(path, position.dx, position.dy, size.width, size.height); } break; case DataMarkerType.none: diff --git a/packages/syncfusion_flutter_core/lib/src/utils/helper.dart b/packages/syncfusion_flutter_core/lib/src/utils/helper.dart index 92f289f58..fdea113bf 100644 --- a/packages/syncfusion_flutter_core/lib/src/utils/helper.dart +++ b/packages/syncfusion_flutter_core/lib/src/utils/helper.dart @@ -70,86 +70,78 @@ enum DataMarkerType { none, } -/// Draw different marker shapes by using height and width -class ShapeMaker { - /// Draw the circle shape marker - static void drawCircle( - Path path, double x, double y, double width, double height) { - path.addArc( - Rect.fromLTRB( - x - width / 2, y - height / 2, x + width / 2, y + height / 2), - 0.0, - 2 * math.pi); - } +/// Draw the circle shape marker +void drawCircle(Path path, double x, double y, double width, double height) { + path.addArc( + Rect.fromLTRB( + x - width / 2, y - height / 2, x + width / 2, y + height / 2), + 0.0, + 2 * math.pi); +} - /// Draw the Rectangle shape marker - static void drawRectangle( - Path path, double x, double y, double width, double height) { - path.addRect(Rect.fromLTRB( - x - width / 2, y - height / 2, x + width / 2, y + height / 2)); - } +/// Draw the Rectangle shape marker +void drawRectangle(Path path, double x, double y, double width, double height) { + path.addRect(Rect.fromLTRB( + x - width / 2, y - height / 2, x + width / 2, y + height / 2)); +} - ///Draw the Pentagon shape marker - static void drawPentagon( - Path path, double x, double y, double width, double height) { - const int eq = 72; - double xValue; - double yValue; - for (int i = 0; i <= 5; i++) { - xValue = width / 2 * math.cos((math.pi / 180) * (i * eq)); - yValue = height / 2 * math.sin((math.pi / 180) * (i * eq)); - i == 0 - ? path.moveTo(x + xValue, y + yValue) - : path.lineTo(x + xValue, y + yValue); - } - path.close(); +///Draw the Pentagon shape marker +void drawPentagon(Path path, double x, double y, double width, double height) { + const int eq = 72; + double xValue; + double yValue; + for (int i = 0; i <= 5; i++) { + xValue = width / 2 * math.cos((math.pi / 180) * (i * eq)); + yValue = height / 2 * math.sin((math.pi / 180) * (i * eq)); + i == 0 + ? path.moveTo(x + xValue, y + yValue) + : path.lineTo(x + xValue, y + yValue); } + path.close(); +} - ///Draw the Vertical line shape marker - static void drawVerticalLine( - Path path, double x, double y, double width, double height) { - path.moveTo(x, y + height / 2); - path.lineTo(x, y - height / 2); - } +///Draw the Vertical line shape marker +void drawVerticalLine( + Path path, double x, double y, double width, double height) { + path.moveTo(x, y + height / 2); + path.lineTo(x, y - height / 2); +} - ///Draw the Inverted Triangle shape marker - static void drawInvertedTriangle( - Path path, double x, double y, double width, double height) { - path.moveTo(x + width / 2, y - height / 2); +///Draw the Inverted Triangle shape marker +void drawInvertedTriangle( + Path path, double x, double y, double width, double height) { + path.moveTo(x + width / 2, y - height / 2); - path.lineTo(x, y + height / 2); - path.lineTo(x - width / 2, y - height / 2); - path.lineTo(x + width / 2, y - height / 2); - path.close(); - } + path.lineTo(x, y + height / 2); + path.lineTo(x - width / 2, y - height / 2); + path.lineTo(x + width / 2, y - height / 2); + path.close(); +} - ///Draw the Horizontal line shape marker - static void drawHorizontalLine( - Path path, double x, double y, double width, double height) { - path.moveTo(x - width / 2, y); - path.lineTo(x + width / 2, y); - } +///Draw the Horizontal line shape marker +void drawHorizontalLine( + Path path, double x, double y, double width, double height) { + path.moveTo(x - width / 2, y); + path.lineTo(x + width / 2, y); +} - ///Draw the Diamond shape marker - static void drawDiamond( - Path path, double x, double y, double width, double height) { - path.moveTo(x - width / 2, y); - path.lineTo(x, y + height / 2); - path.lineTo(x + width / 2, y); - path.lineTo(x, y - height / 2); - path.lineTo(x - width / 2, y); - path.close(); - } +///Draw the Diamond shape marker +void drawDiamond(Path path, double x, double y, double width, double height) { + path.moveTo(x - width / 2, y); + path.lineTo(x, y + height / 2); + path.lineTo(x + width / 2, y); + path.lineTo(x, y - height / 2); + path.lineTo(x - width / 2, y); + path.close(); +} - ///Draw the Triangle shape marker - static void drawTriangle( - Path path, double x, double y, double width, double height) { - path.moveTo(x - width / 2, y + height / 2); - path.lineTo(x + width / 2, y + height / 2); - path.lineTo(x, y - height / 2); - path.lineTo(x - width / 2, y + height / 2); - path.close(); - } +///Draw the Triangle shape marker +void drawTriangle(Path path, double x, double y, double width, double height) { + path.moveTo(x - width / 2, y + height / 2); + path.lineTo(x + width / 2, y + height / 2); + path.lineTo(x, y - height / 2); + path.lineTo(x - width / 2, y + height / 2); + path.close(); } /// This method measures the size for given text and textstyle diff --git a/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart b/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart index 61a109257..0c1d204e1 100644 --- a/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart +++ b/packages/syncfusion_flutter_core/lib/src/utils/shape_helper.dart @@ -42,37 +42,41 @@ enum ShapeMarkerType { /// ShapeMarkerType.lineSeries which draws the line series shape. lineSeries, - /// ShapeMarkerType.lineSeriesWithMarker which draws the - /// line series shape with marker. - lineSeriesWithMarker, + /// ShapeMarkerType.lineSeriesWithDashArray which draws + /// line series shape in dash array + lineSeriesWithDashArray, /// ShapeMarkerType.fastLineSeries which draws the fast /// line series shape. fastLineSeries, - /// ShapeMarkerType.fastLineSeriesWithMarker which draws the fast - /// line series marker. - fastLineSeriesWithMarker, + /// ShapeMarkerType.fastLineSeriesWithDashArray which draws + /// fast line series shape in dash array + fastLineSeriesWithDashArray, /// ShapeMarkerType.stackedLineSeries which draws the stacked /// line series shape. stackedLineSeries, - /// ShapeMarkerType.stackedLineSeriesMarker which draws the stacked - /// line series marker. - stackedLineSeriesWithMarker, + /// ShapeMarkerType.stackedLineSeriesWithDashArray which draws + /// stacked line series shape in dash array + stackedLineSeriesWithDashArray, /// ShapeMarkerType.stackedLine100Series which draws /// the stacked line 100 series shape. stackedLine100Series, - /// ShapeMarkerType.stackedLine100SeriesWithMarker which draws - /// the stacked line 100 series marker. - stackedLine100SeriesWithMarker, + /// ShapeMarkerType.stackedLine100SeriesWithDashArray which draws + /// stacked line 100 series shape in dash array + stackedLine100SeriesWithDashArray, /// ShapeMarkerType.splineSeries which draws the spline series marker. splineSeries, + /// ShapeMarkerType.splineSeriesWithDashArray which draws + /// spline series shape in dash array + splineSeriesWithDashArray, + /// ShapeMarkerType.splineAreaSeries which draws the /// spline area series marker. splineAreaSeries, @@ -104,6 +108,10 @@ enum ShapeMarkerType { /// step line series marker. stepLineSeries, + /// ShapeMarkerType.stepLineSeriesWithDashArray which draws + /// step line series shape in dash array + stepLineSeriesWithDashArray, + /// ShapeMarkerType.bubbleSeries which draws the bubble series marker. bubbleSeries, @@ -173,729 +181,779 @@ enum ShapeMarkerType { funnelSeries } -/// Represents the Shape marker painter. -class ShapePainter { - /// Draws the different marker shapes. - static void paint( - {required Canvas canvas, - required Rect rect, - required ShapeMarkerType shapeType, - required Paint paint, - Path? path, - double? elevation, - Color? elevationColor, - Paint? borderPaint}) { - _processShapes( - canvas: canvas, - rect: rect, - shapeType: shapeType, - paint: paint, - path: path ?? Path(), - borderPaint: borderPaint, - isNeedToReturnPath: false, - elevation: elevation, - elevationColor: elevationColor); - } - - /// Get the various shape path - static Path getShapesPath( - {Canvas? canvas, - Paint? paint, - Paint? borderPaint, - required Rect rect, - required ShapeMarkerType shapeType, - Path? path, - double? pentagonRotation = -pi / 2, - double? radius, - double? degree, - double? startAngle, - double? endAngle}) { - return _processShapes( - canvas: canvas ?? Canvas(PictureRecorder()), - paint: paint ?? Paint(), - borderPaint: borderPaint, - rect: rect, - path: path ?? Path(), - shapeType: shapeType, - isNeedToReturnPath: true, - pentagonRotation: pentagonRotation, - radius: radius, - degree: degree, - startAngle: startAngle, - endAngle: endAngle); - } - - static Path _processShapes( - {required Canvas canvas, - required Rect rect, - required ShapeMarkerType shapeType, - required Paint paint, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - Paint? borderPaint, - double? pentagonRotation = -pi / 2, - double? radius, - double? degree, - double? startAngle, - double? endAngle}) { - switch (shapeType) { - case ShapeMarkerType.circle: - return _processCircleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.rectangle: - return _processRectangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.diamond: - return _processDiamondShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.triangle: - return _processTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.invertedTriangle: - return _processInvertedTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.verticalTriangle: - return _processVerticalTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.verticalInvertedTriangle: - return _processVerticalInvertedTriangleShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.pentagon: - return _processPentagonShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - rotation: pentagonRotation, - elevation: elevation, - elevationColor: elevationColor, - paint: paint, - borderPaint: borderPaint); - case ShapeMarkerType.verticalLine: - return _processVerticalLineShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - borderPaint: borderPaint); - case ShapeMarkerType.horizontalLine: - return _processHorizontalLineShape( - canvas: canvas, - rect: rect, - path: path, - isNeedToReturnPath: isNeedToReturnPath, - borderPaint: borderPaint); - case ShapeMarkerType.lineSeries: - case ShapeMarkerType.fastLineSeries: - case ShapeMarkerType.stackedLineSeries: - case ShapeMarkerType.stackedLine100Series: - return _processLineShape( - canvas: canvas, - path: path, - rect: rect, - borderPaint: paint, - isNeedToReturnPath: isNeedToReturnPath, - isNeedMarker: false); - case ShapeMarkerType.lineSeriesWithMarker: - case ShapeMarkerType.fastLineSeriesWithMarker: - case ShapeMarkerType.stackedLineSeriesWithMarker: - case ShapeMarkerType.stackedLine100SeriesWithMarker: - return _processLineShape( - canvas: canvas, - path: path, - rect: rect, - borderPaint: paint, - isNeedToReturnPath: isNeedToReturnPath, - isNeedMarker: true); - case ShapeMarkerType.splineSeries: - return _processSplineShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.splineAreaSeries: - case ShapeMarkerType.splineRangeAreaSeries: - return _processSplineAreaShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.areaSeries: - case ShapeMarkerType.stackedAreaSeries: - case ShapeMarkerType.rangeAreaSeries: - case ShapeMarkerType.stackedArea100Series: - return _processAreaShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.stepAreaSeries: - return _processStepAreaShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.stepLineSeries: - return _processStepLineShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.bubbleSeries: - return _processBubbleShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.columnSeries: - case ShapeMarkerType.stackedColumnSeries: - case ShapeMarkerType.stackedColumn100Series: - case ShapeMarkerType.rangeColumnSeries: - case ShapeMarkerType.histogramSeries: - return _processColumnShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.barSeries: - case ShapeMarkerType.stackedBarSeries: - case ShapeMarkerType.stackedBar100Series: - return _processBarShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.hiloSeries: - return _processHiloShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.hiloOpenCloseSeries: - case ShapeMarkerType.candleSeries: - return _processHiloOpenCloseShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.waterfallSeries: - case ShapeMarkerType.boxAndWhiskerSeries: - return _processWaterfallShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.pieSeries: - return _processPieShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.doughnutSeries: - return _processDoughnutShape( - canvas: canvas, - rect: rect, - radius: radius!, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.radialBarSeries: - return _processRadialBarShape( - rect: rect, - canvas: canvas, - radius: radius, - path: path, - paint: paint, - borderPaint: borderPaint, - degree: degree, - isNeedToReturnPath: isNeedToReturnPath, - startAngle: startAngle, - endAngle: endAngle); - case ShapeMarkerType.pyramidSeries: - return _processPyramidShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.funnelSeries: - return _processFunnelShape( - canvas: canvas, - rect: rect, - path: path, - paint: paint, - borderPaint: borderPaint, - isNeedToReturnPath: isNeedToReturnPath); - case ShapeMarkerType.image: - return Path(); - } - } - - /// Draw the circle shape marker. - static Path _processCircleShape( - {required Canvas canvas, - required Rect rect, - required Paint paint, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - Paint? borderPaint}) { - path.addOval(rect); - - if (isNeedToReturnPath) { - return path; - } +/// Draws the different marker shapes. +void paint( + {required Canvas canvas, + required Rect rect, + required ShapeMarkerType shapeType, + required Paint paint, + ShapeMarkerType? overlayMarkerType, + Path? path, + double? elevation, + Color? elevationColor, + Paint? borderPaint, + double? degree, + double? startAngle, + double? endAngle}) { + _processShapes( + canvas: canvas, + rect: rect, + shapeType: shapeType, + paint: paint, + path: path ?? Path(), + borderPaint: borderPaint, + isNeedToReturnPath: false, + elevation: elevation, + elevationColor: elevationColor, + overlayMarkerType: overlayMarkerType, + degree: degree, + startAngle: startAngle, + endAngle: endAngle); +} - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } +/// Get the various shape path +Path getShapesPath( + {Canvas? canvas, + Paint? paint, + Paint? borderPaint, + required Rect rect, + required ShapeMarkerType shapeType, + Path? path, + double? pentagonRotation = -pi / 2, + double? radius, + double? degree, + double? startAngle, + double? endAngle}) { + return _processShapes( + canvas: canvas ?? Canvas(PictureRecorder()), + paint: paint ?? Paint(), + borderPaint: borderPaint, + rect: rect, + path: path ?? Path(), + shapeType: shapeType, + isNeedToReturnPath: true, + pentagonRotation: pentagonRotation, + radius: radius, + degree: degree, + startAngle: startAngle, + endAngle: endAngle); +} - canvas.drawPath(path, paint); +Path _processShapes( + {required Canvas canvas, + required Rect rect, + required ShapeMarkerType shapeType, + required Paint paint, + required bool isNeedToReturnPath, + required Path path, + ShapeMarkerType? overlayMarkerType, + double? elevation, + Color? elevationColor, + Paint? borderPaint, + double? pentagonRotation = -pi / 2, + double? radius, + double? degree, + double? startAngle, + double? endAngle}) { + switch (shapeType) { + case ShapeMarkerType.circle: + return _processCircleShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.rectangle: + return _processRectangleShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.diamond: + return _processDiamondShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.triangle: + return _processTriangleShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.invertedTriangle: + return _processInvertedTriangleShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.verticalTriangle: + return _processVerticalTriangleShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.verticalInvertedTriangle: + return _processVerticalInvertedTriangleShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.pentagon: + return _processPentagonShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + rotation: pentagonRotation, + elevation: elevation, + elevationColor: elevationColor, + paint: paint, + borderPaint: borderPaint); + case ShapeMarkerType.verticalLine: + return _processVerticalLineShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null); + case ShapeMarkerType.horizontalLine: + return _processHorizontalLineShape( + canvas: canvas, + rect: rect, + path: path, + isNeedToReturnPath: isNeedToReturnPath, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null); + case ShapeMarkerType.lineSeries: + case ShapeMarkerType.fastLineSeries: + case ShapeMarkerType.stackedLineSeries: + case ShapeMarkerType.stackedLine100Series: + return _processLineShape( + canvas: canvas, + path: path, + rect: rect, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isNeedMarker: true, + overlayMarkerType: overlayMarkerType, + isDashArray: false); + case ShapeMarkerType.lineSeriesWithDashArray: + case ShapeMarkerType.fastLineSeriesWithDashArray: + case ShapeMarkerType.stackedLineSeriesWithDashArray: + case ShapeMarkerType.stackedLine100SeriesWithDashArray: + return _processLineShape( + canvas: canvas, + path: path, + rect: rect, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isNeedMarker: true, + overlayMarkerType: overlayMarkerType, + isDashArray: true); + case ShapeMarkerType.splineSeries: + return _processSplineShape( + canvas: canvas, + rect: rect, + path: path, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: false); + case ShapeMarkerType.splineSeriesWithDashArray: + return _processSplineShape( + canvas: canvas, + rect: rect, + path: path, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: true); + case ShapeMarkerType.splineAreaSeries: + case ShapeMarkerType.splineRangeAreaSeries: + return _processSplineAreaShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.areaSeries: + case ShapeMarkerType.stackedAreaSeries: + case ShapeMarkerType.rangeAreaSeries: + case ShapeMarkerType.stackedArea100Series: + return _processAreaShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.stepAreaSeries: + return _processStepAreaShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.stepLineSeries: + return _processStepLineShape( + canvas: canvas, + rect: rect, + path: path, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: false); + case ShapeMarkerType.stepLineSeriesWithDashArray: + return _processStepLineShape( + canvas: canvas, + rect: rect, + path: path, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath, + isDashArray: true); + case ShapeMarkerType.bubbleSeries: + return _processBubbleShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.columnSeries: + case ShapeMarkerType.stackedColumnSeries: + case ShapeMarkerType.stackedColumn100Series: + case ShapeMarkerType.rangeColumnSeries: + case ShapeMarkerType.histogramSeries: + return _processColumnShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.barSeries: + case ShapeMarkerType.stackedBarSeries: + case ShapeMarkerType.stackedBar100Series: + return _processBarShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.hiloSeries: + return _processHiloShape( + canvas: canvas, + rect: rect, + path: path, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.hiloOpenCloseSeries: + case ShapeMarkerType.candleSeries: + return _processHiloOpenCloseShape( + canvas: canvas, + rect: rect, + path: path, + paint: borderPaint, + shaderPaint: paint.shader != null ? paint : null, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.waterfallSeries: + case ShapeMarkerType.boxAndWhiskerSeries: + return _processWaterfallShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.pieSeries: + return _processPieShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.doughnutSeries: + return _processDoughnutShape( + canvas: canvas, + rect: rect, + radius: radius, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.radialBarSeries: + return _processRadialBarShape( + rect: rect, + canvas: canvas, + radius: radius, + path: path, + paint: paint, + borderPaint: borderPaint, + degree: degree, + startAngle: startAngle, + endAngle: endAngle, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.pyramidSeries: + return _processPyramidShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.funnelSeries: + return _processFunnelShape( + canvas: canvas, + rect: rect, + path: path, + paint: paint, + borderPaint: borderPaint, + isNeedToReturnPath: isNeedToReturnPath); + case ShapeMarkerType.image: + return Path(); + } +} - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } +/// Draw the circle shape marker. +Path _processCircleShape( + {required Canvas canvas, + required Rect rect, + required Paint paint, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + Paint? borderPaint}) { + path.addOval(rect); + + if (isNeedToReturnPath) { return path; } - /// Draw the rectangle shape marker. - static Path _processRectangleShape( - {required Canvas canvas, - required Rect rect, - required Paint paint, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - Paint? borderPaint}) { - path.addRect(rect); - - if (isNeedToReturnPath) { - return path; - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } + canvas.drawPath(path, paint); - canvas.drawPath(path, paint); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + return path; +} - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } +/// Draw the rectangle shape marker. +Path _processRectangleShape( + {required Canvas canvas, + required Rect rect, + required Paint paint, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + Paint? borderPaint}) { + path.addRect(rect); + + if (isNeedToReturnPath) { return path; } - /// Draw the inverted triangle shape marker. - static Path _processInvertedTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { - path.moveTo(rect.left, rect.top); - path.lineTo(rect.left + rect.width, rect.top); - path.lineTo(rect.left + (rect.width / 2), rect.top + rect.height); - path.close(); - - if (isNeedToReturnPath) { - return path; - } - - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } - canvas.drawPath(path, paint); + canvas.drawPath(path, paint); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + return path; +} +/// Draw the inverted triangle shape marker. +Path _processInvertedTriangleShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint}) { + path.moveTo(rect.left, rect.top); + path.lineTo(rect.left + rect.width, rect.top); + path.lineTo(rect.left + (rect.width / 2), rect.top + rect.height); + path.close(); + + if (isNeedToReturnPath) { return path; } - /// Draw the triangle shape marker. - static Path _processTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { - path.moveTo(rect.left + (rect.width / 2), rect.top); - path.lineTo(rect.left, rect.top + rect.height); - path.lineTo(rect.left + rect.width, rect.top + rect.height); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } + canvas.drawPath(path, paint); - canvas.drawPath(path, paint); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} + +/// Draw the triangle shape marker. +Path _processTriangleShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint}) { + path.moveTo(rect.left + (rect.width / 2), rect.top); + path.lineTo(rect.left, rect.top + rect.height); + path.lineTo(rect.left + rect.width, rect.top + rect.height); + path.close(); + + if (isNeedToReturnPath) { return path; } - ///Draw the triangle shape marker - static Path _processVerticalTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { - path.moveTo(rect.left, rect.top + (rect.height / 2)); - path.lineTo(rect.left + rect.width, rect.top); - path.lineTo(rect.left + rect.width, rect.top + rect.height); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } + canvas.drawPath(path, paint); - canvas.drawPath(path, paint); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + return path; +} - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } +///Draw the triangle shape marker +Path _processVerticalTriangleShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint}) { + path.moveTo(rect.left, rect.top + (rect.height / 2)); + path.lineTo(rect.left + rect.width, rect.top); + path.lineTo(rect.left + rect.width, rect.top + rect.height); + path.close(); + + if (isNeedToReturnPath) { return path; } - ///Draw the vertical inverted triangle shape marker - static Path _processVerticalInvertedTriangleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { - path.moveTo(rect.left, rect.top); - path.lineTo(rect.left + rect.width, rect.top + (rect.height / 2)); - path.lineTo(rect.left, rect.top + rect.height); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } + canvas.drawPath(path, paint); - canvas.drawPath(path, paint); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + return path; +} - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } +///Draw the vertical inverted triangle shape marker +Path _processVerticalInvertedTriangleShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint}) { + path.moveTo(rect.left, rect.top); + path.lineTo(rect.left + rect.width, rect.top + (rect.height / 2)); + path.lineTo(rect.left, rect.top + rect.height); + path.close(); + + if (isNeedToReturnPath) { return path; } - /// Draw the diamond shape marker. - static Path _processDiamondShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint}) { - path.moveTo(rect.left + rect.width / 2.0, rect.top); - path.lineTo(rect.left, rect.top + rect.height / 2.0); - path.lineTo(rect.left + rect.width / 2.0, rect.top + rect.height); - path.lineTo(rect.left + rect.width, rect.top + rect.height / 2.0); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } + canvas.drawPath(path, paint); - canvas.drawPath(path, paint); - - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + return path; +} +/// Draw the diamond shape marker. +Path _processDiamondShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint}) { + path.moveTo(rect.left + rect.width / 2.0, rect.top); + path.lineTo(rect.left, rect.top + rect.height / 2.0); + path.lineTo(rect.left + rect.width / 2.0, rect.top + rect.height); + path.lineTo(rect.left + rect.width, rect.top + rect.height / 2.0); + path.close(); + + if (isNeedToReturnPath) { return path; } - ///Draw the pentagon shape marker - static Path _processPentagonShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? elevation, - Color? elevationColor, - required Paint paint, - Paint? borderPaint, - double? rotation}) { - const int numberOfSides = 5; - final double left = rect.left + rect.width / 2; - final double top = rect.top + rect.height / 2; - final double radius = rect.width / 2; - - for (int i = 0; i <= numberOfSides; i++) { - final double angle = ((i / 5) * pi * 2 + rotation!).toDouble(); - i == 0 - ? path.moveTo(cos(angle) * radius + left, sin(angle) * radius + top) - : path.lineTo(cos(angle) * radius + left, sin(angle) * radius + top); - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint); - if (elevation != null && elevation > 0 && elevationColor != null) { - canvas.drawShadow(path, elevationColor, elevation, true); - } - canvas.drawPath(path, paint); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } - return path; + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); } - /// Draw the vertical line shape marker. - static Path _processVerticalLineShape( - {required Canvas canvas, - required bool isNeedToReturnPath, - required Path path, - required Rect rect, - required Paint? borderPaint}) { - final double left = rect.left + rect.width / 2; - final double top = rect.top + rect.height / 2; + return path; +} - path.moveTo(left, top + rect.height / 2); - path.lineTo(left, top - rect.height / 2); +///Draw the pentagon shape marker +Path _processPentagonShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + double? elevation, + Color? elevationColor, + required Paint paint, + Paint? borderPaint, + double? rotation}) { + const int numberOfSides = 5; + final double left = rect.left + rect.width / 2; + final double top = rect.top + rect.height / 2; + final double radius = rect.width / 2; + + for (int i = 0; i <= numberOfSides; i++) { + final double angle = ((i / 5) * pi * 2 + rotation!).toDouble(); + i == 0 + ? path.moveTo(cos(angle) * radius + left, sin(angle) * radius + top) + : path.lineTo(cos(angle) * radius + left, sin(angle) * radius + top); + } - if (isNeedToReturnPath) { - return path; - } + if (isNeedToReturnPath) { + return path; + } - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + if (elevation != null && elevation > 0 && elevationColor != null) { + canvas.drawShadow(path, elevationColor, elevation, true); + } + canvas.drawPath(path, paint); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + return path; +} +/// Draw the vertical line shape marker. +Path _processVerticalLineShape( + {required Canvas canvas, + required bool isNeedToReturnPath, + required Path path, + required Rect rect, + required Paint? paint, + Paint? shaderPaint}) { + final double left = rect.left + rect.width / 2; + final double top = rect.top + rect.height / 2; + + path.moveTo(left, top + rect.height / 2); + path.lineTo(left, top - rect.height / 2); + + if (isNeedToReturnPath) { return path; } - /// Draw the horizontal line shape marker. - static Path _processHorizontalLineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - required Paint? borderPaint}) { - final double left = rect.left + rect.width / 2; - final double top = rect.top + rect.height / 2; - - path.moveTo(left - rect.width / 2, top); - path.lineTo(left + rect.width / 2, top); + if (paint != null) { + paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; + canvas.drawPath(path, paint); + } - if (isNeedToReturnPath) { - return path; - } + return path; +} - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } +/// Draw the horizontal line shape marker. +Path _processHorizontalLineShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + required Paint? paint, + Paint? shaderPaint}) { + final double left = rect.left + rect.width / 2; + final double top = rect.top + rect.height / 2; + + path.moveTo(left - rect.width / 2, top); + path.lineTo(left + rect.width / 2, top); + + if (isNeedToReturnPath) { return path; } - /// Draw the step line series type. - static Path _processStepLineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - const num padding = 10; - path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); - path.lineTo(x - (width / 2) + (width / 10), y + (height / 2)); - path.lineTo(x - (width / 2) + (width / 10), y); - path.lineTo(x - (width / 10), y); - path.lineTo(x - (width / 10), y + (height / 2)); - path.lineTo(x + (width / 5), y + (height / 2)); - path.lineTo(x + (width / 5), y - (height / 2)); - path.lineTo(x + (width / 2), y - (height / 2)); - path.lineTo(x + (width / 2), y + (height / 2)); - path.lineTo(x + (width / 2) + (padding / 4), y + (height / 2)); - - if (isNeedToReturnPath) { - return path; - } + if (paint != null) { + paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; + canvas.drawPath(path, paint); + } + return path; +} - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } +/// Draw the step line series type. +Path _processStepLineShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, + bool? isDashArray}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + const num padding = 10; + path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); + path.lineTo(x - (width / 2) + (width / 10), y + (height / 2)); + path.lineTo(x - (width / 2) + (width / 10), y); + path.lineTo(x - (width / 10), y); + path.lineTo(x - (width / 10), y + (height / 2)); + path.lineTo(x + (width / 5), y + (height / 2)); + path.lineTo(x + (width / 5), y - (height / 2)); + path.lineTo(x + (width / 2), y - (height / 2)); + path.lineTo(x + (width / 2), y + (height / 2)); + path.lineTo(x + (width / 2) + (padding / 4), y + (height / 2)); + + if (isNeedToReturnPath) { return path; } - /// Draw the pie series type. - static Path _processPieShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - final double r = min(height, width) / 2; - path.moveTo(x, y); - path.lineTo(x + r, y); - path.arcTo( - Rect.fromCircle(center: Offset(x, y), radius: r), - _degreesToRadians(0).toDouble(), - _degreesToRadians(270).toDouble(), - false); - path.close(); - path.moveTo(x + width / 10, y - height / 10); - path.lineTo(x + r, y - height / 10); - path.arcTo( - Rect.fromCircle(center: Offset(x + 2, y - 2), radius: r), - _degreesToRadians(-5).toDouble(), - _degreesToRadians(-80).toDouble(), - false); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + if (paint != null) { + paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; + canvas.drawPath( + isDashArray! + ? _processDashPath(path, + dashArray: _CircularIntervalList([3, 2])) + : path, + paint..style = PaintingStyle.stroke); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the pie series type. +Path _processPieShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + final double r = min(height, width) / 2; + path.moveTo(x, y); + path.lineTo(x + r, y); + path.arcTo( + Rect.fromCircle(center: Offset(x, y), radius: r), + _degreesToRadians(0).toDouble(), + _degreesToRadians(270).toDouble(), + false); + path.close(); + path.moveTo(x + width / 10, y - height / 10); + path.lineTo(x + r, y - height / 10); + path.arcTo( + Rect.fromCircle(center: Offset(x + 2, y - 2), radius: r), + _degreesToRadians(-5).toDouble(), + _degreesToRadians(-80).toDouble(), + false); + path.close(); + + if (isNeedToReturnPath) { return path; } - /// Draw the doughnut series type. - static Path _processDoughnutShape( - {required Canvas canvas, - required Rect rect, - required double radius, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - late Path path1, path2; + canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + + return path; +} + +/// Draw the doughnut series type. +Path _processDoughnutShape( + {required Canvas canvas, + required Rect rect, + required Path path, + required bool isNeedToReturnPath, + double? radius, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + late Path path1, path2; + radius ??= (rect.width + rect.height) / 2; + + if (isNeedToReturnPath) { if (borderPaint != null) { path1 = _getArcPath( path, radius / 4, radius / 2, Offset(x, y), 0, 270, 270, true); @@ -904,41 +962,49 @@ class ShapePainter { -5, -85, -85, true); } - if (isNeedToReturnPath) { - return path; - } - - canvas.drawPath(path1, paint!); - if (borderPaint != null) { - canvas.drawPath(path1, borderPaint); - } - canvas.drawPath(path2, paint); - if (borderPaint != null) { - canvas.drawPath(path2, borderPaint); - } - return path; } - /// Draw the radial bar series type. - static Path _processRadialBarShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - double? degree, - double? startAngle, - double? endAngle, - double? radius, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; + path1 = _getArcPath( + path, radius / 4, radius / 2, Offset(x, y), 0, 270, 270, true); - late Path path1, path2; + path2 = _getArcPath( + Path(), radius / 4, radius / 2, Offset(x + 1, y - 1), -5, -85, -85, true); - radius ??= (rect.width + rect.height) / 2; + canvas.drawPath(path1, paint!); + if (borderPaint != null) { + canvas.drawPath( + path1, borderPaint..color = Colors.grey.shade300.withOpacity(0.5)); + } + canvas.drawPath(path2, paint); + if (borderPaint != null) { + canvas.drawPath( + path2, borderPaint..color = Colors.grey.shade300.withOpacity(0.5)); + } + + return path; +} +/// Draw the radial bar series type. +Path _processRadialBarShape( + {required Canvas canvas, + required Rect rect, + required Path path, + required bool isNeedToReturnPath, + double? degree, + double? startAngle, + double? endAngle, + double? radius, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + + late Path path1, path2; + + radius ??= (rect.width + rect.height) / 2; + + if (isNeedToReturnPath) { if (borderPaint != null) { path1 = _getArcPath(path, (radius / 2) - 2, radius / 2, Offset(x, y), 0, 360 - 0.01, 360 - 0.01, true); @@ -946,541 +1012,586 @@ class ShapePainter { path2 = _getArcPath(path, (radius / 2) - 2, radius / 2, Offset(x, y), startAngle!, endAngle!, degree!, true); } + return path; + } - if (isNeedToReturnPath) { - return path; - } + path1 = _getArcPath(path, (radius / 2) - 2, radius / 2, Offset(x, y), 0, + 360 - 0.01, 360 - 0.01, true); - canvas.drawPath(path1, paint!); + path2 = _getArcPath(Path(), (radius / 2) - 2, radius / 2, Offset(x, y), + startAngle!, endAngle!, degree!, true); - if (borderPaint != null) { - canvas.drawPath(path1, borderPaint); - } + if (borderPaint != null) { + canvas.drawPath( + path1, + Paint() + ..color = Colors.grey.shade100 + ..strokeWidth = borderPaint.strokeWidth); - canvas.drawPath(path2, paint); + canvas.drawPath( + path1, borderPaint..color = Colors.grey.shade300.withOpacity(0.5)); + } - if (borderPaint != null) { - canvas.drawPath(path2, borderPaint); - } + canvas.drawPath(path2, paint!); - return path; + if (borderPaint != null) { + canvas.drawPath(path2, borderPaint..color = Colors.transparent); } - /// Get the path of arc - static Path _getArcPath( - Path path, - double innerRadius, - double radius, - Offset center, - double startAngle, - double endAngle, - double degree, - bool isAnimate) { - startAngle = _degreesToRadians(startAngle); - endAngle = _degreesToRadians(endAngle); - degree = _degreesToRadians(degree); - - final Point innerRadiusStartPoint = Point( - innerRadius * cos(startAngle) + center.dx, - innerRadius * sin(startAngle) + center.dy); - final Point innerRadiusEndPoint = Point( - innerRadius * cos(endAngle) + center.dx, - innerRadius * sin(endAngle) + center.dy); - - final Point radiusStartPoint = Point( - radius * cos(startAngle) + center.dx, - radius * sin(startAngle) + center.dy); - - if (isAnimate) { - path.moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); - } - - final bool isFullCircle = - // ignore: unnecessary_null_comparison - startAngle != null && - // ignore: unnecessary_null_comparison - endAngle != null && - endAngle - startAngle == 2 * pi; - - final num midpointAngle = (endAngle + startAngle) / 2; - - if (isFullCircle) { - path.arcTo( - Rect.fromCircle(center: center, radius: radius.toDouble()), - startAngle.toDouble(), - midpointAngle.toDouble() - startAngle.toDouble(), - true); - path.arcTo( - Rect.fromCircle(center: center, radius: radius.toDouble()), - midpointAngle.toDouble(), - endAngle.toDouble() - midpointAngle.toDouble(), - true); - } else { - path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - path.arcTo(Rect.fromCircle(center: center, radius: radius.toDouble()), - startAngle.toDouble(), degree.toDouble(), true); - } + return path; +} - if (isFullCircle) { - path.arcTo( - Rect.fromCircle(center: center, radius: innerRadius.toDouble()), - endAngle.toDouble(), - midpointAngle.toDouble() - endAngle.toDouble(), - true); - path.arcTo( - Rect.fromCircle(center: center, radius: innerRadius.toDouble()), - midpointAngle.toDouble(), - startAngle - midpointAngle.toDouble(), - true); - } else { - path.lineTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); - path.arcTo( - Rect.fromCircle(center: center, radius: innerRadius.toDouble()), - endAngle.toDouble(), - startAngle.toDouble() - endAngle.toDouble(), - true); - path.lineTo(radiusStartPoint.x, radiusStartPoint.y); - } - return path; +/// Get the path of arc +Path _getArcPath(Path path, double innerRadius, double radius, Offset center, + double startAngle, double endAngle, double degree, bool isAnimate) { + startAngle = _degreesToRadians(startAngle); + endAngle = _degreesToRadians(endAngle); + degree = _degreesToRadians(degree); + + final Point innerRadiusStartPoint = Point( + innerRadius * cos(startAngle) + center.dx, + innerRadius * sin(startAngle) + center.dy); + final Point innerRadiusEndPoint = Point( + innerRadius * cos(endAngle) + center.dx, + innerRadius * sin(endAngle) + center.dy); + + final Point radiusStartPoint = Point( + radius * cos(startAngle) + center.dx, + radius * sin(startAngle) + center.dy); + + if (isAnimate) { + path.moveTo(innerRadiusStartPoint.x, innerRadiusStartPoint.y); } - /// Convert degree to radian - static double _degreesToRadians(double deg) => deg * (pi / 180); - - /// Draw the hilo series type. - static Path _processHiloShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double height = rect.height; + final bool isFullCircle = + // ignore: unnecessary_null_comparison + startAngle != null && + // ignore: unnecessary_null_comparison + endAngle != null && + endAngle - startAngle == 2 * pi; - path.moveTo(x, y + height / 2); - path.lineTo(x, y - height / 2); + final num midpointAngle = (endAngle + startAngle) / 2; - if (isNeedToReturnPath) { - return path; - } + if (isFullCircle) { + path.arcTo( + Rect.fromCircle(center: center, radius: radius.toDouble()), + startAngle.toDouble(), + midpointAngle.toDouble() - startAngle.toDouble(), + true); + path.arcTo( + Rect.fromCircle(center: center, radius: radius.toDouble()), + midpointAngle.toDouble(), + endAngle.toDouble() - midpointAngle.toDouble(), + true); + } else { + path.lineTo(radiusStartPoint.x, radiusStartPoint.y); + path.arcTo(Rect.fromCircle(center: center, radius: radius.toDouble()), + startAngle.toDouble(), degree.toDouble(), true); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + if (isFullCircle) { + path.arcTo( + Rect.fromCircle(center: center, radius: innerRadius.toDouble()), + endAngle.toDouble(), + midpointAngle.toDouble() - endAngle.toDouble(), + true); + path.arcTo(Rect.fromCircle(center: center, radius: innerRadius.toDouble()), + midpointAngle.toDouble(), startAngle - midpointAngle.toDouble(), true); + } else { + path.lineTo(innerRadiusEndPoint.x, innerRadiusEndPoint.y); + path.arcTo(Rect.fromCircle(center: center, radius: innerRadius.toDouble()), + endAngle.toDouble(), startAngle.toDouble() - endAngle.toDouble(), true); + path.lineTo(radiusStartPoint.x, radiusStartPoint.y); + } + return path; +} +/// Convert degree to radian +double _degreesToRadians(double deg) => deg * (pi / 180); + +/// Draw the hilo series type. +Path _processHiloShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double height = rect.height; + + path.moveTo(x, y + height / 2); + path.lineTo(x, y - height / 2); + + if (isNeedToReturnPath) { return path; } - /// Draw the hilo open close series type. - static Path _processHiloOpenCloseShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - - path.moveTo(x - width / 2, y); - path.lineTo(x + width / 2, y); + if (paint != null) { + paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; + canvas.drawPath(path, paint); + } - if (isNeedToReturnPath) { - return path; - } + return path; +} - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } +/// Draw the hilo open close series type. +Path _processHiloOpenCloseShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + + path.moveTo(x - width / 2, y); + path.lineTo(x + width / 2, y); + + if (isNeedToReturnPath) { return path; } - /// Draw the waterfall series type. - static Path _processWaterfallShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - path.addRect(Rect.fromLTRB( - x - width / 2, y - height / 2, x + width / 2, y + height / 2)); - - if (isNeedToReturnPath) { - return path; - } + if (paint != null) { + paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; + canvas.drawPath(path, paint); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the waterfall series type. +Path _processWaterfallShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + path.addRect(Rect.fromLTRB( + x - width / 2, y - height / 2, x + width / 2, y + height / 2)); + + if (isNeedToReturnPath) { return path; } - /// Draw the pyramid series type. - static Path _processPyramidShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - path.moveTo(x - width / 2, y + height / 2); - path.lineTo(x + width / 2, y + height / 2); - path.lineTo(x, y - height / 2); - path.lineTo(x - width / 2, y + height / 2); - path.close(); - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the pyramid series type. +Path _processPyramidShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + path.moveTo(x - width / 2, y + height / 2); + path.lineTo(x + width / 2, y + height / 2); + path.lineTo(x, y - height / 2); + path.lineTo(x - width / 2, y + height / 2); + path.close(); + if (isNeedToReturnPath) { return path; } - /// Draw the funnel series type. - static Path _processFunnelShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - path.moveTo(x + width / 2, y - height / 2); - path.lineTo(x, y + height / 2); - path.lineTo(x - width / 2, y - height / 2); - path.lineTo(x + width / 2, y - height / 2); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the funnel series type. +Path _processFunnelShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + path.moveTo(x + width / 2, y - height / 2); + path.lineTo(x, y + height / 2); + path.lineTo(x - width / 2, y - height / 2); + path.lineTo(x + width / 2, y - height / 2); + path.close(); + + if (isNeedToReturnPath) { return path; } - /// Draw the bubble series type. - static Path _processBubbleShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - path.addArc(Rect.fromLTWH(x - width / 2, y - height / 2, width, height), - 0.0, 2 * pi); - - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the bubble series type. +Path _processBubbleShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + path.addArc( + Rect.fromLTWH(x - width / 2, y - height / 2, width, height), 0.0, 2 * pi); + + if (isNeedToReturnPath) { return path; } - /// Draw the step are series type. - static Path _processStepAreaShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - const num padding = 10; - path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); - path.lineTo(x - (width / 2) - (padding / 4), y - (height / 4)); - path.lineTo(x - (width / 2) + (width / 10), y - (height / 4)); - path.lineTo(x - (width / 2) + (width / 10), y - (height / 2)); - path.lineTo(x - (width / 10), y - (height / 2)); - path.lineTo(x - (width / 10), y); - path.lineTo(x + (width / 5), y); - path.lineTo(x + (width / 5), y - (height / 3)); - path.lineTo(x + (width / 2), y - (height / 3)); - path.lineTo(x + (width / 2), y + (height / 2)); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the step are series type. +Path _processStepAreaShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + const num padding = 10; + path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); + path.lineTo(x - (width / 2) - (padding / 4), y - (height / 4)); + path.lineTo(x - (width / 2) + (width / 10), y - (height / 4)); + path.lineTo(x - (width / 2) + (width / 10), y - (height / 2)); + path.lineTo(x - (width / 10), y - (height / 2)); + path.lineTo(x - (width / 10), y); + path.lineTo(x + (width / 5), y); + path.lineTo(x + (width / 5), y - (height / 3)); + path.lineTo(x + (width / 2), y - (height / 3)); + path.lineTo(x + (width / 2), y + (height / 2)); + path.close(); + + if (isNeedToReturnPath) { return path; } - /// Draw the spline area series type. - static Path _processSplineAreaShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - path.moveTo(x - width / 2, y + height / 2); - path.quadraticBezierTo(x, y - height, x, y + height / 5); - path.quadraticBezierTo( - x + width / 2, y - height / 2, x + width / 2, y + height / 2); - - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the spline area series type. +Path _processSplineAreaShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + path.moveTo(x - width / 2, y + height / 2); + path.quadraticBezierTo(x, y - height, x, y + height / 5); + path.quadraticBezierTo( + x + width / 2, y - height / 2, x + width / 2, y + height / 2); + + if (isNeedToReturnPath) { return path; } - /// Draw the line series type. - static Path _processLineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? borderPaint, - bool? isNeedMarker}) { - final double left = rect.left + rect.width / 2; - final double top = rect.top + rect.height / 2; + canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - path.moveTo(left - rect.width / 1.5, top); - path.lineTo(left + rect.width / 1.5, top); + return path; +} - if (isNeedToReturnPath) { - return path; - } - if (borderPaint != null) { - canvas.drawPath(path, borderPaint..style = PaintingStyle.stroke); - } +/// Draw the line series type. +Path _processLineShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, + bool? isNeedMarker, + ShapeMarkerType? overlayMarkerType, + bool? isDashArray}) { + if (isNeedMarker! && overlayMarkerType != null) { + final Rect pathRect = Rect.fromCenter( + center: rect.center, + width: rect.width / 1.5, + height: rect.height / 1.5); + canvas.drawPath(getShapesPath(rect: pathRect, shapeType: overlayMarkerType), + Paint()..color = paint!.color); + } - if (isNeedMarker! && borderPaint != null) { - path.close(); - path.addOval(Rect.fromCenter( - center: Offset(left, top), - width: rect.width / 1.5, - height: rect.height / 1.5)); - canvas.drawPath(path, borderPaint..style = PaintingStyle.fill); - } + final double left = rect.left + rect.width / 2; + final double top = rect.top + rect.height / 2; + + path.moveTo(left - rect.width / 1.5, top); + path.lineTo(left + rect.width / 1.5, top); + + if (isNeedToReturnPath) { return path; } + if (paint != null) { + paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; + + canvas.drawPath( + isDashArray! + ? _processDashPath(path, + dashArray: _CircularIntervalList([3, 2])) + : path, + paint..style = PaintingStyle.stroke); + } + return path; +} - /// Draw the column series type. - static Path _processColumnShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double left = rect.left + rect.width / 2; - final double top = rect.top + rect.height / 2; - - const num padding = 10; - const num temp = padding / 2; - const num space = 3; - - path.moveTo(left - space * (rect.width / temp), top - (rect.height / temp)); - path.lineTo( - left + space * (-rect.width / padding), top - (rect.height / temp)); - path.lineTo( - left + space * (-rect.width / padding), top + (rect.height / 2)); - path.lineTo(left - space * (rect.width / temp), top + (rect.height / 2)); - path.close(); - - path.moveTo(left - (rect.width / padding) - (rect.width / (padding * 2)), - top - (rect.height / 4) - (padding / 2)); - path.lineTo(left + (rect.width / padding) + (rect.width / (padding * 2)), - top - (rect.height / 4) - (padding / 2)); - path.lineTo(left + (rect.width / padding) + (rect.width / (padding * 2)), - top + (rect.height / 2)); - path.lineTo(left - (rect.width / padding) - (rect.width / (padding * 2)), - top + (rect.height / 2)); - path.close(); - - path.moveTo(left + space * (rect.width / padding), top); - path.lineTo(left + space * (rect.width / temp), top); - path.lineTo(left + space * (rect.width / temp), top + (rect.height / 2)); - path.lineTo(left + space * (rect.width / padding), top + (rect.height / 2)); - path.close(); - - if (isNeedToReturnPath) { - return path; - } +/// Draw the column series type. +Path _processColumnShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double left = rect.left + rect.width / 2; + final double top = rect.top + rect.height / 2; + + const num padding = 10; + const num temp = padding / 2; + const num space = 3; + + path.moveTo(left - space * (rect.width / temp), top - (rect.height / temp)); + path.lineTo( + left + space * (-rect.width / padding), top - (rect.height / temp)); + path.lineTo(left + space * (-rect.width / padding), top + (rect.height / 2)); + path.lineTo(left - space * (rect.width / temp), top + (rect.height / 2)); + path.close(); + + path.moveTo(left - (rect.width / padding) - (rect.width / (padding * 2)), + top - (rect.height / 4) - (padding / 2)); + path.lineTo(left + (rect.width / padding) + (rect.width / (padding * 2)), + top - (rect.height / 4) - (padding / 2)); + path.lineTo(left + (rect.width / padding) + (rect.width / (padding * 2)), + top + (rect.height / 2)); + path.lineTo(left - (rect.width / padding) - (rect.width / (padding * 2)), + top + (rect.height / 2)); + path.close(); + + path.moveTo(left + space * (rect.width / padding), top); + path.lineTo(left + space * (rect.width / temp), top); + path.lineTo(left + space * (rect.width / temp), top + (rect.height / 2)); + path.lineTo(left + space * (rect.width / padding), top + (rect.height / 2)); + path.close(); + + if (isNeedToReturnPath) { + return path; + } - canvas.drawPath(path, paint!); + canvas.drawPath(path, paint!); - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } + return path; +} + +/// Draw the area series type. +Path _processAreaShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + const num padding = 10; + path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); + path.lineTo(x - (width / 4) - (padding / 8), y - (height / 2)); + path.lineTo(x, y + (height / 4)); + path.lineTo(x + (width / 4) + (padding / 8), y - (height / 2) + (height / 4)); + path.lineTo(x + (height / 2) + (padding / 4), y + (height / 2)); + path.close(); + + if (isNeedToReturnPath) { return path; } - /// Draw the area series type. - static Path _processAreaShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - const num padding = 10; - path.moveTo(x - (width / 2) - (padding / 4), y + (height / 2)); - path.lineTo(x - (width / 4) - (padding / 8), y - (height / 2)); - path.lineTo(x, y + (height / 4)); - path.lineTo( - x + (width / 4) + (padding / 8), y - (height / 2) + (height / 4)); - path.lineTo(x + (height / 2) + (padding / 4), y + (height / 2)); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint!); - canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the bar series type. +Path _processBarShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? borderPaint}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + const num padding = 10; + + path.moveTo(x - (width / 2) - padding / 4, y - 3 * (height / 5)); + path.lineTo(x + 3 * (width / 10), y - 3 * (height / 5)); + path.lineTo(x + 3 * (width / 10), y - 3 * (height / 10)); + path.lineTo(x - (width / 2) - padding / 4, y - 3 * (height / 10)); + path.close(); + path.moveTo( + x - (width / 2) - (padding / 4), y - (height / 5) + (padding / 20)); + path.lineTo( + x + (width / 2) + (padding / 4), y - (height / 5) + (padding / 20)); + path.lineTo( + x + (width / 2) + (padding / 4), y + (height / 10) + (padding / 20)); + path.lineTo( + x - (width / 2) - (padding / 4), y + (height / 10) + (padding / 20)); + path.close(); + path.moveTo( + x - (width / 2) - (padding / 4), y + (height / 5) + (padding / 10)); + path.lineTo(x - width / 4, y + (height / 5) + (padding / 10)); + path.lineTo(x - width / 4, y + (height / 2) + (padding / 10)); + path.lineTo( + x - (width / 2) - (padding / 4), y + (height / 2) + (padding / 10)); + path.close(); + + if (isNeedToReturnPath) { return path; } - /// Draw the bar series type. - static Path _processBarShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - const num padding = 10; - - path.moveTo(x - (width / 2) - padding / 4, y - 3 * (height / 5)); - path.lineTo(x + 3 * (width / 10), y - 3 * (height / 5)); - path.lineTo(x + 3 * (width / 10), y - 3 * (height / 10)); - path.lineTo(x - (width / 2) - padding / 4, y - 3 * (height / 10)); - path.close(); - path.moveTo( - x - (width / 2) - (padding / 4), y - (height / 5) + (padding / 20)); - path.lineTo( - x + (width / 2) + (padding / 4), y - (height / 5) + (padding / 20)); - path.lineTo( - x + (width / 2) + (padding / 4), y + (height / 10) + (padding / 20)); - path.lineTo( - x - (width / 2) - (padding / 4), y + (height / 10) + (padding / 20)); - path.close(); - path.moveTo( - x - (width / 2) - (padding / 4), y + (height / 5) + (padding / 10)); - path.lineTo(x - width / 4, y + (height / 5) + (padding / 10)); - path.lineTo(x - width / 4, y + (height / 2) + (padding / 10)); - path.lineTo( - x - (width / 2) - (padding / 4), y + (height / 2) + (padding / 10)); - path.close(); - - if (isNeedToReturnPath) { - return path; - } + canvas.drawPath(path, paint!); - canvas.drawPath(path, paint!); + if (borderPaint != null) { + canvas.drawPath(path, borderPaint); + } - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); - } + return path; +} +/// Draw the spline series type. +Path _processSplineShape( + {required Canvas canvas, + required Rect rect, + required bool isNeedToReturnPath, + required Path path, + Paint? paint, + Paint? shaderPaint, + bool? isDashArray}) { + final double x = rect.left + rect.width / 2; + final double y = rect.top + rect.height / 2; + final double width = rect.width; + final double height = rect.height; + + path.moveTo(x - width / 2, y + height / 5); + path.quadraticBezierTo(x, y - height, x, y + height / 5); + path.moveTo(x, y + height / 5); + path.quadraticBezierTo( + x + width / 2, y + height / 2, x + width / 2, y - height / 2); + + if (isNeedToReturnPath) { return path; } - /// Draw the spline series type. - static Path _processSplineShape( - {required Canvas canvas, - required Rect rect, - required bool isNeedToReturnPath, - required Path path, - Paint? paint, - Paint? borderPaint}) { - final double x = rect.left + rect.width / 2; - final double y = rect.top + rect.height / 2; - final double width = rect.width; - final double height = rect.height; - - path.moveTo(x - width / 2, y + height / 5); - path.quadraticBezierTo(x, y - height, x, y + height / 5); - path.moveTo(x, y + height / 5); - path.quadraticBezierTo( - x + width / 2, y + height / 2, x + width / 2, y - height / 2); - - if (isNeedToReturnPath) { - return path; - } + if (paint != null) { + paint.shader = shaderPaint != null ? shaderPaint.shader : paint.shader; - canvas.drawPath(path, paint!); + canvas.drawPath( + isDashArray! + ? _processDashPath(path, + dashArray: _CircularIntervalList([3, 2])) + : path, + paint..style = PaintingStyle.stroke); + } - if (borderPaint != null) { - canvas.drawPath(path, borderPaint); + return path; +} + +Path _processDashPath( + Path source, { + required _CircularIntervalList dashArray, +}) { + const double intialValue = 0.0; + final Path path = Path(); + for (final PathMetric measurePath in source.computeMetrics()) { + double distance = intialValue; + bool draw = true; + while (distance < measurePath.length) { + final double length = dashArray.next; + if (draw) { + path.addPath( + measurePath.extractPath(distance, distance + length), Offset.zero); + } + distance += length; + draw = !draw; } + } + return path; +} - return path; +/// A circular array for dash offsets and lengths. +class _CircularIntervalList { + _CircularIntervalList(this._values); + final List _values; + int _index = 0; + T get next { + if (_index >= _values.length) { + _index = 0; + } + return _values[_index++]; } } diff --git a/packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart b/packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart new file mode 100644 index 000000000..78cb0c29e --- /dev/null +++ b/packages/syncfusion_flutter_core/lib/src/widgets/interactive_scroll_viewer.dart @@ -0,0 +1,306 @@ +part of interactive_scroll_viewer_internal; + +/// Triggers when double tap zoom invoked. +typedef _DoubleTapZoomInvokedCallback = Offset Function( + Offset value, Offset tapPosition); + +/// [InteractiveScrollViewer] enables pan and zoom interactions with its child. +@immutable +class InteractiveScrollViewer extends StatefulWidget { + /// Constructor for InteractiveScrollable. + const InteractiveScrollViewer(this.child, + {Key? key, + this.clipBehavior = Clip.hardEdge, + this.onDoubleTapZoomInvoked, + this.alignPanAxis = false, + this.boundaryMargin = EdgeInsets.zero, + // These default scale values were eyeballed as reasonable + //limits for common use cases. + this.maxScale = 3, + this.minScale = 1, + this.onInteractionStart, + this.onInteractionUpdate, + this.onInteractionEnd, + this.panEnabled = true, + this.scaleEnabled = true, + this.constrained = true, + this.enableDoubleTapZooming = true, + this.transformationController}) + : super(key: key); + + /// Whether the normal size constraints at this point in the widget tree are + /// applied to the child. + /// + /// If set to false, then the child will be given infinite constraints. + /// This is often useful, + /// when a child should be bigger than the InteractiveScrollable. + final bool constrained; + + /// If set to [Clip.none],the child may extend + /// beyond the size of the InteractiveScrollable, + /// but it will not receive gestures in these areas. + /// Be sure that the InteractiveScrollable is + /// the desired size when using [Clip.none]. + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + /// If true, panning is only allowed in the direction of the horizontal axis + /// or the vertical axis. + /// + /// In other words, when this is true, diagonal panning is not allowed. A + /// single gesture begun along one axis cannot also cause panning along the + /// other axis without stopping and beginning a new gesture. This is a common + /// pattern in tables where data is displayed in columns and rows. + /// + /// See also: + /// * [constrained], which has an example of creating a table that uses + /// alignPanAxis. + final bool alignPanAxis; + + /// A margin for the visible boundaries of the child. + /// + /// Any transformation that results in the viewport being able to view outside + /// of the boundaries will be stopped at the boundary. The boundaries do not + /// rotate with the rest of the scene, so they are always aligned with the + /// viewport. + /// + /// To produce no boundaries at all, pass infinite [EdgeInsets], such as + /// `EdgeInsets.all(double.infinity)`. + /// + /// No edge can be NaN. + /// + /// Defaults to [EdgeInsets.zero], which results in boundaries that are the + /// exact same size and position as the [child]. + final EdgeInsets boundaryMargin; + + /// The Widget to perform the transformations on. + /// + /// Cannot be null. + final Widget child; + + /// If false, the user will be prevented from panning. + /// + /// Defaults to true. + /// + /// See also: + /// + /// * [scaleEnabled], which is similar but for scale. + final bool panEnabled; + + /// If false, the user will be prevented from scaling. + /// + /// Defaults to true. + /// + /// See also: + /// + /// * [panEnabled], which is similar but for panning. + final bool scaleEnabled; + + /// The maximum allowed scale. + /// + /// The scale will be clamped between this and [minScale] inclusively. + /// + /// Defaults to 3. + /// + /// Cannot be null, and must be greater than zero and greater than minScale. + final double maxScale; + + /// The minimum allowed scale. + /// + /// The scale will be clamped between this and [maxScale] inclusively. + /// + /// Scale is also affected by [boundaryMargin]. If the scale would result in + /// viewing beyond the boundary, then it will not be allowed. By default, + /// boundaryMargin is EdgeInsets.zero, so scaling below 1.0 will not be + /// allowed in most cases without first increasing the boundaryMargin. + /// + /// Defaults to 1. + /// + /// Cannot be null, and must be a finite number greater than zero and less + /// than maxScale. + final double minScale; + + /// Called when the user begins a pan or scale gesture on the widget. + final GestureScaleStartCallback? onInteractionStart; + + /// Called when the user updates a pan or scale gesture on the widget. + final GestureScaleUpdateCallback? onInteractionUpdate; + + /// Called when the user ends a pan or scale gesture on the widget. + final GestureScaleEndCallback? onInteractionEnd; + + /// A [TransformationController] for the transformation performed on the + /// child. + final TransformationController? transformationController; + + /// If true, double tap zooming is enabled. + final bool enableDoubleTapZooming; + + /// Triggers when double tap + /// + /// Default to null + /// + final _DoubleTapZoomInvokedCallback? onDoubleTapZoomInvoked; + + @override + InteractiveScrollViewerState createState() => InteractiveScrollViewerState(); +} + +/// State for [InteractiveScrollViewer]. +class InteractiveScrollViewerState extends State { + Offset _tapPosition = Offset.zero; + + /// Scrolls to the specified offset + /// ``` dart + ///final GlobalKey _interactiveViewerKey = + ///GlobalKey(); + ///TransformationController transformationController= + ///TransformationController(); + ///@override + ///Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('InteractiveScrollViewer'), + /// actions: [ + /// IconButton( + /// icon: const Icon( + /// Icons.arrow_circle_down, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// _interactiveViewerKey.currentState?.scrollTo(Offset(150,2000)); + /// }, + /// ), + /// ], + /// ), + /// body: InteractiveScrollViewer( + /// Image.network('https://picsum.photos/250?image=9'), + /// key: _interactiveViewerKey, + /// transformationController: transformationController, + /// )); + /// } + /// ``` + void scrollTo(Offset offset) { + if (widget.transformationController != null) { + final Offset previousOffset = + widget.transformationController!.toScene(Offset.zero); + widget.transformationController?.value = + widget.transformationController!.value.clone() + ..translate( + previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); + } + } + + /// Scales the child to the specified scale value + /// ``` dart + ///final GlobalKey _interactiveViewerKey = + ///GlobalKey(); + ///TransformationController transformationController= + ///TransformationController(); + ///@override + ///Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('InteractiveScrollViewer'), + /// actions: [ + /// IconButton( + /// icon: const Icon( + /// Icons.arrow_circle_down, + /// color: Colors.white, + /// ), + /// onPressed: () { + /// _interactiveViewerKey.currentState?.scaleTo(2); + /// }, + /// ), + /// ], + /// ), + /// body: InteractiveScrollViewer( + /// Image.network('https://picsum.photos/250?image=9'), + /// key: _interactiveViewerKey, + /// transformationController: transformationController, + /// )); + /// } + /// ``` + void scaleTo(double scale) { + if (widget.transformationController != null) { + scale = scale.clamp(widget.minScale, widget.maxScale); + final double zoomLevel = + widget.transformationController!.value.getMaxScaleOnAxis(); + final Offset previousOffset = + widget.transformationController!.toScene(Offset.zero); + widget.transformationController?.value = + widget.transformationController!.value.clone() + ..scale(scale / zoomLevel, scale / zoomLevel); + scrollTo(previousOffset); + } + } + + /// This method used to get the tap positions + void _handleDoubleTapDown(TapDownDetails details) { + _tapPosition = details.localPosition; + } + + /// Handles the double tap behavior. + void _handleDoubleTap() { + if (widget.transformationController != null) { + const double minimumZoomLevel = 1; + const double maximumZoomLevel = 2; + final double zoomLevel = + widget.transformationController!.value.getMaxScaleOnAxis(); + Offset normalizedOffset; + Offset offset; + if (zoomLevel <= minimumZoomLevel) { + normalizedOffset = (-_tapPosition - + widget.transformationController!.toScene(Offset.zero) * + zoomLevel) / + zoomLevel; + scaleTo(maximumZoomLevel); + offset = (-_tapPosition - normalizedOffset * maximumZoomLevel) / + maximumZoomLevel; + // Triggered when double tap + if (widget.onDoubleTapZoomInvoked != null) { + offset = widget.onDoubleTapZoomInvoked!(offset, _tapPosition); + } + scrollTo(offset); + } else { + normalizedOffset = (-_tapPosition - + widget.transformationController!.toScene(Offset.zero) * + zoomLevel) / + zoomLevel; + scaleTo(minimumZoomLevel); + offset = (-_tapPosition - normalizedOffset * minimumZoomLevel) / + minimumZoomLevel; + // Triggered when double tap + if (widget.onDoubleTapZoomInvoked != null) { + offset = widget.onDoubleTapZoomInvoked!(offset, _tapPosition); + } + scrollTo(offset); + } + } + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onDoubleTapDown: + widget.enableDoubleTapZooming ? _handleDoubleTapDown : null, + onDoubleTap: widget.enableDoubleTapZooming ? _handleDoubleTap : null, + child: InteractiveViewer( + minScale: widget.minScale, + maxScale: widget.maxScale, + constrained: widget.constrained, + onInteractionStart: widget.onInteractionStart, + onInteractionUpdate: widget.onInteractionUpdate, + onInteractionEnd: widget.onInteractionEnd, + scaleEnabled: widget.scaleEnabled, + panEnabled: widget.panEnabled, + alignPanAxis: widget.alignPanAxis, + transformationController: widget.transformationController, + boundaryMargin: widget.boundaryMargin, + clipBehavior: widget.clipBehavior, + child: widget.child, + ), + ); + } +} diff --git a/packages/syncfusion_flutter_core/lib/tooltip_internal.dart b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart index e4e7766dc..6fe1572cd 100644 --- a/packages/syncfusion_flutter_core/lib/tooltip_internal.dart +++ b/packages/syncfusion_flutter_core/lib/tooltip_internal.dart @@ -3,9 +3,7 @@ library tooltip_internal; import 'dart:async'; import 'dart:ui'; import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:flutter/rendering.dart'; import 'package:flutter/material.dart'; - +import 'package:flutter/rendering.dart'; import 'core.dart'; - -part 'src/tooltip/tooltip.dart'; \ No newline at end of file +part 'src/tooltip/tooltip.dart'; diff --git a/packages/syncfusion_flutter_core/pubspec.yaml b/packages/syncfusion_flutter_core/pubspec.yaml index 146db511c..88be4743d 100644 --- a/packages/syncfusion_flutter_core/pubspec.yaml +++ b/packages/syncfusion_flutter_core/pubspec.yaml @@ -7,7 +7,7 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: - vector_math: ^2.1.0 + vector_math: ">=2.1.0 <=3.0.0" flutter: sdk: flutter diff --git a/packages/syncfusion_flutter_datagrid/CHANGELOG.md b/packages/syncfusion_flutter_datagrid/CHANGELOG.md index 219b04037..3d6eb9491 100644 --- a/packages/syncfusion_flutter_datagrid/CHANGELOG.md +++ b/packages/syncfusion_flutter_datagrid/CHANGELOG.md @@ -1,4 +1,17 @@ ## Unreleased + +**Features** +* Provided the support to resize the columns by tapping and dragging the right border of the column header. +* Provided the support to show an additional unbound row to display a summary or totals. Users can display a minimum, maximum, average, and count in columns. +* Provided the support to export the DataGrid content, such as rows, stacked header rows, and table summary rows, to Excel and PDF format with several customization options. +* Provided the support to show a checkbox in each row to select entire rows when the boxes are checked. Users can select or deselect all the rows by selecting the checkbox in the header. +* Provided the support to sort all the rows in DataGrid instead of current page alone when the paging is used. +* Provided the support to set the size for the page buttons in `SfDataPager`. + +**Breaking changes** +* The `onCellRenderersCreated` callback has been removed from the `SfDataGrid`. + +## [19.2.44-beta] - 06/30/2021 **Features** * Provided the support to edit cell values. An editor widget can be loaded based on the column type to edit cell values. @@ -10,6 +23,8 @@ **Breaking changes** * [GridTextColumn](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/GridTextColumn-class.html) class has been deprecated. Use [GridColumn](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/GridColumn-class.html) instead. +* \#I324459 - The DataGrid's built-in left and top borders have been removed. Set the required border configuration in the [Container](https://api.flutter.dev/flutter/widgets/Container-class.html) widget and add `SfDataGrid` as a child. +* The `DataGridSource` class's `handleSort` method has been removed. To write the whole logic for custom sorting, override the `performSorting` method in `DataGridSource` class. ## [19.1.67-beta] - 06/08/2021 diff --git a/packages/syncfusion_flutter_datagrid/README.md b/packages/syncfusion_flutter_datagrid/README.md index 74155b214..01e55f8ab 100644 --- a/packages/syncfusion_flutter_datagrid/README.md +++ b/packages/syncfusion_flutter_datagrid/README.md @@ -39,7 +39,7 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta ![Columns are sorted in flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-sorting.gif) -**Selection** - Select one or more rows. Keyboard navigation is supported for web platforms. +**Selection** - Select one or more rows. Keyboard navigation is supported for web platforms. Built-in checkbox columns allow display of a checkbox in each row to select entire rows when the boxes are checked. Users can also select or deselect all the rows by selecting the checkbox in the header. ![Flutter DataGrid shows rows with selection](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-selection.png) @@ -52,6 +52,14 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta ![Flutter datagrid shows multiple column headers](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-stacked-headers.png) +**Summary row** - Show an additional unbound row to display a summary or totals. Users can display a minimum, maximum, average, and count in columns. + +![Flutter datagrid shows table summary rows](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-table-summary-row.png) + +**Column resizing** - Resize the columns by tapping and dragging the right border of the column header. + +![Column resizing in Flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-column-resizing.gif) + **Load more** - Display an interactive view when the grid reaches its maximum offset while scrolling down. ![infinite scrolling in Flutter datagrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-load-more.gif) @@ -62,7 +70,7 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta **Footer** - Show an additional row that can be displayed below to last row. Widgets can also be displayed in the footer row. -[Footer view in Flutter DataGrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-footer-view.png) +![Footer view in Flutter DataGrid](https://cdn.syncfusion.com/content/images/Flutter/pub_images/flutter-datagrid-footer-view.png) **Freeze Panes** - Freeze the rows and columns when scrolling the grid. @@ -72,6 +80,8 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta **Pull to refresh** - Allows users to refresh data when the DataGrid is pulled down. +**Exporting** - Export the DataGrid content, such as rows, stacked header rows, and table summary rows, to Excel and PDF format with several customization options. + **Theme** - Use a dark or light theme. **Accessibility** - The DataGrid can easily be accessed by screen readers. @@ -80,7 +90,6 @@ The Flutter DataTable or DataGrid is used to display and manipulate data in a ta ## Coming soon -* Column resizing * Column drag and drop * Grouping * Row drag and drop @@ -242,7 +251,7 @@ Widget build(BuildContext context) { body: SfDataGrid( source: employeeDataSource, columns: [ - GridTextColumn( + GridColumn( columnName: 'id', label: Container( padding: EdgeInsets.all(16.0), @@ -250,20 +259,20 @@ Widget build(BuildContext context) { child: Text( 'ID', ))), - GridTextColumn( + GridColumn( columnName: 'name', label: Container( padding: EdgeInsets.all(16.0), alignment: Alignment.centerLeft, child: Text('Name'))), - GridTextColumn( + GridColumn( columnName: 'designation', width: 120, label: Container( padding: EdgeInsets.all(16.0), alignment: Alignment.centerLeft, child: Text('Designation'))), - GridTextColumn( + GridColumn( columnName: 'salary', label: Container( padding: EdgeInsets.all(16.0), @@ -288,4 +297,4 @@ The following screenshot illustrates the result of the above code sample. 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,600+ 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), and [Blazor](https://www.syncfusion.com/blazor-components)) , mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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 +Today we provide 1,600+ 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), and [Blazor](https://www.syncfusion.com/blazor-components)) , mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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_datagrid/example/pubspec.yaml b/packages/syncfusion_flutter_datagrid/example/pubspec.yaml index 59832701d..fbceb3988 100644 --- a/packages/syncfusion_flutter_datagrid/example/pubspec.yaml +++ b/packages/syncfusion_flutter_datagrid/example/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: path: ../ cupertino_icons: ^1.0.2 + flutter: uses-material-design: true \ No newline at end of file diff --git a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart index 7e6cbacf6..c8024aaf5 100644 --- a/packages/syncfusion_flutter_datagrid/lib/datagrid.dart +++ b/packages/syncfusion_flutter_datagrid/lib/datagrid.dart @@ -4,6 +4,8 @@ /// /// To use, import `package:syncfusion_flutter_datagrid/datagrid.dart`. /// +/// {@youtube 560 315 https://www.youtube.com/watch?v=-ULsEfjxFuY} +/// /// See also: /// /// * [Syncfusion Flutter DataGrid product page](https://www.syncfusion.com/flutter-widgets/flutter-datagrid) @@ -11,96 +13,37 @@ /// * [Knowledge base](https://www.syncfusion.com/kb/flutter/sfdatagrid) library datagrid; -import 'dart:async'; -import 'dart:collection'; -import 'dart:core'; -import 'dart:math'; -import 'dart:ui'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; -import 'package:collection/collection.dart'; - -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/localizations.dart'; - -// DataPager -part './src/datapager/sfdatapager.dart'; - -//DataGrid -part './src/sfdatagrid.dart'; -part './src/control/datagrid_datasource.dart'; - -//ScrollablePanel -part './src/control/scrollable_panel/scrollview_widget.dart'; -part './src/control/scrollable_panel/visual_container_widget.dart'; -part './src/control/scrollable_panel/visual_container_helper.dart'; - -//Runtime -part './src/control/runtime/grid_column.dart'; -part './src/control/runtime/column_sizer.dart'; -part './src/control/runtime/stacked_header.dart'; - -//Generator -part './src/control/generator/row_generator.dart'; -part './src/control/generator/data_row.dart'; -part './src/control/generator/data_row_base.dart'; -part './src/control/generator/data_cell.dart'; -part './src/control/generator/data_cell_base.dart'; -part './src/control/generator/spanned_data_row.dart'; - -//Helper -part './src/control/helper/enums.dart'; -part './src/control/helper/grid_index_resolver.dart'; -part './src/control/helper/sfdatagrid_helper.dart'; - -//CellControl -part './src/control/cell_control/virtualizing_cells_widget.dart'; -part './src/control/cell_control/grid_cell_widget.dart'; -part './src/control/cell_control/grid_header_cell_widget.dart'; - -// QueryStyle -part './src/control/callbackargs.dart'; - -//CellRenderer -part './src/cell_renderer/grid_cell_renderer_base.dart'; -part './src/cell_renderer/grid_virtualizing_cell_renderer_base.dart'; -part './src/cell_renderer/grid_cell_text_field_renderer.dart'; -part './src/cell_renderer/grid_header_cell_renderer.dart'; -part './src/cell_renderer/grid_cell_stacked_header_renderer.dart'; - -//Selection Controller -part './src/control/selection_controller/row_selection_manager.dart'; -part './src/control/selection_controller/selected_row_info.dart'; -part './src/control/selection_controller/selection_helper.dart'; -part './src/control/selection_controller/selection_manager_base.dart'; -part './src/control/selection_controller/current_cell_manager.dart'; - -//GridCommon -part './src/grid_common/list_base.dart'; -part './src/grid_common/list_generic_base.dart'; -part './src/grid_common/math_helper.dart'; -part './src/grid_common/collections/tree_table.dart'; -part './src/grid_common/collections/tree_table_with_counter.dart'; -part './src/grid_common/collections/tree_table_with_summary.dart'; -part './src/grid_common/scroll_axis_base/distance_counter_subset.dart'; -part './src/grid_common/scroll_axis_base/distance_counter_collection_base.dart'; -part './src/grid_common/scroll_axis_base/editable_line_size_host_base.dart'; -part './src/grid_common/scroll_axis_base/line_size_host_base.dart'; -part './src/grid_common/scroll_axis_base/scrollbar_base.dart'; -part './src/grid_common/scroll_axis_base/line_scroll_axis.dart'; -part './src/grid_common/scroll_axis_base/line_size_collection.dart'; -part './src/grid_common/scroll_axis_base/pixel_scroll_axis.dart'; -part './src/grid_common/scroll_axis_base/range_changed_event.dart'; -part './src/grid_common/scroll_axis_base/distance_range_counter_collection.dart'; -part './src/grid_common/scroll_axis_base/row_column_index.dart'; -part './src/grid_common/scroll_axis_base/scroll_axis_base.dart'; -part './src/grid_common/scroll_axis_base/scroll_axis_region.dart'; -part './src/grid_common/scroll_axis_base/scroll_info.dart'; -part './src/grid_common/scroll_axis_base/sorted_range_value_list.dart'; -part './src/grid_common/scroll_axis_base/visible_line_info.dart'; -part './src/grid_common/utility/double_span.dart'; -part './src/grid_common/utility/int_32_span.dart'; \ No newline at end of file +export './src/datagrid_widget/sfdatagrid.dart' + hide + updateSelectedIndex, + updateSelectedRow, + updateCurrentCellIndex, + updateVerticalOffset, + updateHorizontalOffset, + notifyDataGridPropertyChangeListeners, + handleLoadMoreRows, + handleRefresh, + updateDataSource, + effectiveRows, + setPageCount, + setChildColumnIndexes, + getChildColumnIndexes; +export './src/datapager/sfdatapager.dart'; +export './src/grid_common/row_column_index.dart'; +export 'src/datagrid_widget/helper/callbackargs.dart' + hide setColumnSizerInRowHeightDetailsArgs; +export 'src/datagrid_widget/helper/enums.dart'; +export 'src/datagrid_widget/runtime/column.dart' + hide + ColumnResizeController, + refreshColumnSizer, + initialRefresh, + resetAutoCalculation, + updateColumnSizerLoadedInitiallyFlag, + getSortIconWidth, + getAutoFitRowHeight, + setStateDetailsInColumnSizer, + isColumnSizerLoadedInitially, + GridCheckboxColumn; +export 'src/datagrid_widget/selection/selection_manager.dart' + show RowSelectionManager, SelectionManagerBase; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart deleted file mode 100644 index 39f8e4819..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_renderer_base.dart +++ /dev/null @@ -1,22 +0,0 @@ -part of datagrid; - -/// The base class for the cell renderer that used to display the widget. -abstract class GridCellRendererBase { - /// Creates the [GridCellRendererBase] for [SfDataGrid] widget. - GridCellRendererBase() { - _isEditable = true; - } - - late _DataGridStateDetails _dataGridStateDetails; - - /// Decide to enable the editing in the renderer. - late bool _isEditable; - - /// Called when the child widgets for the GridCell are prepared. - Widget? onPrepareWidgets(DataCellBase dataCell) { - return null; - } - - /// Called when the style is set for the cell. - void setCellStyle(DataCellBase dataCell) {} -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart deleted file mode 100644 index 3e8808b7f..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_stacked_header_renderer.dart +++ /dev/null @@ -1,43 +0,0 @@ -part of datagrid; - -/// A cell renderer which displays the header text in the -/// stacked columns of the stacked header rows. -class _GridStackedHeaderCellRenderer - extends GridVirtualizingCellRendererBase { - /// Creates the [GridStackedHeaderCellRenderer] for [SfDataGrid] widget. - _GridStackedHeaderCellRenderer(_DataGridStateDetails dataGridStateDetails) { - _dataGridStateDetails = dataGridStateDetails; - } - - @override - void onInitializeDisplayWidget(DataCellBase dataCell) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final bool isLight = - dataGridSettings.dataGridThemeData!.brightness == Brightness.light; - Widget? label = DefaultTextStyle( - style: isLight - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Colors.black87) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)), - child: dataCell._stackedHeaderCell!.child); - - dataCell._columnElement = GridCell( - key: dataCell._key!, - dataCell: dataCell, - backgroundColor: isLight - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(33, 33, 33, 1), - isDirty: dataGridSettings.container._isDirty || dataCell._isDirty, - child: label, - ); - - label = null; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart deleted file mode 100644 index e8f7bfbe6..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_cell_text_field_renderer.dart +++ /dev/null @@ -1,56 +0,0 @@ -part of datagrid; - -/// A cell renderer which displays the String value in the cell. -/// -/// This renderer is typically used for [GridTextColumn]. -class GridCellTextFieldRenderer - extends GridVirtualizingCellRendererBase { - /// Creates the [GridCellTextFieldRenderer] for [SfDataGrid] widget. - GridCellTextFieldRenderer(_DataGridStateDetails dataGridStateDetails) { - _dataGridStateDetails = dataGridStateDetails; - super._isEditable = true; - } - - @override - void setCellStyle(DataCellBase? dataCell) { - if (dataCell != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - dataCell._textStyle = _getCellTextStyle(dataGridSettings, dataCell); - super.setCellStyle(dataCell); - } - } - - TextStyle _getCellTextStyle( - _DataGridSettings dataGridSettings, DataCellBase dataCell) { - final DataRowBase? dataRow = dataCell._dataRow; - if (dataRow != null && dataRow.isSelectedRow) { - return dataRow._isHoveredRow - ? dataGridSettings.dataGridThemeData!.rowHoverTextStyle - : dataGridSettings.dataGridThemeData!.brightness == Brightness.light - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Color.fromRGBO(0, 0, 0, 0.87)) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)); - } else { - return dataRow!._isHoveredRow - ? dataGridSettings.dataGridThemeData!.rowHoverTextStyle - : dataGridSettings.dataGridThemeData!.brightness == Brightness.light - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Colors.black87) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)); - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart deleted file mode 100644 index 52bca93ea..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_header_cell_renderer.dart +++ /dev/null @@ -1,47 +0,0 @@ -part of datagrid; - -/// A cell renderer which displays the header text in the columns. -class GridHeaderCellRenderer - extends GridVirtualizingCellRendererBase { - /// Creates the [GridHeaderCellRenderer] for [SfDataGrid] widget. - GridHeaderCellRenderer(_DataGridStateDetails dataGridStateDetails) { - _dataGridStateDetails = dataGridStateDetails; - } - - @override - void onInitializeDisplayWidget(DataCellBase dataCell) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final Widget child = dataCell.gridColumn!.label; - dataCell._columnElement = GridHeaderCell( - key: dataCell._key!, - dataCell: dataCell, - child: DefaultTextStyle( - key: dataCell._key, style: dataCell._textStyle!, child: child), - backgroundColor: Colors.transparent, - isDirty: dataGridSettings.container._isDirty || - dataCell._isDirty || - dataCell._dataRow!._isDirty, - ); - } - - @override - void setCellStyle(DataCellBase dataCell) { - final SfDataGridThemeData themeData = - _dataGridStateDetails().dataGridThemeData!; - TextStyle getDefaultHeaderTextStyle() { - return themeData.brightness == Brightness.light - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Colors.black87) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)); - } - - dataCell._textStyle = getDefaultHeaderTextStyle(); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart deleted file mode 100644 index 5bfed7232..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/cell_renderer/grid_virtualizing_cell_renderer_base.dart +++ /dev/null @@ -1,51 +0,0 @@ -part of datagrid; - -/// A base class for cell renderer classes which displays widget in a cell. -abstract class GridVirtualizingCellRendererBase extends GridCellRendererBase { - /// Creates the [GridVirtualizingCellRendererBase] for [SfDataGrid] widget. - GridVirtualizingCellRendererBase(); - - /// Initializes the column element of a [DataCell] - /// - /// object with the given widget and required values. - void onInitializeDisplayWidget(DataCellBase dataCell) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - // Need to restrict invisible rows (rowIndex == -1) from the item collection. - // Because we generate the `ensureColumns` for all the data rows in the - // `rowGenerator.items` collection not visible rows. - if (dataCell.columnIndex < 0 || dataCell.rowIndex < 0) { - return; - } - - final int index = _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, dataCell.columnIndex); - final Widget child = dataCell._dataRow!._dataGridRowAdapter!.cells[index]; - - Widget getChild() { - if (dataGridSettings.currentCell.isEditing && dataCell._isEditing) { - return dataCell._editingWidget ?? child; - } else { - return child; - } - } - - dataCell._columnElement = GridCell( - key: dataCell._key!, - dataCell: dataCell, - child: DefaultTextStyle( - key: dataCell._key, style: dataCell._textStyle!, child: getChild()), - backgroundColor: Colors.transparent, - isDirty: dataGridSettings.container._isDirty || - dataCell._isDirty || - dataCell._dataRow!._isDirty, - ); - } - - @override - Widget? onPrepareWidgets(DataCellBase dataCell) { - onInitializeDisplayWidget(dataCell); - return dataCell._columnElement; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_cell_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_cell_widget.dart deleted file mode 100644 index 9c68314b1..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_cell_widget.dart +++ /dev/null @@ -1,1026 +0,0 @@ -part of datagrid; - -/// A widget which displays in the cells. -class GridCell extends StatefulWidget { - /// Creates the [GridCell] for [SfDataGrid] widget. - const GridCell({ - required Key key, - required this.dataCell, - required this.isDirty, - required this.backgroundColor, - required this.child, - }) : super(key: key); - - /// Holds the information required to display the cell. - final DataCellBase dataCell; - - /// The [child] contained by the [GridCell]. - final Widget child; - - /// The color to paint behind the [child]. - final Color backgroundColor; - - /// Decides whether the [GridCell] should be refreshed when [SfDataGrid] is - /// rebuild. - final bool isDirty; - - @override - State createState() => _GridCellState(); -} - -class _GridCellState extends State { - late PointerDeviceKind _kind; - Timer? tapTimer; - - bool _isDoubleTapEnabled(_DataGridSettings dataGridSettings) => - dataGridSettings.onCellDoubleTap != null || - (dataGridSettings.allowEditing && - dataGridSettings.editingGestureType == EditingGestureType.doubleTap); - - void _handleOnTapDown(TapDownDetails details) { - _kind = details.kind!; - final DataCellBase dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - if (_isDoubleTapEnabled(dataGridSettings)) { - _handleDoubleTapOnEditing(dataGridSettings, dataCell, details); - } - } - - void _handleDoubleTapOnEditing(_DataGridSettings dataGridSettings, - DataCellBase dataCell, TapDownDetails details) { - if (tapTimer != null && tapTimer!.isActive) { - tapTimer!.cancel(); - } else { - tapTimer?.cancel(); - // 190 millisecond to satisfies all desktop touchpad double-tap - // action - tapTimer = Timer(const Duration(milliseconds: 190), () { - if (dataGridSettings.allowEditing && dataCell._isEditing) { - tapTimer?.cancel(); - return; - } - _handleOnTapUp( - tapDownDetails: details, - tapUpDetails: null, - dataGridSettings: dataGridSettings, - dataCell: dataCell, - kind: _kind); - tapTimer?.cancel(); - }); - } - } - - Widget _wrapInsideGestureDetector() { - final DataCellBase dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - // Check the DoubleTap is enabled - // If its enable, we have to ignore the onTapUp and we need to handle both - // tap and double tap in onTap itself to avoid the delay on double tap - // callback - final bool isDoubleTapEnabled = _isDoubleTapEnabled(dataGridSettings); - return GestureDetector( - onTapUp: isDoubleTapEnabled - ? null - : (TapUpDetails details) { - _handleOnTapUp( - tapUpDetails: details, - tapDownDetails: null, - dataGridSettings: dataGridSettings, - dataCell: dataCell, - kind: _kind); - }, - onTapDown: _handleOnTapDown, - onTap: isDoubleTapEnabled - ? () { - if (tapTimer != null && !tapTimer!.isActive) { - _handleOnDoubleTap( - dataCell: dataCell, dataGridSettings: dataGridSettings); - } - } - : null, - onTapCancel: () { - if (tapTimer != null && tapTimer!.isActive) { - tapTimer?.cancel(); - } - }, - onLongPressEnd: (LongPressEndDetails details) { - _handleOnLongPressEnd( - longPressEndDetails: details, - dataGridSettings: dataGridSettings, - dataCell: dataCell); - }, - onSecondaryTapUp: (TapUpDetails details) { - _handleOnSecondaryTapUp( - tapUpDetails: details, - dataGridSettings: dataGridSettings, - dataCell: dataCell, - kind: _kind); - }, - onSecondaryTapDown: _handleOnTapDown, - child: _wrapInsideContainer(), - ); - } - - Widget _wrapInsideContainer() => Container( - key: widget.key, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration(border: _getCellBorder(widget.dataCell)), - alignment: Alignment.center, - child: _wrapInsideCellContainer( - child: widget.child, - dataCell: widget.dataCell, - key: widget.key!, - backgroundColor: widget.backgroundColor, - )); - - @override - Widget build(BuildContext context) { - return _GridCellRenderObjectWidget( - key: widget.key, - dataCell: widget.dataCell, - isDirty: widget.isDirty, - child: _wrapInsideGestureDetector(), - ); - } - - @override - void dispose() { - super.dispose(); - if (tapTimer != null) { - tapTimer = null; - } - } -} - -class _GridCellRenderObjectWidget extends SingleChildRenderObjectWidget { - _GridCellRenderObjectWidget( - {required Key? key, - required this.dataCell, - required this.isDirty, - required this.child}) - : super(key: key, child: RepaintBoundary.wrap(child, 0)); - - @override - final Widget child; - final DataCellBase dataCell; - final bool isDirty; - - @override - _RenderGridCell createRenderObject(BuildContext context) => _RenderGridCell( - dataCell: dataCell, - isDirty: isDirty, - ); - - @override - void updateRenderObject(BuildContext context, _RenderGridCell renderObject) { - super.updateRenderObject(context, renderObject); - renderObject - ..isDirty = isDirty - ..dataCell = dataCell; - } -} - -class _RenderGridCell extends RenderBox - with RenderObjectWithChildMixin { - _RenderGridCell( - {RenderBox? child, required DataCellBase dataCell, required bool isDirty}) - : _dataCell = dataCell, - _isDirty = isDirty { - this.child = child; - } - - DataCellBase get dataCell => _dataCell; - DataCellBase _dataCell; - - set dataCell(DataCellBase newDataColumn) { - if (_dataCell == newDataColumn) { - return; - } - - _dataCell = newDataColumn; - markNeedsLayout(); - markNeedsPaint(); - } - - bool get isDirty => _isDirty; - bool _isDirty = false; - - set isDirty(bool newValue) { - _isDirty = newValue; - if (_isDirty) { - markNeedsLayout(); - markNeedsPaint(); - } - - dataCell._isDirty = false; - } - - Rect? columnRect = Rect.zero; - - Rect? cellClipRect; - - Rect? _measureColumnRect(double rowHeight) { - if (dataCell._dataRow != null && - dataCell._dataRow!.isVisible && - dataCell.isVisible) { - final DataRowBase dataRow = dataCell._dataRow!; - final _DataGridSettings _dataGridSettings = - dataRow._dataGridStateDetails!(); - final double lineWidth = dataRow._getColumnWidth( - dataCell.columnIndex, dataCell.columnIndex + dataCell._columnSpan); - final double lineHeight = dataRow._getRowHeight( - dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); - - if (dataRow.rowType == RowType.stackedHeaderRow) { - columnRect = - _getStackedHeaderCellRect(_dataGridSettings, lineWidth, lineHeight); - } else { - final _VisibleLineInfo? lineInfo = - dataRow._getColumnVisibleLineInfo(dataCell.columnIndex); - final double origin = lineInfo != null ? lineInfo.origin : 0.0; - columnRect = _getCellRect( - _dataGridSettings, lineInfo, origin, lineWidth, lineHeight); - } - } else { - columnRect = Rect.zero; - } - - return columnRect; - } - - Rect? _getCellRect( - _DataGridSettings dataGridSettings, - _VisibleLineInfo? lineInfo, - double origin, - double lineWidth, - double lineHeight) { - final DataRowBase dataRow = dataCell._dataRow!; - final int rowIndex = dataCell.rowIndex; - final int rowSpan = dataCell._rowSpan; - - origin += dataGridSettings.container.horizontalOffset; - - // To overcome grid common RightToLeft clipping line creation problem - // instead of handling in grid common source. - if (dataGridSettings.textDirection == TextDirection.rtl && - lineInfo != null && - lineInfo.visibleIndex == - _SfDataGridHelper.getVisibleLines(dataRow._dataGridStateDetails!()) - .firstBodyVisibleIndex) { - origin += lineInfo.scrollOffset; - } - - if (dataCell._cellType != CellType.stackedHeaderCell) { - // Clipping the column when frozen column applied - cellClipRect = _getCellClipRect(dataGridSettings, lineInfo, lineHeight); - } - - final double topPosition = (rowSpan > 0) - ? -dataRow._getRowHeight(rowIndex - rowSpan, rowIndex - 1) - : 0.0; - - columnRect = Rect.fromLTWH(origin, topPosition, lineWidth, lineHeight); - return columnRect; - } - - Rect? _getStackedHeaderCellRect( - _DataGridSettings dataGridSettings, double lineWidth, double lineHeight) { - final DataRowBase dataRow = dataCell._dataRow!; - final int cellStartIndex = dataCell.columnIndex; - final int columnSpan = dataCell._columnSpan; - final int cellEndIndex = cellStartIndex + columnSpan; - final int frozenColumns = dataGridSettings.container.frozenColumns; - final int frozenColumnsCount = dataGridSettings.frozenColumnsCount; - final int footerFrozenColumns = - dataGridSettings.container.footerFrozenColumns; - final int footerFrozenColumnsCount = - dataGridSettings.footerFrozenColumnsCount; - final int columnsLength = dataGridSettings.columns.length; - final _ScrollAxisBase scrollColumns = - dataGridSettings.container.scrollColumns; - Rect? columnRect = Rect.zero; - double? origin; - _VisibleLineInfo? lineInfo; - - if (frozenColumns > cellStartIndex && frozenColumns <= cellEndIndex) { - if (dataGridSettings.textDirection == TextDirection.ltr) { - for (int index = cellEndIndex; - index >= frozenColumnsCount - 1; - index--) { - lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); - if (lineInfo != null) { - final _VisibleLineInfo? startLineInfo = - scrollColumns.getVisibleLineAtLineIndex(cellStartIndex); - origin = startLineInfo?.origin; - lineWidth = _getClippedWidth( - dataGridSettings, cellStartIndex, cellEndIndex); - break; - } - } - } else { - for (int index = cellEndIndex; index >= cellStartIndex; index--) { - lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); - if (lineInfo != null) { - origin = lineInfo.origin < 0 ? 0.0 : lineInfo.origin; - lineWidth = _getClippedWidth( - dataGridSettings, cellStartIndex, cellEndIndex); - if (lineInfo.origin < 0) { - lineWidth += lineInfo.origin; - } - break; - } - } - } - } else if (footerFrozenColumns > 0 && - columnsLength - footerFrozenColumnsCount <= cellEndIndex) { - int span = 0; - if (dataGridSettings.textDirection == TextDirection.ltr) { - for (int index = cellStartIndex; index <= cellEndIndex; index++) { - lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); - if (lineInfo != null) { - if (index == columnsLength - footerFrozenColumns) { - origin = lineInfo.origin; - lineWidth = - dataRow._getColumnWidth(cellStartIndex + span, cellEndIndex); - break; - } else { - origin = lineInfo.clippedOrigin; - lineWidth = _getClippedWidth( - dataGridSettings, cellStartIndex, cellEndIndex); - break; - } - } - span += 1; - } - } else { - int span = 0; - for (int index = cellStartIndex; index <= cellEndIndex; index++) { - lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); - if (lineInfo != null) { - final _VisibleLineInfo? line = - scrollColumns.getVisibleLineAtLineIndex(cellEndIndex); - if (line != null) { - if (index == columnsLength - footerFrozenColumnsCount) { - origin = line.origin; - lineWidth = dataRow._getColumnWidth( - cellStartIndex + span, cellEndIndex); - break; - } else { - origin = line.clippedOrigin - lineInfo.scrollOffset; - lineWidth = _getClippedWidth( - dataGridSettings, cellStartIndex, cellEndIndex); - break; - } - } - } - span += 1; - } - } - } else { - int span = dataCell._columnSpan; - if (dataGridSettings.textDirection == TextDirection.ltr) { - for (int index = cellStartIndex; index <= cellEndIndex; index++) { - lineInfo = dataRow._getColumnVisibleLineInfo(index); - if (lineInfo != null) { - origin = lineInfo.origin + - dataRow._getColumnWidth(index, index + span) - - dataRow._getColumnWidth(cellStartIndex, cellEndIndex); - - cellClipRect = _getSpannedCellClipRect( - dataGridSettings, dataRow, dataCell, lineHeight, lineWidth); - break; - } - span -= 1; - } - } else { - for (int index = cellEndIndex; index >= cellStartIndex; index--) { - lineInfo = dataRow._getColumnVisibleLineInfo(index); - if (lineInfo != null) { - origin = lineInfo.origin + - dataRow._getColumnWidth(index - span, index) - - dataRow._getColumnWidth(cellStartIndex, cellEndIndex); - - cellClipRect = _getSpannedCellClipRect( - dataGridSettings, dataRow, dataCell, lineHeight, lineWidth); - break; - } - span -= 1; - } - } - } - - if (lineInfo != null) { - columnRect = _getCellRect( - dataGridSettings, lineInfo, origin!, lineWidth, lineHeight); - } - return columnRect; - } - - double _getClippedWidth( - _DataGridSettings dataGridSettings, int startIndex, int endIndex) { - double clippedWidth = 0; - for (int index = startIndex; index <= endIndex; index++) { - final _VisibleLineInfo? newline = - dataCell._dataRow!._getColumnVisibleLineInfo(index); - if (newline != null) { - if (dataGridSettings.textDirection == TextDirection.ltr) { - clippedWidth += - newline.isClipped ? newline.clippedSize : newline.size; - } else { - clippedWidth += newline.isClipped - ? newline.clippedCornerExtent > 0 - ? newline.clippedCornerExtent - : newline.clippedSize - : newline.size; - } - } - } - return clippedWidth; - } - - @override - bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { - if (child == null) { - return false; - } - - final BoxParentData childParentData = child!.parentData! as BoxParentData; - final bool isHit = result.addWithPaintOffset( - offset: childParentData.offset, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) => - child!.hitTest(result, position: transformed)); - if (isHit) { - return true; - } else { - return false; - } - } - - @override - bool hitTestSelf(Offset position) => true; - - @override - bool get isRepaintBoundary => true; - - @override - void performLayout() { - size = constraints - .constrain(Size(constraints.maxWidth, constraints.maxHeight)); - - if (child != null) { - child!.layout( - BoxConstraints.tightFor( - width: constraints.maxWidth, height: constraints.maxHeight), - parentUsesSize: true); - } - } - - @override - void paint(PaintingContext context, Offset offset) { - if (child != null) { - context.paintChild(child!, offset); - } - - super.paint(context, offset); - } -} - -BorderDirectional _getCellBorder(DataCellBase dataCell) { - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - final Color borderColor = dataGridSettings.dataGridThemeData!.gridLineColor; - final double borderWidth = - dataGridSettings.dataGridThemeData!.gridLineStrokeWidth; - - final int rowIndex = (dataCell._rowSpan > 0) - ? dataCell.rowIndex - dataCell._rowSpan - : dataCell.rowIndex; - final int columnIndex = dataCell.columnIndex; - final bool isStackedHeaderCell = - dataCell._cellType == CellType.stackedHeaderCell; - final bool isHeaderCell = dataCell._cellType == CellType.headerCell; - - final bool canDrawHeaderHorizontalBorder = - (dataGridSettings.headerGridLinesVisibility == - GridLinesVisibility.horizontal || - dataGridSettings.headerGridLinesVisibility == - GridLinesVisibility.both) && - (isHeaderCell || isStackedHeaderCell); - - final bool canDrawHeaderVerticalBorder = - (dataGridSettings.headerGridLinesVisibility == - GridLinesVisibility.vertical || - dataGridSettings.headerGridLinesVisibility == - GridLinesVisibility.both) && - (isHeaderCell || isStackedHeaderCell); - - final bool canDrawHorizontalBorder = (dataGridSettings.gridLinesVisibility == - GridLinesVisibility.horizontal || - dataGridSettings.gridLinesVisibility == GridLinesVisibility.both) && - !isHeaderCell && - !isStackedHeaderCell; - - final bool canDrawVerticalBorder = (dataGridSettings.gridLinesVisibility == - GridLinesVisibility.vertical || - dataGridSettings.gridLinesVisibility == GridLinesVisibility.both) && - !isStackedHeaderCell && - !isHeaderCell; - - // Frozen column and row checking - final bool canDrawBottomFrozenBorder = dataGridSettings - .frozenRowsCount.isFinite && - dataGridSettings.frozenRowsCount > 0 && - _GridIndexResolver.getLastFrozenRowIndex(dataGridSettings) == rowIndex; - - final bool canDrawTopFrozenBorder = - dataGridSettings.footerFrozenRowsCount.isFinite && - dataGridSettings.footerFrozenRowsCount > 0 && - _GridIndexResolver.getStartFooterFrozenRowIndex(dataGridSettings) == - rowIndex; - - final bool canDrawRightFrozenBorder = - dataGridSettings.frozenColumnsCount.isFinite && - dataGridSettings.frozenColumnsCount > 0 && - _GridIndexResolver.getLastFrozenColumnIndex(dataGridSettings) == - columnIndex; - - final bool canDrawLeftFrozenBorder = dataGridSettings - .footerFrozenColumnsCount.isFinite && - dataGridSettings.footerFrozenColumnsCount > 0 && - _GridIndexResolver.getStartFooterFrozenColumnIndex(dataGridSettings) == - columnIndex; - - final bool isFrozenPaneElevationApplied = - dataGridSettings.dataGridThemeData!.frozenPaneElevation > 0.0; - - final Color frozenPaneLineColor = - dataGridSettings.dataGridThemeData!.frozenPaneLineColor; - - final double frozenPaneLineWidth = - dataGridSettings.dataGridThemeData!.frozenPaneLineWidth; - - BorderSide _getLeftBorder() { - if ((columnIndex == 0 && - (canDrawVerticalBorder || canDrawHeaderVerticalBorder)) || - canDrawLeftFrozenBorder) { - if (canDrawLeftFrozenBorder && - !isStackedHeaderCell && - !isFrozenPaneElevationApplied) { - return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if (columnIndex > 0 && - ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && - !canDrawLeftFrozenBorder)) { - return BorderSide(width: borderWidth, color: borderColor); - } else { - return BorderSide.none; - } - } else { - return BorderSide.none; - } - } - - BorderSide _getTopBorder() { - if ((rowIndex == 0 && - (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder)) || - canDrawTopFrozenBorder) { - if (canDrawTopFrozenBorder && - !isStackedHeaderCell && - !isFrozenPaneElevationApplied) { - return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else { - return BorderSide.none; - } - } else { - return BorderSide.none; - } - } - - BorderSide _getRightBorder() { - if (canDrawVerticalBorder || - canDrawHeaderVerticalBorder || - canDrawRightFrozenBorder) { - if (canDrawRightFrozenBorder && - !isStackedHeaderCell && - !isFrozenPaneElevationApplied) { - return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && - !canDrawRightFrozenBorder) { - return BorderSide(width: borderWidth, color: borderColor); - } else { - return BorderSide.none; - } - } else { - return BorderSide.none; - } - } - - BorderSide _getBottomBorder() { - if (canDrawHorizontalBorder || - canDrawHeaderHorizontalBorder || - canDrawBottomFrozenBorder) { - if (canDrawBottomFrozenBorder && - !isStackedHeaderCell && - !isFrozenPaneElevationApplied) { - return BorderSide( - width: frozenPaneLineWidth, color: frozenPaneLineColor); - } else if (!canDrawBottomFrozenBorder) { - return BorderSide(width: borderWidth, color: borderColor); - } else { - return BorderSide.none; - } - } else { - return BorderSide.none; - } - } - - return BorderDirectional( - start: _getLeftBorder(), - top: _getTopBorder(), - end: _getRightBorder(), - bottom: _getBottomBorder(), - ); -} - -Widget _wrapInsideCellContainer( - {required DataCellBase dataCell, - required Key key, - required Color backgroundColor, - required Widget child}) { - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - - final Color color = - dataGridSettings.dataGridThemeData!.currentCellStyle.borderColor; - final double borderWidth = - dataGridSettings.dataGridThemeData!.currentCellStyle.borderWidth; - - Border getBorder() { - final bool isCurrentCell = dataCell.isCurrentCell; - return Border( - bottom: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, - left: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, - top: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, - right: isCurrentCell - ? BorderSide(color: color, width: borderWidth) - : BorderSide.none, - ); - } - - double getCellHeight(DataCellBase dataCell, double defaultHeight) { - double height; - if (dataCell._rowSpan > 0) { - height = dataCell._dataRow!._getRowHeight( - dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); - } else { - height = defaultHeight; - } - return height; - } - - double getCellWidth(DataCellBase dataCell, double defaultWidth) { - double width; - if (dataCell._columnSpan > 0) { - width = dataCell._dataRow!._getColumnWidth( - dataCell.columnIndex, dataCell.columnIndex + dataCell._columnSpan); - } else { - width = defaultWidth; - } - return width; - } - - Widget getChild(BoxConstraints constraint) { - final double width = getCellWidth(dataCell, constraint.maxWidth); - final double height = getCellHeight(dataCell, constraint.maxHeight); - - if (dataCell.isCurrentCell) { - return Stack( - children: [ - Container(width: width, height: height, child: child), - Positioned( - left: 0, - top: 0, - width: width, - height: height, - child: IgnorePointer( - ignoring: true, - child: Container( - key: key, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - color: backgroundColor, border: getBorder())), - )), - ], - ); - } else { - return Container(width: width, height: height, child: child); - } - } - - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraint) { - return getChild(constraint); - }); -} - -Rect? _getCellClipRect(_DataGridSettings _dataGridSettings, - _VisibleLineInfo? lineInfo, double rowHeight) { - // FLUT-1971 Need to check whether the lineInfo is null or not. Because it - // will be null when load empty to the columns collection. - if (lineInfo == null) { - return null; - } - if (lineInfo.isClippedBody && - lineInfo.isClippedOrigin && - lineInfo.isClippedCorner) { - final double left = _dataGridSettings.textDirection == TextDirection.ltr - ? lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent - : lineInfo.clippedSize; - final double right = _dataGridSettings.textDirection == TextDirection.ltr - ? lineInfo.clippedSize - : lineInfo.clippedCornerExtent; - - return Rect.fromLTWH(left, 0.0, right, rowHeight); - } else if (lineInfo.isClippedBody && lineInfo.isClippedOrigin) { - final double left = _dataGridSettings.textDirection == TextDirection.ltr - ? lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent - : 0.0; - final double right = _dataGridSettings.textDirection == TextDirection.ltr - ? lineInfo.size - : lineInfo.size - lineInfo.scrollOffset; - - return Rect.fromLTWH(left, 0.0, right, rowHeight); - } else if (lineInfo.isClippedBody && lineInfo.isClippedCorner) { - final double left = _dataGridSettings.textDirection == TextDirection.ltr - ? 0.0 - : lineInfo.size - (lineInfo.size - lineInfo.clippedSize); - final double right = _dataGridSettings.textDirection == TextDirection.ltr - ? lineInfo.clippedSize - : lineInfo.size; - - return Rect.fromLTWH(left, 0.0, right, rowHeight); - } else { - return null; - } -} - -Rect? _getSpannedCellClipRect( - _DataGridSettings dataGridSettings, - DataRowBase dataRow, - DataCellBase dataCell, - double cellHeight, - double cellWidth) { - Rect? clipRect; - int firstVisibleStackedColumnIndex = dataCell.columnIndex; - double lastCellClippedSize = 0.0; - bool isLastCellClippedCorner = false; - bool isLastCellClippedBody = false; - - double getClippedWidth(DataCellBase dataCell, DataRowBase dataRow, - {bool columnsNotInViewWidth = false, bool allCellsClippedWidth = false}) { - final int startIndex = dataCell.columnIndex; - final int endIndex = dataCell.columnIndex + dataCell._columnSpan; - double clippedWidth = 0; - for (int index = startIndex; index <= endIndex; index++) { - final _VisibleLineInfo? newline = - dataRow._getColumnVisibleLineInfo(index); - if (columnsNotInViewWidth) { - if (newline == null) { - clippedWidth += - dataGridSettings.container.scrollColumns.getLineSize(index); - } else { - firstVisibleStackedColumnIndex = index; - break; - } - } - if (allCellsClippedWidth) { - if (newline != null) { - if (dataGridSettings.textDirection == TextDirection.ltr) { - clippedWidth += - newline.isClipped ? newline.clippedSize : newline.size; - } else { - clippedWidth += newline.isClipped - ? newline.clippedCornerExtent > 0 - ? newline.clippedCornerExtent - : newline.clippedSize - : newline.size; - } - lastCellClippedSize = newline.clippedSize; - isLastCellClippedCorner = newline.isClippedCorner; - isLastCellClippedBody = newline.isClippedBody; - } - } - } - return clippedWidth; - } - - if (dataGridSettings.frozenColumnsCount < 0 || - dataGridSettings.footerFrozenColumnsCount < 0) { - return null; - } - - if (dataCell._renderer != null) { - final double columnsNotInViewWidth = - getClippedWidth(dataCell, dataRow, columnsNotInViewWidth: true); - final double clippedWidth = - getClippedWidth(dataCell, dataRow, allCellsClippedWidth: true); - final _VisibleLineInfo? visibleLineInfo = - dataRow._getColumnVisibleLineInfo(firstVisibleStackedColumnIndex); - - if (visibleLineInfo != null) { - if (visibleLineInfo.isClippedOrigin && visibleLineInfo.isClippedCorner) { - final double clippedOrigin = columnsNotInViewWidth + - visibleLineInfo.size - - (visibleLineInfo.clippedSize + visibleLineInfo.clippedCornerExtent); - - final double left = dataGridSettings.textDirection == TextDirection.ltr - ? clippedOrigin - : visibleLineInfo.clippedSize; - final double right = dataGridSettings.textDirection == TextDirection.ltr - ? clippedWidth - : visibleLineInfo.clippedCornerExtent; - - clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); - } else if (visibleLineInfo.isClippedOrigin) { - final double clippedOriginLTR = columnsNotInViewWidth + - visibleLineInfo.size - - visibleLineInfo.clippedSize; - final double clippedOriginRTL = - (isLastCellClippedCorner && isLastCellClippedBody) - ? lastCellClippedSize - : 0.0; - - final double left = dataGridSettings.textDirection == TextDirection.ltr - ? clippedOriginLTR - : clippedOriginRTL; - final double right = dataGridSettings.textDirection == TextDirection.ltr - ? clippedWidth - : cellWidth - - (columnsNotInViewWidth + visibleLineInfo.scrollOffset); - - clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); - } else if (isLastCellClippedCorner && isLastCellClippedBody) { - final double left = dataGridSettings.textDirection == TextDirection.ltr - ? columnsNotInViewWidth - : dataCell.columnIndex < firstVisibleStackedColumnIndex - ? 0.0 - : cellWidth - clippedWidth; - final double right = dataGridSettings.textDirection == TextDirection.ltr - ? clippedWidth - : cellWidth; - - clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); - } else { - if (clippedWidth < cellWidth) { - double left; - if (dataCell.columnIndex < firstVisibleStackedColumnIndex) { - left = dataGridSettings.textDirection == TextDirection.ltr - ? cellWidth - clippedWidth - : 0.0; - } else { - left = dataGridSettings.textDirection == TextDirection.ltr - ? 0.0 - : cellWidth - clippedWidth; - } - - clipRect = Rect.fromLTWH(left, 0.0, clippedWidth, cellHeight); - } else if (clipRect != null) { - clipRect = null; - } - } - } - } - return clipRect; -} - -// Gesture Events - -void _handleOnTapUp( - {required TapUpDetails? tapUpDetails, - required TapDownDetails? tapDownDetails, - required DataCellBase dataCell, - required _DataGridSettings dataGridSettings, - required PointerDeviceKind kind}) { - // End edit the current editing cell if its editing mode is differed - if (dataGridSettings.currentCell.isEditing) { - if (dataGridSettings.currentCell._canSubmitCell(dataGridSettings)) { - dataGridSettings.currentCell - ._onCellSubmit(dataGridSettings, cancelCanSubmitCell: true); - } else { - return; - } - } - - if (dataGridSettings.onCellTap != null) { - final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapDownDetails != null - ? tapDownDetails.globalPosition - : tapUpDetails!.globalPosition, - localPosition: tapDownDetails != null - ? tapDownDetails.localPosition - : tapUpDetails!.localPosition, - kind: kind); - dataGridSettings.onCellTap!(details); - } - - dataGridSettings.dataGridFocusNode?.requestFocus(); - dataCell._onTouchUp(); - - // Init the editing based on the editing mode - if (dataGridSettings.editingGestureType == EditingGestureType.tap) { - dataGridSettings.currentCell._onCellBeginEdit(editingDataCell: dataCell); - } -} - -void _handleOnDoubleTap( - {required DataCellBase dataCell, - required _DataGridSettings dataGridSettings}) { - // End edit the current editing cell if its editing mode is differed - if (dataGridSettings.currentCell.isEditing) { - if (dataGridSettings.currentCell._canSubmitCell(dataGridSettings)) { - dataGridSettings.currentCell - ._onCellSubmit(dataGridSettings, cancelCanSubmitCell: true); - } else { - return; - } - } - - if (dataGridSettings.onCellDoubleTap != null) { - final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( - rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!); - dataGridSettings.onCellDoubleTap!(details); - } - - dataGridSettings.dataGridFocusNode?.requestFocus(); - dataCell._onTouchUp(); - - // Init the editing based on the editing mode - if (dataGridSettings.editingGestureType == EditingGestureType.doubleTap) { - dataGridSettings.currentCell._onCellBeginEdit(editingDataCell: dataCell); - } -} - -void _handleOnLongPressEnd( - {required LongPressEndDetails longPressEndDetails, - required DataCellBase dataCell, - required _DataGridSettings dataGridSettings}) { - // Need to end the editing cell when interacting with other tap gesture - if (dataGridSettings.currentCell.isEditing) { - if (dataGridSettings.currentCell._canSubmitCell(dataGridSettings)) { - dataGridSettings.currentCell - ._onCellSubmit(dataGridSettings, cancelCanSubmitCell: true); - } else { - return; - } - } - - if (dataGridSettings.onCellLongPress != null) { - final DataGridCellLongPressDetails details = DataGridCellLongPressDetails( - rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: longPressEndDetails.globalPosition, - localPosition: longPressEndDetails.localPosition, - velocity: longPressEndDetails.velocity); - dataGridSettings.onCellLongPress!(details); - } -} - -void _handleOnSecondaryTapUp( - {required TapUpDetails tapUpDetails, - required DataCellBase dataCell, - required _DataGridSettings dataGridSettings, - required PointerDeviceKind kind}) { - // Need to end the editing cell when interacting with other tap gesture - if (dataGridSettings.currentCell.isEditing) { - if (dataGridSettings.currentCell._canSubmitCell(dataGridSettings)) { - dataGridSettings.currentCell - ._onCellSubmit(dataGridSettings, cancelCanSubmitCell: true); - } else { - return; - } - } - - if (dataGridSettings.onCellSecondaryTap != null) { - final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapUpDetails.globalPosition, - localPosition: tapUpDetails.localPosition, - kind: kind); - dataGridSettings.onCellSecondaryTap!(details); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart deleted file mode 100644 index 972f1466f..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/grid_header_cell_widget.dart +++ /dev/null @@ -1,452 +0,0 @@ -part of datagrid; - -/// A widget which displays in the header cells. -class GridHeaderCell extends StatefulWidget { - /// Creates the [GridHeaderCell] for [SfDataGrid] widget. - const GridHeaderCell({ - required Key key, - required this.dataCell, - required this.backgroundColor, - required this.isDirty, - required this.child, - }) : super(key: key); - - /// Holds the information required to display the cell. - final DataCellBase dataCell; - - /// The [child] contained by the [GridCell]. - final Widget child; - - /// The color to paint behind the [child]. - final Color backgroundColor; - - /// Decides whether the [GridCell] should be refreshed when [SfDataGrid] is - /// rebuild. - final bool isDirty; - - @override - State createState() => _GridHeaderCellState(); -} - -class _GridHeaderCellState extends State { - DataGridSortDirection? _sortDirection; - Color _sortIconColor = Colors.transparent; - int _sortNumber = -1; - Color _sortNumberBackgroundColor = Colors.transparent; - Color _sortNumberTextColor = Colors.transparent; - late PointerDeviceKind _kind; - - void _handleOnTapUp(TapUpDetails tapUpDetails) { - final DataCellBase dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - // Clear editing when tap on the header cell - _clearEditing(dataGridSettings); - if (dataGridSettings.onCellTap != null) { - final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapUpDetails.globalPosition, - localPosition: tapUpDetails.localPosition, - kind: _kind); - dataGridSettings.onCellTap!(details); - } - - dataGridSettings.dataGridFocusNode?.requestFocus(); - if (dataGridSettings.sortingGestureType == SortingGestureType.tap) { - _sort(dataCell); - } - } - - void _handleOnDoubleTap() { - final DataCellBase dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - // Clear editing when tap on the header cell - _clearEditing(dataGridSettings); - if (dataGridSettings.onCellDoubleTap != null) { - final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( - rowColumnIndex: - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!); - dataGridSettings.onCellDoubleTap!(details); - } - - dataGridSettings.dataGridFocusNode?.requestFocus(); - if (dataGridSettings.sortingGestureType == SortingGestureType.doubleTap) { - _sort(dataCell); - } - } - - void _handleOnLongPressEnd(LongPressEndDetails longPressEndDetails) { - final DataCellBase dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - // Clear editing when tap on the header cell - _clearEditing(dataGridSettings); - if (dataGridSettings.onCellLongPress != null) { - final DataGridCellLongPressDetails details = DataGridCellLongPressDetails( - rowColumnIndex: - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: longPressEndDetails.globalPosition, - localPosition: longPressEndDetails.localPosition, - velocity: longPressEndDetails.velocity); - dataGridSettings.onCellLongPress!(details); - } - } - - void _handleOnSecondaryTapUp(TapUpDetails tapUpDetails) { - final DataCellBase dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - // Clear editing when tap on the header cell - _clearEditing(dataGridSettings); - if (dataGridSettings.onCellSecondaryTap != null) { - final DataGridCellTapDetails details = DataGridCellTapDetails( - rowColumnIndex: - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), - column: dataCell.gridColumn!, - globalPosition: tapUpDetails.globalPosition, - localPosition: tapUpDetails.localPosition, - kind: _kind); - dataGridSettings.onCellSecondaryTap!(details); - } - } - - void _handleOnTapDown(TapDownDetails details) { - _kind = details.kind!; - final DataCellBase dataCell = widget.dataCell; - final _DataGridSettings dataGridSettings = - dataCell._dataRow!._dataGridStateDetails!(); - // Clear editing when tap on the header cell - _clearEditing(dataGridSettings); - } - - /// Helps to clear the editing cell when tap on header cells - void _clearEditing(_DataGridSettings dataGridSettings) { - if (dataGridSettings.currentCell.isEditing) { - dataGridSettings.currentCell._onCellSubmit(dataGridSettings); - } - } - - Widget _wrapInsideGestureDetector() { - final _DataGridSettings dataGridSettings = - widget.dataCell._dataRow!._dataGridStateDetails!(); - return GestureDetector( - onTapUp: dataGridSettings.onCellTap != null || - dataGridSettings.sortingGestureType == SortingGestureType.tap - ? _handleOnTapUp - : null, - onTapDown: _handleOnTapDown, - onDoubleTap: dataGridSettings.onCellDoubleTap != null || - dataGridSettings.sortingGestureType == - SortingGestureType.doubleTap - ? _handleOnDoubleTap - : null, - onLongPressEnd: dataGridSettings.onCellLongPress != null - ? _handleOnLongPressEnd - : null, - onSecondaryTapUp: dataGridSettings.onCellSecondaryTap != null - ? _handleOnSecondaryTapUp - : null, - onSecondaryTapDown: _handleOnTapDown, - child: _wrapInsideContainer(), - ); - } - - Widget _wrapInsideContainer() { - final _DataGridSettings dataGridSettings = - widget.dataCell._dataRow!._dataGridStateDetails!(); - final GridColumn? column = widget.dataCell.gridColumn; - - Widget checkHeaderCellConstraints(Widget child) { - final double iconWidth = - dataGridSettings.columnSizer._getSortIconWidth(column!); - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - if (_sortDirection == null || constraints.maxWidth < iconWidth) { - return child; - } else { - return _getCellWithSortIcon(child); - } - }); - } - - _ensureSortIconVisiblity(column!, dataGridSettings); - - return Container( - key: widget.key, - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration(border: _getCellBorder(widget.dataCell)), - child: _wrapInsideCellContainer( - child: checkHeaderCellConstraints(widget.child), - dataCell: widget.dataCell, - key: widget.key!, - backgroundColor: widget.backgroundColor, - )); - } - - Color? _getHoverBackgroundColor() { - final _DataGridSettings dataGridSettings = - widget.dataCell._dataRow!._dataGridStateDetails!(); - - return dataGridSettings.dataGridThemeData!.headerHoverColor; - } - - @override - Widget build(BuildContext context) { - final Widget child = MouseRegion( - child: Material( - color: Colors.transparent, - child: InkWell( - focusColor: Colors.transparent, - mouseCursor: SystemMouseCursors.basic, - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - hoverColor: _getHoverBackgroundColor(), - onTap: () {}, - child: _wrapInsideGestureDetector(), - ), - ), - ); - - return _GridCellRenderObjectWidget( - key: widget.key, - dataCell: widget.dataCell, - isDirty: widget.isDirty, - child: child, - ); - } - - void _ensureSortIconVisiblity( - GridColumn column, _DataGridSettings? dataGridSettings) { - if (dataGridSettings != null) { - final SortColumnDetails? sortColumn = dataGridSettings - .source.sortedColumns - .firstWhereOrNull((SortColumnDetails sortColumn) => - sortColumn.name == column.columnName); - if (dataGridSettings.source.sortedColumns.isNotEmpty && - sortColumn != null) { - final int sortNumber = - dataGridSettings.source.sortedColumns.indexOf(sortColumn) + 1; - final bool isLight = - dataGridSettings.dataGridThemeData!.brightness == Brightness.light; - _sortDirection = sortColumn.sortDirection; - _sortIconColor = dataGridSettings.dataGridThemeData!.sortIconColor; - _sortNumberBackgroundColor = - isLight ? Colors.grey[350]! : Colors.grey[700]!; - _sortNumberTextColor = isLight ? Colors.black87 : Colors.white54; - if (dataGridSettings.source.sortedColumns.length > 1 && - dataGridSettings.showSortNumbers) { - _sortNumber = sortNumber; - } else { - _sortNumber = -1; - } - } else { - _sortDirection = null; - _sortNumber = -1; - } - } - } - - Widget _getCellWithSortIcon(Widget child) { - final List children = []; - - children.add(_SortIcon( - sortDirection: _sortDirection!, - sortIconColor: _sortIconColor, - )); - - if (_sortNumber != -1) { - children.add(_getSortNumber()); - } - - return Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Flexible( - child: Container(child: child), - ), - Container( - padding: const EdgeInsets.only(left: 4.0, right: 4.0), - child: Center(child: Row(children: children)), - ) - ]); - } - - Widget _getSortNumber() { - return Container( - width: 18, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: _sortNumberBackgroundColor, - ), - child: Center( - child: Text(_sortNumber.toString(), - style: TextStyle(fontSize: 12, color: _sortNumberTextColor)), - ), - ); - } - - void _sort(DataCellBase dataCell) { - final _DataGridSettings dataGridSettings = - widget.dataCell._dataRow!._dataGridStateDetails!(); - if (dataCell._dataRow?.rowType == RowType.headerRow && - dataCell._dataRow?.rowIndex == - _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - _makeSort(dataCell); - } - } - - void _makeSort(DataCellBase dataCell) { - final _DataGridSettings dataGridSettings = - widget.dataCell._dataRow!._dataGridStateDetails!(); - - //End-edit before perform sorting - if (dataGridSettings.currentCell.isEditing) { - dataGridSettings.currentCell - ._onCellSubmit(dataGridSettings, canRefresh: false); - } - - final GridColumn column = dataCell.gridColumn!; - - if (column.allowSorting && dataGridSettings.allowSorting) { - final String sortColumnName = column.columnName; - final bool allowMultiSort = dataGridSettings._isDesktop - ? (dataGridSettings.isControlKeyPressed && - dataGridSettings.allowMultiColumnSorting) - : dataGridSettings.allowMultiColumnSorting; - final DataGridSource source = dataGridSettings.source; - - final List sortedColumns = source.sortedColumns; - if (sortedColumns.isNotEmpty && allowMultiSort) { - SortColumnDetails? sortedColumn = sortedColumns.firstWhereOrNull( - (SortColumnDetails sortColumn) => - sortColumn.name == sortColumnName); - if (sortedColumn == null) { - final SortColumnDetails newSortColumn = SortColumnDetails( - name: sortColumnName, - sortDirection: DataGridSortDirection.ascending); - sortedColumns.add(newSortColumn); - } else { - if (sortedColumn.sortDirection == DataGridSortDirection.descending && - dataGridSettings.allowTriStateSorting) { - final SortColumnDetails? removedSortColumn = - sortedColumns.firstWhereOrNull((SortColumnDetails sortColumn) => - sortColumn.name == sortColumnName); - sortedColumns.remove(removedSortColumn); - } else { - sortedColumn = SortColumnDetails( - name: sortedColumn.name, - sortDirection: sortedColumn.sortDirection == - DataGridSortDirection.ascending - ? DataGridSortDirection.descending - : DataGridSortDirection.ascending); - final SortColumnDetails? removedSortColumn = - sortedColumns.firstWhereOrNull((SortColumnDetails sortColumn) => - sortColumn.name == sortedColumn!.name); - sortedColumns - ..remove(removedSortColumn) - ..add(sortedColumn); - } - } - } else { - SortColumnDetails? currentSortColumn = sortedColumns.firstWhereOrNull( - (SortColumnDetails sortColumn) => - sortColumn.name == sortColumnName); - if (sortedColumns.isNotEmpty && currentSortColumn != null) { - if (currentSortColumn.sortDirection == - DataGridSortDirection.descending && - dataGridSettings.allowTriStateSorting) { - sortedColumns.clear(); - } else { - currentSortColumn = SortColumnDetails( - name: currentSortColumn.name, - sortDirection: currentSortColumn.sortDirection == - DataGridSortDirection.ascending - ? DataGridSortDirection.descending - : DataGridSortDirection.ascending); - sortedColumns - ..clear() - ..add(currentSortColumn); - } - } else { - final SortColumnDetails sortColumn = SortColumnDetails( - name: sortColumnName, - sortDirection: DataGridSortDirection.ascending); - if (sortedColumns.isNotEmpty) { - sortedColumns - ..clear() - ..add(sortColumn); - } else { - sortedColumns.add(sortColumn); - } - } - } - // Refreshes the datagrid source and performs sorting based on - // `DataGridSource.sortedColumns`. - source.sort(); - } - } -} - -class _SortIcon extends StatefulWidget { - const _SortIcon({required this.sortDirection, required this.sortIconColor}); - final DataGridSortDirection sortDirection; - final Color sortIconColor; - @override - _SortIconState createState() => _SortIconState(); -} - -class _SortIconState extends State<_SortIcon> - with SingleTickerProviderStateMixin { - late AnimationController _animationController; - late Animation _sortingAnimation; - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - duration: const Duration(milliseconds: 150), - vsync: this, - ); - _sortingAnimation = Tween(begin: 0.0, end: pi).animate( - CurvedAnimation(parent: _animationController, curve: Curves.easeIn)); - if (widget.sortDirection == DataGridSortDirection.descending) { - _animationController.value = 1.0; - } - } - - @override - void didUpdateWidget(_SortIcon oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.sortDirection == DataGridSortDirection.ascending && - widget.sortDirection == DataGridSortDirection.descending) { - _animationController.forward(); - } else if (oldWidget.sortDirection == DataGridSortDirection.descending && - widget.sortDirection == DataGridSortDirection.ascending) { - _animationController.reverse(); - } - } - - @override - Widget build(BuildContext context) { - return Container( - child: AnimatedBuilder( - animation: _animationController, - builder: (BuildContext context, Widget? child) { - return Transform.rotate( - angle: _sortingAnimation.value, - child: Icon(Icons.arrow_upward, - color: widget.sortIconColor, size: 16)); - })); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart deleted file mode 100644 index f027d6629..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/cell_control/virtualizing_cells_widget.dart +++ /dev/null @@ -1,839 +0,0 @@ -part of datagrid; - -class _VirtualizingCellsWidget extends StatefulWidget { - const _VirtualizingCellsWidget( - {required Key key, required this.dataRow, required this.isDirty}) - : super(key: key); - - final DataRowBase dataRow; - final bool isDirty; - - @override - State createState() => _VirtualizingCellsWidgetState(); -} - -class _VirtualizingCellsWidgetState extends State<_VirtualizingCellsWidget> { - @override - Widget build(BuildContext context) { - final List children = []; - - if (widget.dataRow.rowType == RowType.footerRow) { - if (widget.dataRow._footerView != null) { - children.add(widget.dataRow._footerView!); - } - } else { - children.addAll(widget.dataRow._visibleColumns - .map((DataCellBase dataCell) => dataCell._columnElement!) - .toList(growable: false)); - } - - return _VirtualizingCellsRenderObjectWidget( - key: widget.key!, - dataRow: widget.dataRow, - isDirty: widget.isDirty, - children: List.from(children), - ); - } -} - -class _VirtualizingCellsRenderObjectWidget - extends MultiChildRenderObjectWidget { - _VirtualizingCellsRenderObjectWidget( - {required Key key, - required this.dataRow, - required this.isDirty, - required this.children}) - : super( - key: key, - children: RepaintBoundary.wrapAll(List.from(children))); - - @override - final List children; - final DataRowBase dataRow; - final bool isDirty; - - @override - _RenderVirtualizingCellsWidget createRenderObject(BuildContext context) => - _RenderVirtualizingCellsWidget(dataRow: dataRow, isDirty: isDirty); - - @override - void updateRenderObject( - BuildContext context, _RenderVirtualizingCellsWidget renderObject) { - super.updateRenderObject(context, renderObject); - renderObject - ..dataRow = dataRow - ..isDirty = isDirty; - } -} - -class _VirtualizingCellWidgetParentData - extends ContainerBoxParentData { - _VirtualizingCellWidgetParentData(); - - double width = 0.0; - double height = 0.0; - Rect? cellClipRect; - - void reset() { - width = 0.0; - height = 0.0; - offset = Offset.zero; - cellClipRect = null; - } -} - -class _RenderVirtualizingCellsWidget extends RenderBox - with - ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin - implements - MouseTrackerAnnotation { - _RenderVirtualizingCellsWidget( - {List? children, - required DataRowBase dataRow, - bool isDirty = false}) - : _dataRow = dataRow, - _isDirty = isDirty { - addAll(children); - } - - bool get isDirty => _isDirty; - bool _isDirty = false; - - set isDirty(bool newValue) { - _isDirty = newValue; - if (_isDirty) { - markNeedsLayout(); - markNeedsPaint(); - } - - dataRow._isDirty = false; - } - - DataRowBase get dataRow => _dataRow; - DataRowBase _dataRow; - - set dataRow(DataRowBase newValue) { - if (_dataRow == newValue) { - return; - } - - _dataRow = newValue; - markNeedsLayout(); - markNeedsPaint(); - } - - Rect rowRect = Rect.zero; - - Rect? rowClipRect; - - DataGridRowSwipeDirection? swipeDirection; - - // It's helps to find the difference of dy action between on [PointerDownEvent] - // and [PointerMoveEvent] event. - double dy = 0.0; - - Rect _measureRowRect(double width) { - if (dataRow.isVisible) { - final _DataGridSettings dataGridSettings = - dataRow._dataGridStateDetails!(); - final _VisualContainerHelper container = dataGridSettings.container; - - final _VisibleLineInfo? lineInfo = - container.scrollRows.getVisibleLineAtLineIndex(dataRow.rowIndex); - - final double lineSize = lineInfo != null ? lineInfo.size : 0.0; - double origin = (lineInfo != null) ? lineInfo.origin : 0.0; - - origin += container.verticalOffset; - - if (dataRow.rowIndex > - _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - final double headerRowsHeight = container.scrollRows - .rangeToRegionPoints( - 0, dataGridSettings.headerLineCount - 1, true)[1] - .length; - origin -= headerRowsHeight; - } - - // Clipping the column when frozen row applied - if (lineInfo != null && - (lineInfo.isClippedBody && lineInfo.isClippedOrigin)) { - final double top = - lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent; - if (dataGridSettings.allowSwiping && dataRow._isSwipingRow) { - rowClipRect = _getSwipingRowClipRect( - dataGridSettings: dataGridSettings, top: top, height: lineSize); - } else { - rowClipRect = Rect.fromLTWH(0, top, width, lineSize); - } - } else if (dataGridSettings.allowSwiping && dataRow._isSwipingRow) { - rowClipRect = _getSwipingRowClipRect( - dataGridSettings: dataGridSettings, top: 0.0, height: lineSize); - } else { - rowClipRect = null; - } - - rowRect = Rect.fromLTWH(0, origin, width, lineSize); - return rowRect; - } else { - return Rect.zero; - } - } - - Rect? _getSwipingRowClipRect( - {required _DataGridSettings dataGridSettings, - required double top, - required double height}) { - if (dataGridSettings.swipingAnimation == null) { - return null; - } - double leftPosition = 0.0; - final double viewWidth = dataGridSettings.viewWidth; - final double extentWidth = dataGridSettings.container.extentWidth; - final double swipingDelta = dataGridSettings.swipingOffset >= 0 - ? dataGridSettings.swipingAnimation!.value - : -dataGridSettings.swipingAnimation!.value; - - if (dataGridSettings.textDirection == TextDirection.rtl && - viewWidth > extentWidth) { - leftPosition = dataGridSettings.swipingOffset >= 0 - ? viewWidth - extentWidth - : (viewWidth - extentWidth) + swipingDelta; - } else { - leftPosition = dataGridSettings.swipingOffset >= 0 ? 0 : swipingDelta; - } - return Rect.fromLTWH(leftPosition, top, extentWidth - swipingDelta, height); - } - - Rect _getRowRect(_DataGridSettings dataGridSettings, Offset offset, - {bool isHoveredLayer = false}) { - bool needToSetMaxConstraint() => - dataGridSettings.container.extentWidth < dataGridSettings.viewWidth && - dataGridSettings.textDirection == TextDirection.rtl; - - final Rect rect = Rect.fromLTWH( - needToSetMaxConstraint() - ? constraints.maxWidth - - min(dataGridSettings.container.extentWidth, - dataGridSettings.viewWidth) - - (offset.dx + dataGridSettings.container.horizontalOffset) - : offset.dx + dataGridSettings.container.horizontalOffset, - offset.dy, - needToSetMaxConstraint() - ? constraints.maxWidth - : min(dataGridSettings.container.extentWidth, - dataGridSettings.viewWidth), - (isHoveredLayer && - dataRow._isHoveredRow && - (dataGridSettings.gridLinesVisibility == - GridLinesVisibility.horizontal || - dataGridSettings.gridLinesVisibility == - GridLinesVisibility.both)) - ? constraints.maxHeight - - dataGridSettings.dataGridThemeData!.gridLineStrokeWidth - : constraints.maxHeight); - - return rect; - } - - void _drawRowBackground(_DataGridSettings dataGridSettings, - PaintingContext context, Offset offset) { - final Rect rect = _getRowRect(dataGridSettings, offset); - Color? backgroundColor; - - Color getDefaultRowBackgroundColor() { - return dataGridSettings.dataGridThemeData!.brightness == Brightness.light - ? const Color.fromRGBO(255, 255, 255, 1) - : const Color.fromRGBO(33, 33, 33, 1); - } - - void drawSpannedRowBackgroundColor(Color backgroundColor) { - final bool isRowSpanned = dataRow._visibleColumns - .any((DataCellBase dataCell) => dataCell._rowSpan > 0); - - if (isRowSpanned) { - RenderBox? child = lastChild; - while (child != null && child is _RenderGridCell) { - final _VirtualizingCellWidgetParentData childParentData = - child.parentData! as _VirtualizingCellWidgetParentData; - final DataCellBase dataCell = child.dataCell; - final _VisibleLineInfo? lineInfo = - dataRow._getColumnVisibleLineInfo(dataCell.columnIndex); - if (dataCell._rowSpan > 0 && lineInfo != null) { - final Rect? columnRect = child.columnRect; - final Rect? cellClipRect = child.cellClipRect; - final double height = dataRow._getRowHeight( - dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); - Rect cellRect = Rect.zero; - if (cellClipRect != null) { - double left = columnRect!.left; - double width = cellClipRect.width; - if (cellClipRect.left > 0 && columnRect.width <= width) { - left += cellClipRect.left; - width = columnRect.width - cellClipRect.left; - } else if (cellClipRect.left > 0 && width < columnRect.width) { - left += cellClipRect.left; - } - - cellRect = Rect.fromLTWH(left, columnRect.top, width, height); - } else { - cellRect = Rect.fromLTWH( - columnRect!.left, columnRect.top, columnRect.width, height); - } - dataGridSettings.gridPaint?.color = backgroundColor; - context.canvas.drawRect(cellRect, dataGridSettings.gridPaint!); - } - child = childParentData.previousSibling; - } - } - } - - if (dataGridSettings.gridPaint != null) { - dataGridSettings.gridPaint!.style = PaintingStyle.fill; - - if (dataRow.rowRegion == RowRegion.header && - dataRow.rowType == RowType.headerRow || - dataRow.rowType == RowType.stackedHeaderRow) { - backgroundColor = dataGridSettings.dataGridThemeData!.headerColor; - drawSpannedRowBackgroundColor(backgroundColor); - } else if (dataRow.rowType == RowType.footerRow) { - backgroundColor = getDefaultRowBackgroundColor(); - } else { - /// Need to check the rowStyle Please look the previous version and - /// selection preference - backgroundColor = dataRow.isSelectedRow - ? dataGridSettings.dataGridThemeData!.selectionColor - : dataRow._dataGridRowAdapter!.color; - } - - // Default theme color are common for both the HeaderBackgroundColor and - // CellBackgroundColor, so we have checked commonly at outside of the - // condition - backgroundColor ??= getDefaultRowBackgroundColor(); - - dataGridSettings.gridPaint?.color = backgroundColor; - context.canvas.drawRect(rect, dataGridSettings.gridPaint!); - } - } - - void _drawCurrentRowBorder(PaintingContext context, Offset offset) { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - - if (dataGridSettings.boxPainter != null && - dataGridSettings.selectionMode == SelectionMode.multiple && - dataGridSettings.navigationMode == GridNavigationMode.row && - dataGridSettings.currentCell.rowIndex == dataRow.rowIndex) { - bool needToSetMaxConstraint() => - dataGridSettings.container.extentWidth < dataGridSettings.viewWidth && - dataGridSettings.textDirection == TextDirection.rtl; - - const double stokeWidth = 1; - final int origin = (stokeWidth / 2 + - dataGridSettings.dataGridThemeData!.gridLineStrokeWidth) - .ceil(); - - final Rect rowRect = _getRowRect(dataGridSettings, offset); - final double maxWidth = needToSetMaxConstraint() - ? rowRect.width - rowRect.left - : rowRect.right - rowRect.left; - - final bool isHorizontalGridLinesEnabled = - dataGridSettings.gridLinesVisibility == GridLinesVisibility.both || - dataGridSettings.gridLinesVisibility == - GridLinesVisibility.horizontal; - - dataGridSettings.boxPainter!.paint( - context.canvas, - Offset(rowRect.left + origin, rowRect.top + (origin / 2)), - dataGridSettings.configuration!.copyWith( - size: Size( - maxWidth - (origin * 2), - constraints.maxHeight - - (origin * (isHorizontalGridLinesEnabled ? 1.5 : 1))))); - } - } - - @override - void setupParentData(RenderObject child) { - super.setupParentData(child); - if (child.parentData != null && - child.parentData != _VirtualizingCellWidgetParentData()) { - child.parentData = _VirtualizingCellWidgetParentData(); - } - } - - void _handleSwipingListener() { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - dataGridSettings.source - ._notifyDataGridPropertyChangeListeners(propertyName: 'Swiping'); - } - - @override - void attach(PipelineOwner owner) { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - dataGridSettings.swipingAnimationController - ?.addListener(_handleSwipingListener); - super.attach(owner); - } - - @override - void detach() { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - dataGridSettings.swipingAnimationController - ?.removeListener(_handleSwipingListener); - - super.detach(); - } - - @override - bool get isRepaintBoundary => true; - - @override - bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { - RenderBox? child = lastChild; - while (child != null) { - final _VirtualizingCellWidgetParentData childParentData = - child.parentData! as _VirtualizingCellWidgetParentData; - final bool isHit = result.addWithPaintOffset( - offset: childParentData.offset, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) { - if (child is _RenderGridCell && - child.cellClipRect != null && - !child.cellClipRect!.contains(transformed)) { - return false; - } - - return child!.hitTest(result, position: transformed); - }, - ); - - if (isHit) { - return true; - } - child = childParentData.previousSibling; - } - return false; - } - - @override - bool hitTest(BoxHitTestResult result, {required Offset position}) { - final bool isRowSpanned = dataRow._visibleColumns - .any((DataCellBase dataCell) => dataCell._rowSpan > 0); - - if (isRowSpanned) { - RenderBox? child = lastChild; - while (child != null) { - final _VirtualizingCellWidgetParentData childParentData = - child.parentData! as _VirtualizingCellWidgetParentData; - if (child is _RenderGridCell && - child.columnRect != null && - child.columnRect!.contains(position)) { - // Need to resolve the position when dataCell has row span. - if (child.dataCell._rowSpan > 0) { - final DataCellBase dataCell = child.dataCell; - final double cellActualHeight = dataCell._dataRow! - ._getRowHeight(dataCell.rowIndex, dataCell.rowIndex); - final double cellTotalHeight = dataCell._dataRow!._getRowHeight( - dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex); - - final double resolvedXPosition = child.globalToLocal(position).dx; - final double resolvedYPosition = - cellTotalHeight + (position.dy - cellActualHeight); - - return child.hitTest(result, - position: Offset(resolvedXPosition, resolvedYPosition)); - } - return super.hitTest(result, position: position); - } - child = childParentData.previousSibling; - } - } - - return super.hitTest(result, position: position); - } - - void _updateSwipingAnimation(_DataGridSettings dataGridSettings) { - dataGridSettings.swipingAnimation = Tween( - begin: 0.0, - end: dataGridSettings.swipingOffset.sign >= 0 - ? dataGridSettings.swipeMaxOffset - : -dataGridSettings.swipeMaxOffset) - .animate(dataGridSettings.swipingAnimationController!); - } - - void _handleSwipeStart( - PointerDownEvent event, _DataGridSettings dataGridSettings) { - // Need to reset the swiping and scrolling state to default when pointer - // up and touch again - dataGridSettings.isSwipingApplied = false; - dataGridSettings.scrollingState = ScrollDirection.idle; - swipeDirection = null; - - // Need to check whether tap action placed on another [DataGridRow] - // instead of swiped [DataGridRow]. - // - // If its tapped on the same swiped [DataGridRow], we don't do anything. - // If it's tapped on different [DataGridRow] or scrolled, we need to end - // the swiping. - final DataRowBase? swipedRow = dataGridSettings.rowGenerator.items - .firstWhereOrNull((DataRowBase row) => - row._isSwipingRow && dataGridSettings.swipingOffset.abs() > 0); - - if (swipedRow != null && swipedRow.rowIndex != dataRow.rowIndex) { - dataGridSettings.container - .resetSwipeOffset(swipedRow: swipedRow, canUpdate: true); - dataGridSettings.swipingOffset = event.localDelta.dx; - dy = event.localDelta.dy; - } - } - - void _handleSwipeUpdate( - PointerMoveEvent event, _DataGridSettings dataGridSettings) { - final double currentSwipingDelta = - dataGridSettings.swipingOffset + event.localDelta.dx; - dy = dy - event.localDelta.dy; - - // If it's fling or scrolled, we have to ignore the swiping action - if (!dataGridSettings.isSwipingApplied && - (dataGridSettings.scrollingState == ScrollDirection.forward || - dy.abs() > 3)) { - dataGridSettings.isSwipingApplied = false; - return; - } - - final ScrollController horizontalController = - dataGridSettings.horizontalScrollController!; - - /// Swipe must to happen when it's reach the max and min scroll extend. - if (currentSwipingDelta > 2) { - if (dataGridSettings.container.horizontalOffset == - horizontalController.position.minScrollExtent && - swipeDirection == null) { - swipeDirection = _SfDataGridHelper.getSwipeDirection( - dataGridSettings, currentSwipingDelta); - // Resricted the continuous swiping of both directions by dragging. - } else if (swipeDirection == DataGridRowSwipeDirection.endToStart) { - swipeDirection = null; - } - } else if (currentSwipingDelta < -2) { - if (dataGridSettings.container.horizontalOffset == - horizontalController.position.maxScrollExtent && - swipeDirection == null) { - swipeDirection = _SfDataGridHelper.getSwipeDirection( - dataGridSettings, currentSwipingDelta); - // Resricted the continuous swiping of both directions by dragging. - } else if (swipeDirection == DataGridRowSwipeDirection.startToEnd) { - swipeDirection = null; - } - } - - if (swipeDirection != null && - _SfDataGridHelper.canSwipeRow( - dataGridSettings, swipeDirection!, currentSwipingDelta)) { - bool canStartSwiping = true; - final double oldSwipingDelta = dataGridSettings.swipingOffset; - final int rowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, dataRow.rowIndex); - - // Need to skip the [onSwipeStart] callback when swiping is applied. - if (dataGridSettings.onSwipeStart != null && - !dataGridSettings.isSwipingApplied) { - final DataGridSwipeStartDetails swipeStartDetails = - DataGridSwipeStartDetails( - rowIndex: rowIndex, swipeDirection: swipeDirection!); - canStartSwiping = dataGridSettings.onSwipeStart!(swipeStartDetails); - } - - if (canStartSwiping) { - dataGridSettings.isSwipingApplied = true; - if (dataGridSettings.onSwipeUpdate != null) { - final DataGridSwipeUpdateDetails swipeUpdateDetails = - DataGridSwipeUpdateDetails( - rowIndex: rowIndex, - swipeDirection: swipeDirection!, - swipeOffset: currentSwipingDelta); - canStartSwiping = dataGridSettings.onSwipeUpdate!(swipeUpdateDetails); - } - - if (!canStartSwiping) { - return; - } - - if (dataGridSettings.swipingAnimationController!.isAnimating) { - return; - } - - if (currentSwipingDelta >= dataGridSettings.swipeMaxOffset) { - dataGridSettings.swipingOffset = dataGridSettings.swipeMaxOffset; - } else { - dataGridSettings.swipingOffset += event.localDelta.dx; - } - - dataRow._isSwipingRow = true; - - if (oldSwipingDelta.sign != currentSwipingDelta.sign) { - _updateSwipingAnimation(dataGridSettings); - } - if (!dataGridSettings.swipingAnimationController!.isAnimating) { - dataGridSettings.swipingAnimationController!.value = - dataGridSettings.swipingOffset.abs() / - dataGridSettings.swipeMaxOffset; - } - } else { - dataGridSettings.container.resetSwipeOffset(canUpdate: true); - } - } - } - - void _handleSwipeEnd( - PointerUpEvent event, _DataGridSettings dataGridSettings) { - void _onSwipeEnd() { - if (dataGridSettings.onSwipeEnd != null) { - final int rowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, dataRow.rowIndex); - final DataGridRowSwipeDirection swipeDirection = - _SfDataGridHelper.getSwipeDirection( - dataGridSettings, dataGridSettings.swipingOffset); - final DataGridSwipeEndDetails swipeEndDetails = DataGridSwipeEndDetails( - rowIndex: rowIndex, swipeDirection: swipeDirection); - dataGridSettings.onSwipeEnd!(swipeEndDetails); - } - } - - if (dataRow._isSwipingRow) { - if (dataGridSettings.swipingAnimationController!.isAnimating) { - return; - } - - dataGridSettings.isSwipingApplied = false; - if (dataGridSettings.swipingOffset.abs() > - dataGridSettings.swipeMaxOffset / 2) { - dataGridSettings.swipingOffset = dataGridSettings.swipingOffset >= 0 - ? dataGridSettings.swipeMaxOffset - : -dataGridSettings.swipeMaxOffset; - dataGridSettings.swipingAnimationController! - .forward() - .then((_) => _onSwipeEnd()); - } else { - if (dataGridSettings.swipingOffset.abs() < - dataGridSettings.swipeMaxOffset) { - dataGridSettings.swipingAnimationController!.reverse().then((_) { - _onSwipeEnd(); - dataGridSettings.container.resetSwipeOffset(swipedRow: dataRow); - }); - } - } - } - - dy = 0.0; - dataGridSettings.scrollingState = ScrollDirection.idle; - } - - void _handleSwiping(PointerEvent event) { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - if (dataGridSettings.allowSwiping && dataRow.rowType == RowType.dataRow) { - if (event is PointerDownEvent) { - _handleSwipeStart(event, dataGridSettings); - } - if (event is PointerMoveEvent) { - _handleSwipeUpdate(event, dataGridSettings); - } - if (event is PointerUpEvent) { - _handleSwipeEnd(event, dataGridSettings); - } - } - } - - @override - void handleEvent(PointerEvent event, BoxHitTestEntry entry) { - super.handleEvent(event, entry); - _handleSwiping(event); - } - - @override - void performLayout() { - void _layout( - {required RenderBox child, - required double width, - required double height}) { - child.layout(BoxConstraints.tightFor(width: width, height: height), - parentUsesSize: true); - } - - RenderBox? child = firstChild; - while (child != null) { - final _VirtualizingCellWidgetParentData _parentData = - child.parentData! as _VirtualizingCellWidgetParentData; - if (dataRow.isVisible && - child is _RenderGridCell && - child.dataCell.isVisible) { - final Rect columnRect = - child._measureColumnRect(constraints.maxHeight)!; - size = constraints.constrain(Size(columnRect.width, columnRect.height)); - _parentData - ..width = columnRect.width - ..height = columnRect.height - ..cellClipRect = child.cellClipRect; - _layout( - child: child, width: _parentData.width, height: _parentData.height); - _parentData.offset = Offset(columnRect.left, columnRect.top); - } else { - if (dataRow.rowType == RowType.footerRow) { - final _DataGridSettings dataGridSettings = - dataRow._dataGridStateDetails!(); - final Rect cellRect = Rect.fromLTWH( - dataGridSettings.container.horizontalOffset, - 0.0, - dataGridSettings.viewWidth, - dataGridSettings.footerHeight); - - size = constraints.constrain(Size(cellRect.width, cellRect.height)); - _parentData - ..width = cellRect.width - ..height = cellRect.height - ..offset = Offset(cellRect.left, cellRect.top); - _layout( - child: child, - width: _parentData.width, - height: _parentData.height); - } else { - size = constraints.constrain(Size.zero); - child.layout(const BoxConstraints.tightFor(width: 0, height: 0)); - _parentData.reset(); - } - } - child = _parentData.nextSibling; - } - } - - void _drawRowHoverBackground(_DataGridSettings dataGridSettings, - PaintingContext context, Offset offset) { - if (dataGridSettings._isDesktop && - dataGridSettings.highlightRowOnHover && - dataRow._isHoveredRow) { - dataGridSettings.gridPaint?.color = - dataGridSettings.dataGridThemeData!.rowHoverColor; - context.canvas.drawRect( - _getRowRect(dataGridSettings, offset, isHoveredLayer: true), - dataGridSettings.gridPaint!); - } - } - - @override - void paint(PaintingContext context, Offset offset) { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - - // Remove the below method if the mentioned report has resolved - // form framework side - // https://github.com/flutter/flutter/issues/29702 - _drawRowBackground(dataGridSettings, context, offset); - - _drawRowHoverBackground(dataGridSettings, context, offset); - - RenderBox? child = firstChild; - while (child != null) { - final _VirtualizingCellWidgetParentData childParentData = - child.parentData! as _VirtualizingCellWidgetParentData; - if (childParentData.width != 0.0 && childParentData.height != 0.0) { - if (childParentData.cellClipRect != null) { - context.pushClipRect( - needsCompositing, - childParentData.offset + offset, - childParentData.cellClipRect!, - (PaintingContext context, Offset offset) { - context.paintChild(child!, offset); - }, - clipBehavior: Clip.antiAlias, - ); - } else { - context.paintChild(child, childParentData.offset + offset); - } - } - child = childParentData.nextSibling; - } - if (dataGridSettings._isDesktop) { - _drawCurrentRowBorder(context, offset); - } - } - - @override - MouseCursor get cursor => MouseCursor.defer; - - @override - PointerEnterEventListener? get onEnter { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - if (dataGridSettings.highlightRowOnHover && - dataGridSettings._isDesktop && - dataRow.rowType == RowType.dataRow) { - dataRow._isHoveredRow = true; - - final TextStyle rowStyle = - dataGridSettings.dataGridThemeData!.brightness == Brightness.light - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Colors.black87) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)); - if (dataGridSettings.dataGridThemeData!.rowHoverTextStyle != rowStyle) { - dataRow._rowIndexChanged(); - dataGridSettings.source._notifyDataGridPropertyChangeListeners( - propertyName: 'hoverOnCell'); - } - markNeedsPaint(); - } - } - - @override - PointerExitEventListener? get onExit { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - if (dataGridSettings.highlightRowOnHover && - dataGridSettings._isDesktop && - dataRow.rowType == RowType.dataRow) { - dataRow._isHoveredRow = false; - - final TextStyle rowStyle = - dataGridSettings.dataGridThemeData!.brightness == Brightness.light - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Colors.black87) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)); - if (dataGridSettings.dataGridThemeData!.rowHoverTextStyle != rowStyle) { - dataRow._rowIndexChanged(); - dataGridSettings.source._notifyDataGridPropertyChangeListeners( - propertyName: 'hoverOnCell'); - } - markNeedsPaint(); - } - } - - @override - bool get validForMouseTracker { - final _DataGridSettings dataGridSettings = dataRow._dataGridStateDetails!(); - if (dataGridSettings.highlightRowOnHover && - dataRow.rowType == RowType.dataRow) { - return true; - } - return false; - } -} - -class _HeaderCellsWidget extends _VirtualizingCellsWidget { - const _HeaderCellsWidget( - {required Key key, required DataRowBase dataRow, bool isDirty = false}) - : super(key: key, dataRow: dataRow, isDirty: isDirty); -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart deleted file mode 100644 index 3fdb7302c..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/datagrid_datasource.dart +++ /dev/null @@ -1,893 +0,0 @@ -part of datagrid; - -/// Signature for the [DataGridSourceChangeNotifier] listener. -typedef _DataGridSourceListener = void Function( - {RowColumnIndex? rowColumnIndex}); - -/// Signature for the [DataGridSourceChangeNotifier] listener. -typedef _DataGridPropertyChangeListener = void Function( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight}); - -/// A datasource for obtaining the row data for the [SfDataGrid]. -/// -/// The following APIs are mandatory to process the data, -/// * [rows] - The number of rows in a datagrid and row selection depends -/// on the [rows]. So, set the collection required for datagrid in -/// [rows]. -/// * [buildRow] - The data needed for the cells is obtained from -/// [buildRow]. -/// -/// Call the [notifyDataSourceListeners] when performing CRUD in the underlying -/// datasource. -/// -/// [DataGridSource] objects are expected to be long-lived, not recreated with -/// each build. -/// ``` dart -/// final List _employees = []; -/// -/// class EmployeeDataSource extends DataGridSource { -/// @override -/// List get rows => _employees -/// .map((dataRow) => DataGridRow(cells: [ -/// DataGridCell(columnName: 'id', value: dataRow.id), -/// DataGridCell(columnName: 'name', value: dataRow.name), -/// DataGridCell( -/// columnName: 'designation', value: dataRow.designation), -/// DataGridCell(columnName: 'salary', value: dataRow.salary), -/// ])) -/// .toList(); -/// -/// @override -/// DataGridRowAdapter? buildRow(DataGridRow row) { -/// return DataGridRowAdapter( -/// cells: row.getCells().map((dataCell) { -/// return Text(dataCell.value.toString()); -/// }).toList()); -/// } -/// } -/// ``` - -abstract class DataGridSource extends DataGridSourceChangeNotifier - with DataPagerDelegate { - /// The collection of rows to display in [SfDataGrid]. - /// - /// This must be non-null, but may be empty. - List get rows => List.empty(); - - /// Called to obtain the widget for each cell of the row. - /// - /// This method will be called for every row that are visible in datagrid’s - /// view port from the collection which is assigned to [DataGridSource.rows] - /// property. - /// - /// Return the widgets in the order in which those should be displayed in - /// each column of a row in [DataGridRowAdapter.cells]. - /// - /// The number of widgets in the collection must be exactly as many cells - /// as [SfDataGrid.columns] in the [SfDataGrid]. - /// - /// This method will be called whenever you call the [notifyListeners] method. - DataGridRowAdapter? buildRow(DataGridRow row); - - List _effectiveRows = []; - - List _unSortedRows = []; - - /// Called whenever you call [notifyListeners] or [notifyDataSourceListeners] - /// in the DataGridSource class. If you want to recalculate all columns - /// width (may be when underlying data gets changed), return true. - /// - /// Returning true may impact performance as the column widths are - /// recalculated again (whenever the notifyListeners is called). - /// - /// If you are aware that column widths are going to be same whenever - /// underlying data changes, return 'false' from this method. - /// - /// Note: Column widths will be recalculated automatically whenever a new - /// instance of DataGridSource is assigned to SfDataGrid. - /// ``` dart - /// class EmployeeDataSource extends DataGridSource { - /// @override - /// List get rows => _employees - /// .map((dataRow) => DataGridRow(cells: [ - /// DataGridCell(columnName: 'id', value: dataRow.id), - /// DataGridCell(columnName: 'name', value: dataRow.name), - /// DataGridCell( - /// columnName: 'designation', value: dataRow.designation), - /// DataGridCell(columnName: 'salary', value: dataRow.salary), - /// ])) - /// .toList(); - /// - /// @override - /// bool shouldRecalculateColumnWidths() { - /// return true; - /// } - /// - /// @override - /// DataGridRowAdapter? buildRow(DataGridRow row) { - /// return DataGridRowAdapter( - /// cells: row.getCells().map((dataCell) { - /// return Text(dataCell.value.toString()); - /// }).toList()); - /// } - /// } - /// - /// ``` - @protected - bool shouldRecalculateColumnWidths() { - return false; - } - - /// The collection of [SortColumnDetails] objects to sort the columns in - /// [SfDataGrid]. - /// - /// You can use this property to sort the columns programmatically also. - /// Call [sort] method after you added the column details in [sortedColumns] - /// programmatically. - /// - /// The following example show how to sort the columns on datagrid loading, - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar( - /// title: const Text('Syncfusion Flutter DataGrid'), - /// ), - /// body: Column( - /// children: [ - /// TextButton( - /// child: Text('Click'), - /// onPressed: () { - /// _employeeDataSource.sortedColumns - /// .add(SortColumnDetails('id', SortDirection.ascending)); - /// _employeeDataSource.sort(); - /// }, - /// ), - /// SfDataGrid( - /// source: _employeeDataSource, - /// allowSorting: true, - /// columns: [ - /// GridColumn(columnName: 'id', label: Text('ID')), - /// GridColumn(columnName: 'name', label: Text('Name')), - /// GridColumn(columnName: 'designation', label: Text('Designation')), - /// GridColumn(columnName: 'salary', label: Text('Salary')), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } - ///``` - /// See also: - /// - /// * [SfDataGrid.allowSorting] – which allows users to sort the columns in - /// [SfDataGrid]. - /// * [GridColumn.allowSorting] - which allows users to sort the corresponding - /// column in [SfDataGrid]. - /// * [DataGridSource.sort] - call this method when you are adding the - /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. - List get sortedColumns => _sortedColumns; - final List _sortedColumns = []; - - /// Called when the sorting is applied to the column. - /// - /// Overriding this method gives complete control over sorting. You can handle - /// the sorting completely in your own way. The rows argument provides the - /// unsorted [DataGridRow] collection. - /// - /// You can apply the sorting to rows argument. DataGrid will render the rows - /// based on the [rows] argument. You don’t need to call [notifyListeners] - /// within this method. However, you must override this method only if you - /// want to write the entire sorting logic by yourself. Otherwise, for custom - /// comparison, you can just override [DataGridSource.compare] method and - /// return the custom sorting order. - /// - /// For most of your cases, the 'compare' method should be sufficient. - /// The [DataGridSource.compare] method can be used to do custom sorting based - /// on the length of the text, case insensitive sorting, and so on. - /// - /// See also, - /// - /// [DataGridSource.compare] – To write the custom sorting for most of the use - /// cases. - @protected - void performSorting(List rows) { - if (sortedColumns.isEmpty) { - return; - } - rows.sort((DataGridRow a, DataGridRow b) { - return _compareValues(sortedColumns, a, b); - }); - } - - int _compareValues( - List sortedColumns, DataGridRow a, DataGridRow b) { - if (sortedColumns.length > 1) { - for (int i = 0; i < sortedColumns.length; i++) { - final SortColumnDetails sortColumn = sortedColumns[i]; - final int compareResult = compare(a, b, sortColumn); - if (compareResult != 0) { - return compareResult; - } else { - final List remainingSortColumns = sortedColumns - .skipWhile((SortColumnDetails value) => value == sortColumn) - .toList(growable: false); - return _compareValues(remainingSortColumns, a, b); - } - } - } - final SortColumnDetails sortColumn = sortedColumns.last; - return compare(a, b, sortColumn); - } - - /// Called when the sorting is applied for column. This method compares the - /// two objects and returns the order either they are equal, or one is - /// greater than or lesser than the other. - /// - /// You can return the following values, - /// * a negative integer if a is smaller than b, - /// * zero if a is equal to b, and - /// * a positive integer if a is greater than b. - /// - /// You can override this method and do the custom sorting based - /// on your requirement. Here [sortColumn] provides the details about the - /// column which is currently sorted with the sort direction. You can get the - /// currently sorted column and do the custom sorting for specific column. - /// - /// - /// The below example shows how to sort the `name` column based on the case - /// insensitive in ascending or descending order. - /// - /// ```dart - /// class EmployeeDataSource extends DataGridSource { - /// @override - /// List get rows => _employees - /// .map((dataRow) => DataGridRow(cells: [ - /// DataGridCell(columnName: 'id', value: dataRow.id), - /// DataGridCell(columnName: 'name', value: dataRow.name), - /// DataGridCell( - /// columnName: 'designation', value: dataRow.designation), - /// DataGridCell(columnName: 'salary', value: dataRow.salary), - /// ])) - /// .toList(); - /// - /// @override - /// DataGridRowAdapter? buildRow(DataGridRow row) { - /// return DataGridRowAdapter( - /// cells: row.getCells().map((dataCell) { - /// return Text(dataCell.value.toString()); - /// }).toList()); - /// } - /// - /// @override - /// int compare(DataGridRow? a, DataGridRow? b, SortColumnDetails sortColumn) { - /// if (sortColumn.name == 'name') { - /// final String? valueA = a - /// ?.getCells() - /// .firstWhereOrNull((dataCell) => dataCell.columnName == 'name') - /// ?.value; - /// final String? valueB = b - /// ?.getCells() - /// .firstWhereOrNull((dataCell) => dataCell.columnName == 'name') - /// ?.value; - /// - /// if (valueA == null || valueB == null) { - /// return 0; - /// } - /// - /// if (sortColumn.sortDirection == DataGridSortDirection.ascending) { - /// return valueA.toLowerCase().compareTo(valueB.toLowerCase()); - /// } else { - /// return valueA.toLowerCase().compareTo(valueB.toLowerCase()); - /// } - /// } - /// - /// return super.compare(a, b, sortColumn); - /// } - /// - /// ``` - @protected - int compare(DataGridRow? a, DataGridRow? b, SortColumnDetails sortColumn) { - Object? _getCellValue(List? cells, String columnName) { - return cells - ?.firstWhereOrNull( - (DataGridCell element) => element.columnName == columnName) - ?.value; - } - - final Object? valueA = _getCellValue(a?.getCells(), sortColumn.name); - final Object? valueB = _getCellValue(b?.getCells(), sortColumn.name); - return _compareTo(valueA, valueB, sortColumn.sortDirection); - } - - int _compareTo( - dynamic value1, dynamic value2, DataGridSortDirection sortDirection) { - if (sortDirection == DataGridSortDirection.ascending) { - if (value1 == null) { - return -1; - } else if (value2 == null) { - return 1; - } - return value1.compareTo(value2) as int; - } else { - if (value1 == null) { - return 1; - } else if (value2 == null) { - return -1; - } - return value2.compareTo(value1) as int; - } - } - - void _updateDataSource() { - if (sortedColumns.isNotEmpty) { - _unSortedRows = rows.toList(); - _effectiveRows = _unSortedRows; - } else { - _effectiveRows = rows; - } - // Should refresh sorting when the data grid source is updated. - performSorting(_effectiveRows); - } - - /// Call this method when you are adding the [SortColumnDetails] - /// programmatically to the [DataGridSource.sortedColumns]. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar( - /// title: const Text('Syncfusion Flutter DataGrid'), - /// ), - /// body: Column( - /// children: [ - /// FlatButton( - /// child: Text('Click'), - /// onPressed: () { - /// _employeeDataSource.sortedColumns - /// .add(SortColumnDetails('id', SortDirection.ascending)); - /// _employeeDataSource.sort(); - /// }, - /// ), - /// SfDataGrid( - /// source: _employeeDataSource, - /// allowSorting: true, - /// columns: [ - /// GridColumn(columnName: 'id', label:Text('ID')), - /// GridColumn(columnName: 'name', label:Text('Name')), - /// GridColumn(columnName: 'designation', label: Text('Designation')), - /// GridColumn(columnName: 'salary', label: Text('Salary')), - /// ], - /// ), - /// ], - /// ), - /// ); - /// } - /// ``` - void sort() { - _updateDataSource(); - _notifyDataGridPropertyChangeListeners(propertyName: 'Sorting'); - } - - /// An indexer to retrieve the data from the underlying datasource. If the - /// sorting is applied, the data will be retrieved from the sorted datasource. - DataGridRow operator [](int index) => _effectiveRows[index]; - - /// Called when [LoadMoreRows] function is called from the - /// [SfDataGrid.loadMoreViewBuilder]. - /// - /// Call the [notifyListeners] to refresh the datagrid based on current - /// available rows. - /// - /// See also, - /// - /// [SfDataGrid.loadMoreViewBuilder] - A builder that sets the widget to - /// display at end of the datagrid when end of the datagrid is reached on - /// vertical scrolling. - @protected - Future handleLoadMoreRows() async {} - - /// Called when the `swipe to refresh` is performed in [SfDataGrid]. - /// - /// This method will be called only if the - /// [SfDataGrid.allowPullToRefresh] property returns true. - /// - /// Call the [notifyListeners] to refresh the datagrid based on current - /// available rows. - @protected - Future handleRefresh() async {} - - /// Called to obtain the widget when a cell is moved into edit mode. - /// - /// The following example shows how to override this method and return the - /// widget for specific column. - /// - /// ```dart - /// - /// TextEditingController editingController = TextEditingController(); - /// - /// dynamic newCellValue; - /// - /// @override - /// Widget? buildEditWidget(DataGridRow dataGridRow, - /// RowColumnIndex rowColumnIndex, GridColumn column, - /// CellSubmit submitCell) { - /// // To set the value for TextField when cell is moved into edit mode. - /// final String displayText = dataGridRow - /// .getCells() - /// .firstWhere((DataGridCell dataGridCell) => - /// dataGridCell.columnName == column.columnName) - /// .value - /// ?.toString() ?? - /// ''; - /// - /// /// Returning the TextField with the numeric keyboard configuration. - /// if (column.columnName == 'id') { - /// return Container( - /// padding: const EdgeInsets.all(8.0), - /// alignment: Alignment.centerRight, - /// child: TextField( - /// autofocus: true, - /// controller: editingController..text = displayText, - /// textAlign: TextAlign.right, - /// decoration: const InputDecoration( - /// contentPadding: EdgeInsets.all(0), - /// border: InputBorder.none, - /// isDense: true), - /// inputFormatters: [ - /// FilteringTextInputFormatter.allow(RegExp('[0-9]')) - /// ], - /// keyboardType: TextInputType.number, - /// onChanged: (String value) { - /// if (value.isNotEmpty) { - /// print(value); - /// newCellValue = int.parse(value); - /// } else { - /// newCellValue = null; - /// } - /// }, - /// onSubmitted: (String value) { - /// /// Call [CellSubmit] callback to fire the canSubmitCell and - /// /// onCellSubmit to commit the new value in single place. - /// submitCell(); - /// }, - /// )); - /// } - /// } - /// ``` - /// Call the cellSubmit function whenever you are trying to save the cell - /// values. When you call this method, it will call [canSubmitCell] and - /// [onCellSubmit] methods. So, your usual cell value updation will be done - /// in single place. - Widget? buildEditWidget(DataGridRow dataGridRow, - RowColumnIndex rowColumnIndex, GridColumn column, CellSubmit submitCell) { - return null; - } - - /// Called whenever the cell is moved into edit mode. - /// - /// If you want to disable editing for the cells in specific scenarios, - /// you can return false. - /// - /// [rowColumnIndex] represents the index of row and column which are - /// currently in view not based on the actual index. If you want to get the - /// actual row index even after sorting is applied, you can use - /// `DataGridSource.rows.indexOf` method and pass the [dataGridRow]. It will - /// provide the actual row index from unsorted [DataGridRow] collection. - bool onCellBeginEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) { - return true; - } - - /// Called whenever the cell’s editing is completed. - /// - /// Typically, this will be called whenever the [notifyListeners] is called - /// when cell is in editing mode and key navigation is performed to move a - /// cell to another cell from the cell which is currently editing. - /// For eg, Enter key, TAB key and so on. - /// - /// The following example show how to override this method and save the - /// currently edited value for specific column. - /// - /// ``` dart - /// @override - /// void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - /// GridColumn column) { - /// final dynamic oldValue = dataGridRow - /// .getCells() - /// .firstWhereOrNull((DataGridCell dataGridCell) => - /// dataGridCell.columnName == column.columnName) - /// ?.value ?? - /// ''; - /// - /// final int dataRowIndex = rows.indexOf(dataGridRow); - /// - /// if (newCellValue == null || oldValue == newCellValue) { - /// return; - /// } - /// - /// if (column.columnName == 'id') { - /// rows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] = - /// DataGridCell(columnName: 'id', value: newCellValue); - /// - /// // Save the new cell value to model collection also. - /// employees[dataRowIndex].id = newCellValue as int; - /// } - /// - /// // To reset the new cell value after successfully updated to DataGridRow - /// //and underlying mode. - /// newCellValue = null; - /// } - ///``` - /// This method will never be called when you return false from [onCellBeginEdit]. - void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) {} - - /// Called whenever the cell’s editing is completed i.e. prior to - /// [onCellSubmit] method. - /// - /// If you want to restrict the cell from being end its editing, you can - /// return false. Otherwise, return true. [onCellSubmit] will be called only - /// if the [canSubmitCell] returns true. - bool canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) { - return true; - } - - /// Called when you press the [LogicalKeyboardKey.escape] key when - /// the [DataGridCell] on editing to cancel the editing. - void onCellCancelEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - GridColumn column) {} -} - -/// Controls a [SfDataGrid] widget. -/// -/// This can be used to control the selection and currentcell operations such -/// as programmatically select a row or rows, move the currentcell to -/// required position. -/// -/// DataGrid controllers are typically stored as member variables in [State] -/// objects and are reused in each [State.build]. -class DataGridController extends DataGridSourceChangeNotifier { - /// Creates the [DataGridController] with the [selectedIndex], [selectedRow] - /// and [selectedRows]. - DataGridController( - {int selectedIndex = -1, - DataGridRow? selectedRow, - List selectedRows = const []}) - : _selectedRow = selectedRow, - _selectedIndex = selectedIndex, - _selectedRows = selectedRows.toList() { - _currentCell = RowColumnIndex(-1, -1); - _horizontalOffset = 0.0; - _verticalOffset = 0.0; - } - - _DataGridStateDetails? _dataGridStateDetails; - - /// The collection of objects that contains object of corresponding - /// to the selected rows in [SfDataGrid]. - List get selectedRows => _selectedRows; - List _selectedRows = List.empty(); - - /// The collection of objects that contains object of corresponding - /// to the selected rows in [SfDataGrid]. - set selectedRows(List newSelectedRows) { - if (_selectedRows == newSelectedRows) { - return; - } - - _selectedRows = newSelectedRows; - _notifyDataGridPropertyChangeListeners(propertyName: 'selectedRows'); - } - - /// An index of the corresponding selected row. - int get selectedIndex => _selectedIndex; - int _selectedIndex; - - /// An index of the corresponding selected row. - set selectedIndex(int newSelectedIndex) { - if (_selectedIndex == newSelectedIndex) { - return; - } - - _selectedIndex = newSelectedIndex; - _notifyDataGridPropertyChangeListeners(propertyName: 'selectedIndex'); - } - - /// An object of the corresponding selected row. - /// - /// The given object must be given from the underlying datasource of the - /// [SfDataGrid]. - DataGridRow? get selectedRow => _selectedRow; - DataGridRow? _selectedRow; - - /// An object of the corresponding selected row. - /// - /// The given object must be given from the underlying datasource of the - /// [SfDataGrid]. - set selectedRow(DataGridRow? newSelectedRow) { - if (_selectedRow == newSelectedRow) { - return; - } - - _selectedRow = newSelectedRow; - _notifyDataGridPropertyChangeListeners(propertyName: 'selectedRow'); - } - - /// The current scroll offset of the vertical scrollbar. - double get verticalOffset => _verticalOffset; - late double _verticalOffset; - - /// The current scroll offset of the horizontal scrollbar. - double get horizontalOffset => _horizontalOffset; - late double _horizontalOffset; - - ///If the [rowIndex] alone is given, the entire row will be set as dirty. - ///So, data which is displayed in a row will be refreshed. - /// You can call this method when the data is updated in row in - /// underlying datasource. - /// - /// If the `recalculateRowHeight` is set as true along with the [rowIndex], - /// [SfDataGrid.onQueryRowHeight] callback will be called for that row. - /// So, the row height can be reset based on the modified data. - /// This is useful when setting auto row height - /// using [SfDataGrid.onQueryRowHeight] callback. - void refreshRow(int rowIndex, {bool recalculateRowHeight = false}) { - _notifyDataGridPropertyChangeListeners( - rowColumnIndex: RowColumnIndex(rowIndex, -1), - propertyName: 'refreshRow', - recalculateRowHeight: recalculateRowHeight); - } - - /// A cell which is currently active. - /// - /// This is used to identify the currently active cell to process the - /// key navigation. - RowColumnIndex get currentCell => _currentCell; - RowColumnIndex _currentCell = RowColumnIndex.empty; - - /// Moves the currentcell to the specified cell coordinates. - void moveCurrentCellTo(RowColumnIndex rowColumnIndex) { - if (_dataGridStateDetails != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - if (rowColumnIndex != RowColumnIndex(-1, -1) && - dataGridSettings.selectionMode != SelectionMode.none && - dataGridSettings.navigationMode != GridNavigationMode.row) { - final int rowIndex = _GridIndexResolver.resolveToRowIndex( - dataGridSettings, rowColumnIndex.rowIndex); - final int columnIndex = - _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, rowColumnIndex.columnIndex); - // Ignore the scrolling when the row index or column index are in negative - // or invalid. - if (rowIndex.isNegative || columnIndex.isNegative) { - return; - } - final SelectionManagerBase rowSelectionController = - dataGridSettings.rowSelectionManager; - if (rowSelectionController is RowSelectionManager) { - rowSelectionController._processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(rowIndex, columnIndex), - isProgrammatic: true); - } - } - } - } - - /// Scrolls the [SfDataGrid] to the given row and column index. - /// - /// If you want animation on scrolling, you can pass true as canAnimate argument. - /// - /// Also, you can control the position of a row when it comes to view by - /// passing the [DataGridScrollPosition] as an argument for rowPosition where - /// as you can pass [DataGridScrollPosition] as an argument for columnPosition - /// to control the position of a column. - Future scrollToCell(double rowIndex, double columnIndex, - {bool canAnimate = false, - DataGridScrollPosition rowPosition = DataGridScrollPosition.start, - DataGridScrollPosition columnPosition = - DataGridScrollPosition.start}) async { - if (_dataGridStateDetails != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - final _ScrollAxisBase scrollRows = dataGridSettings.container.scrollRows; - final _ScrollAxisBase scrollColumns = - dataGridSettings.container.scrollColumns; - - if (rowIndex > dataGridSettings.container.rowCount || - columnIndex > scrollColumns.lineCount || - (rowIndex.isNegative && columnIndex.isNegative)) { - return; - } - - final int _rowIndex = _GridIndexResolver.resolveToRowIndex( - dataGridSettings, rowIndex.toInt()); - final int _columnIndex = - _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, columnIndex.toInt()); - double verticalOffset = - _SfDataGridHelper.getVerticalOffset(dataGridSettings, _rowIndex); - double horizontalOffset = - _SfDataGridHelper.getHorizontalOffset(dataGridSettings, _columnIndex); - - if (dataGridSettings.textDirection == TextDirection.rtl && - columnIndex == -1) { - horizontalOffset = dataGridSettings.container.extentWidth - - dataGridSettings.viewWidth - - horizontalOffset > - 0 - ? dataGridSettings.container.extentWidth - - dataGridSettings.viewWidth - - horizontalOffset - : 0; - } - - verticalOffset = _SfDataGridHelper.resolveScrollOffsetToPosition( - rowPosition, - scrollRows, - verticalOffset, - dataGridSettings.viewHeight, - scrollRows.headerExtent, - scrollRows.footerExtent, - dataGridSettings.rowHeight, - dataGridSettings.container.verticalOffset, - _rowIndex); - - horizontalOffset = _SfDataGridHelper.resolveScrollOffsetToPosition( - columnPosition, - scrollColumns, - horizontalOffset, - dataGridSettings.viewWidth, - scrollColumns.headerExtent, - scrollColumns.footerExtent, - dataGridSettings.defaultColumnWidth, - dataGridSettings.container.horizontalOffset, - _columnIndex); - - _SfDataGridHelper.scrollVertical( - dataGridSettings, verticalOffset, canAnimate); - // Need to add await for the horizontal scrolling alone, to avoid the delay time between vertical and horizontal scrolling. - await _SfDataGridHelper.scrollHorizontal( - dataGridSettings, horizontalOffset, canAnimate); - } - } - - /// Scrolls the [SfDataGrid] to the given index. - /// - /// If you want animation on scrolling, you can pass true as canAnimate argument. - /// - /// Also, you can control the position of a row when it comes to view by passing - /// the [DataGridScrollPosition] as an argument for position. - Future scrollToRow(double rowIndex, - {bool canAnimate = false, - DataGridScrollPosition position = DataGridScrollPosition.start}) async { - return scrollToCell(rowIndex, -1, - canAnimate: canAnimate, rowPosition: position); - } - - /// Scrolls the [SfDataGrid] to the given column index. - /// - /// If you want animation on scrolling, you can pass true as canAnimate argument. - /// - /// Also, you can control the position of a row when it comes to view by passing - /// the [DataGridScrollPosition] as an argument for position. - Future scrollToColumn(double columnIndex, - {bool canAnimate = false, - DataGridScrollPosition position = DataGridScrollPosition.start}) async { - return scrollToCell(-1, columnIndex, - canAnimate: canAnimate, columnPosition: position); - } - - /// Scroll the vertical scrollbar from current position to the given value. - /// - /// If you want animation on scrolling, you can pass true as canAnimate argument. - Future scrollToVerticalOffset(double offset, - {bool canAnimate = false}) async { - if (_dataGridStateDetails != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - return _SfDataGridHelper.scrollVertical( - dataGridSettings, offset, canAnimate); - } - } - - /// Scroll the horizontal scrollbar from current value to the given value. - /// - /// If you want animation on scrolling, you can pass true as canAnimate argument. - Future scrollToHorizontalOffset(double offset, - {bool canAnimate = false}) async { - if (_dataGridStateDetails != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - return _SfDataGridHelper.scrollHorizontal( - dataGridSettings, offset, canAnimate); - } - } - - /// Begins the edit to the given [RowColumnIndex] in [SfDataGrid]. - void beginEdit(RowColumnIndex rowColumnIndex) { - if (_dataGridStateDetails != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - if (!dataGridSettings.allowEditing || - dataGridSettings.selectionMode == SelectionMode.none || - dataGridSettings.navigationMode == GridNavigationMode.row) { - return; - } - - dataGridSettings.currentCell._onCellBeginEdit( - editingRowColumnIndex: rowColumnIndex, isProgrammatic: true); - } - } - - /// Ends the current editing of a cell in [SfDataGrid]. - void endEdit() { - if (_dataGridStateDetails != null) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - if (!dataGridSettings.allowEditing || - dataGridSettings.selectionMode == SelectionMode.none || - dataGridSettings.navigationMode == GridNavigationMode.row) { - return; - } - - dataGridSettings.currentCell._onCellSubmit(dataGridSettings); - } - } -} - -/// ToDO -class DataGridSourceChangeNotifier extends ChangeNotifier { - final ObserverList<_DataGridSourceListener> _dataGridSourceListeners = - ObserverList<_DataGridSourceListener>(); - - void _addDataGridSourceListener(_DataGridSourceListener listener) { - _dataGridSourceListeners.add(listener); - } - - void _removeDataGridSourceListener(_DataGridSourceListener listener) { - _dataGridSourceListeners.remove(listener); - } - - final ObserverList<_DataGridPropertyChangeListener> - _dataGridPropertyChangeListeners = - ObserverList<_DataGridPropertyChangeListener>(); - - void _addDataGridPropertyChangeListener( - _DataGridPropertyChangeListener listener) { - _dataGridPropertyChangeListeners.add(listener); - } - - void _removeDataGridPropertyChangeListener( - _DataGridPropertyChangeListener listener) { - _dataGridPropertyChangeListeners.remove(listener); - } - - @protected - @override - void notifyListeners() { - super.notifyListeners(); - } - - /// Calls all the datagrid source listeners. - /// Call this method whenever the underlying data is added or removed. If the value of the specific cell is updated, call this method with RowColumnIndex argument where it refers the corresponding row and column index of the cell. - @protected - void notifyDataSourceListeners({RowColumnIndex? rowColumnIndex}) { - for (final Function listener in _dataGridSourceListeners) { - listener(rowColumnIndex: rowColumnIndex); - } - } - - /// Call this method whenever the rowColumnIndex, propertyName and recalculateRowHeight of the underlying data are updated internally. - void _notifyDataGridPropertyChangeListeners( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { - for (final Function listener in _dataGridPropertyChangeListeners) { - listener( - rowColumnIndex: rowColumnIndex, - propertyName: propertyName, - recalculateRowHeight: recalculateRowHeight); - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart deleted file mode 100644 index 2b029d006..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of datagrid; - -/// Provides functionality to display the cell. -class DataCell extends DataCellBase { - /// Creates [DataCell] for [GridCell] widget. - DataCell(); - - @override - Widget? _onInitializeColumnElement(bool isInEdit) { - if (_cellType != CellType.indentCell) { - if (_renderer != null) { - _renderer!.setCellStyle(this); - return _renderer!.onPrepareWidgets(this); - } else { - return null; - } - } else { - return null; - } - } - - @override - void _updateColumn() { - if (_renderer == null) { - return; - } - _renderer! - ..setCellStyle(this) - ..onPrepareWidgets(this); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart deleted file mode 100644 index ee1a6ee3d..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_cell_base.dart +++ /dev/null @@ -1,77 +0,0 @@ -part of datagrid; - -/// A base class which provides functionalities for [DataCell]. -abstract class DataCellBase { - /// Creates [DataCell] for [SfDataGrid] - DataCellBase() { - _isVisible = true; - _isEnsured = false; - _isDirty = false; - _columnSpan = 0; - _rowSpan = 0; - _isEditing = false; - } - - Widget? _columnElement; - - Key? _key; - - TextStyle? _textStyle; - - GridCellRendererBase? _renderer; - - CellType? _cellType; - - late bool _isVisible; - - late bool _isEnsured; - - DataRowBase? _dataRow; - - late bool _isDirty; - - /// The column index of the [DataCell]. - int columnIndex = -1; - - /// The row index of the [DataCell]. - int rowIndex = -1; - - /// [GridColumn] which is associated with [DataCell]. - GridColumn? gridColumn; - - /// Decides whether the [DataCell] has the currentcell. - bool isCurrentCell = false; - - late int _columnSpan; - - late int _rowSpan; - - StackedHeaderCell? _stackedHeaderCell; - - Widget? _editingWidget; - - late bool _isEditing; - - /// Decides whether the [DataCell] is visible. - bool get isVisible => _isVisible; - - Widget? _onInitializeColumnElement(bool isInEdit) => null; - - void _updateColumn() {} - - void _onTouchUp() { - if (_dataRow != null) { - final _DataGridSettings dataGridSettings = - _dataRow!._dataGridStateDetails!(); - if (rowIndex <= _GridIndexResolver.getHeaderIndex(dataGridSettings) || - _GridIndexResolver.isFooterWidgetRow(rowIndex, dataGridSettings) || - dataGridSettings.selectionMode == SelectionMode.none) { - return; - } - - final RowColumnIndex currentRowColumnIndex = - RowColumnIndex(rowIndex, columnIndex); - dataGridSettings.rowSelectionManager.handleTap(currentRowColumnIndex); - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart deleted file mode 100644 index 7e0fe9bca..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row.dart +++ /dev/null @@ -1,280 +0,0 @@ -part of datagrid; - -/// Provides functionality to process the row. -class DataRow extends DataRowBase { - /// Creates [DataCell] for [SfDataGrid] widget. - DataRow(); - - @override - void _onGenerateVisibleColumns(_VisibleLinesCollection visibleColumnLines) { - _visibleColumns.clear(); - - int startColumnIndex = 0; - int endColumnIndex = -1; - - for (int i = 0; i < 3; i++) { - if (i == 0) { - if (visibleColumnLines.firstBodyVisibleIndex <= 0) { - continue; - } - - startColumnIndex = 0; - endColumnIndex = - visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex - 1] - .lineIndex; - } else if (i == 1) { - if (visibleColumnLines.firstBodyVisibleIndex <= 0 && - visibleColumnLines.lastBodyVisibleIndex < 0) { - continue; - } - - if (visibleColumnLines.length > - visibleColumnLines.firstBodyVisibleIndex) { - startColumnIndex = - visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex] - .lineIndex; - } else { - continue; - } - - endColumnIndex = - visibleColumnLines[visibleColumnLines.lastBodyVisibleIndex] - .lineIndex; - } else { - if (visibleColumnLines.firstFooterVisibleIndex >= - visibleColumnLines.length) { - continue; - } - - startColumnIndex = - visibleColumnLines[visibleColumnLines.firstFooterVisibleIndex] - .lineIndex; - endColumnIndex = - visibleColumnLines[visibleColumnLines.length - 1].lineIndex; - } - - for (int index = startColumnIndex; index <= endColumnIndex; index++) { - DataCellBase? dc = _createColumn(index); - _visibleColumns.add(dc); - dc = null; - } - } - } - - @override - void _ensureColumns(_VisibleLinesCollection visibleColumnLines) { - // Need to ignore footer view Row. because footer view row doesn't have - // visible columns. - if (rowType == RowType.footerRow) { - return; - } - - for (int i = 0; i < _visibleColumns.length; i++) { - _visibleColumns[i]._isEnsured = false; - } - - int startColumnIndex = 0; - int endColumnIndex = -1; - - for (int i = 0; i < 3; i++) { - if (i == 0) { - if (visibleColumnLines.firstBodyVisibleIndex <= 0) { - continue; - } - - startColumnIndex = 0; - endColumnIndex = - visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex - 1] - .lineIndex; - } else if (i == 1) { - if (visibleColumnLines.firstBodyVisibleIndex <= 0 && - visibleColumnLines.lastBodyVisibleIndex < 0) { - continue; - } - - if (visibleColumnLines.length > - visibleColumnLines.firstBodyVisibleIndex) { - startColumnIndex = - visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex] - .lineIndex; - } else { - continue; - } - - endColumnIndex = - visibleColumnLines[visibleColumnLines.lastBodyVisibleIndex] - .lineIndex; - } else { - if (visibleColumnLines.firstFooterVisibleIndex >= - visibleColumnLines.length) { - continue; - } - - startColumnIndex = - visibleColumnLines[visibleColumnLines.firstFooterVisibleIndex] - .lineIndex; - endColumnIndex = - visibleColumnLines[visibleColumnLines.length - 1].lineIndex; - } - - for (int index = startColumnIndex; index <= endColumnIndex; index++) { - DataCellBase? dc = _indexer(index); - if (dc == null) { - DataCellBase? dataCell = _reUseCell(startColumnIndex, endColumnIndex); - - dataCell ??= _visibleColumns.firstWhereOrNull((DataCellBase col) => - col.columnIndex == -1 && col._cellType != CellType.indentCell); - - _updateColumn(dataCell, index); - dataCell = null; - } - - dc ??= _visibleColumns - .firstWhereOrNull((DataCellBase col) => col.columnIndex == index); - - if (dc != null) { - if (!dc._isVisible) { - dc._isVisible = true; - } - } else { - dc = _createColumn(index); - _visibleColumns.add(dc); - } - - dc._isEnsured = true; - dc = null; - } - } - - for (final DataCellBase col in _visibleColumns) { - if (!col._isEnsured || col.columnIndex == -1) { - col._isVisible = false; - } - } - } - - DataCellBase _createColumn(int index) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - final bool canIncrementHeight = rowType == RowType.headerRow && - dataGridSettings.stackedHeaderRows.isNotEmpty; - final DataCellBase dc = DataCell() - .._dataRow = this - ..columnIndex = index - ..rowIndex = rowIndex; - dc._key = ObjectKey(dc); - final int columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, index); - dc.gridColumn = dataGridSettings.columns[columnIndex]; - _checkForCurrentCell(dataGridSettings, dc); - if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings) && - rowIndex >= 0) { - dc - .._renderer = dataGridSettings.cellRenderers['ColumnHeader'] - .._cellType = CellType.headerCell; - } else { - dc - .._renderer = dataGridSettings.cellRenderers['TextField'] - .._cellType = CellType.gridCell; - } - if (canIncrementHeight) { - final int rowSpan = _StackedHeaderHelper._getRowSpan( - dataGridSettings, dc._dataRow!.rowIndex - 1, index, false, - mappingName: dc.gridColumn!.columnName); - dc._rowSpan = rowSpan; - } - - dc._columnElement = dc._onInitializeColumnElement(false); - return dc; - } - - DataCellBase? _indexer(int index) { - for (final DataCellBase column in _visibleColumns) { - if (column.columnIndex == index) { - return column; - } - } - - return null; - } - - DataCellBase? _reUseCell(int startColumnIndex, int endColumnIndex) => - _visibleColumns.firstWhereOrNull((DataCellBase cell) => - cell.gridColumn != null && - (cell.columnIndex < 0 || - cell.columnIndex < startColumnIndex || - cell.columnIndex > endColumnIndex) && - !cell._isEnsured && - !cell._isEditing); - - void _updateColumn(DataCellBase? dc, int index) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - final bool canIncrementHeight = rowType == RowType.headerRow && - dataGridSettings.stackedHeaderRows.isNotEmpty; - if (dc != null) { - if (index < 0 || index >= dataGridSettings.container.columnCount) { - dc._isVisible = false; - } else { - dc - ..columnIndex = index - ..rowIndex = rowIndex - .._key = dc._key - .._isVisible = true; - final int columnIndex = - _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, index); - dc.gridColumn = dataGridSettings.columns[columnIndex]; - _checkForCurrentCell(dataGridSettings, dc); - _updateRenderer(dataGridSettings, dc, dc.gridColumn); - if (canIncrementHeight) { - final int rowSpan = _StackedHeaderHelper._getRowSpan( - dataGridSettings, dc._dataRow!.rowIndex - 1, index, false, - mappingName: dc.gridColumn!.columnName); - dc._rowSpan = rowSpan; - } else { - dc._rowSpan = 0; - } - dc - .._columnElement = dc._onInitializeColumnElement(false) - .._isEnsured = true; - } - - if (dc._isVisible != true) { - dc._isVisible = true; - } - } else { - dc = _createColumn(index); - _visibleColumns.add(dc); - } - } - - void _updateRenderer(_DataGridSettings dataGridSettings, - DataCellBase dataColumn, GridColumn? column) { - GridCellRendererBase? newRenderer; - if (rowRegion == RowRegion.header && rowType == RowType.headerRow) { - newRenderer = dataGridSettings.cellRenderers['ColumnHeader']; - dataColumn._cellType = CellType.headerCell; - } else { - newRenderer = dataGridSettings.cellRenderers['TextField']; - dataColumn._cellType = CellType.gridCell; - } - - dataColumn._renderer = newRenderer; - newRenderer = null; - } - - void _checkForCurrentCell( - _DataGridSettings dataGridSettings, DataCellBase dc) { - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - final _CurrentCellManager currentCellManager = - dataGridSettings.currentCell; - if (currentCellManager.columnIndex != -1 && - currentCellManager.rowIndex != -1 && - currentCellManager.rowIndex == rowIndex && - currentCellManager.columnIndex == dc.columnIndex) { - dc.isCurrentCell = true; - } else { - dc.isCurrentCell = false; - } - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart deleted file mode 100644 index de13b2da6..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/data_row_base.dart +++ /dev/null @@ -1,139 +0,0 @@ -part of datagrid; - -/// A base class which provides functionalities for [DataRow]. -abstract class DataRowBase { - /// Creates [DataRow] for the [SfDataGrid]. - DataRowBase() { - _isDirty = false; - _isEnsured = false; - _isVisible = true; - _visibleColumns = []; - _isSwipingRow = false; - _isEditing = false; - _isHoveredRow = false; - } - - _DataGridStateDetails? _dataGridStateDetails; - - Key? _key; - - Widget? _footerView; - - late bool _isDirty; - - late bool _isEnsured; - - late bool _isVisible; - - late List _visibleColumns; - - // This flag is used to indicating whether the row is swiped or not. - late bool _isSwipingRow; - - late bool _isEditing; - - /// The row index of the [DataRow]. - int rowIndex = -1; - - /// The type of the [DataRow]. - RowType rowType = RowType.headerRow; - - /// The region of the [DataRow] to identify whether the row is scrollable - RowRegion rowRegion = RowRegion.header; - - /// Decides whether the [DataRow] is visible. - bool get isVisible => _isVisible; - - /// Decides whether the [DataRow] is currently active - bool isCurrentRow = false; - - /// Decides whether the [DataRow] is hovered. - late bool _isHoveredRow; - - DataGridRow? _dataGridRow; - - DataGridRowAdapter? _dataGridRowAdapter; - - void _rowIndexChanged() { - if (rowIndex < 0) { - return; - } - - for (final DataCellBase col in _visibleColumns) { - col - ..rowIndex = rowIndex - .._dataRow = this - .._updateColumn(); - } - } - - void _onGenerateVisibleColumns(_VisibleLinesCollection visibleColumnLines) {} - - void _initializeDataRow(_VisibleLinesCollection visibleColumnLines) { - _onGenerateVisibleColumns(visibleColumnLines); - } - - void _ensureColumns(_VisibleLinesCollection visibleColumnLines) {} - - _VisibleLineInfo? _getColumnVisibleLineInfo(int index) => - _dataGridStateDetails!() - .container - .scrollColumns - .getVisibleLineAtLineIndex(index); - - _VisibleLineInfo? _getRowVisibleLineInfo(int index) => - _dataGridStateDetails!() - .container - .scrollRows - .getVisibleLineAtLineIndex(index); - - double _getColumnWidth(int startIndex, int endIndex) { - if (startIndex != endIndex) { - final List<_DoubleSpan> currentPos = _dataGridStateDetails!() - .container - .scrollColumns - .rangeToRegionPoints(startIndex, endIndex, true); - return currentPos[1].length; - } - - final _VisibleLineInfo? line = _getColumnVisibleLineInfo(startIndex); - if (line == null) { - return 0; - } - - return line.size; - } - - double _getRowHeight(int startIndex, int endIndex) { - if (startIndex != endIndex) { - final List<_DoubleSpan> currentPos = _dataGridStateDetails!() - .container - .scrollRows - .rangeToRegionPoints(startIndex, endIndex, true); - return currentPos[1].length; - } - - final _VisibleLineInfo? line = _getRowVisibleLineInfo(startIndex); - if (line == null) { - return 0; - } - - return line.size; - } - - /// Decides whether the [DataRow] is selected. - bool get isSelectedRow => _isSelectedRow; - bool _isSelectedRow = false; - - /// Decides whether the [DataRow] is selected. - set isSelectedRow(bool newValue) { - if (_isSelectedRow != newValue) { - _isSelectedRow = newValue; - for (final DataCellBase dataCell in _visibleColumns) { - dataCell - .._isDirty = true - .._updateColumn(); - } - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart deleted file mode 100644 index 9fadf5ce4..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/row_generator.dart +++ /dev/null @@ -1,426 +0,0 @@ -part of datagrid; - -class _RowGenerator { - _RowGenerator({required _DataGridStateDetails dataGridStateDetails}) { - _dataGridStateDetails = dataGridStateDetails; - items = []; - } - - late List items; - - _DataGridStateDetails get dataGridStateDetails => _dataGridStateDetails; - late _DataGridStateDetails _dataGridStateDetails; - - _VisualContainerHelper get container => dataGridStateDetails().container; - - void _preGenerateRows(_VisibleLinesCollection? visibleRows, - _VisibleLinesCollection? visibleColumns) { - if (items.isNotEmpty || dataGridStateDetails().container.rowCount <= 0) { - return; - } - - if (visibleRows != null && visibleColumns != null) { - for (int i = 0; i < visibleRows.length; i++) { - _VisibleLineInfo? line = visibleRows[i]; - late DataRowBase? dr; - switch (line.region) { - case _ScrollAxisRegion.header: - dr = _createHeaderRow(line.lineIndex, visibleColumns); - break; - case _ScrollAxisRegion.body: - dr = _createDataRow(line.lineIndex, visibleColumns); - break; - case _ScrollAxisRegion.footer: - dr = _createFooterRow(line.lineIndex, visibleColumns); - break; - } - - items.add(dr); - dr = null; - line = null; - } - } - } - - void _ensureRows(_VisibleLinesCollection visibleRows, - _VisibleLinesCollection visibleColumns) { - List? actualStartAndEndIndex = []; - RowRegion? region = RowRegion.header; - - List reUseRows() => items - .where((DataRowBase row) => - (row.rowIndex < 0 || - row.rowIndex < actualStartAndEndIndex![0] || - row.rowIndex > actualStartAndEndIndex[1]) && - !row._isEnsured && - !row._isEditing) - .toList(growable: false); - - for (final DataRowBase row in items) { - row._isEnsured = false; - } - - for (int i = 0; i < 3; i++) { - if (i == 0) { - region = RowRegion.header; - actualStartAndEndIndex = container._getStartEndIndex(visibleRows, i); - } else if (i == 1) { - region = RowRegion.body; - actualStartAndEndIndex = container._getStartEndIndex(visibleRows, i); - } else { - region = RowRegion.footer; - actualStartAndEndIndex = container._getStartEndIndex(visibleRows, i); - } - - for (int index = actualStartAndEndIndex[0]; - index <= actualStartAndEndIndex[1]; - index++) { - DataRowBase? dr = _indexer(index); - if (dr == null) { - List? rows = reUseRows(); - if (rows.isNotEmpty) { - _updateRow(rows, index, region); - rows = null; - } - } - - dr ??= - items.firstWhereOrNull((DataRowBase row) => row.rowIndex == index); - - if (dr != null) { - if (!dr._isVisible) { - dr._isVisible = true; - } - - dr._isEnsured = true; - } else { - if (region == RowRegion.header) { - dr = _createHeaderRow(index, visibleColumns); - } else { - dr = _createDataRow(index, visibleColumns); - } - - dr._isEnsured = true; - items.add(dr); - } - - dr = null; - } - } - - for (final DataRowBase row in items) { - if (!row._isEnsured || row.rowIndex == -1) { - row._isVisible = false; - } - } - - actualStartAndEndIndex = null; - region = null; - } - - void _ensureColumns(_VisibleLinesCollection visibleColumns) { - for (final DataRowBase row in items) { - row._ensureColumns(visibleColumns); - } - } - - DataRowBase _createDataRow( - int rowIndex, _VisibleLinesCollection visibleColumns, - {RowRegion rowRegion = RowRegion.body}) { - final _DataGridSettings dataGridSettings = dataGridStateDetails(); - DataRow dr; - if (_GridIndexResolver.isFooterWidgetRow(rowIndex, dataGridSettings)) { - dr = DataRow() - .._dataGridStateDetails = dataGridStateDetails - ..rowIndex = rowIndex - ..rowRegion = rowRegion - ..rowType = RowType.footerRow; - dr._key = ObjectKey(dr); - dr._footerView = _buildFooterWidget(dataGridSettings, rowIndex); - return dr; - } else { - dr = DataRow() - .._dataGridStateDetails = dataGridStateDetails - ..rowIndex = rowIndex - ..rowRegion = rowRegion - ..rowType = RowType.dataRow; - dr._key = ObjectKey(dr); - dr - .._dataGridRow = - _SfDataGridHelper.getDataGridRow(dataGridSettings, rowIndex) - .._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( - dataGridSettings, dr._dataGridRow!); - assert(_SfDataGridHelper.debugCheckTheLength( - dataGridSettings.columns.length, - dr._dataGridRowAdapter!.cells.length, - 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); - _checkForCurrentRow(dr); - _checkForSelection(dr); - dr._initializeDataRow(visibleColumns); - return dr; - } - } - - DataRowBase _createHeaderRow( - int rowIndex, _VisibleLinesCollection visibleColumns) { - final _DataGridSettings dataGridSettings = dataGridStateDetails(); - DataRowBase dr; - if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - dr = DataRow() - .._dataGridStateDetails = dataGridStateDetails - ..rowIndex = rowIndex - ..rowRegion = RowRegion.header - ..rowType = RowType.headerRow; - dr - .._key = ObjectKey(dr) - .._initializeDataRow(visibleColumns); - return dr; - } else if (rowIndex < dataGridSettings.stackedHeaderRows.length) { - dr = _SpannedDataRow(); - dr._key = ObjectKey(dr); - dr.rowIndex = rowIndex; - dr._dataGridStateDetails = dataGridStateDetails; - dr.rowRegion = RowRegion.header; - dr.rowType = RowType.stackedHeaderRow; - _createStackedHeaderCell( - dataGridSettings.stackedHeaderRows[rowIndex], rowIndex); - dr._initializeDataRow(visibleColumns); - return dr; - } else { - return _createDataRow(rowIndex, visibleColumns, - rowRegion: RowRegion.header); - } - } - - void _createStackedHeaderCell(StackedHeaderRow header, int rowIndex) { - final _DataGridSettings dataGridSettings = dataGridStateDetails(); - for (final StackedHeaderCell column in header.cells) { - final List childSequence = _StackedHeaderHelper._getChildSequence( - dataGridSettings, column, rowIndex); - childSequence.sort(); - column._childColumnIndexes = childSequence; - } - } - - DataRowBase _createFooterRow( - int rowIndex, _VisibleLinesCollection visibleColumns) { - return _createDataRow(rowIndex, visibleColumns, - rowRegion: RowRegion.footer); - } - - Widget? _buildFooterWidget(_DataGridSettings dataGridSettings, int rowIndex) { - if (dataGridSettings.footer == null) { - return null; - } - - BoxDecoration drawBorder() { - final SfDataGridThemeData themeData = dataGridSettings.dataGridThemeData!; - final GridLinesVisibility gridLinesVisibility = - dataGridSettings.gridLinesVisibility; - - final bool canDrawHorizontalBorder = - gridLinesVisibility == GridLinesVisibility.horizontal || - gridLinesVisibility == GridLinesVisibility.both; - final bool canDrawVerticalBorder = - gridLinesVisibility == GridLinesVisibility.vertical || - gridLinesVisibility == GridLinesVisibility.both; - - final bool canDrawTopFrozenBorder = dataGridSettings - .footerFrozenRowsCount.isFinite && - dataGridSettings.footerFrozenRowsCount > 0 && - _GridIndexResolver.getStartFooterFrozenRowIndex(dataGridSettings) == - rowIndex; - - return BoxDecoration( - border: BorderDirectional( - top: canDrawTopFrozenBorder && themeData.frozenPaneElevation <= 0.0 - ? BorderSide( - color: themeData.frozenPaneLineColor, - width: themeData.frozenPaneLineWidth) - : BorderSide.none, - bottom: canDrawHorizontalBorder - ? BorderSide( - color: themeData.gridLineColor, - width: themeData.gridLineStrokeWidth) - : BorderSide.none, - end: canDrawVerticalBorder - ? BorderSide( - color: themeData.gridLineColor, - width: themeData.gridLineStrokeWidth) - : BorderSide.none, - ), - ); - } - - return Container( - decoration: drawBorder(), - clipBehavior: Clip.antiAlias, - child: dataGridSettings.footer); - } - - DataRowBase? _indexer(int index) { - for (int i = 0; i < items.length; i++) { - if (items[i].rowIndex == index) { - return items[i]; - } - } - - return null; - } - - void _updateRow(List rows, int index, RowRegion region) { - final _DataGridSettings dataGridSettings = dataGridStateDetails(); - DataRowBase? dr; - if (region == RowRegion.header) { - if (index == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - dr = rows.firstWhereOrNull( - (DataRowBase row) => row.rowType == RowType.headerRow); - if (dr != null) { - dr - .._key = dr._key - ..rowIndex = index - .._dataGridStateDetails = dataGridStateDetails - ..rowRegion = RowRegion.header - ..rowType = RowType.headerRow - .._rowIndexChanged(); - dr = null; - } else { - dr = _createHeaderRow( - index, _SfDataGridHelper.getVisibleLines(dataGridStateDetails())); - items.add(dr); - dr = null; - } - } else if (index < dataGridSettings.stackedHeaderRows.length) { - dr = rows.firstWhereOrNull( - (DataRowBase r) => r.rowType == RowType.stackedHeaderRow); - if (dr != null) { - dr._key = dr._key; - dr.rowIndex = index; - _dataGridStateDetails = dataGridStateDetails; - dr.rowRegion = RowRegion.header; - dr.rowType = RowType.stackedHeaderRow; - dr._rowIndexChanged(); - _createStackedHeaderCell( - dataGridSettings.stackedHeaderRows[index], index); - dr._initializeDataRow( - dataGridSettings.container.scrollRows.getVisibleLines()); - } else { - dr = _createHeaderRow( - index, _SfDataGridHelper.getVisibleLines(dataGridStateDetails())); - items.add(dr); - dr = null; - } - } else { - _updateDataRow(rows, index, region, dataGridSettings); - } - } else if (_GridIndexResolver.isFooterWidgetRow(index, dataGridSettings)) { - dr = rows - .firstWhereOrNull((DataRowBase r) => r.rowType == RowType.footerRow); - if (dr != null) { - dr - .._key = dr._key - ..rowIndex = index - ..rowRegion = region - ..rowType = RowType.footerRow; - dr._footerView = _buildFooterWidget(dataGridSettings, index); - } else { - dr = _createDataRow( - index, _SfDataGridHelper.getVisibleLines(dataGridStateDetails())); - items.add(dr); - dr = null; - } - } else { - _updateDataRow(rows, index, region, dataGridSettings); - } - } - - void _updateDataRow(List rows, int index, RowRegion region, - _DataGridSettings dataGridSettings) { - DataRowBase? row = rows.firstWhereOrNull( - (DataRowBase row) => row is DataRow && row.rowType == RowType.dataRow); - - if (row != null && row is DataRow) { - if (index < 0 || index >= container.scrollRows.lineCount) { - row._isVisible = false; - } else { - row - .._key = row._key - ..rowIndex = index - ..rowRegion = region - .._dataGridRow = - _SfDataGridHelper.getDataGridRow(dataGridSettings, index) - .._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( - dataGridSettings, row._dataGridRow!); - assert(_SfDataGridHelper.debugCheckTheLength( - dataGridSettings.columns.length, - row._dataGridRowAdapter!.cells.length, - 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); - _checkForCurrentRow(row); - _checkForSelection(row); - row._rowIndexChanged(); - } - row = null; - } else { - DataRowBase? dr = _createDataRow( - index, _SfDataGridHelper.getVisibleLines(dataGridStateDetails())); - items.add(dr); - dr = null; - } - } - - double _queryRowHeight(int rowIndex, double height) { - final _DataGridSettings dataGridSettings = dataGridStateDetails(); - double rowHeight = height; - if (dataGridSettings.onQueryRowHeight != null) { - final RowHeightDetails details = RowHeightDetails(rowIndex, height) - .._columnSizer = dataGridSettings.columnSizer; - rowHeight = dataGridSettings.onQueryRowHeight!(details); - } - - return rowHeight; - } - - void _checkForSelection(DataRowBase row) { - final _DataGridSettings dataGridSettings = dataGridStateDetails(); - if (dataGridSettings.selectionMode != SelectionMode.none) { - final RowSelectionManager rowSelectionManager = - dataGridSettings.rowSelectionManager as RowSelectionManager; - final int recordIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, row.rowIndex); - final DataGridRow record = - dataGridSettings.source._effectiveRows[recordIndex]; - row._isSelectedRow = rowSelectionManager._selectedRows.contains(record); - } - } - - void _checkForCurrentRow(DataRow dr) { - final _DataGridSettings dataGridSettings = dataGridStateDetails(); - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - final _CurrentCellManager currentCellManager = - dataGridSettings.currentCell; - - DataCellBase? getDataCell() { - if (dr._visibleColumns.isEmpty) { - return null; - } - - final DataCellBase? dc = dr._visibleColumns.firstWhereOrNull( - (DataCellBase dataCell) => - dataCell.columnIndex == currentCellManager.columnIndex); - return dc; - } - - if (currentCellManager.rowIndex != -1 && - currentCellManager.columnIndex != -1 && - currentCellManager.rowIndex == dr.rowIndex) { - final DataCellBase? dataCell = getDataCell(); - currentCellManager._setCurrentCellDirty(dr, dataCell, true); - } else if (dr.isCurrentRow) { - final DataCellBase? dataCell = getDataCell(); - currentCellManager._setCurrentCellDirty(dr, dataCell, false); - } else { - dr.isCurrentRow = false; - } - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart deleted file mode 100644 index 0dee857a2..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/generator/spanned_data_row.dart +++ /dev/null @@ -1,163 +0,0 @@ -part of datagrid; - -/// Provides functionality to process the spanned data row. -class _SpannedDataRow extends DataRow { - @override - void _onGenerateVisibleColumns(_VisibleLinesCollection visibleColumnLines) { - if (visibleColumnLines.isEmpty) { - return; - } - _visibleColumns.clear(); - - _SpannedDataColumn dc; - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - if (rowType == RowType.stackedHeaderRow) { - if (dataGridSettings.stackedHeaderRows.isNotEmpty) { - final List stackedColumns = - dataGridSettings.stackedHeaderRows[rowIndex].cells; - for (final StackedHeaderCell column in stackedColumns) { - final List> columnsSequence = - _StackedHeaderHelper._getConsecutiveRanges( - column._childColumnIndexes); - - for (final List columns in columnsSequence) { - final int columnIndex = columns.reduce(min); - dc = _createStackedHeaderColumn( - columnIndex, columns.length - 1, column); - _visibleColumns.add(dc); - } - } - } - } - } - - _SpannedDataColumn _createStackedHeaderColumn( - int index, int columnSpan, StackedHeaderCell stackedHeaderCell) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - final GridColumn gridColumn = dataGridSettings.columns[index]; - final int rowSpan = _StackedHeaderHelper._getRowSpan( - dataGridSettings, rowIndex, index, true, - stackedHeaderCell: stackedHeaderCell); - final _SpannedDataColumn dc = _SpannedDataColumn() - .._dataRow = this - ..columnIndex = index - ..rowIndex = rowIndex; - dc._key = ObjectKey(dc); - dc - .._cellType = CellType.stackedHeaderCell - .._stackedHeaderCell = stackedHeaderCell - .._columnSpan = columnSpan - .._rowSpan = rowSpan - ..gridColumn = gridColumn - .._isEnsured = true; - - if (rowType == RowType.stackedHeaderRow) { - dc._renderer = dataGridSettings.cellRenderers['StackedHeader']; - } - dc._columnElement = dc._onInitializeColumnElement(false); - return dc; - } - - @override - void _ensureColumns(_VisibleLinesCollection visibleColumnLines) { - if (rowIndex == -1) { - return; - } - - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - if (dataGridSettings.stackedHeaderRows.isNotEmpty) { - final StackedHeaderRow stackedHeaderRow = - dataGridSettings.stackedHeaderRows[rowIndex]; - final List stackedColumns = stackedHeaderRow.cells; - dataGridSettings.rowGenerator - ._createStackedHeaderCell(stackedHeaderRow, rowIndex); - for (final StackedHeaderCell column in stackedColumns) { - final List> columnsSequence = - _StackedHeaderHelper._getConsecutiveRanges( - column._childColumnIndexes); - - for (final List columns in columnsSequence) { - final int actualColumnIndex = columns.reduce(min); - DataCellBase? dc = _indexer(actualColumnIndex); - - if (dc == null) { - DataCellBase? dataCell = _reUseCell( - actualColumnIndex, actualColumnIndex + columns.length - 1); - dataCell ??= _visibleColumns.firstWhereOrNull((DataCellBase col) => - col.columnIndex == -1 && col._cellType != CellType.indentCell); - - _updateStackedHeaderColumn( - dataCell, actualColumnIndex, columns.length - 1, column); - dataCell = null; - } - - dc ??= _visibleColumns.firstWhereOrNull( - (DataCellBase col) => col.columnIndex == actualColumnIndex); - - if (dc != null) { - if (!dc._isVisible) { - dc._isVisible = true; - } - } else { - dc = _createStackedHeaderColumn( - actualColumnIndex, columns.toList().length - 1, column); - _visibleColumns.add(dc); - } - - dc._isEnsured = true; - dc = null; - } - } - } - - for (final DataCellBase col in _visibleColumns) { - if (!col._isEnsured || col.columnIndex == -1) { - col._isVisible = false; - } - } - } - - void _updateStackedHeaderColumn(DataCellBase? dc, int index, int columnSpan, - StackedHeaderCell stackedHeaderCell) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails!(); - if (dc != null) { - if (index < 0 || index >= dataGridSettings.container.columnCount) { - dc._isVisible = false; - } else { - final int columnIndex = - _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, index); - final GridColumn gridColumn = dataGridSettings.columns[columnIndex]; - final int rowSpan = _StackedHeaderHelper._getRowSpan( - dataGridSettings, rowIndex, index, true, - stackedHeaderCell: stackedHeaderCell); - dc - ..columnIndex = index - ..rowIndex = rowIndex - .._stackedHeaderCell = stackedHeaderCell - .._cellType = CellType.stackedHeaderCell - .._columnSpan = columnSpan - .._rowSpan = rowSpan - .._key = dc._key - .._isVisible = true; - dc.gridColumn = gridColumn; - if (rowType == RowType.stackedHeaderRow) { - dc._renderer = dataGridSettings.cellRenderers['StackedHeader']; - } - dc - .._columnElement = dc._onInitializeColumnElement(false) - .._isEnsured = true; - } - - if (dc._isVisible != true) { - dc._isVisible = true; - } - } else { - dc = _createStackedHeaderColumn(index, columnSpan, stackedHeaderCell); - _visibleColumns.add(dc); - } - } -} - -/// Provides functionality to display the spanned cell. -class _SpannedDataColumn extends DataCell {} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart deleted file mode 100644 index cd5b55d16..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/grid_index_resolver.dart +++ /dev/null @@ -1,245 +0,0 @@ -part of datagrid; - -@protected -class _GridIndexResolver { - // Get the header index in [SfDataGrid]. - static int getHeaderIndex(_DataGridSettings dataGridSettings) { - final int headerIndex = dataGridSettings.headerLineCount - 1; - return headerIndex < 0 ? 0 : headerIndex; - } - - /// Helps to find the column index based on [SfDataGrid.columns] count - /// In this we will not include the static column like, indent column, row - /// header etc - static int resolveToGridVisibleColumnIndex( - _DataGridSettings dataGridSettings, int columnIndex) { - if (columnIndex >= dataGridSettings.container.columnCount) { - return -1; - } - - return columnIndex; - } - - /// Help to resolve the record index to [SfDataGrid] position row index. - static int resolveToRowIndex( - _DataGridSettings dataGridSettings, int rowIndex) { - if (rowIndex < 0) { - return -1; - } - - rowIndex = rowIndex + - _GridIndexResolver.resolveStartIndexBasedOnPosition(dataGridSettings); - if (rowIndex >= 0 && rowIndex <= dataGridSettings.container.rowCount) { - return rowIndex; - } else { - return -1; - } - } - - /// Help to resolve the [SfDataGrid] position row index to record index. - static int resolveToRecordIndex( - _DataGridSettings dataGridSettings, int rowIndex) { - if (rowIndex < 0) { - return -1; - } - - if (rowIndex == 0) { - return 0; - } - - rowIndex = rowIndex - - _GridIndexResolver.resolveStartIndexBasedOnPosition(dataGridSettings); - if (rowIndex >= 0 && - rowIndex <= dataGridSettings.source._effectiveRows.length - 1) { - return rowIndex; - } else { - return -1; - } - } - - /// Helps to resolve the [SfDataGrid] row column index to record [RowColumnIndex]. - static RowColumnIndex resolveToRecordRowColumnIndex( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { - final int rowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, rowColumnIndex.rowIndex); - final int columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, rowColumnIndex.columnIndex); - return RowColumnIndex(rowIndex, columnIndex); - } - - static RowColumnIndex resolveToRowColumnIndex( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { - final int rowIndex = _GridIndexResolver.resolveToRowIndex( - dataGridSettings, rowColumnIndex.rowIndex); - final int columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, rowColumnIndex.columnIndex); - return RowColumnIndex(rowIndex, columnIndex); - } - - /// Helps to find the exact starting scrolling row index. - static int resolveStartIndexBasedOnPosition( - _DataGridSettings dataGridSettings) { - return dataGridSettings.headerLineCount; - } - - /// Helps to find the actual starting scrolling column index - /// Its ignore the indent column, row header and provide the actual staring - /// column index - static int resolveToStartColumnIndex(_DataGridSettings dataGridSettings) => 0; - - /// Helps to resolve the provided column index based on [SfDataGrid] column - /// order. Its ignore the indent column, row header and provide the exact - /// column index - static int resolveToScrollColumnIndex( - _DataGridSettings dataGridSettings, int gridColumnIndex) => - gridColumnIndex; - - /// Get the last index of frozen column in left side view, it will - /// consider the row header,indent column and frozen column - static int getLastFrozenColumnIndex(_DataGridSettings dataGridSettings) { - if (dataGridSettings.frozenColumnsCount <= 0) { - return -1; - } - - final int startScrollColumnIndex = - dataGridSettings.container.frozenColumns - 1; - return startScrollColumnIndex.isFinite ? startScrollColumnIndex : -1; - } - - /// Get the starting index of frozen column in right side view, it - /// will consider the right frozen pane - static int getStartFooterFrozenColumnIndex( - _DataGridSettings dataGridSettings) { - final int columnsCount = dataGridSettings.container.columnCount; - if (columnsCount <= 0 || dataGridSettings.footerFrozenColumnsCount <= 0) { - return -1; - } - - return columnsCount - dataGridSettings.container.footerFrozenColumns; - } - - /// Get the last frozen row index in top of data grid, it - /// will consider the stacked header, header and top frozen pane - static int getLastFrozenRowIndex(_DataGridSettings dataGridSettings) { - if (dataGridSettings.frozenRowsCount <= 0) { - return -1; - } - - final int rowIndex = dataGridSettings.container.frozenRows - 1; - return rowIndex.isFinite ? rowIndex : -1; - } - - /// Get the starting frozen row index in bottom of data grid, it - /// will consider the bottom table summary and bottom frozen pane - static int getStartFooterFrozenRowIndex(_DataGridSettings dataGridSettings) { - final int rowCount = dataGridSettings.container.rowCount; - if (rowCount <= 0 || dataGridSettings.footerFrozenRowsCount <= 0) { - return -1; - } - - final int rowIndex = rowCount - dataGridSettings.container.footerFrozenRows; - return rowIndex.isFinite ? rowIndex : -1; - } - - /// Checks whether the row is a footer widget row or not. - static bool isFooterWidgetRow( - int rowIndex, _DataGridSettings dataGridSettings) { - return dataGridSettings.footer != null && - rowIndex == dataGridSettings.container.rowCount - 1; - } -} - -@protected -class _StackedHeaderHelper { - static List _getChildSequence(_DataGridSettings dataGridSettings, - StackedHeaderCell? column, int rowIndex) { - final List childSequenceNo = []; - - if (column != null && column.columnNames.isNotEmpty) { - for (final String child in column.columnNames) { - final List columns = dataGridSettings.columns; - for (int i = 0; i < columns.length; ++i) { - if (columns[i].columnName == child) { - childSequenceNo.add(i); - break; - } - } - } - } - return childSequenceNo; - } - - static int _getRowSpan(_DataGridSettings dataGridSettings, int rowindex, - int columnIndex, bool isStackedHeader, - {String? mappingName, StackedHeaderCell? stackedHeaderCell}) { - int rowSpan = 0; - int startIndex = 0; - int endIndex = 0; - if (isStackedHeader && stackedHeaderCell != null) { - final List> spannedColumns = - _getConsecutiveRanges(stackedHeaderCell._childColumnIndexes); - final List? spannedColumn = spannedColumns.singleWhereOrNull( - (List element) => element.first == columnIndex); - if (spannedColumn != null) { - startIndex = spannedColumn.reduce(min); - endIndex = startIndex + spannedColumn.length - 1; - } - rowindex = rowindex - 1; - } else { - if (rowindex >= dataGridSettings.stackedHeaderRows.length) { - return rowSpan; - } - } - - while (rowindex >= 0) { - final StackedHeaderRow stackedHeaderRow = - dataGridSettings.stackedHeaderRows[rowindex]; - for (final StackedHeaderCell stackedColumn in stackedHeaderRow.cells) { - if (isStackedHeader) { - final List> columnsRange = - _getConsecutiveRanges(stackedColumn._childColumnIndexes); - for (final List column in columnsRange) { - if ((startIndex >= column.first && startIndex <= column.last) || - (endIndex >= column.first && endIndex <= column.last)) { - return rowSpan; - } - } - } else if (stackedColumn.columnNames.isNotEmpty) { - final List children = stackedColumn.columnNames; - for (int child = 0; child < children.length; child++) { - if (children[child] == mappingName) { - return rowSpan; - } - } - } - } - - rowSpan += 1; - rowindex -= 1; - } - - return rowSpan; - } - - static List> _getConsecutiveRanges(List columnsIndex) { - int endIndex = 1; - final List> list = >[]; - if (columnsIndex.isEmpty) { - return list; - } - for (int i = 1; i <= columnsIndex.length; i++) { - if (i == columnsIndex.length || - columnsIndex[i] - columnsIndex[i - 1] != 1) { - if (endIndex == 1) { - list.add(columnsIndex.sublist(i - endIndex, (i - endIndex) + 1)); - } else { - list.add(columnsIndex.sublist(i - endIndex, i)); - } - endIndex = 1; - } else { - endIndex++; - } - } - return list; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart deleted file mode 100644 index 55b4b293a..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/sfdatagrid_helper.dart +++ /dev/null @@ -1,330 +0,0 @@ -part of datagrid; - -@protected -class _SfDataGridHelper { - /// Get the visible line based on view size and scroll offset. - /// Based on the [TextDirection] it will return visible lines info. - static _VisibleLinesCollection getVisibleLines( - _DataGridSettings _dataGridSettings) { - if (_dataGridSettings.textDirection == TextDirection.rtl) { - _dataGridSettings.container.scrollColumns.markDirty(); - } - - return _dataGridSettings.container.scrollColumns - .getVisibleLines(_dataGridSettings.textDirection == TextDirection.rtl); - } - - /// Helps to scroll the [SfDataGrid] vertically. - /// [canAnimate]: decide to apply animation on scrolling or not. - static Future scrollVertical( - _DataGridSettings dataGridSettings, double verticalOffset, - [bool canAnimate = false]) async { - final ScrollController? verticalController = - dataGridSettings.verticalScrollController; - - if (verticalController == null || !verticalController.hasClients) { - return; - } - - verticalOffset = - verticalOffset > verticalController.position.maxScrollExtent - ? verticalController.position.maxScrollExtent - : verticalOffset; - verticalOffset = verticalOffset.isNegative || verticalOffset == 0.0 - ? verticalController.position.minScrollExtent - : verticalOffset; - - if (canAnimate) { - await dataGridSettings.verticalScrollController!.animateTo(verticalOffset, - duration: const Duration(milliseconds: 1000), - curve: Curves.fastOutSlowIn); - } else { - dataGridSettings.verticalScrollController!.jumpTo(verticalOffset); - } - dataGridSettings.container.updateScrollBars(); - } - - /// Helps to scroll the [SfDataGrid] horizontally. - /// [canAnimate]: decide to apply animation on scrolling or not. - static Future scrollHorizontal( - _DataGridSettings dataGridSettings, double horizontalOffset, - [bool canAnimate = false]) async { - final ScrollController? horizontalController = - dataGridSettings.horizontalScrollController; - - if (horizontalController == null || !horizontalController.hasClients) { - return; - } - - horizontalOffset = - horizontalOffset > horizontalController.position.maxScrollExtent - ? horizontalController.position.maxScrollExtent - : horizontalOffset; - horizontalOffset = horizontalOffset.isNegative || horizontalOffset == 0.0 - ? horizontalController.position.minScrollExtent - : horizontalOffset; - - if (canAnimate) { - await dataGridSettings.horizontalScrollController!.animateTo( - horizontalOffset, - duration: const Duration(milliseconds: 1000), - curve: Curves.fastOutSlowIn); - } else { - dataGridSettings.horizontalScrollController!.jumpTo(horizontalOffset); - } - dataGridSettings.container.updateScrollBars(); - } - - /// Decide to enable swipe in [SfDataGrid] - static bool canSwipeRow(_DataGridSettings dataGridSettings, - DataGridRowSwipeDirection swipeDirection, double swipeOffset) { - if (dataGridSettings.container.horizontalOffset == 0) { - if ((dataGridSettings.container.extentWidth > - dataGridSettings.viewWidth) && - swipeDirection == DataGridRowSwipeDirection.endToStart && - swipeOffset <= 0) { - return false; - } else { - return true; - } - } else if (dataGridSettings.container.horizontalOffset == - dataGridSettings.container.extentWidth - dataGridSettings.viewWidth) { - if ((dataGridSettings.container.extentWidth > - dataGridSettings.viewWidth) && - swipeDirection == DataGridRowSwipeDirection.startToEnd && - swipeOffset >= 0) { - return false; - } else { - return true; - } - } else { - return false; - } - } - - /// Decide the swipe direction based on [TextDirection]. - static DataGridRowSwipeDirection getSwipeDirection( - _DataGridSettings dataGridSettings, double swipingOffset) { - return swipingOffset >= 0 - ? dataGridSettings.textDirection == TextDirection.ltr - ? DataGridRowSwipeDirection.startToEnd - : DataGridRowSwipeDirection.endToStart - : dataGridSettings.textDirection == TextDirection.ltr - ? DataGridRowSwipeDirection.endToStart - : DataGridRowSwipeDirection.startToEnd; - } - - /// Helps to get the [DataGridRow] based on respective rowIndex. - static DataGridRow getDataGridRow( - _DataGridSettings dataGridSettings, int rowIndex) { - final int recordIndex = - _GridIndexResolver.resolveToRecordIndex(dataGridSettings, rowIndex); - return dataGridSettings.source._effectiveRows[recordIndex]; - } - - /// Helps to get the [DataGridRowAdapter] based on respective [DataGridRow]. - static DataGridRowAdapter? getDataGridRowAdapter( - _DataGridSettings dataGridSettings, DataGridRow dataGridRow) { - DataGridRowAdapter buildBlankRow(DataGridRow dataGridRow) { - return DataGridRowAdapter( - cells: dataGridSettings.columns - .map( - (GridColumn dataCell) => SizedBox.fromSize(size: Size.zero)) - .toList()); - } - - return dataGridSettings.source.buildRow(dataGridRow) ?? - buildBlankRow(dataGridRow); - } - - /// Check the length of two list. - /// If its not satisfies it throw a exception. - static bool debugCheckTheLength( - int columnLength, int cellLength, String message) { - assert(() { - if (columnLength != cellLength) { - throw FlutterError.fromParts([ - ErrorSummary('$message: is not true'), - ]); - } - return true; - }()); - return true; - } - - /// Return the cumulative distance of frozen top rows. The cumulative distance - /// covered the header, stacked header and freeze pane - static double getCumulativeFrozenRowsHeight( - _DataGridSettings dataGridSettings) { - final int topFrozenRowsLength = dataGridSettings.container.frozenRows; - double cumulativeFrozenRowsHeight = 0.0; - for (int index = 0; index < topFrozenRowsLength; index++) { - cumulativeFrozenRowsHeight += - dataGridSettings.container.rowHeights[index]; - } - return cumulativeFrozenRowsHeight; - } - - /// Return the cumulative distance of frozen bottom rows. - static double getCumulativeFooterFrozenRowsHeight( - _DataGridSettings dataGridSettings) { - final int bottomFrozenRowsLength = - dataGridSettings.container.footerFrozenRows; - double cumulativeFooterFrozenRowsHeight = 0.0; - for (int index = 0; index < bottomFrozenRowsLength; index++) { - final int rowIndex = dataGridSettings.container.rowCount - index; - cumulativeFooterFrozenRowsHeight += - dataGridSettings.container.rowHeights[rowIndex]; - } - return cumulativeFooterFrozenRowsHeight; - } - - /// Return the cumulative distance of frozen column on left side. The - /// cumulative distance covered the row header, indent cell, freeze pane - static double getCumulativeFrozenColumnsWidth( - _DataGridSettings dataGridSettings) { - final int leftStaticColumnCount = dataGridSettings.container.frozenColumns; - double cumulativeFrozenColumnWidth = 0.0; - for (int index = 0; index < leftStaticColumnCount; index++) { - cumulativeFrozenColumnWidth += - dataGridSettings.container.columnWidths[index]; - } - return cumulativeFrozenColumnWidth; - } - - /// Return the cumulative distance of frozen right side columns. - static double getCumulativeFooterFrozenColumnsWidth( - _DataGridSettings dataGridSettings) { - final int rightStaticColumnCount = - dataGridSettings.container.footerFrozenColumns; - double cumulativeFooterFrozenColumnWidth = 0.0; - for (int index = 0; index < rightStaticColumnCount; index++) { - final int columnIndex = dataGridSettings.container.columnCount - index; - cumulativeFooterFrozenColumnWidth += - dataGridSettings.container.columnWidths[columnIndex]; - } - return cumulativeFooterFrozenColumnWidth; - } - - /// Resolve the cumulative horizontal offset with frozen rows. - static double resolveVerticalScrollOffset( - _DataGridSettings dataGridSettings, double verticalOffset) { - final double leftStaticOffset = - _SfDataGridHelper.getCumulativeFrozenRowsHeight(dataGridSettings); - final double rightStaticOffset = - _SfDataGridHelper.getCumulativeFooterFrozenRowsHeight(dataGridSettings); - final double bottomOffset = - dataGridSettings.container.extentHeight - rightStaticOffset; - if (verticalOffset >= bottomOffset) { - return dataGridSettings - .verticalScrollController!.position.maxScrollExtent; - } - - if (verticalOffset <= leftStaticOffset) { - return dataGridSettings - .verticalScrollController!.position.minScrollExtent; - } - - for (int i = 0; i < dataGridSettings.container.frozenRows; i++) { - verticalOffset -= dataGridSettings.container.rowHeights[i]; - } - - return verticalOffset; - } - - /// Resolve the cumulative horizontal offset with frozen column. - static double resolveHorizontalScrollOffset( - _DataGridSettings dataGridSettings, double horizontalOffset) { - final double topStaticOffset = - _SfDataGridHelper.getCumulativeFrozenColumnsWidth(dataGridSettings); - final double bottomStaticOffset = - _SfDataGridHelper.getCumulativeFooterFrozenColumnsWidth( - dataGridSettings); - final double rightOffset = - dataGridSettings.container.extentWidth - bottomStaticOffset; - if (horizontalOffset >= rightOffset) { - return dataGridSettings - .horizontalScrollController!.position.maxScrollExtent; - } - - if (horizontalOffset <= topStaticOffset) { - return dataGridSettings - .horizontalScrollController!.position.minScrollExtent; - } - - for (int i = 0; i < dataGridSettings.container.frozenColumns; i++) { - horizontalOffset -= dataGridSettings.container.columnWidths[i]; - } - - return horizontalOffset; - } - - /// Get the vertical offset with reduction of frozen rows - static double getVerticalOffset( - _DataGridSettings dataGridSettings, int rowIndex) { - if (rowIndex < 0) { - return dataGridSettings.container.verticalOffset; - } - - final double cumulativeOffset = - _SelectionHelper.getVerticalCumulativeDistance( - dataGridSettings, rowIndex); - - return resolveVerticalScrollOffset(dataGridSettings, cumulativeOffset); - } - - /// Get the vertical offset with reduction of frozen columns - static double getHorizontalOffset( - _DataGridSettings dataGridSettings, int columnIndex) { - if (columnIndex < 0) { - return dataGridSettings.container.horizontalOffset; - } - - final double cumulativeOffset = - _SelectionHelper.getHorizontalCumulativeDistance( - dataGridSettings, columnIndex); - - return resolveHorizontalScrollOffset(dataGridSettings, cumulativeOffset); - } - - /// Resolve the scroll offset to [DataGridScrollPosition]. - /// It's helps to get the position of rows and column scroll into desired - /// DataGridScrollPosition. - static double resolveScrollOffsetToPosition( - DataGridScrollPosition position, - _ScrollAxisBase scrollAxisBase, - double measuredScrollOffset, - double viewDimension, - double headerExtent, - double bottomExtent, - double defaultDimension, - double defaultScrollOffset, - int index) { - if (position == DataGridScrollPosition.center) { - measuredScrollOffset = measuredScrollOffset - - ((viewDimension - bottomExtent - headerExtent) / 2) + - (defaultDimension / 2); - } else if (position == DataGridScrollPosition.end) { - measuredScrollOffset = measuredScrollOffset - - (viewDimension - bottomExtent - headerExtent) + - defaultDimension; - } else if (position == DataGridScrollPosition.makeVisible) { - final _VisibleLinesCollection visibleLines = - scrollAxisBase.getVisibleLines(); - final int startIndex = - visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex; - final int endIndex = - visibleLines[visibleLines.lastBodyVisibleIndex].lineIndex; - if (index > startIndex && index < endIndex) { - measuredScrollOffset = defaultScrollOffset; - } - if (defaultScrollOffset - measuredScrollOffset < 0) { - measuredScrollOffset = measuredScrollOffset - - (viewDimension - bottomExtent - headerExtent) + - defaultDimension; - } - } - - return measuredScrollOffset; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart deleted file mode 100644 index 69919518e..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/column_sizer.dart +++ /dev/null @@ -1,868 +0,0 @@ -part of datagrid; - -/// Handles the sizing for all the columns in the [SfDataGrid]. -/// -/// You can override any available methods in this class to calculate the -/// column width based on your requirement and set the instance to the -/// [SfDataGrid.columnSizer]. -/// -/// ``` dart -/// class CustomGridColumnSizer extends ColumnSizer { -/// @override -/// double computeHeaderCellWidth(GridColumn column, TextStyle style) { -/// return super.computeHeaderCellWidth(column, style); -/// } -/// -/// @override -/// double computeCellWidth(GridColumn column, DataGridRow row, Object? cellValue, -/// TextStyle textStyle) { -/// return super.computeCellWidth(column, row, cellValue, textStyle); -/// } -/// -/// @override -/// double computeHeaderCellHeight(GridColumn column, TextStyle textStyle) { -/// return super.computeHeaderCellHeight(column, textStyle); -/// } -/// -/// @override -/// double computeCellHeight(GridColumn column, DataGridRow row, -/// Object? cellValue, TextStyle textStyle) { -/// return super.computeCellHeight(column, row, cellValue, textStyle); -/// } -/// } -/// -/// final CustomGridColumnSizer _customGridColumnSizer = CustomGridColumnSizer(); -/// -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: _employeeDataSource, -/// columnSizer: _customGridColumnSizer, -/// columnWidthMode: ColumnWidthMode.auto, -/// columns: [ -/// GridColumn(columnName: 'id', label: Text('ID')), -/// GridColumn(columnName: 'name', label: Text('Name')), -/// GridColumn(columnName: 'designation', label: Text('Designation')), -/// GridColumn(columnName: 'salary', label: Text('Salary')), -/// ); -/// } -/// ``` -class ColumnSizer { - /// Creates the [ColumnSizer] for [SfDataGrid] widget. - ColumnSizer() { - _isColumnSizerLoadedInitially = false; - } - - GridColumn? _autoFillColumn; - bool _isColumnSizerLoadedInitially = false; - - static const double _sortIconWidth = 20.0; - static const double _sortNumberWidth = 18.0; - - late _DataGridStateDetails _dataGridStateDetails; - - void _initialRefresh(double availableWidth) { - final _LineSizeCollection lineSizeCollection = - _dataGridStateDetails().container.columnWidths as _LineSizeCollection; - lineSizeCollection.suspendUpdates(); - _refresh(availableWidth); - lineSizeCollection.resumeUpdates(); - } - - void _refresh(double availableWidth) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final bool hasAnySizerColumn = dataGridSettings.columns.any( - (GridColumn column) => - (column.columnWidthMode != ColumnWidthMode.none) || - (column.width != double.nan) || - !column.visible); - - final _PaddedEditableLineSizeHostBase paddedEditableLineSizeHostBase = - dataGridSettings.container.columnWidths; - final _LineSizeCollection? lineSizeCollection = - paddedEditableLineSizeHostBase is _LineSizeCollection - ? paddedEditableLineSizeHostBase - : null; - - if (lineSizeCollection == null) { - return; - } - - lineSizeCollection.suspendUpdates(); - _ensureColumnVisibility(dataGridSettings); - - if (dataGridSettings.columnWidthMode != ColumnWidthMode.none || - hasAnySizerColumn) { - _sizerColumnWidth(dataGridSettings, availableWidth); - } - dataGridSettings.container.updateScrollBars(); - lineSizeCollection.resumeUpdates(); - } - - void _ensureColumnVisibility(_DataGridSettings dataGridSettings) { - for (final GridColumn column in dataGridSettings.columns) { - final int index = dataGridSettings.columns.indexOf(column); - dataGridSettings.container.columnWidths - .setHidden(index, index, !column.visible); - } - // Columns will be auto sized only if Columns doesn't have explicit width - // defined. - _sizerColumnWidth(dataGridSettings, 0.0); - } - - void _sizerColumnWidth( - _DataGridSettings dataGridSettings, double viewPortWidth) { - double totalColumnSize = 0.0; - final List calculatedColumns = []; - - _autoFillColumn = _getColumnToFill(dataGridSettings); - - // Hide Hidden columns - final List hiddenColumns = dataGridSettings.columns - .where((GridColumn column) => !column.visible) - .toList(); - for (final GridColumn column in hiddenColumns) { - final int index = _GridIndexResolver.resolveToScrollColumnIndex( - dataGridSettings, dataGridSettings.columns.indexOf(column)); - dataGridSettings.container.columnWidths.setHidden(index, index, true); - calculatedColumns.add(column); - } - - // Set width based on Column.Width - final List widthColumns = dataGridSettings.columns - .skipWhile((GridColumn column) => !column.visible) - .where((GridColumn column) => !(column.width).isNaN) - .toList(); - for (final GridColumn column in widthColumns) { - totalColumnSize += - _setColumnWidth(dataGridSettings, column, column.width); - calculatedColumns.add(column); - } - - // Set width based on fitByCellValue mode - final List fitByCellValueColumns = dataGridSettings.columns - .skipWhile((GridColumn column) => !column.visible) - .where((GridColumn column) => - column.columnWidthMode == ColumnWidthMode.fitByCellValue && - column.width.isNaN) - .toList(); - for (final GridColumn column in fitByCellValueColumns) { - if (column._autoWidth.isNaN) { - final double columnWidth = _getWidthBasedOnColumn( - dataGridSettings, column, ColumnWidthMode.fitByCellValue); - totalColumnSize += columnWidth; - _setAutoWidth(column, columnWidth); - } else { - totalColumnSize += - _setColumnWidth(dataGridSettings, column, column._autoWidth); - } - calculatedColumns.add(column); - } - - // Set width based on fitByColumnName mode - final List fitByColumnNameColumns = dataGridSettings.columns - .skipWhile((GridColumn column) => !column.visible) - .where((GridColumn column) => - column.columnWidthMode == ColumnWidthMode.fitByColumnName && - column.width.isNaN) - .toList(); - for (final GridColumn column in fitByColumnNameColumns) { - totalColumnSize += _getWidthBasedOnColumn( - dataGridSettings, column, ColumnWidthMode.fitByColumnName); - calculatedColumns.add(column); - } - - // Set width based on auto and lastColumnFill - List autoColumns = dataGridSettings.columns - .where((GridColumn column) => - column.columnWidthMode == ColumnWidthMode.auto && - column.visible && - column.width.isNaN) - .toList(); - - final List lastColumnFill = dataGridSettings.columns - .skipWhile((GridColumn column) => calculatedColumns.contains(column)) - .where((GridColumn col) => - col.columnWidthMode == ColumnWidthMode.lastColumnFill && - !_isLastFillColum(col)) - .toList(); - - autoColumns = (autoColumns + lastColumnFill).toSet().toList(); - - for (final GridColumn column in autoColumns) { - if (column._autoWidth.isNaN) { - final double columnWidth = _getWidthBasedOnColumn( - dataGridSettings, column, ColumnWidthMode.auto); - totalColumnSize += columnWidth; - _setAutoWidth(column, columnWidth); - } else { - totalColumnSize += - _setColumnWidth(dataGridSettings, column, column._autoWidth); - } - calculatedColumns.add(column); - } - _setWidthBasedOnGrid( - dataGridSettings, totalColumnSize, calculatedColumns, viewPortWidth); - _autoFillColumn = null; - } - - GridColumn? _getColumnToFill(_DataGridSettings dataGridSettings) { - final GridColumn? column = dataGridSettings.columns.lastWhereOrNull( - (GridColumn c) => - c.visible && - c.width.isNaN && - c.columnWidthMode == ColumnWidthMode.lastColumnFill); - if (column != null) { - return column; - } else { - if (dataGridSettings.columnWidthMode == ColumnWidthMode.lastColumnFill) { - final GridColumn? lastColumn = dataGridSettings.columns - .lastWhereOrNull((GridColumn c) => c.visible && c.width.isNaN); - if (lastColumn == null) { - return null; - } - - if (lastColumn.columnWidthMode == ColumnWidthMode.none) { - return lastColumn; - } - } - } - return null; - } - - void _setWidthBasedOnGrid( - _DataGridSettings dataGridSettings, - double totalColumnSize, - List calculatedColumns, - double viewPortWidth) { - for (final GridColumn column in dataGridSettings.columns) { - if (calculatedColumns.contains(column) || - column.columnWidthMode == ColumnWidthMode.fill || - _isLastFillColum(column)) { - continue; - } - - switch (dataGridSettings.columnWidthMode) { - case ColumnWidthMode.fitByCellValue: - if (column._autoWidth.isNaN) { - final double columnWidth = _getWidthBasedOnColumn( - dataGridSettings, column, ColumnWidthMode.fitByCellValue); - totalColumnSize += columnWidth; - _setAutoWidth(column, columnWidth); - } else { - totalColumnSize += - _setColumnWidth(dataGridSettings, column, column._autoWidth); - } - calculatedColumns.add(column); - break; - case ColumnWidthMode.fitByColumnName: - totalColumnSize += _getWidthBasedOnColumn( - dataGridSettings, column, ColumnWidthMode.fitByColumnName); - calculatedColumns.add(column); - break; - case ColumnWidthMode.auto: - case ColumnWidthMode.lastColumnFill: - if (column._autoWidth.isNaN) { - final double columnWidth = _getWidthBasedOnColumn( - dataGridSettings, column, ColumnWidthMode.auto); - totalColumnSize += columnWidth; - _setAutoWidth(column, columnWidth); - } else { - totalColumnSize += - _setColumnWidth(dataGridSettings, column, column._autoWidth); - } - calculatedColumns.add(column); - break; - case ColumnWidthMode.none: - if (column.visible) { - totalColumnSize += _setColumnWidth(dataGridSettings, column, - dataGridSettings.container.columnWidths.defaultLineSize); - calculatedColumns.add(column); - } - break; - default: - break; - } - } - - final List remainingColumns = []; - - for (final GridColumn column in dataGridSettings.columns) { - if (!calculatedColumns.contains(column)) { - remainingColumns.add(column); - } - } - - final double remainingColumnWidths = viewPortWidth - totalColumnSize; - - if (remainingColumnWidths > 0 && - (totalColumnSize != 0 || - (totalColumnSize == 0 && remainingColumns.length == 1) || - (dataGridSettings.columns.any((GridColumn col) => - col.columnWidthMode == ColumnWidthMode.fill) || - dataGridSettings.columnWidthMode == ColumnWidthMode.fill))) { - _setFillWidth(dataGridSettings, remainingColumnWidths, remainingColumns); - } else { - _setRemainingColumnsWidth(dataGridSettings, remainingColumns); - } - } - - double _getWidthBasedOnColumn(_DataGridSettings dataGridSettings, - GridColumn column, ColumnWidthMode columnWidthMode) { - double width = 0.0; - switch (columnWidthMode) { - case ColumnWidthMode.fitByCellValue: - width = _calculateAllCellsExceptHeaderWidth(column); - break; - case ColumnWidthMode.fitByColumnName: - width = _calculateColumnHeaderWidth(column); - break; - case ColumnWidthMode.auto: - width = _calculateAllCellsWidth(column); - break; - default: - return width; - } - return _setColumnWidth(dataGridSettings, column, width); - } - - double _calculateAllCellsWidth(GridColumn column) { - final double headerWidth = - _calculateColumnHeaderWidth(column, setWidth: false); - final double cellWidth = - _calculateAllCellsExceptHeaderWidth(column, setWidth: false); - return _getColumnWidth(column, max(cellWidth, headerWidth)); - } - - double _calculateColumnHeaderWidth(GridColumn column, - {bool setWidth = true}) { - final double width = - _getHeaderCellWidth(column) + _getSortIconWidth(column); - _updateSetWidth(setWidth, column, width); - return width; - } - - double _calculateAllCellsExceptHeaderWidth(GridColumn column, - {bool setWidth = true}) { - final double width = _calculateCellWidth(column); - _updateSetWidth(setWidth, column, width); - return width; - } - - void _updateSetWidth(bool setWidth, GridColumn column, double columnWidth) { - if (setWidth) { - column._actualWidth = columnWidth; - } - } - - double _calculateCellWidth(GridColumn column) { - double autoFitWidth = 0.0; - int startRowIndex, endRowIndex; - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - if (dataGridSettings.source.rows.isEmpty) { - return double.nan; - } - - switch (dataGridSettings.columnWidthCalculationRange) { - case ColumnWidthCalculationRange.allRows: - startRowIndex = 0; - endRowIndex = dataGridSettings.source.rows.length - 1; - break; - case ColumnWidthCalculationRange.visibleRows: - final _VisibleLinesCollection visibleLines = - dataGridSettings.container.scrollRows.getVisibleLines( - dataGridSettings.textDirection == TextDirection.rtl); - startRowIndex = - visibleLines.firstBodyVisibleIndex <= visibleLines.length - 1 - ? visibleLines.firstBodyVisibleIndex - : 0; - endRowIndex = visibleLines.lastBodyVisibleIndex; - break; - } - - for (int rowIndex = startRowIndex; rowIndex <= endRowIndex; rowIndex++) { - autoFitWidth = max(_getCellWidth(column, rowIndex), autoFitWidth); - } - - return autoFitWidth; - } - - double _getHeaderCellWidth(GridColumn column) { - return computeHeaderCellWidth( - column, _getDefaultTextStyle(_dataGridStateDetails(), true)); - } - - double _getCellWidth(GridColumn column, int rowIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (_GridIndexResolver.isFooterWidgetRow(rowIndex, dataGridSettings)) { - return 0.0; - } - - late DataGridRow dataGridRow; - switch (dataGridSettings.columnWidthCalculationRange) { - case ColumnWidthCalculationRange.allRows: - dataGridRow = dataGridSettings.source._effectiveRows[rowIndex]; - break; - case ColumnWidthCalculationRange.visibleRows: - dataGridRow = - _SfDataGridHelper.getDataGridRow(dataGridSettings, rowIndex); - break; - } - return _measureCellWidth( - _getCellValue(dataGridRow, column), column, dataGridRow); - } - - Object? _getCellValue(DataGridRow dataGridRow, GridColumn column) { - return dataGridRow - .getCells() - .firstWhereOrNull( - (DataGridCell cell) => cell.columnName == column.columnName) - ?.value; - } - - void _setFillWidth(_DataGridSettings dataGridSettings, - double remainingColumnWidth, List remainingColumns) { - final List removedColumns = []; - final List columns = remainingColumns; - double totalRemainingFillValue = remainingColumnWidth; - - double removedWidth = 0; - GridColumn? fillColumn; - bool isRemoved; - while (columns.isNotEmpty) { - isRemoved = false; - removedWidth = 0; - final double fillWidth = - (totalRemainingFillValue / columns.length).floorToDouble(); - final GridColumn column = columns.first; - if (column == _autoFillColumn && - (column.columnWidthMode == ColumnWidthMode.lastColumnFill || - dataGridSettings.columnWidthMode == - ColumnWidthMode.lastColumnFill)) { - columns.remove(column); - fillColumn = column; - continue; - } - - final double computedWidth = - _setColumnWidth(dataGridSettings, column, fillWidth); - if (fillWidth != computedWidth && fillWidth > 0) { - isRemoved = true; - columns.remove(column); - for (final GridColumn removedColumn in removedColumns) { - if (!columns.contains(removedColumn)) { - removedWidth += removedColumn._actualWidth; - columns.add(removedColumn); - } - } - removedColumns.clear(); - totalRemainingFillValue += removedWidth; - } - - column._actualWidth = computedWidth; - totalRemainingFillValue -= computedWidth; - if (!isRemoved) { - columns.remove(column); - if (!removedColumns.contains(column)) { - removedColumns.add(column); - } - } - } - - if (fillColumn != null) { - double columnWidth = 0.0; - if (fillColumn._autoWidth.isNaN) { - _setAutoWidth(fillColumn, columnWidth); - } else { - columnWidth = fillColumn._autoWidth; - } - - _setColumnWidth(dataGridSettings, fillColumn, - max(totalRemainingFillValue, columnWidth)); - } - } - - void _setRemainingColumnsWidth( - _DataGridSettings dataGridSettings, List remainingColumns) { - for (final GridColumn column in remainingColumns) { - if (_isLastFillColum(column) || - !_isFillColumn(dataGridSettings, column)) { - _setColumnWidth(dataGridSettings, column, - dataGridSettings.container.columnWidths.defaultLineSize); - } - } - } - - bool _isFillColumn(_DataGridSettings dataGridSettings, GridColumn column) { - if (!column.width.isNaN) { - return false; - } else { - return column.columnWidthMode == ColumnWidthMode.none - ? dataGridSettings.columnWidthMode == ColumnWidthMode.fill - : column.columnWidthMode == ColumnWidthMode.fill; - } - } - - bool _isLastFillColum(GridColumn column) => column == _autoFillColumn; - - void _setAutoWidth(GridColumn? column, double width) { - if (column != null) { - column._autoWidth = width; - } - } - - void _resetAutoCalculation() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - for (final GridColumn column in dataGridSettings.columns) { - column._autoWidth = double.nan; - } - } - - double _getSortIconWidth(GridColumn column) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - double width = 0.0; - if (column.allowSorting && dataGridSettings.allowSorting) { - width += _sortIconWidth; - if (dataGridSettings.allowMultiColumnSorting && - dataGridSettings.showSortNumbers) { - width += _sortNumberWidth; - } - } - return width; - } - - double _setColumnWidth(_DataGridSettings dataGridSettings, GridColumn column, - double columnWidth) { - final int columnIndex = dataGridSettings.columns.indexOf(column); - final double width = _getColumnWidth(column, columnWidth); - column._actualWidth = width; - dataGridSettings.container.columnWidths[columnIndex] = column._actualWidth; - return width; - } - - double _getColumnWidth(GridColumn column, double columnWidth) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final int columnIndex = dataGridSettings.columns.indexOf(column); - if (column.width < column._actualWidth) { - return columnWidth; - } - - final double width = dataGridSettings.container.columnWidths[columnIndex]; - return _checkWidthConstraints(column, columnWidth, width); - } - - double _checkWidthConstraints( - GridColumn column, double width, double columnWidth) { - if (!column.minimumWidth.isNaN || !column.maximumWidth.isNaN) { - if (!column.maximumWidth.isNaN) { - if (!width.isNaN && column.maximumWidth > width) { - columnWidth = width; - } else { - columnWidth = column.maximumWidth; - } - } - - if (!column.minimumWidth.isNaN) { - if (!width.isNaN && column.minimumWidth < width) { - if (width > column.maximumWidth) { - columnWidth = column.maximumWidth; - } else { - columnWidth = width; - } - } else { - columnWidth = column.minimumWidth; - } - } - } else { - if (!width.isNaN) { - columnWidth = width; - } - } - return columnWidth; - } - - /// Calculates the width of the header cell based on the [GridColumn.columnName]. - /// You can override this method to perform the custom calculation for height. - /// - /// If you want to calculate the width based on different [TextStyle], you can - /// override this method and call the super method with the required [TextStyle]. - /// Set the custom [ColumnSizer] to [SfDataGrid.columnSizer] property. - /// - /// ``` dart - ///class CustomColumnSizer extends ColumnSizer { - /// @override - /// double computeHeaderCellWidth(GridColumn column, - /// TextStyle textStyle) { - /// TextStyle style = textStyle; - /// if (column.columnName == 'Name') - /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); - /// return super.computeHeaderCellWidth(column, style); - /// } - ///} - ///``` - /// - /// The auto size is calculated based on default [TextStyle] of the datagrid. - @protected - double computeHeaderCellWidth(GridColumn column, TextStyle style) { - return _calculateTextSize( - column: column, - textStyle: style, - width: double.infinity, - value: column.columnName, - rowIndex: _GridIndexResolver.getHeaderIndex(_dataGridStateDetails()), - ).width.roundToDouble(); - } - - /// Calculates the width of the cell based on the [DataGridCell.value]. You - /// can override this method to perform the custom calculation for width. - /// - /// If you want to calculate the width based on different [TextStyle], you can - /// override this method and return the super method with the required [TextStyle]. - /// Set the custom [ColumnSizer] to [SfDataGrid.columnSizer] property. - /// - /// The following example show how to pass the different TextStyle for width calculation, - /// - /// ``` dart - ///class CustomColumnSizer extends ColumnSizer { - /// @override - /// double computeCellWidth(GridColumn column, DataGridRow row, Object cellValue, - /// TextStyle textStyle) { - /// TextStyle style = textStyle; - /// if (column.columnName == 'Name') - /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); - /// return super.computeCellWidth(column, row, cellValue, style); - /// } - ///} - ///``` - /// - /// The auto size is calculated based on default [TextStyle] of the datagrid. - @protected - double computeCellWidth(GridColumn column, DataGridRow row, Object? cellValue, - TextStyle textStyle) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final int rowIndex = _GridIndexResolver.resolveToRowIndex( - dataGridSettings, dataGridSettings.source._effectiveRows.indexOf(row)); - - return _calculateTextSize( - column: column, - value: cellValue, - rowIndex: rowIndex, - textStyle: textStyle, - width: double.infinity) - .width - .roundToDouble(); - } - - /// Calculates the height of the header cell based on the [GridColumn.columnName]. - /// - /// ``` dart - ///class CustomColumnSizer extends ColumnSizer { - /// @override - /// double computeHeaderCellHeight(GridColumn column, - /// TextStyle textStyle) { - /// TextStyle style = textStyle; - /// if (column.columnName == 'Name') - /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); - /// return super.computeHeaderCellHeight(column, style); - /// } - ///} - ///``` - /// - /// The auto size is calculated based on default [TextStyle] of the datagrid. - @protected - double computeHeaderCellHeight(GridColumn column, TextStyle textStyle) { - return _measureCellHeight( - column, - _GridIndexResolver.getHeaderIndex(_dataGridStateDetails()), - column.columnName, - textStyle); - } - - /// Calculates the height of the cell based on the [DataGridCell.value]. - /// You can override this method to perform the custom calculation for hight. - /// - /// If you want to calculate the width based on different [TextStyle], you can - /// override this method and call the super method with the required [TextStyle]. - /// Set the custom [ColumnSizer] to [SfDataGrid.columnSizer] property. - /// - /// ``` dart - ///class CustomColumnSizer extends ColumnSizer { - /// @override - /// double computeCellHeight(GridColumn column, DataGridRow row, Object cellValue, - /// TextStyle textStyle) { - /// TextStyle style = textStyle; - /// if (column.columnName == 'Name') - /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); - /// return super.computeCellWidth(column, row, cellValue, style); - /// } - ///} - ///``` - /// - /// The auto size is calculated based on default [TextStyle] of the datagrid. - @protected - double computeCellHeight(GridColumn column, DataGridRow row, - Object? cellValue, TextStyle textStyle) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final int rowIndex = _GridIndexResolver.resolveToRowIndex( - dataGridSettings, dataGridSettings.source._effectiveRows.indexOf(row)); - return _measureCellHeight(column, rowIndex, cellValue, textStyle); - } - - double _getAutoFitRowHeight(int rowIndex, - {bool canIncludeHiddenColumns = false, - List excludedColumns = const []}) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - double autoFitHeight = 0.0; - if (dataGridSettings.stackedHeaderRows.isNotEmpty && - rowIndex <= dataGridSettings.stackedHeaderRows.length - 1) { - return dataGridSettings.headerRowHeight; - } - - for (int index = 0; index < dataGridSettings.columns.length; index++) { - final GridColumn column = dataGridSettings.columns[index]; - if ((!column.visible && !canIncludeHiddenColumns) || - excludedColumns.contains(column.columnName)) { - continue; - } - - autoFitHeight = max(_getRowHeight(column, rowIndex), autoFitHeight); - } - return autoFitHeight; - } - - double _getRowHeight(GridColumn column, int rowIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (rowIndex == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - return computeHeaderCellHeight( - column, _getDefaultTextStyle(dataGridSettings, true)); - } else { - final DataGridRow row = - _SfDataGridHelper.getDataGridRow(dataGridSettings, rowIndex); - return computeCellHeight(column, row, _getCellValue(row, column), - _getDefaultTextStyle(dataGridSettings, false)); - } - } - - double _measureCellWidth( - Object? cellValue, GridColumn column, DataGridRow dataGridRow) { - return computeCellWidth(column, dataGridRow, cellValue, - _getDefaultTextStyle(_dataGridStateDetails(), false)); - } - - double _measureCellHeight( - GridColumn column, int rowIndex, Object? cellValue, TextStyle textStyle) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final int columnIndex = dataGridSettings.columns.indexOf(column); - double columnWidth = !column.visible || column.width == 0.0 - ? dataGridSettings.defaultColumnWidth - : dataGridSettings.container.columnWidths[columnIndex]; - - final double strokeWidth = _getGridLineStrokeWidth( - rowIndex: rowIndex, dataGridSettings: dataGridSettings) - .width; - - 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. - columnWidth -= _getSortIconWidth(column) + horizontalPadding + strokeWidth; - - return _calculateTextSize( - column: column, - value: cellValue, - width: columnWidth, - textStyle: textStyle, - rowIndex: rowIndex, - ).height.roundToDouble(); - } - - TextStyle _getDefaultTextStyle( - _DataGridSettings dataGridSettings, bool isHeader) { - final bool isLight = - dataGridSettings.dataGridThemeData!.brightness == Brightness.light; - if (isHeader) { - return isLight - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Colors.black87) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w500, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)); - } else { - return isLight - ? const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Colors.black87) - : const TextStyle( - fontFamily: 'Roboto', - fontWeight: FontWeight.w400, - fontSize: 14, - color: Color.fromRGBO(255, 255, 255, 1)); - } - } - - Size _getGridLineStrokeWidth( - {required int rowIndex, required _DataGridSettings dataGridSettings}) { - final double strokeWidth = - dataGridSettings.dataGridThemeData!.gridLineStrokeWidth; - - final GridLinesVisibility gridLinesVisibility = - rowIndex <= _GridIndexResolver.getHeaderIndex(dataGridSettings) - ? dataGridSettings.headerGridLinesVisibility - : dataGridSettings.gridLinesVisibility; - - switch (gridLinesVisibility) { - case GridLinesVisibility.none: - return Size.zero; - case GridLinesVisibility.both: - return Size(strokeWidth, strokeWidth); - case GridLinesVisibility.vertical: - return Size(strokeWidth, 0); - case GridLinesVisibility.horizontal: - return Size(0, strokeWidth); - } - } - - Size _calculateTextSize({ - required Object? value, - required int rowIndex, - required double width, - required GridColumn column, - required TextStyle textStyle, - }) { - late Size textSize; - - // TextPainter's maxWidth should not be less than or equal to its minWidth. - // So, We have restricted the behavior by providing a default width to 10.0 - // when it reached the limit While resizing the column. - width = max(width, 10.0); - - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - final Size strokeWidthSize = _getGridLineStrokeWidth( - rowIndex: rowIndex, dataGridSettings: dataGridSettings); - - final TextPainter textPainter = TextPainter( - text: TextSpan(text: value?.toString() ?? '', style: textStyle), - textScaleFactor: dataGridSettings.textScaleFactor, - textDirection: dataGridSettings.textDirection) - ..layout(maxWidth: width); - - textSize = Size( - textPainter.size.width + - strokeWidthSize.width + - column.autoFitPadding.horizontal, - textPainter.size.height + - strokeWidthSize.height + - column.autoFitPadding.vertical); - - return textSize; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart deleted file mode 100644 index b04cd0878..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/grid_column.dart +++ /dev/null @@ -1,172 +0,0 @@ -part of datagrid; - -/// Provides the base functionalities for all the column types in [SfDataGrid]. -class GridColumn { - /// Creates the [GridColumn] for [SfDataGrid] widget. - GridColumn( - {required this.columnName, - required this.label, - this.columnWidthMode = ColumnWidthMode.none, - this.visible = true, - this.allowSorting = true, - this.autoFitPadding = const EdgeInsets.all(16.0), - this.minimumWidth = double.nan, - this.maximumWidth = double.nan, - this.width = double.nan, - this.allowEditing = true}) { - _actualWidth = double.nan; - _autoWidth = double.nan; - } - - late double _autoWidth; - - /// The label of column header. - /// - /// Typically, this will be [Text] widget. You can also set [Icon] - /// (Typically using size 18), or a [Row] with an icon and [Text]. - /// - /// If you want to take the entire space for widget, - /// e.g. when you want to use [Center], you can wrap it with an [Expanded]. - /// - /// The widget will be loaded in text area alone. When sorting is applied, - /// the default sort icon will be loaded along with the widget. - final Widget label; - - /// How the column widths are determined. - /// - /// This takes higher priority than [SfDataGrid.columnWidthMode]. - /// - /// Defaults to [ColumnWidthMode.none] - /// - /// Also refer [ColumnWidthMode] - final ColumnWidthMode columnWidthMode; - - /// The name of a column.The name should be unique. - /// - /// This must not be empty or null. - final String columnName; - - /// The actual display width of the column when auto fitted based on - /// [SfDataGrid.columnWidthMode] or [columnWidthMode]. - /// - /// Defaults to [double.nan] - late double _actualWidth; - - /// The minimum width of the column. - /// - /// The column width could not be set or auto sized lesser than the - /// [minimumWidth] - /// - /// Defaults to [double.nan] - final double minimumWidth; - - /// The maximum width of the column. - /// - /// The column width could not be set or auto sized greater than the - /// [maximumWidth] - /// - /// Defaults to [double.nan] - final double maximumWidth; - - /// The width of the column. - /// - /// If value is lesser than [minimumWidth], then [minimumWidth] - /// is set to [width]. - /// Otherwise, If value is greater than [maximumWidth], then the - /// [maximumWidth] is set to [width]. - /// - /// Defaults to [double.nan] - final double width; - - /// Whether column should be hidden. - /// - /// Defaults to false. - final bool visible; - - /// Decides whether user can sort the column simply by tapping the column - /// header. - /// - /// Defaults to true. - /// - /// This is applicable only if the [SfDataGrid.allowSorting] is set as true. - /// - /// See also: - /// - /// * [SfDataGrid.allowSorting] – which allows users to sort the columns in - /// [SfDataGrid]. - /// * [DataGridSource.sortedColumns] - which is the collection of - /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. - /// * [DataGridSource.sort] - call this method when you are adding the - /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. - final bool allowSorting; - - /// Decides whether cell should be moved into edit mode based on - /// [SfDataGrid.editingGestureType]. - /// - /// Defaults to false. - /// - /// Editing can be enabled only if the [SfDataGrid.selectionMode] is other - /// than none and [SfDataGrid.navigationMode] is cell. - /// - /// See also, - /// - /// You can load the required widget on editing using - /// [DataGridSource.buildEditWidget] method. - /// - /// See also, - /// [DataGridSource.onCellBeginEdit]- This will be triggered when a cell is - /// moved to edit mode. - /// [DataGridSource.onCellSubmit] – This will be triggered when the cell’s - /// editing is completed. - final bool allowEditing; - - /// The amount of space which should be added with the auto size calculated - /// when you use [SfDataGrid.columnWidthMode] as [ColumnWidthMode.auto] or - /// [ColumnWidthMode.fitByCellValue] or [ColumnWidthMode.fitByColumnName] - /// option. - final EdgeInsets autoFitPadding; -} - -/// A column which displays the values of the string in its cells. -/// -/// This column has all the required APIs to customize the widget [Text] as it -/// displays [Text] for all the cells. -/// -/// ``` dart -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: employeeDataSource, -/// columns: [ -/// GridTextColumn(columnName: 'name', label: Text('Name')), -/// GridTextColumn(columnName: 'designation', label: Text('Designation')), -/// ], -/// ); -/// } -/// ``` -@Deprecated('Use GridColumn instead.') -class GridTextColumn extends GridColumn { - /// Creates a String column using [columnName] and [label]. - GridTextColumn({ - required String columnName, - required Widget label, - ColumnWidthMode columnWidthMode = ColumnWidthMode.none, - EdgeInsets autoFitPadding = const EdgeInsets.all(16.0), - bool visible = true, - bool allowSorting = true, - double minimumWidth = double.nan, - double maximumWidth = double.nan, - double width = double.nan, - bool allowEditing = true, - }) : super( - columnName: columnName, - label: label, - columnWidthMode: columnWidthMode, - autoFitPadding: autoFitPadding, - visible: visible, - allowSorting: allowSorting, - minimumWidth: minimumWidth, - maximumWidth: maximumWidth, - width: width, - allowEditing: allowEditing); -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart deleted file mode 100644 index d4a861a03..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/runtime/stacked_header.dart +++ /dev/null @@ -1,46 +0,0 @@ -part of datagrid; - -/// Row configuration for stacked header in [SfDataGrid]. The columns for this -/// stacked header row are provided in the [StackedHeaderCell] property of the -/// [StackedHeaderRow] object. -/// -/// See also: -/// -/// [StackedHeaderCell] – which provides the configuration for column in stacked -/// header row. -class StackedHeaderRow { - /// Creates the [StackedHeaderRow] for [SfDataGrid] widget. - StackedHeaderRow({required this.cells}); - - /// The collection of [StackedHeaderCell] in stacked header row. - List cells; -} - -/// Column configuration for stacked header row in `SfDataGrid`. -/// -/// See also: -/// -/// [StackedHeaderRow] – which provides configuration for stacked header row. -class StackedHeaderCell { - /// Creates the [StackedHeaderCell] for [StackedHeaderRow]. - StackedHeaderCell({required this.columnNames, required this.child}) { - _childColumnIndexes = []; - } - - /// The collection of string which is the [GridColumn.columnName] of the - /// columns defined in the [SfDataGrid]. - /// - /// The columns are spanned as a stacked header based on this collection. If - /// the given collection has the sequence of columns which are presented in - /// the [SfDataGrid], those columns will be spanned. Otherwise, stacked header - /// is added for each column which are not in sequence order in regular - /// columns. - final List columnNames; - - /// The widget that represents the data of this cell. - /// - /// Typically, a [Text] widget. - final Widget child; - - late List _childColumnIndexes; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart deleted file mode 100644 index f422127ff..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/scrollview_widget.dart +++ /dev/null @@ -1,861 +0,0 @@ -part of datagrid; - -class _ScrollViewWidget extends StatefulWidget { - const _ScrollViewWidget( - {required this.dataGridStateDetails, - required this.width, - required this.height}); - - final _DataGridStateDetails dataGridStateDetails; - final double width; - final double height; - - @override - State createState() => _ScrollViewWidgetState(); -} - -class _ScrollViewWidgetState extends State<_ScrollViewWidget> { - ScrollController? _verticalController; - ScrollController? _horizontalController; - FocusNode? _dataGridFocusNode; - double _width = 0.0; - double _height = 0.0; - bool _isScrolling = false; - - // This flag is used to restrict the load more view from being shown for - // every moment when datagrid reached at bottom on vertical scrolling. - bool _isLoadMoreViewLoaded = false; - - @override - void initState() { - _verticalController = _dataGridSettings.verticalScrollController; - _verticalController!.addListener(_verticalListener); - _horizontalController = _dataGridSettings.horizontalScrollController; - _horizontalController!.addListener(_horizontalListener); - _height = widget.height; - _width = widget.width; - - _dataGridSettings.rowSelectionManager - .addListener(_handleSelectionController); - - if (_dataGridFocusNode == null) { - // [FocusNode.onKey] callback is not firing on key navigation after flutter - // 2.2.0 and its breaking our key navigation on tab, shift + tab, arrow keys. - // So, we have used the focus widget and its need to remove when below - // mentioned issue is resolved on framework end. - //_dataGridFocusNode = FocusNode(onKey: _handleFocusKeyOperation); - _dataGridFocusNode = FocusNode(); - _dataGridSettings.dataGridFocusNode = _dataGridFocusNode!; - if (_dataGridSettings.source.sortedColumns.isNotEmpty) { - _dataGridFocusNode!.requestFocus(); - } - } - - super.initState(); - } - - _DataGridSettings get _dataGridSettings => widget.dataGridStateDetails(); - - _VisualContainerHelper get _container => _dataGridSettings.container; - - _RowGenerator get _rowGenerator => _dataGridSettings.rowGenerator; - - SelectionManagerBase get _rowSelectionManager => - _dataGridSettings.rowSelectionManager; - - void _verticalListener() { - setState(() { - final double newValue = _verticalController!.offset; - _container.verticalOffset = newValue; - _container.setRowHeights(); - _container.resetSwipeOffset(); - _isScrolling = true; - _container._isDirty = true; - _dataGridSettings.scrollingState = ScrollDirection.forward; - _isLoadMoreViewLoaded = false; - }); - } - - void _horizontalListener() { - setState(() { - final double newValue = _horizontalController!.offset; - _container.horizontalOffset = newValue; - // Updating the width of all columns initially and inside the - // `ScrollViewWidget` build method when setting the container_isDirty to - // `true`. Thus, Don't necessary to update column widths while horizontal - // scrolling. - // _dataGridSettings.columnSizer._refresh(widget.width); - _container.resetSwipeOffset(); - _dataGridSettings.scrollingState = ScrollDirection.forward; - _isScrolling = true; - _container._isDirty = true; - }); - } - - void _updateAxis() { - final double width = _width; - final double height = _height; - _container.updateAxis(Size(width, height)); - } - - void _setHorizontalOffset() { - if (_container._needToSetHorizontalOffset) { - _container.horizontalOffset = _horizontalController!.hasClients - ? _horizontalController!.offset - : 0.0; - _container.scrollColumns.markDirty(); - } - - _container._needToSetHorizontalOffset = false; - } - - void _updateColumnSizer() { - final ColumnSizer columnSizer = _dataGridSettings.columnSizer; - if (columnSizer._isColumnSizerLoadedInitially) { - columnSizer - .._initialRefresh(widget.width) - .._isColumnSizerLoadedInitially = false; - } else { - columnSizer._refresh(widget.width); - } - } - - void _ensureWidgets() { - if (!_container._isPreGenerator) { - _container.preGenerateItems(); - } else { - if (_container._needToRefreshColumn) { - _ensureItems(true); - _container._needToRefreshColumn = false; - } else { - _ensureItems(false); - } - } - } - - void _ensureItems(bool needToRefresh) { - final _VisibleLinesCollection visibleRows = - _container.scrollRows.getVisibleLines(); - final _VisibleLinesCollection visibleColumns = - _SfDataGridHelper.getVisibleLines(_rowGenerator.dataGridStateDetails()); - - if (_container._isGridLoaded && visibleColumns.isNotEmpty) { - _rowGenerator._ensureRows(visibleRows, visibleColumns); - } - - if (needToRefresh) { - if (visibleColumns.isNotEmpty) { - _rowGenerator._ensureColumns(visibleColumns); - } - } - } - - Widget _buildScrollView(double extentWidth, double scrollViewHeight, - double extentHeight, Size containerSize) { - Widget scrollView = Scrollbar( - isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, - controller: _verticalController, - child: SingleChildScrollView( - controller: _verticalController, - physics: _dataGridSettings.isSwipingApplied - ? const NeverScrollableScrollPhysics() - : _dataGridSettings.verticalScrollPhysics, - child: ConstrainedBox( - constraints: - BoxConstraints(minHeight: min(scrollViewHeight, extentHeight)), - child: Scrollbar( - isAlwaysShown: _dataGridSettings.isScrollbarAlwaysShown, - controller: _horizontalController, - child: SingleChildScrollView( - controller: _horizontalController, - scrollDirection: Axis.horizontal, - physics: _dataGridSettings.isSwipingApplied - ? const NeverScrollableScrollPhysics() - : _dataGridSettings.horizontalScrollPhysics, - child: ConstrainedBox( - constraints: BoxConstraints(minWidth: min(_width, extentWidth)), - child: _VisualContainer( - key: const ValueKey('SfDataGrid-VisualContainer'), - isDirty: _container._isDirty, - rowGenerator: _rowGenerator, - containerSize: containerSize, - dataGridSettings: _dataGridSettings, - ), - ), - ), - ), - ), - ), - ); - - if (_dataGridSettings.allowPullToRefresh) { - scrollView = RefreshIndicator( - child: scrollView, - key: _dataGridSettings.refreshIndicatorKey, - onRefresh: _dataGridSettings.source.handleRefresh, - strokeWidth: _dataGridSettings.refreshIndicatorStrokeWidth, - displacement: _dataGridSettings.refreshIndicatorDisplacement, - ); - } - - return scrollView; - } - - void _addScrollView(List children) { - final double extentWidth = _container.extentWidth; - final double headerRowsHeight = _container.scrollRows - .rangeToRegionPoints(0, _dataGridSettings.headerLineCount - 1, true)[1] - .length; - final double extentHeight = _container.extentHeight - headerRowsHeight; - final double scrollViewHeight = _height - headerRowsHeight; - - final Size containerSize = Size( - _canDisableHorizontalScrolling(_dataGridSettings) - ? _width - : max(_width, extentWidth), - _canDisableVerticalScrolling(_dataGridSettings) - ? scrollViewHeight - : (extentHeight > scrollViewHeight - ? extentHeight - : scrollViewHeight)); - - final Widget scrollView = _buildScrollView( - extentWidth, scrollViewHeight, extentHeight, containerSize); - - final Positioned wrapScrollView = Positioned.fill( - top: headerRowsHeight, - child: scrollView, - ); - - children.add(wrapScrollView); - } - - void _addHeaderRows(List children) { - final double containerWidth = - _canDisableHorizontalScrolling(_dataGridSettings) - ? _width - : max(_width, _container.extentWidth); - - List _buildHeaderRows() { - final List headerRows = []; - if (_dataGridSettings.stackedHeaderRows.isNotEmpty) { - headerRows.addAll(_rowGenerator.items - .where((DataRowBase rows) => - rows.rowRegion == RowRegion.header && - rows.rowType == RowType.stackedHeaderRow) - .map((DataRowBase dataRow) => _HeaderCellsWidget( - key: dataRow._key!, - dataRow: dataRow, - isDirty: _container._isDirty || dataRow._isDirty, - )) - .toList(growable: false)); - } - headerRows.addAll(_rowGenerator.items - .where((DataRowBase rows) => - rows.rowRegion == RowRegion.header && - rows.rowType == RowType.headerRow) - .map((DataRowBase dataRow) => _HeaderCellsWidget( - key: dataRow._key!, - dataRow: dataRow, - isDirty: _container._isDirty || dataRow._isDirty, - )) - .toList(growable: false)); - return headerRows; - } - - double getStartX() { - if (_dataGridSettings.textDirection == TextDirection.ltr) { - return -_container.horizontalOffset; - } else { - if (!_horizontalController!.hasClients || - _horizontalController!.offset <= 0.0 || - _horizontalController!.position.maxScrollExtent <= 0.0 || - _container.extentWidth <= _width) { - return 0.0; - } else if (_horizontalController!.position.maxScrollExtent == - _horizontalController!.offset) { - return -_horizontalController!.position.maxScrollExtent; - } - - return -(_horizontalController!.position.maxScrollExtent - - _container.horizontalOffset); - } - } - - if (_rowGenerator.items.isNotEmpty) { - final List headerRows = _buildHeaderRows(); - for (int i = 0; i < headerRows.length; i++) { - final _VisibleLineInfo? lineInfo = - _container.scrollRows.getVisibleLineAtLineIndex(i); - final Positioned header = Positioned.directional( - textDirection: _dataGridSettings.textDirection, - start: getStartX(), - top: lineInfo?.origin, - height: lineInfo?.size, - // FLUT-1971 Changed the header row widget as extendwidth instead of - // device width to resloved the issue of apply sorting to the - // invisible columns. - width: containerWidth, - child: headerRows[i]); - children.add(header); - } - } - } - - void _addLoadMoreView(List children) { - Future loadMoreRows() async { - _isLoadMoreViewLoaded = true; - await _dataGridSettings.source.handleLoadMoreRows(); - } - - if (_verticalController!.hasClients && - _dataGridSettings.loadMoreViewBuilder != null) { - // FLUT-3038 Need to restrict load more view when rows exist within the - // view height. - if ((_verticalController!.position.maxScrollExtent > 0.0) && - (_verticalController!.offset >= - _verticalController!.position.maxScrollExtent) && - !_isLoadMoreViewLoaded) { - final Widget? loadMoreView = - _dataGridSettings.loadMoreViewBuilder!(context, loadMoreRows); - - if (loadMoreView != null) { - final Alignment loadMoreAlignment = - _dataGridSettings.textDirection == TextDirection.ltr - ? Alignment.bottomLeft - : Alignment.bottomRight; - - children.add(Positioned( - top: 0.0, - width: _width, - height: _height, - child: Align( - alignment: loadMoreAlignment, - child: loadMoreView, - ))); - } - } - } - } - - void _addFreezePaneLinesElevation(List children) { - final SfDataGridThemeData? dataGridThemeData = - _dataGridSettings.dataGridThemeData; - if (dataGridThemeData!.frozenPaneElevation <= 0.0 || - _dataGridSettings.columns.isEmpty || - _dataGridSettings.source._effectiveRows.isEmpty) { - return; - } - - void drawElevation({ - EdgeInsets? margin, - double? bottom, - double? start, - double? end, - double? top, - Axis? axis, - }) { - final Widget elevationLine = ClipRect( - child: Container( - width: axis == Axis.vertical ? 1 : 0, - height: axis == Axis.horizontal ? 1 : 0, - margin: margin, - decoration: BoxDecoration( - color: const Color(0xFF000000), - boxShadow: [ - BoxShadow( - color: dataGridThemeData.brightness == Brightness.light - ? const Color(0x3D000000) - : const Color(0x3DFFFFFF), - offset: Offset.zero, - spreadRadius: 3.0, - blurRadius: dataGridThemeData.frozenPaneElevation, - ) - ]))); - - children.add(Positioned.directional( - top: top, - end: end, - start: start, - bottom: bottom, - child: elevationLine, - textDirection: _dataGridSettings.textDirection, - )); - } - - double getTopPosition(DataRowBase columnHeaderRow, int columnIndex) { - double top = 0.0; - if (_dataGridSettings.stackedHeaderRows.isNotEmpty) { - top = columnHeaderRow._getRowHeight( - 0, _dataGridSettings.stackedHeaderRows.length - 1); - final DataCellBase? dataCell = columnHeaderRow._visibleColumns - .firstWhereOrNull( - (DataCellBase cell) => cell.columnIndex == columnIndex); - // Need to ignore header cell spanned height from the total stacked - // header rows height if it is spanned. - if (dataCell != null && dataCell._rowSpan > 0) { - top -= columnHeaderRow._getRowHeight( - dataCell.rowIndex - dataCell._rowSpan, dataCell.rowIndex - 1); - } - } - return top; - } - - // The field remainingViewPortHeight and remainingViewPortWidth are used to - // restrict the elevation height and width fill in the entire screen when - // extent width and height is smaller than the view size. - final double remainingViewPortHeight = - (_dataGridSettings.container.extentHeight < _height) - ? _height - _dataGridSettings.container.extentHeight - : 0.0; - final double remainingViewPortWidth = - (_dataGridSettings.container.extentWidth < _width) - ? _width - _dataGridSettings.container.extentWidth - : 0.0; - - final DataRowBase? columnHeaderRow = - _dataGridSettings.container.rowGenerator.items.firstWhereOrNull( - (DataRowBase row) => row.rowType == RowType.headerRow); - - // Provided the margin to allow shadow only to the corresponding side. - // 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 = dataGridThemeData.frozenPaneElevation + 4.0; - - final int frozenColumnIndex = - _GridIndexResolver.getLastFrozenColumnIndex(_dataGridSettings); - final int footerFrozenColumnIndex = - _GridIndexResolver.getStartFooterFrozenColumnIndex(_dataGridSettings); - final int frozenRowIndex = - _GridIndexResolver.getLastFrozenRowIndex(_dataGridSettings); - final int footerFrozenRowIndex = - _GridIndexResolver.getStartFooterFrozenRowIndex(_dataGridSettings); - - if (columnHeaderRow != null && - frozenColumnIndex >= 0 && - !_canDisableHorizontalScrolling(_dataGridSettings)) { - final double top = getTopPosition(columnHeaderRow, frozenColumnIndex); - final double left = columnHeaderRow._getColumnWidth( - 0, _dataGridSettings.frozenColumnsCount - 1); - - drawElevation( - top: top, - start: left, - bottom: remainingViewPortHeight, - axis: Axis.horizontal, - margin: _dataGridSettings.textDirection == TextDirection.rtl - ? EdgeInsets.only(left: margin) - : EdgeInsets.only(right: margin)); - } - - if (columnHeaderRow != null && - footerFrozenColumnIndex >= 0 && - !_canDisableHorizontalScrolling(_dataGridSettings)) { - final double top = - getTopPosition(columnHeaderRow, footerFrozenColumnIndex); - final double right = columnHeaderRow._getColumnWidth( - footerFrozenColumnIndex, _dataGridSettings.container.columnCount); - - drawElevation( - top: top, - bottom: remainingViewPortHeight, - end: right + remainingViewPortWidth, - axis: Axis.horizontal, - margin: _dataGridSettings.textDirection == TextDirection.rtl - ? EdgeInsets.only(right: margin) - : EdgeInsets.only(left: margin)); - } - - if (columnHeaderRow != null && - frozenRowIndex >= 0 && - !_canDisableVerticalScrolling(_dataGridSettings)) { - final double top = columnHeaderRow._getRowHeight(0, frozenRowIndex); - - drawElevation( - top: top, - start: 0.0, - end: remainingViewPortWidth, - axis: Axis.vertical, - margin: EdgeInsets.only(bottom: margin)); - } - - if (columnHeaderRow != null && - footerFrozenRowIndex >= 0 && - !_canDisableVerticalScrolling(_dataGridSettings)) { - final double bottom = columnHeaderRow._getRowHeight( - footerFrozenRowIndex, _dataGridSettings.container.rowCount); - - drawElevation( - start: 0.0, - end: remainingViewPortWidth, - axis: Axis.vertical, - bottom: bottom + remainingViewPortHeight, - margin: EdgeInsets.only(top: margin)); - } - } - - void _handleSelectionController() { - setState(() { - /* Rebuild the DataGrid when the selection or currentcell is processed. */ - }); - } - - // ------------------------------------------------------------------------- - // Below method and callback are used in [RawKeyboardListener] widget. - // Due to break on [FocusNode.onKey] callback after flutter 2.2.0. - // So, instead of using the [RawKeyboardListener] we replaced with [Focus] - // widget to adapt our all use case on key navigation. Need to remove the Focus - // widget and its related method and callback when the below mentioned github - // issue resolved on framework end. - // [https://github.com/flutter/flutter/issues/83023] - //--------------------------------------------------------------------------- - - // KeyEventResult _handleFocusKeyOperation(FocusNode focusNode, RawKeyEvent e) { - // final _DataGridSettings dataGridSettings = _dataGridSettings; - // final _CurrentCellManager currentCell = dataGridSettings.currentCell; - // - // KeyEventResult needToMoveFocus() { - // bool canAllowToRemoveFocus(int rowIndex, int columnIndex) => - // (dataGridSettings.navigationMode == GridNavigationMode.cell && - // currentCell.rowIndex == rowIndex && - // currentCell.columnIndex == columnIndex) || - // (dataGridSettings.navigationMode == GridNavigationMode.row && - // currentCell.rowIndex == rowIndex) || - // (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); - // - // if (e.isShiftPressed) { - // final int firstRowIndex = - // _SelectionHelper.getFirstRowIndex(_dataGridSettings); - // final int firstCellIndex = - // _SelectionHelper.getFirstCellIndex(_dataGridSettings); - // - // if (canAllowToRemoveFocus(firstRowIndex, firstCellIndex)) { - // return KeyEventResult.ignored; - // } else { - // return KeyEventResult.handled; - // } - // } else { - // final int lastRowIndex = - // _SelectionHelper.getLastNavigatingRowIndex(_dataGridSettings); - // final int lastCellIndex = - // _SelectionHelper.getLastCellIndex(_dataGridSettings); - // - // if (canAllowToRemoveFocus(lastRowIndex, lastCellIndex)) { - // return KeyEventResult.ignored; - // } else { - // return KeyEventResult.handled; - // } - // } - // } - // - // if (e.logicalKey == LogicalKeyboardKey.tab) { - // return needToMoveFocus(); - // } else { - // return _handleKeys(e); - // } - // } - - // KeyEventResult _handleKeyOperation( - // FocusNode focusNode, RawKeyEvent keyEvent) { - // final _DataGridSettings dataGridSettings = _dataGridSettings; - // final _CurrentCellManager currentCell = dataGridSettings.currentCell; - // - // KeyEventResult needToMoveFocus() { - // bool canAllowToRemoveFocus(int rowIndex, int columnIndex) => - // (dataGridSettings.navigationMode == GridNavigationMode.cell && - // currentCell.rowIndex == rowIndex && - // currentCell.columnIndex == columnIndex) || - // (dataGridSettings.navigationMode == GridNavigationMode.row && - // currentCell.rowIndex == rowIndex) || - // (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); - // - // if (e.isShiftPressed) { - // final int firstRowIndex = - // _SelectionHelper.getFirstRowIndex(_dataGridSettings); - // final int firstCellIndex = - // _SelectionHelper.getFirstCellIndex(_dataGridSettings); - // - // if (canAllowToRemoveFocus(firstRowIndex, firstCellIndex)) { - // return KeyEventResult.ignored; - // } else { - // return KeyEventResult.handled; - // } - // } else { - // final int lastRowIndex = - // _SelectionHelper.getLastNavigatingRowIndex(_dataGridSettings); - // final int lastCellIndex = - // _SelectionHelper.getLastCellIndex(_dataGridSettings); - // - // if (canAllowToRemoveFocus(lastRowIndex, lastCellIndex)) { - // return KeyEventResult.ignored; - // } else { - // return KeyEventResult.handled; - // } - // } - // } - // - // if (e.logicalKey == LogicalKeyboardKey.tab) { - // return needToMoveFocus(); - // } else { - // return _handleKeys(e); - // } - // } - - // KeyEventResult _handleKeys(RawKeyEvent keyEvent) { - // final _CurrentCellManager currentCell = _dataGridSettings.currentCell; - // if (_dataGridSettings.allowEditing && - // currentCell.isEditing && - // !_dataGridFocusNode!.hasPrimaryFocus) { - // if (keyEvent.logicalKey == LogicalKeyboardKey.tab || - // keyEvent.logicalKey == LogicalKeyboardKey.escape || - // keyEvent.logicalKey == LogicalKeyboardKey.arrowDown || - // keyEvent.logicalKey == LogicalKeyboardKey.arrowUp || - // keyEvent.logicalKey == LogicalKeyboardKey.pageUp || - // keyEvent.logicalKey == LogicalKeyboardKey.pageDown) { - // return KeyEventResult.handled; - // } - // } - // - // return _dataGridFocusNode!.hasPrimaryFocus - // ? KeyEventResult.handled - // : KeyEventResult.ignored; - // } - - // -------------------------------------------------------------------------- - - KeyEventResult _handleKeyOperation( - FocusNode focusNode, RawKeyEvent keyEvent) { - final _DataGridSettings dataGridSettings = _dataGridSettings; - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - - void processKeys() { - if (keyEvent.runtimeType == RawKeyDownEvent) { - _rowSelectionManager.handleKeyEvent(keyEvent); - if (keyEvent.isControlPressed) { - dataGridSettings.isControlKeyPressed = true; - } - } - if (keyEvent.runtimeType == RawKeyUpEvent) { - if (keyEvent.logicalKey == LogicalKeyboardKey.controlLeft || - keyEvent.logicalKey == LogicalKeyboardKey.controlRight) { - dataGridSettings.isControlKeyPressed = false; - } - } - } - - // Move to the next focusable widget when it reach the last and first - // [DataGridCell] on tab & shift + tab key. - KeyEventResult needToMoveFocus() { - bool canAllowToRemoveFocus(int rowIndex, int columnIndex) => - (dataGridSettings.navigationMode == GridNavigationMode.cell && - currentCell.rowIndex == rowIndex && - currentCell.columnIndex == columnIndex) || - (dataGridSettings.navigationMode == GridNavigationMode.row && - currentCell.rowIndex == rowIndex) || - (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); - - if (keyEvent.isShiftPressed) { - final int firstRowIndex = - _SelectionHelper.getFirstRowIndex(_dataGridSettings); - final int firstCellIndex = - _SelectionHelper.getFirstCellIndex(_dataGridSettings); - - if (canAllowToRemoveFocus(firstRowIndex, firstCellIndex)) { - return KeyEventResult.ignored; - } else { - return KeyEventResult.handled; - } - } else { - final int lastRowIndex = - _SelectionHelper.getLastNavigatingRowIndex(_dataGridSettings); - final int lastCellIndex = - _SelectionHelper.getLastCellIndex(_dataGridSettings); - - if (canAllowToRemoveFocus(lastRowIndex, lastCellIndex)) { - return KeyEventResult.ignored; - } else { - return KeyEventResult.handled; - } - } - } - - if (_dataGridFocusNode!.hasPrimaryFocus) { - if (keyEvent.logicalKey == LogicalKeyboardKey.tab && - needToMoveFocus() != KeyEventResult.handled) { - return KeyEventResult.ignored; - } - - processKeys(); - return KeyEventResult.handled; - } else { - // On Editing, we have to handle below [LogicalKeyboardKey]'s. For, that - // we have return [KeyEventResult.handled] to handle those keys on - // editing. - if (_dataGridSettings.allowEditing && - currentCell.isEditing && - !_dataGridFocusNode!.hasPrimaryFocus) { - if (keyEvent.logicalKey == LogicalKeyboardKey.tab || - keyEvent.logicalKey == LogicalKeyboardKey.escape || - keyEvent.logicalKey == LogicalKeyboardKey.arrowDown || - keyEvent.logicalKey == LogicalKeyboardKey.arrowUp || - keyEvent.logicalKey == LogicalKeyboardKey.pageUp || - keyEvent.logicalKey == LogicalKeyboardKey.pageDown || - keyEvent.logicalKey == LogicalKeyboardKey.enter) { - processKeys(); - return KeyEventResult.handled; - } - } - - return KeyEventResult.ignored; - } - } - - @override - void didUpdateWidget(_ScrollViewWidget oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.width != widget.width || - oldWidget.height != widget.height || - _container._needToSetHorizontalOffset) { - /// Need not to change the height when onScreenKeyboard appears. - /// Cause: If we change the height on editing, editable widget will not move - /// above the onScreenKeyboard on mobile platforms. - final bool needToResizeHeight = !_dataGridSettings._isDesktop && - _dataGridSettings.currentCell.isEditing; - - _width = widget.width; - _height = !needToResizeHeight ? widget.height : _height; - _container - .._needToSetHorizontalOffset = true - .._isDirty = true; - if (oldWidget.width != widget.width || - oldWidget.height != widget.height) { - _container.resetSwipeOffset(); - } - // FLUT-2047 Need to mark all visible rows height as dirty when DataGrid - // size is changed if onQueryRowHeight is not null. - if (oldWidget.width != widget.width && - _dataGridSettings.onQueryRowHeight != null) { - _container.rowHeightManager.reset(); - } - } - - if (_verticalController != - widget.dataGridStateDetails().verticalScrollController) { - _verticalController!.removeListener(_verticalListener); - _verticalController = - widget.dataGridStateDetails().verticalScrollController ?? - ScrollController(); - _verticalController!.addListener(_verticalListener); - } - - if (_horizontalController != - widget.dataGridStateDetails().horizontalScrollController) { - _horizontalController!.removeListener(_horizontalListener); - _horizontalController = - widget.dataGridStateDetails().horizontalScrollController ?? - ScrollController(); - _horizontalController!.addListener(_horizontalListener); - } - } - - @override - Widget build(BuildContext context) { - if (_container._isDirty && !_isScrolling) { - _updateAxis(); - _updateColumnSizer(); - _container - ..setRowHeights() - .._needToRefreshColumn = true; - } - - if (_container._needToSetHorizontalOffset) { - _setHorizontalOffset(); - } - - if (_container._isDirty) { - _ensureWidgets(); - } - - final List children = []; - - _addHeaderRows(children); - - _addScrollView(children); - - _addFreezePaneLinesElevation(children); - - _addLoadMoreView(children); - - _container._isDirty = false; - _isScrolling = false; - - // return RawKeyboardListener( - // focusNode: _dataGridFocusNode!, - // onKey: _handleKeyOperation, - // child: Container( - // height: _height, - // width: _width, - // decoration: const BoxDecoration( - // color: Colors.transparent, - // ), - // clipBehavior: Clip.antiAlias, - // child: _dataGridSettings.allowColumnsResizing - // ? _wrapInsideGestureDetector(children) - // : _wrapInsideStack(children))); - - // [FocusNode.onKey] callback is not firing on key navigation after flutter - // 2.2.0 and its breaking our key navigation on tab, shift + tab, arrow keys. - // So, we have used the focus widget. Need to remove [Focus] widget when - // below mentioned issue is resolved on framework end. - // [https://github.com/flutter/flutter/issues/83023] - return Focus( - focusNode: _dataGridFocusNode, - onKey: _handleKeyOperation, - child: Container( - height: _height, - width: _width, - decoration: const BoxDecoration( - color: Colors.transparent, - ), - clipBehavior: Clip.antiAlias, - child: _wrapInsideStack(children), - ), - ); - } - - Widget _wrapInsideStack(List children) { - return Stack( - fit: StackFit.passthrough, children: List.from(children)); - } - - @override - void dispose() { - if (_verticalController != null) { - _verticalController! - ..removeListener(_verticalListener) - ..dispose(); - } - - if (_horizontalController != null) { - _horizontalController! - ..removeListener(_horizontalListener) - ..dispose(); - } - - super.dispose(); - } -} - -bool _canDisableVerticalScrolling(_DataGridSettings dataGridSettings) { - final _VisualContainerHelper container = dataGridSettings.container; - return (container.scrollRows.headerExtent + - container.scrollRows.footerExtent) > - dataGridSettings.viewHeight; -} - -bool _canDisableHorizontalScrolling(_DataGridSettings dataGridSettings) { - final _VisualContainerHelper container = dataGridSettings.container; - return (container.scrollColumns.headerExtent + - container.scrollColumns.footerExtent) > - dataGridSettings.viewWidth; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart deleted file mode 100644 index bc2adb4e2..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_helper.dart +++ /dev/null @@ -1,671 +0,0 @@ -part of datagrid; - -class _VisualContainerHelper { - _VisualContainerHelper({required this.rowGenerator}) { - _isDirty = false; - _isGridLoaded = false; - _needToSetHorizontalOffset = false; - rowHeightsProvider = onCreateRowHeights(); - columnWidthsProvider = onCreateColumnWidths(); - } - - bool _isPreGenerator = false; - bool _needToRefreshColumn = false; - int _headerLineCount = 1; - - late bool _isDirty; - late bool _isGridLoaded; - // Used to set the horizontal offset for LTR to RTL and vise versa, - late bool _needToSetHorizontalOffset; - - _DataGridStateDetails? get dataGridStateDetails => - rowGenerator.dataGridStateDetails; - - late _RowGenerator rowGenerator; - - late _PaddedEditableLineSizeHostBase rowHeightsProvider; - - late _PaddedEditableLineSizeHostBase columnWidthsProvider; - - _RowHeightManager rowHeightManager = _RowHeightManager(); - - _PaddedEditableLineSizeHostBase get rowHeights => rowHeightsProvider; - - _PaddedEditableLineSizeHostBase get columnWidths => columnWidthsProvider; - - _ScrollAxisBase get scrollRows { - _scrollRows ??= - createScrollAxis(true, verticalScrollBar, rowHeightsProvider); - _scrollRows!.name = 'ScrollRows'; - - return _scrollRows!; - } - - _ScrollAxisBase? _scrollRows; - - set scrollRows(_ScrollAxisBase newValue) => _scrollRows = newValue; - - _ScrollAxisBase get scrollColumns { - _scrollColumns ??= - createScrollAxis(true, horizontalScrollBar, columnWidthsProvider); - _scrollColumns!.name = 'ScrollColumns'; - return _scrollColumns!; - } - - _ScrollAxisBase? _scrollColumns; - - set scrollColumns(_ScrollAxisBase newValue) => _scrollColumns = newValue; - - _ScrollBarBase get horizontalScrollBar => - _horizontalScrollBar ?? (_horizontalScrollBar = _ScrollInfo()); - _ScrollBarBase? _horizontalScrollBar; - - _ScrollBarBase get verticalScrollBar => - _verticalScrollBar ?? (_verticalScrollBar = _ScrollInfo()); - _ScrollBarBase? _verticalScrollBar; - - int get rowCount => rowHeightsProvider.lineCount; - - set rowCount(int newValue) { - if (newValue > rowCount) { - insertRows(rowCount, newValue - rowCount); - } else if (newValue < rowCount) { - removeRows(newValue, rowCount - newValue); - } - } - - int get columnCount => columnWidthsProvider.lineCount; - - set columnCount(int newValue) { - if (newValue > columnCount) { - insertColumns(columnCount, newValue - columnCount); - } else if (newValue < columnCount) { - removeColumns(newValue, columnCount - newValue); - } - } - - int get frozenRows => rowHeightsProvider.headerLineCount; - - set frozenRows(int newValue) { - if (newValue < 0 || frozenRows == newValue) { - return; - } - - rowHeightsProvider.headerLineCount = newValue; - } - - int get footerFrozenRows => rowHeightsProvider.footerLineCount; - - set footerFrozenRows(int newValue) { - if (newValue < 0 || footerFrozenRows == newValue) { - return; - } - - rowHeightsProvider.footerLineCount = newValue; - } - - int get frozenColumns => columnWidthsProvider.headerLineCount; - - set frozenColumns(int newValue) { - if (newValue < 0 || frozenColumns == newValue) { - return; - } - - columnWidthsProvider.headerLineCount = newValue; - } - - int get footerFrozenColumns => columnWidthsProvider.footerLineCount; - - set footerFrozenColumns(int newValue) { - if (newValue < 0 || footerFrozenColumns == newValue) { - return; - } - - columnWidthsProvider.footerLineCount = newValue; - } - - double get horizontalOffset => - horizontalScrollBar.value - horizontalScrollBar.minimum; - - set horizontalOffset(double newValue) { - final _DataGridSettings dataGridSettings = dataGridStateDetails!(); - if (dataGridSettings.textDirection == TextDirection.ltr) { - horizontalScrollBar.value = newValue + horizontalScrollBar.minimum; - } else { - horizontalScrollBar.value = max(horizontalScrollBar.minimum, - horizontalScrollBar.maximum - horizontalScrollBar.largeChange) - - newValue; - } - dataGridSettings.controller._horizontalOffset = horizontalScrollBar.value; - - _needToRefreshColumn = true; - } - - double get verticalOffset => - verticalScrollBar.value - verticalScrollBar.minimum; - - set verticalOffset(double newValue) { - if (verticalScrollBar.value != (newValue + verticalScrollBar.minimum)) { - verticalScrollBar.value = newValue + verticalScrollBar.minimum; - dataGridStateDetails!().controller._verticalOffset = - verticalScrollBar.value; - } - } - - double get extentWidth { - final _PixelScrollAxis _scrollColumns = scrollColumns as _PixelScrollAxis; - return _scrollColumns.totalExtent; - } - - double get extentHeight { - final _PixelScrollAxis _scrollRows = scrollRows as _PixelScrollAxis; - return _scrollRows.totalExtent; - } - - void setRowHeights() { - if (dataGridStateDetails!().onQueryRowHeight == null) { - return; - } - - final _VisibleLinesCollection visibleRows = scrollRows.getVisibleLines(); - - int endIndex = 0; - - if (visibleRows.length <= visibleRows.firstBodyVisibleIndex) { - return; - } - - endIndex = visibleRows[visibleRows.lastBodyVisibleIndex].lineIndex; - - const int headerStart = 0; - - final int headerEnd = scrollRows.headerLineCount - 1; - rowHeightHelper(headerStart, headerEnd, RowRegion.header); - - rowHeightManager.updateRegion(headerStart, headerEnd, RowRegion.header); - - final int footerStart = - visibleRows.length > visibleRows.firstFooterVisibleIndex && - scrollRows.footerLineCount > 0 - ? visibleRows[visibleRows.firstFooterVisibleIndex].lineIndex - : -1; - final int footerEnd = - scrollRows.footerLineCount > 0 ? scrollRows.lineCount - 1 : -1; - - rowHeightHelper(footerStart, footerEnd, RowRegion.footer); - - rowHeightManager.updateRegion(footerStart, footerEnd, RowRegion.footer); - - final double bodyStart = - visibleRows[visibleRows.firstBodyVisibleIndex].origin; - - final double bodyEnd = - visibleRows[visibleRows.firstFooterVisibleIndex - 1].corner; - - final int bodyStartLineIndex = - visibleRows[visibleRows.firstBodyVisibleIndex].lineIndex; - - double current = bodyStart; - int currentEnd = endIndex; - - final _LineSizeCollection lineSizeCollection = - rowHeights as _LineSizeCollection; - lineSizeCollection.suspendUpdates(); - for (int index = bodyStartLineIndex; - current <= bodyEnd && index < scrollRows.firstFooterLineIndex; - index++) { - double height = rowHeights[index]; - - if (!rowHeightManager.contains(index, RowRegion.body) && - !_GridIndexResolver.isFooterWidgetRow( - index, dataGridStateDetails!())) { - final double rowHeight = rowGenerator._queryRowHeight(index, height); - if (rowHeight != height) { - height = rowHeight; - rowHeights.setRange(index, index, height); - } - } - - current += height; - currentEnd = index; - } - - rowHeightManager.updateRegion( - bodyStartLineIndex, currentEnd, RowRegion.body); - - if (rowHeightManager.dirtyRows.isNotEmpty) { - for (final int index in rowHeightManager.dirtyRows) { - if (index < 0 || index >= rowHeights.lineCount) { - continue; - } - - final double height = rowHeights[index]; - final double rowHeight = rowGenerator._queryRowHeight(index, height); - if (rowHeight != height) { - rowHeights.setRange(index, index, rowHeight); - } - } - - rowHeightManager.dirtyRows.clear(); - } - - lineSizeCollection.resumeUpdates(); - scrollRows.updateScrollBar(false); - } - - void rowHeightHelper(int startIndex, int endIndex, RowRegion region) { - if (startIndex < 0 || endIndex < 0) { - return; - } - - final _DataGridSettings dataGridSettings = dataGridStateDetails!(); - for (int index = startIndex; index <= endIndex; index++) { - if (!rowHeightManager.contains(index, region)) { - final double height = dataGridSettings.container.rowHeights[index]; - final double rowHeight = rowGenerator._queryRowHeight(index, height); - if (rowHeight != height) { - rowHeights.setRange(index, index, rowHeight); - - if (region == RowRegion.header && - index == _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - dataGridSettings.headerRowHeight = rowHeight; - } - } - } - } - } - - void preGenerateItems() { - final _VisibleLinesCollection visibleRows = scrollRows.getVisibleLines(); - final _VisibleLinesCollection visibleColumns = - _SfDataGridHelper.getVisibleLines(rowGenerator.dataGridStateDetails()); - - if (visibleRows.isNotEmpty && visibleColumns.isNotEmpty) { - rowGenerator._preGenerateRows(visibleRows, visibleColumns); - _isPreGenerator = true; - } - } - - _PaddedEditableLineSizeHostBase onCreateRowHeights() { - final _LineSizeCollection lineSizeCollection = _LineSizeCollection(); - return lineSizeCollection; - } - - _PaddedEditableLineSizeHostBase onCreateColumnWidths() { - final _LineSizeCollection lineSizeCollection = _LineSizeCollection(); - return lineSizeCollection; - } - - void insertRows(int insertAtRowIndex, int count) { - rowHeightsProvider.insertLines(insertAtRowIndex, count, null); - } - - void removeRows(int removeAtRowIndex, int count) { - rowHeightsProvider.removeLines(removeAtRowIndex, count, null); - } - - void insertColumns(int insertAtColumnIndex, int count) { - columnWidthsProvider.insertLines(insertAtColumnIndex, count, null); - } - - void removeColumns(int removeAtColumnIndex, int count) { - final _LineSizeCollection lineSizeCollection = - columnWidths as _LineSizeCollection; - lineSizeCollection.suspendUpdates(); - columnWidthsProvider.removeLines(removeAtColumnIndex, count, null); - lineSizeCollection.resumeUpdates(); - } - - void updateScrollBars() { - scrollRows.updateScrollBar(false); - scrollColumns.updateScrollBar(false); - } - - void updateAxis(Size availableSize) { - scrollRows.renderSize = availableSize.height; - scrollColumns.renderSize = availableSize.width; - } - - _ScrollAxisBase createScrollAxis(bool isPixelScroll, _ScrollBarBase scrollBar, - _LineSizeHostBase lineSizes) { - if (isPixelScroll) { - final Object _lineSizes = lineSizes; - if (lineSizes is _DistancesHostBase) { - return _PixelScrollAxis.fromPixelScrollAxis( - scrollBar, lineSizes, _lineSizes as _DistancesHostBase); - } else { - return _PixelScrollAxis.fromPixelScrollAxis(scrollBar, lineSizes, null); - } - } else { - return _LineScrollAxis(scrollBar, lineSizes); - } - } - - _VisibleLineInfo? getRowVisibleLineInfo(int index) => - scrollRows.getVisibleLineAtLineIndex(index); - - List _getStartEndIndex( - _VisibleLinesCollection visibleLines, int region) { - int startIndex = 0; - int endIndex = -1; - switch (region) { - case 0: - if (visibleLines.firstBodyVisibleIndex > 0) { - startIndex = 0; - endIndex = - visibleLines[visibleLines.firstBodyVisibleIndex - 1].lineIndex; - } - break; - case 1: - if ((visibleLines.firstBodyVisibleIndex <= 0 && - visibleLines.lastBodyVisibleIndex < 0) || - visibleLines.length <= visibleLines.firstBodyVisibleIndex) { - return [startIndex, endIndex]; - } else { - startIndex = - visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex; - endIndex = visibleLines[visibleLines.lastBodyVisibleIndex].lineIndex; - } - break; - case 2: - if (visibleLines.firstFooterVisibleIndex < visibleLines.length) { - startIndex = - visibleLines[visibleLines.firstFooterVisibleIndex].lineIndex; - endIndex = visibleLines[visibleLines.length - 1].lineIndex; - } - break; - } - - return [startIndex, endIndex]; - } - - void _refreshDefaultLineSize() { - final _DataGridSettings dataGridSettings = dataGridStateDetails!(); - rowHeights.defaultLineSize = dataGridSettings.rowHeight; - columnWidths.defaultLineSize = dataGridSettings.defaultColumnWidth; - } - - void _refreshHeaderLineCount() { - final _DataGridSettings dataGridSettings = dataGridStateDetails!(); - _headerLineCount = 1; - if (dataGridSettings.stackedHeaderRows.isNotEmpty) { - _headerLineCount += dataGridSettings.stackedHeaderRows.length; - dataGridSettings.headerLineCount = _headerLineCount; - } else { - dataGridSettings.headerLineCount = 1; - } - } - - void _updateRowAndColumnCount() { - final _LineSizeCollection lineSizeCollection = - columnWidths as _LineSizeCollection; - lineSizeCollection.suspendUpdates(); - final _DataGridSettings dataGridSettings = dataGridStateDetails!(); - _updateColumnCount(dataGridSettings); - _updateRowCount(dataGridSettings); - if (rowCount > 0) { - for (int i = 0; - i <= _GridIndexResolver.getHeaderIndex(dataGridSettings); - i++) { - rowHeights[i] = dataGridSettings.headerRowHeight; - } - } - - //need to update the indent column width here - lineSizeCollection.resumeUpdates(); - updateScrollBars(); - _updateFreezePaneColumns(dataGridSettings); - - rowHeights.lineCount = rowCount; - columnWidths.lineCount = columnCount; - } - - void _updateColumnCount(_DataGridSettings dataGridSettings) { - final int columnCount = dataGridSettings.columns.length; - this.columnCount = columnCount; - } - - void _updateRowCount(_DataGridSettings dataGridSettings) { - final _LineSizeCollection lineSizeCollection = - rowHeights as _LineSizeCollection; - lineSizeCollection.suspendUpdates(); - int _rowCount = 0; - _rowCount = dataGridSettings.source._effectiveRows.isNotEmpty - ? dataGridSettings.source._effectiveRows.length - : 0; - _rowCount += dataGridSettings.headerLineCount; - - // Add footer row widget to the rows - if (dataGridSettings.footer != null) { - _rowCount++; - rowCount = _rowCount; - // set footer row height - rowHeights[rowCount - 1] = dataGridSettings.footerHeight; - } else { - rowCount = _rowCount; - } - - _updateFreezePaneRows(dataGridSettings); - // FLUT-2047 Need to mark all visible rows height as dirty when - // updating the row count if onQueryRowHeight is not null. - if (dataGridStateDetails!().onQueryRowHeight != null) { - rowHeightManager.reset(); - } - //need to reset the hidden state - lineSizeCollection.resumeUpdates(); - //need to check again need to call the updateFreezePaneRows here - _updateFreezePaneRows(dataGridSettings); - } - - void _updateFreezePaneColumns(_DataGridSettings dataGridSettings) { - final int frozenColumnCount = _GridIndexResolver.resolveToScrollColumnIndex( - dataGridSettings, dataGridSettings.frozenColumnsCount); - if (frozenColumnCount > 0 && columnCount >= frozenColumnCount) { - frozenColumns = frozenColumnCount; - } else { - frozenColumns = 0; - } - - final int footerFrozenColumnsCount = - dataGridSettings.footerFrozenColumnsCount; - if (footerFrozenColumnsCount > 0 && - columnCount > frozenColumnCount + footerFrozenColumnsCount) { - footerFrozenColumns = footerFrozenColumnsCount; - } else { - footerFrozenColumns = 0; - } - } - - void _updateFreezePaneRows(_DataGridSettings dataGridSettings) { - final int frozenRowCount = _GridIndexResolver.resolveToRowIndex( - dataGridSettings, dataGridSettings.frozenRowsCount); - if (frozenRowCount > 0 && rowCount >= frozenRowCount) { - frozenRows = _headerLineCount + dataGridSettings.frozenRowsCount; - } else { - frozenRows = _headerLineCount; - } - - final int footerFrozenRowsCount = dataGridSettings.footerFrozenRowsCount; - if (footerFrozenRowsCount > 0 && - rowCount > frozenRows + footerFrozenRowsCount && - footerFrozenRowsCount < rowCount - frozenRowCount) { - footerFrozenRows = footerFrozenRowsCount; - } else { - footerFrozenRows = 0; - } - } - - /// Helps to reset the [DataGridRow] on each [DataRow] to refresh the - /// [SfDataGrid] with editing and sorting is enabled. - /// - /// cause: - /// * Instead of setting -1 to each rows on editing to refresh. - void _updateDataGridRows(_DataGridSettings dataGridSettings) { - void resetRowIndex(DataRowBase dataRow) { - if (dataRow.rowType == RowType.dataRow) { - final int resolvedRowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, dataRow.rowIndex); - if (resolvedRowIndex.isNegative) { - return; - } - - dataRow._dataGridRow = - dataGridSettings.source._effectiveRows[resolvedRowIndex]; - dataRow._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( - dataGridSettings, dataRow._dataGridRow!); - dataRow._rowIndexChanged(); - } - } - - rowGenerator.items.forEach(resetRowIndex); - } - - void _refreshView({bool clearEditing = true}) { - void resetRowIndex(DataRowBase dataRow) { - if (!clearEditing && dataRow._isEditing) { - return; - } - dataRow.rowIndex = -1; - } - - rowGenerator.items.forEach(resetRowIndex); - } - - void _refreshViewStyle() { - void updateColumn(DataCellBase dataCell) { - dataCell - .._isDirty = true - .._updateColumn(); - } - - for (final DataRowBase dataRow in rowGenerator.items) { - dataRow - .._isDirty = true - .._visibleColumns.forEach(updateColumn); - } - } - - void resetSwipeOffset({DataRowBase? swipedRow, bool canUpdate = false}) { - final _DataGridSettings dataGridSettings = dataGridStateDetails!(); - if (!dataGridSettings.allowSwiping) { - return; - } - - swipedRow = swipedRow ?? - dataGridSettings.rowGenerator.items - .firstWhereOrNull((DataRowBase row) => row._isSwipingRow); - - if (swipedRow != null) { - swipedRow._isSwipingRow = false; - } - - dataGridSettings.swipingOffset = 0.0; - dataGridSettings.isSwipingApplied = false; - - if (canUpdate) { - dataGridSettings.source - ._notifyDataGridPropertyChangeListeners(propertyName: 'Swiping'); - } - } -} - -class _RowHeightManager { - _Range header = _Range(); - _Range body = _Range(); - _Range footer = _Range(); - List dirtyRows = []; - - bool contains(int index, RowRegion region) { - _Range range; - if (region == RowRegion.header) { - range = header; - } else if (region == RowRegion.body) { - range = body; - } else { - range = footer; - } - - if (range.isEmpty()) { - if (dirtyRows.contains(index)) { - dirtyRows.remove(index); - } - - return false; - } - - if (index >= range.start && index <= range.end) { - if (dirtyRows.contains(index)) { - dirtyRows.remove(index); - return false; - } - - return true; - } - - return false; - } - - void setDirty(int rowIndex) { - if (!dirtyRows.contains(rowIndex)) { - dirtyRows.add(rowIndex); - } - } - - _Range getRange(int index) { - if (index == 0) { - return header; - } else if (index == 1) { - return body; - } else { - return footer; - } - } - - void reset() { - header.start = - header.end = body.start = body.end = footer.start = footer.end = -1; - } - - void resetBody() { - body.start = body.end = -1; - } - - void resetFooter() { - footer.start = footer.end = -1; - } - - void updateBody(int index, int count) { - if ((index + count) <= body.start) { - resetBody(); - return; - } else if (index > body.end) { - return; - } else { - body.end = index; - } - } - - void updateRegion(int start, int end, RowRegion region) { - _Range range; - if (region == RowRegion.header) { - range = header; - } else if (region == RowRegion.body) { - range = body; - } else { - range = footer; - } - - range - ..start = start - ..end = end; - } -} - -class _Range { - _Range(); - - int start = -1; - int end = -1; - - bool isEmpty() => start < 0 || end < 0; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart deleted file mode 100644 index 221d5739e..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/scrollable_panel/visual_container_widget.dart +++ /dev/null @@ -1,367 +0,0 @@ -part of datagrid; - -class _VisualContainer extends StatefulWidget { - const _VisualContainer( - {required Key key, - required this.rowGenerator, - required this.containerSize, - required this.isDirty, - required this.dataGridSettings}) - : super(key: key); - - final Size containerSize; - final _RowGenerator rowGenerator; - final bool isDirty; - final _DataGridSettings dataGridSettings; - - @override - State createState() => _VisualContainerState(); -} - -class _VisualContainerState extends State<_VisualContainer> { - void _addSwipeBackgroundWidget(List children) { - final _DataGridSettings dataGridSettings = widget.dataGridSettings; - if (dataGridSettings.allowSwiping && - dataGridSettings.swipingOffset.abs() > 0.0) { - final DataRowBase? swipeRow = widget.rowGenerator.items - .where((DataRowBase row) => - (row.rowRegion == RowRegion.body || - row.rowType == RowType.dataRow) && - row.rowIndex >= 0) - .firstWhereOrNull((DataRowBase row) => row._isSwipingRow); - if (swipeRow != null) { - final DataGridRowSwipeDirection swipeDirection = - _SfDataGridHelper.getSwipeDirection( - dataGridSettings, dataGridSettings.swipingOffset); - - switch (swipeDirection) { - case DataGridRowSwipeDirection.startToEnd: - if (dataGridSettings.startSwipeActionsBuilder != null) { - final Widget? startSwipeWidget = dataGridSettings - .startSwipeActionsBuilder!(context, swipeRow._dataGridRow!); - children.add(startSwipeWidget ?? Container()); - } - break; - case DataGridRowSwipeDirection.endToStart: - if (dataGridSettings.endSwipeActionsBuilder != null) { - final Widget? endSwipeWidget = dataGridSettings - .endSwipeActionsBuilder!(context, swipeRow._dataGridRow!); - children.add(endSwipeWidget ?? Container()); - } - break; - } - } - } - } - - void _addFooterRow(List children) { - if (widget.dataGridSettings.footer != null) { - final DataRowBase? footerRow = widget.rowGenerator.items.firstWhereOrNull( - (DataRowBase row) => - row.rowType == RowType.footerRow && row.rowIndex >= 0); - if (footerRow != null) { - children.add(_VirtualizingCellsWidget( - key: footerRow._key!, - dataRow: footerRow, - isDirty: widget.isDirty || footerRow._isDirty, - )); - } - } - } - - @override - Widget build(BuildContext context) { - // Need to restrict the layout of the currently unused rows that keep the - // row index to -1 in the `rowGenerator.items` collection. - final List children = widget.rowGenerator.items - .where((DataRowBase row) => - row.rowType == RowType.dataRow && row.rowIndex >= 0) - .map((DataRowBase dataRow) => _VirtualizingCellsWidget( - key: dataRow._key!, - dataRow: dataRow, - isDirty: widget.isDirty || dataRow._isDirty, - )) - .toList(); - - _addFooterRow(children); - - _addSwipeBackgroundWidget(children); - - return _VisualContainerRenderObjectWidget( - key: widget.key, - containerSize: widget.containerSize, - isDirty: widget.isDirty, - children: children, - dataGridSettings: widget.dataGridSettings, - ); - } -} - -class _VisualContainerRenderObjectWidget extends MultiChildRenderObjectWidget { - _VisualContainerRenderObjectWidget( - {required Key? key, - required this.containerSize, - required this.isDirty, - required this.children, - required this.dataGridSettings}) - : super( - key: key, - children: RepaintBoundary.wrapAll(List.from(children))); - - @override - final List children; - final Size containerSize; - final bool isDirty; - final _DataGridSettings dataGridSettings; - - @override - _RenderVisualContainer createRenderObject(BuildContext context) => - _RenderVisualContainer( - containerSize: containerSize, - isDirty: isDirty, - dataGridSettings: dataGridSettings); - - @override - void updateRenderObject( - BuildContext context, _RenderVisualContainer renderObject) { - super.updateRenderObject(context, renderObject); - renderObject - ..containerSize = containerSize - ..isDirty = isDirty - .._dataGridSettings = dataGridSettings; - } -} - -class _VisualContainerParentData extends ContainerBoxParentData { - _VisualContainerParentData(); - - double width = 0.0; - double height = 0.0; - Rect? rowClipRect; - - void reset() { - width = 0.0; - height = 0.0; - offset = Offset.zero; - rowClipRect = null; - } -} - -class _RenderVisualContainer extends RenderBox - with - ContainerRenderObjectMixin, - RenderBoxContainerDefaultsMixin { - _RenderVisualContainer( - {List? children, - Size containerSize = Size.zero, - bool isDirty = false, - _DataGridSettings? dataGridSettings}) - : _containerSize = containerSize, - _dataGridSettings = dataGridSettings!, - _isDirty = isDirty { - addAll(children); - } - - bool get isDirty => _isDirty; - bool _isDirty = false; - - set isDirty(bool newValue) { - _isDirty = newValue; - if (_isDirty) { - markNeedsLayout(); - markNeedsPaint(); - } - } - - Size get containerSize => _containerSize; - Size _containerSize = Size.zero; - - set containerSize(Size newContainerSize) { - if (_containerSize == newContainerSize) { - return; - } - _containerSize = newContainerSize; - markNeedsLayout(); - markNeedsPaint(); - } - - late _DataGridSettings _dataGridSettings; - - _RenderVirtualizingCellsWidget? _swipeWholeRowElement; - - @override - bool get isRepaintBoundary => true; - - @override - void setupParentData(RenderObject child) { - super.setupParentData(child); - if (child.parentData is! _VisualContainerParentData) { - child.parentData = _VisualContainerParentData(); - } - } - - @override - bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { - RenderBox? child = lastChild; - while (child != null) { - final _VisualContainerParentData childParentData = - child.parentData! as _VisualContainerParentData; - final bool isHit = result.addWithPaintOffset( - offset: childParentData.offset, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) { - // Need to ensure whether the child type is _RenderVirtualizingCellsWidget - // or not before accessing it. Because swiping widget's render object won't be - // _RenderVirtualizingCellsWidget. - final RenderBox? wholeRowElement = child; - if (wholeRowElement != null && - wholeRowElement is _RenderVirtualizingCellsWidget && - wholeRowElement.rowClipRect != null && - !wholeRowElement.rowClipRect!.contains(transformed)) { - return false; - } - return child!.hitTest(result, position: transformed); - }, - ); - - if (isHit) { - return true; - } - child = childParentData.previousSibling; - } - return false; - } - - Offset _getSwipingChildOffset(Rect swipeRowRect) { - double dxPosition = 0.0; - final double viewWidth = _dataGridSettings.viewWidth; - final double maxSwipeOffset = _dataGridSettings.swipeMaxOffset; - final double extentWidth = _dataGridSettings.container.extentWidth; - - if (_dataGridSettings.textDirection == TextDirection.rtl && - viewWidth > extentWidth) { - dxPosition = (_dataGridSettings.swipingOffset >= 0) - ? viewWidth - extentWidth - : viewWidth - maxSwipeOffset; - } else { - dxPosition = (_dataGridSettings.swipingOffset >= 0) - ? 0.0 - : extentWidth - maxSwipeOffset; - } - - return Offset(dxPosition, swipeRowRect.top); - } - - @override - void performLayout() { - size = - constraints.constrain(Size(containerSize.width, containerSize.height)); - - void layout( - {required RenderBox child, - required double width, - required double height}) { - child.layout(BoxConstraints.tightFor(width: width, height: height), - parentUsesSize: true); - } - - RenderBox? child = firstChild; - while (child != null) { - final _VisualContainerParentData parentData = - child.parentData! as _VisualContainerParentData; - // Need to ensure whether the child type is _RenderVirtualizingCellsWidget - // or not before accessing it. Because swiping widget's render object won't be - // _RenderVirtualizingCellsWidget. - final RenderBox wholeRowElement = child; - if (wholeRowElement is _RenderVirtualizingCellsWidget) { - if (wholeRowElement.dataRow.isVisible) { - final Rect rowRect = wholeRowElement._measureRowRect(size.width); - - parentData - ..width = rowRect.width - ..height = rowRect.height - ..rowClipRect = wholeRowElement.rowClipRect - ..offset = Offset( - (wholeRowElement.dataRow._isSwipingRow && - _dataGridSettings.swipingOffset != 0.0 && - _dataGridSettings.swipingAnimation != null) - ? rowRect.left + _dataGridSettings.swipingAnimation!.value - : rowRect.left, - rowRect.top); - - if (wholeRowElement.dataRow._isSwipingRow && - _dataGridSettings.swipingOffset.abs() > 0.0) { - _swipeWholeRowElement = wholeRowElement; - } - - layout( - child: child, width: parentData.width, height: parentData.height); - } else { - child.layout(const BoxConstraints.tightFor(width: 0.0, height: 0.0)); - parentData.reset(); - } - } else { - // We added the swiping widget to the last position of chidren collection. - // So, we can get it diretly from lastChild property. - final RenderBox? swipingWidget = lastChild; - if (swipingWidget != null && _swipeWholeRowElement != null) { - final Rect swipeRowRect = - _swipeWholeRowElement!._measureRowRect(size.width); - - parentData - ..width = _dataGridSettings.swipeMaxOffset - ..height = swipeRowRect.height - ..offset = _getSwipingChildOffset(swipeRowRect); - - layout( - child: swipingWidget, - width: parentData.width, - height: parentData.height); - } - } - - child = parentData.nextSibling; - } - } - - @override - void paint(PaintingContext context, Offset offset) { - RenderBox? child = firstChild; - while (child != null) { - final _VisualContainerParentData childParentData = - child.parentData! as _VisualContainerParentData; - final RenderBox wholeRowElement = child; - if (wholeRowElement is _RenderVirtualizingCellsWidget) { - if (childParentData.width != 0.0 && childParentData.height != 0.0) { - if (childParentData.rowClipRect != null) { - if (wholeRowElement.dataRow._isSwipingRow && - _dataGridSettings.swipingOffset.abs() > 0.0) { - // We added the swiping widget to the last position of chidren collection. - // So, we can get it diretly from lastChild property. - final RenderBox? swipeWidget = lastChild; - if (swipeWidget != null) { - final _VisualContainerParentData childParentData = - swipeWidget.parentData! as _VisualContainerParentData; - context.paintChild( - swipeWidget, childParentData.offset + offset); - } - } - - context.pushClipRect( - needsCompositing, - childParentData.offset + offset, - childParentData.rowClipRect!, - (PaintingContext context, Offset offset) { - context.paintChild(child!, offset); - }, - clipBehavior: Clip.antiAlias, - ); - } else { - context.paintChild(child, childParentData.offset + offset); - } - } - } - child = childParentData.nextSibling; - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart deleted file mode 100644 index 1e6b97eea..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/current_cell_manager.dart +++ /dev/null @@ -1,539 +0,0 @@ -part of datagrid; - -class _CurrentCellManager { - _CurrentCellManager({required _DataGridStateDetails dataGridStateDetails}) { - _dataGridStateDetails = dataGridStateDetails; - } - - late _DataGridStateDetails _dataGridStateDetails; - - int rowIndex = -1; - - int columnIndex = -1; - - /// Current editing dataCell. - DataCellBase? dataCell; - - /// Indicate the any [DataGridCell] is in editing state. - bool isEditing = false; - - bool _handlePointerOperation( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { - if (dataGridSettings.allowSwiping) { - dataGridSettings.container.resetSwipeOffset(); - } - final RowColumnIndex previousRowColumnIndex = - RowColumnIndex(rowIndex, columnIndex); - if (!rowColumnIndex.equals(previousRowColumnIndex) && - dataGridSettings.navigationMode != GridNavigationMode.row) { - if (!_raiseCurrentCellActivating(rowColumnIndex)) { - return false; - } - _setCurrentCell(dataGridSettings, rowColumnIndex.rowIndex, - rowColumnIndex.columnIndex); - _raiseCurrentCellActivated(previousRowColumnIndex); - } else if (dataGridSettings.navigationMode == GridNavigationMode.row && - rowIndex != rowColumnIndex.rowIndex) { - _updateCurrentRowColumnIndex( - rowColumnIndex.rowIndex, rowColumnIndex.columnIndex); - _updateBorderForMultipleSelection(dataGridSettings, - previousRowColumnIndex: previousRowColumnIndex, - nextRowColumnIndex: rowColumnIndex); - } - - return true; - } - - void _setCurrentCell( - _DataGridSettings dataGridSettings, int rowIndex, int columnIndex, - [bool needToUpdateColumn = true]) { - if (this.rowIndex == rowIndex && this.columnIndex == columnIndex) { - return; - } - - _removeCurrentCell(dataGridSettings, needToUpdateColumn); - _updateCurrentRowColumnIndex(rowIndex, columnIndex); - _updateCurrentCell( - dataGridSettings, rowIndex, columnIndex, needToUpdateColumn); - } - - void _updateCurrentCell( - _DataGridSettings dataGridSettings, int rowIndex, int columnIndex, - [bool needToUpdateColumn = true]) { - final DataRowBase? dataRowBase = _getDataRow(dataGridSettings, rowIndex); - if (dataRowBase != null && needToUpdateColumn) { - final DataCellBase? dataCellBase = _getDataCell(dataRowBase, columnIndex); - if (dataCellBase != null) { - dataCell = dataCellBase; - _setCurrentCellDirty(dataRowBase, dataCellBase, true); - dataCellBase._updateColumn(); - } - } - - dataGridSettings.controller._currentCell = - _GridIndexResolver.resolveToRecordRowColumnIndex( - dataGridSettings, RowColumnIndex(rowIndex, columnIndex)); - } - - void _removeCurrentCell(_DataGridSettings dataGridSettings, - [bool needToUpdateColumn = true]) { - if (rowIndex == -1 && columnIndex == -1) { - return; - } - - final DataRowBase? dataRowBase = _getDataRow(dataGridSettings, rowIndex); - if (dataRowBase != null && needToUpdateColumn) { - final DataCellBase? dataCellBase = _getDataCell(dataRowBase, columnIndex); - if (dataCellBase != null) { - _setCurrentCellDirty(dataRowBase, dataCellBase, false); - dataCellBase._updateColumn(); - } - } - - _updateCurrentRowColumnIndex(-1, -1); - dataGridSettings.controller._currentCell = RowColumnIndex(-1, -1); - } - - DataRowBase? _getDataRow(_DataGridSettings dataGridSettings, int rowIndex) { - final List dataRows = dataGridSettings.rowGenerator.items; - if (dataRows.isEmpty) { - return null; - } - - return dataRows - .firstWhereOrNull((DataRowBase row) => row.rowIndex == rowIndex); - } - - DataCellBase? _getDataCell(DataRowBase dataRow, int columnIndex) { - if (dataRow._visibleColumns.isEmpty) { - return null; - } - - return dataRow._visibleColumns.firstWhereOrNull( - (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); - } - - void _updateCurrentRowColumnIndex(int rowIndex, int columnIndex) { - this.rowIndex = rowIndex; - this.columnIndex = columnIndex; - } - - void _setCurrentCellDirty( - DataRowBase? dataRow, DataCellBase? dataCell, bool enableCurrentCell) { - dataCell?.isCurrentCell = enableCurrentCell; - dataCell?._isDirty = true; - dataRow?.isCurrentRow = enableCurrentCell; - dataRow?._isDirty = true; - } - - bool _raiseCurrentCellActivating(RowColumnIndex rowColumnIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.onCurrentCellActivating == null) { - return true; - } - - final RowColumnIndex newRowColumnIndex = - _GridIndexResolver.resolveToRecordRowColumnIndex( - dataGridSettings, rowColumnIndex); - final RowColumnIndex oldRowColumnIndex = - _GridIndexResolver.resolveToRecordRowColumnIndex( - dataGridSettings, RowColumnIndex(rowIndex, columnIndex)); - return dataGridSettings.onCurrentCellActivating!( - newRowColumnIndex, oldRowColumnIndex); - } - - void _raiseCurrentCellActivated(RowColumnIndex previousRowColumnIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.onCurrentCellActivated == null) { - return; - } - - final RowColumnIndex newRowColumnIndex = - _GridIndexResolver.resolveToRecordRowColumnIndex( - dataGridSettings, RowColumnIndex(rowIndex, columnIndex)); - final RowColumnIndex oldRowColumnIndex = - _GridIndexResolver.resolveToRecordRowColumnIndex( - dataGridSettings, previousRowColumnIndex); - dataGridSettings.onCurrentCellActivated!( - newRowColumnIndex, oldRowColumnIndex); - } - - void _moveCurrentCellTo( - _DataGridSettings dataGridSettings, RowColumnIndex nextRowColumnIndex, - {bool isSelectionChanged = false, bool needToUpdateColumn = true}) { - final RowColumnIndex previousRowColumnIndex = RowColumnIndex( - dataGridSettings.currentCell.rowIndex, - dataGridSettings.currentCell.columnIndex); - - _scrollVertical(dataGridSettings, nextRowColumnIndex); - _scrollHorizontal(dataGridSettings, nextRowColumnIndex); - - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - _setCurrentCell(dataGridSettings, nextRowColumnIndex.rowIndex, - nextRowColumnIndex.columnIndex, needToUpdateColumn); - } else { - _updateCurrentRowColumnIndex( - nextRowColumnIndex.rowIndex, nextRowColumnIndex.columnIndex); - } - - if (dataGridSettings.selectionMode != SelectionMode.none && - dataGridSettings.selectionMode != SelectionMode.multiple && - !isSelectionChanged) { - final SelectionManagerBase rowSelectionController = - dataGridSettings.rowSelectionManager; - if (rowSelectionController is RowSelectionManager) { - rowSelectionController - .._processSelection( - dataGridSettings, nextRowColumnIndex, previousRowColumnIndex) - .._pressedRowColumnIndex = nextRowColumnIndex; - } - } - } - - void _processCurrentCell( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex, - {bool isSelectionChanged = false}) { - if (dataGridSettings.navigationMode == GridNavigationMode.row) { - _moveCurrentCellTo(dataGridSettings, rowColumnIndex, - isSelectionChanged: isSelectionChanged); - return; - } - - if (_raiseCurrentCellActivating(rowColumnIndex)) { - _moveCurrentCellTo(dataGridSettings, rowColumnIndex, - isSelectionChanged: isSelectionChanged); - _raiseCurrentCellActivated(rowColumnIndex); - } - } - - void _scrollHorizontal( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { - if (rowColumnIndex.columnIndex < columnIndex) { - if (_SelectionHelper.needToScrollLeft(dataGridSettings, rowColumnIndex)) { - _SelectionHelper.scrollInViewFromRight(dataGridSettings, - previousCellIndex: rowColumnIndex.columnIndex); - } - } - - if (rowColumnIndex.columnIndex > columnIndex) { - if (_SelectionHelper.needToScrollRight( - dataGridSettings, rowColumnIndex)) { - _SelectionHelper.scrollInViewFromLeft(dataGridSettings, - nextCellIndex: rowColumnIndex.columnIndex); - } - } - } - - void _scrollVertical( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { - if (rowColumnIndex.rowIndex < rowIndex) { - if (_SelectionHelper.needToScrollUp( - dataGridSettings, rowColumnIndex.rowIndex)) { - _SelectionHelper.scrollInViewFromDown(dataGridSettings, - previousRowIndex: rowColumnIndex.rowIndex); - } - } - - if (rowColumnIndex.rowIndex > rowIndex) { - if (_SelectionHelper.needToScrollDown( - dataGridSettings, rowColumnIndex.rowIndex)) { - _SelectionHelper.scrollInViewFromTop(dataGridSettings, - nextRowIndex: rowColumnIndex.rowIndex); - } - } - } - - void _updateBorderForMultipleSelection(_DataGridSettings dataGridSettings, - {RowColumnIndex? previousRowColumnIndex, - RowColumnIndex? nextRowColumnIndex}) { - if (dataGridSettings._isDesktop && - dataGridSettings.navigationMode == GridNavigationMode.row && - dataGridSettings.selectionMode == SelectionMode.multiple) { - if (previousRowColumnIndex != null) { - dataGridSettings.currentCell - ._getDataRow(dataGridSettings, previousRowColumnIndex.rowIndex) - ?._isDirty = true; - } - - if (nextRowColumnIndex != null) { - final int firstVisibleColumnIndex = - _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); - _updateCurrentRowColumnIndex( - nextRowColumnIndex.rowIndex >= 0 - ? nextRowColumnIndex.rowIndex - : rowIndex, - nextRowColumnIndex.columnIndex >= 0 - ? nextRowColumnIndex.columnIndex - : firstVisibleColumnIndex); - dataGridSettings.currentCell - ._getDataRow( - dataGridSettings, - nextRowColumnIndex.rowIndex >= 0 - ? nextRowColumnIndex.rowIndex - : rowIndex) - ?._isDirty = true; - } - } - } - - // ------------------------------Editing------------------------------------- - - void _onCellBeginEdit( - {DataCellBase? editingDataCell, - RowColumnIndex? editingRowColumnIndex, - bool isProgrammatic = false, - bool needToResolveIndex = true}) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - final bool checkEditingIsEnabled = dataGridSettings.allowEditing && - dataGridSettings.selectionMode != SelectionMode.none && - dataGridSettings.navigationMode != GridNavigationMode.row; - - bool checkDataCellIsValidForEditing(DataCellBase? editingDataCell) => - editingDataCell != null && - editingDataCell.gridColumn!.allowEditing && - !editingDataCell._isEditing && - editingDataCell._renderer != null && - editingDataCell._renderer!._isEditable && - editingDataCell._dataRow!.rowType == RowType.dataRow; - - if (!checkEditingIsEnabled || - (!isProgrammatic && !checkDataCellIsValidForEditing(editingDataCell))) { - return; - } - - // Enable the current cell first to start the on programmatic case. - if (isProgrammatic) { - if (editingRowColumnIndex == null || - editingRowColumnIndex.rowIndex.isNegative || - editingRowColumnIndex.columnIndex.isNegative) { - return; - } - - // When editing is initiate from the f2 key, we need not to to resolve - // the editing row column index because its already resolved based on the - // SfDataGrid. - editingRowColumnIndex = needToResolveIndex - ? _GridIndexResolver.resolveToRowColumnIndex( - dataGridSettings, editingRowColumnIndex) - : editingRowColumnIndex; - - if (editingRowColumnIndex.rowIndex.isNegative || - editingRowColumnIndex.columnIndex.isNegative || - editingRowColumnIndex.columnIndex > - _SelectionHelper.getLastCellIndex(dataGridSettings) || - editingRowColumnIndex.rowIndex > - _SelectionHelper.getLastRowIndex(dataGridSettings)) { - return; - } - - // If the editing is initiate from f2 key, need not to process the - // handleTap. - if (needToResolveIndex) { - dataGridSettings.rowSelectionManager.handleTap(editingRowColumnIndex); - } else { - // Need to skip the editing when current cell is not in view and we - // process initiate the editing from f2 key. - final DataRowBase? dataRow = - _getDataRow(dataGridSettings, editingRowColumnIndex.rowIndex); - if (dataRow != null) { - dataCell = _getDataCell(dataRow, editingRowColumnIndex.columnIndex); - } else { - return; - } - } - - editingDataCell = dataCell; - } - - if (!checkDataCellIsValidForEditing(editingDataCell)) { - return; - } - - editingRowColumnIndex = _GridIndexResolver.resolveToRecordRowColumnIndex( - dataGridSettings, - RowColumnIndex(editingDataCell!.rowIndex, editingDataCell.columnIndex)); - - if (editingRowColumnIndex.rowIndex.isNegative || - editingRowColumnIndex.columnIndex.isNegative) { - return; - } - - final bool beginEdit = _raiseCellBeginEdit( - dataGridSettings, editingRowColumnIndex, editingDataCell); - - if (beginEdit) { - void onCellSubmit() { - _onCellSubmit(dataGridSettings); - } - - final Widget? child = dataGridSettings.source.buildEditWidget( - editingDataCell._dataRow!._dataGridRow!, - editingRowColumnIndex, - editingDataCell.gridColumn!, - onCellSubmit); - - /// If child is null, we will not initiate the editing - if (child != null) { - /// Wrapped the editing widget inside the FocusScope. - /// To bring the focus automatically to editing widget. - /// canRequestFocus need to set true to auto detect the focus - /// User need to set the autoFocus to true in their editable widget. - editingDataCell._editingWidget = - FocusScope(canRequestFocus: true, child: child); - editingDataCell._isEditing = - editingDataCell._dataRow!._isEditing = isEditing = true; - - dataGridSettings.source._notifyDataGridPropertyChangeListeners( - rowColumnIndex: editingRowColumnIndex, propertyName: 'editing'); - } - } - } - - bool _raiseCellBeginEdit(_DataGridSettings dataGridSettings, - RowColumnIndex rowColumnIndex, DataCellBase dataCell) { - return dataGridSettings.source.onCellBeginEdit( - dataCell._dataRow!._dataGridRow!, rowColumnIndex, dataCell.gridColumn!); - } - - /// Help to end-edit editable widget and refresh the [DataGridCell]. - /// - /// * isCellCancelEdit - Used to avoid the onCellSubmit behaviour and perform - /// the cellCancelEdit behaviour, - /// * Default value is [false]. - /// Case: - /// 1) Keyboard navigation - Escape key - /// - /// * cancelCanCellSubmit - Used to skip the call canCellSubmit. - /// * Default value is [false]. - /// Case: - /// 1) In keyboard navigation we will call the canCellSubmit before the - /// processing the key. So, we need to skip the canCellSubmit second time. so - /// if we pass the cancelCanCellSubmit to false its will skip it. - /// - /// * canRefresh - Used to skip the call notifyListener - /// * Default value is [true]. - /// Case: - /// 1) _onCellSubmit is call from handleDataGridSource we no need to call the - /// _notifyDataGridPropertyChangeListeners to refresh twice.By, set value false - /// it will skip the refreshing. - void _onCellSubmit(_DataGridSettings dataGridSettings, - {bool isCellCancelEdit = false, - bool cancelCanSubmitCell = false, - bool canRefresh = true}) { - if (!isEditing) { - return; - } - - final DataRowBase? dataRow = _getEditingRow(dataGridSettings); - - if (dataRow == null) { - return; - } - - final DataCellBase? dataCell = _getEditingCell(dataRow); - - if (dataCell == null || !dataCell._isEditing) { - return; - } - - if (isEditing) { - final RowColumnIndex rowColumnIndex = - _GridIndexResolver.resolveToRecordRowColumnIndex(dataGridSettings, - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex)); - - if (rowColumnIndex.rowIndex.isNegative || - rowColumnIndex.columnIndex.isNegative) { - return; - } - - final DataGridRow dataGridRow = dataCell._dataRow!._dataGridRow!; - - void resetEditing() { - dataCell._editingWidget = null; - dataCell._isDirty = true; - dataCell._isEditing = dataRow._isEditing = isEditing = false; - } - - if (!isCellCancelEdit) { - bool canSubmitCell = false; - - /// Via keyboard navigation we will check the canCellSubmit before - /// moving to other cell or another row. so we need to skip the - /// canCellSubmit method calling once again - if (!cancelCanSubmitCell) { - canSubmitCell = dataGridSettings.source - .canSubmitCell(dataGridRow, rowColumnIndex, dataCell.gridColumn!); - } else { - canSubmitCell = true; - } - if (canSubmitCell) { - resetEditing(); - dataGridSettings.source - .onCellSubmit(dataGridRow, rowColumnIndex, dataCell.gridColumn!); - } - } else { - resetEditing(); - dataGridSettings.source.onCellCancelEdit( - dataGridRow, rowColumnIndex, dataCell.gridColumn!); - } - - if (canRefresh) { - /// Refresh the visible [DataRow]'s on editing the [DataCell] when - /// sorting enabled - if (dataGridSettings.allowSorting) { - dataGridSettings.source._updateDataSource(); - dataGridSettings.container - .._updateDataGridRows(dataGridSettings) - .._isDirty = true; - } - - dataGridSettings.source._notifyDataGridPropertyChangeListeners( - rowColumnIndex: rowColumnIndex, propertyName: 'editing'); - } - - if (dataGridSettings.dataGridFocusNode != null && - !dataGridSettings.dataGridFocusNode!.hasPrimaryFocus) { - dataGridSettings.dataGridFocusNode!.requestFocus(); - } - } - } - - DataRowBase? _getEditingRow(_DataGridSettings dataGridSettings) { - return dataGridSettings.rowGenerator.items - .firstWhereOrNull((DataRowBase dataRow) => dataRow._isEditing); - } - - DataCellBase? _getEditingCell(DataRowBase dataRow) { - return dataRow._visibleColumns - .firstWhereOrNull((DataCellBase dataCell) => dataCell._isEditing); - } - - bool _canSubmitCell(_DataGridSettings dataGridSettings) { - final DataRowBase? dataRow = _getEditingRow(dataGridSettings); - - if (dataRow == null) { - return false; - } - - final DataCellBase? dataCell = _getEditingCell(dataRow); - - if (dataCell == null || !dataCell._isEditing) { - return false; - } - - final RowColumnIndex rowColumnIndex = - _GridIndexResolver.resolveToRecordRowColumnIndex(dataGridSettings, - RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex)); - - if (rowColumnIndex.rowIndex.isNegative || - rowColumnIndex.columnIndex.isNegative) { - return false; - } - - final DataGridRow dataGridRow = dataCell._dataRow!._dataGridRow!; - - return dataGridSettings.source - .canSubmitCell(dataGridRow, rowColumnIndex, dataCell.gridColumn!); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart deleted file mode 100644 index ddf8a3ad3..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/row_selection_manager.dart +++ /dev/null @@ -1,1088 +0,0 @@ -part of datagrid; - -/// Processes the row selection operation in [SfDataGrid]. -/// -/// You can override the available methods in this class to customize the -/// selection operation -/// in DataGrid and set the instance to the [SfDataGrid.selectionManager]. -/// -/// The following code shows how to change the Enter key behavior by -/// overriding the handleKeyEvent() method in RowSelectionManager, -/// -/// ``` dart -/// final CustomSelectionManager _customSelectionManager = CustomSelectionManager(); - -/// @override -/// Widget build(BuildContext context){ -/// return MaterialApp( -/// home: Scaffold( -/// body: SfDataGrid( -/// source: _employeeDataSource, -/// columns: [ -/// GridColumn(columnName: 'id', label = Text('ID')), -/// GridColumn(columnName: 'name', label = Text('Name')), -/// GridColumn(columnName: 'designation', label = Text('Designation')), -/// GridColumn(columnName: 'salary', label = Text('Salary')), -/// ], -/// selectionMode: SelectionMode.multiple, -/// navigationMode: GridNavigationMode.cell, -/// selectionManager: _customSelectionManager, -/// )) -/// ); -/// } - -/// class CustomSelectionManager extends RowSelectionManager{ -/// @override -/// void handleKeyEvent(RawKeyEvent keyEvent) { -/// if(keyEvent.logicalKey == LogicalKeyboardKey.enter){ -/// //apply your logic -/// return; -/// } - -/// super.handleKeyEvent(keyEvent); -/// } -/// } -/// ``` -class RowSelectionManager extends SelectionManagerBase { - /// Creates the [RowSelectionManager] for [SfDataGrid] widget. - RowSelectionManager() : super(); - - RowColumnIndex _pressedRowColumnIndex = RowColumnIndex(-1, -1); - - void _applySelection(RowColumnIndex rowColumnIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - final int recordIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, rowColumnIndex.rowIndex); - DataGridRow? record = - _SelectionHelper.getRecord(dataGridSettings, recordIndex); - - final List addedItems = []; - final List removeItems = []; - - if (dataGridSettings.selectionMode == SelectionMode.single) { - if (record == null || _selectedRows.contains(record)) { - return; - } - - if (dataGridSettings.onSelectionChanging != null || - dataGridSettings.onSelectionChanged != null) { - addedItems.add(record); - if (_selectedRows.selectedRow.isNotEmpty) { - removeItems.add(_selectedRows.selectedRow.first); - } - } - - if (_raiseSelectionChanging( - newItems: addedItems, oldItems: removeItems)) { - _clearSelectedRow(dataGridSettings); - _addSelection(record, dataGridSettings); - notifyListeners(); - _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); - } - //If user, return false in selection changing, and we should need - // to update the current cell. - else if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - notifyListeners(); - } - } else if (dataGridSettings.selectionMode == SelectionMode.singleDeselect) { - if (dataGridSettings.onSelectionChanging != null || - dataGridSettings.onSelectionChanged != null) { - if (_selectedRows.selectedRow.isNotEmpty) { - removeItems.add(_selectedRows.selectedRow.first); - } - - if (record != null && !_selectedRows.contains(record)) { - addedItems.add(record); - } - } - - if (_raiseSelectionChanging( - newItems: addedItems, oldItems: removeItems)) { - if (record != null && !_selectedRows.contains(record)) { - _clearSelectedRow(dataGridSettings); - _addSelection(record, dataGridSettings); - } else { - _clearSelectedRow(dataGridSettings); - } - - notifyListeners(); - _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); - } - //If user, return false in selection changing, and we should need - // to update the current cell. - else if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - notifyListeners(); - } - } else if (dataGridSettings.selectionMode == SelectionMode.multiple) { - if (dataGridSettings.onSelectionChanging != null || - dataGridSettings.onSelectionChanged != null) { - if (record != null) { - if (!_selectedRows.contains(record)) { - addedItems.add(record); - } else { - removeItems.add(record); - } - } - } - - if (_raiseSelectionChanging( - newItems: addedItems, oldItems: removeItems) && - record != null) { - if (!_selectedRows.contains(record)) { - _addSelection(record, dataGridSettings); - } else { - _removeSelection(record, dataGridSettings); - } - - notifyListeners(); - _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); - } - //If user, return false in selection changing, and we should need to - // update the current cell. - else if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - notifyListeners(); - } - } - - record = null; - } - - void _addSelection(DataGridRow? record, _DataGridSettings dataGridSettings) { - if (record != null && !_selectedRows.contains(record)) { - final int rowIndex = - _SelectionHelper.resolveToRowIndex(dataGridSettings, record); - if (!_selectedRows.selectedRow.contains(record)) { - _selectedRows.selectedRow.add(record); - dataGridSettings.controller._selectedRows.add(record); - _refreshSelection(); - _setRowSelection(rowIndex, dataGridSettings, true); - } - } - } - - void _removeSelection( - DataGridRow? record, _DataGridSettings dataGridSettings) { - if (record != null && _selectedRows.contains(record)) { - final int rowIndex = - _SelectionHelper.resolveToRowIndex(dataGridSettings, record); - _selectedRows.selectedRow.remove(record); - dataGridSettings.controller._selectedRows.remove(record); - _refreshSelection(); - _setRowSelection(rowIndex, dataGridSettings, false); - } - } - - void _clearSelectedRow(_DataGridSettings dataGridSettings) { - if (_selectedRows.selectedRow.isNotEmpty) { - final DataGridRow selectedItem = _selectedRows.selectedRow.first; - _removeSelection(selectedItem, dataGridSettings); - } - } - - void _clearSelectedRows(_DataGridSettings dataGridSettings) { - if (_selectedRows.selectedRow.isNotEmpty) { - for (int i = _selectedRows.selectedRow.length - 1; i >= 0; i--) { - final DataGridRow selectedItem = _selectedRows.selectedRow[i]; - _removeSelection(selectedItem, dataGridSettings); - } - } - - _clearSelection(dataGridSettings); - } - - void _setRowSelection( - int rowIndex, _DataGridSettings dataGridSettings, bool isRowSelected) { - if (dataGridSettings.rowGenerator.items.isEmpty) { - return; - } - - DataRowBase? row = dataGridSettings.rowGenerator.items - .firstWhereOrNull((DataRowBase item) => item.rowIndex == rowIndex); - if (row != null && row.rowType == RowType.dataRow) { - row - .._isDirty = true - .._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( - dataGridSettings, row._dataGridRow!) - ..isSelectedRow = isRowSelected; - } - - row = null; - } - - void _clearSelection(_DataGridSettings dataGridSettings) { - _selectedRows.selectedRow.clear(); - dataGridSettings.controller._selectedRows.clear(); - dataGridSettings.controller._selectedRow = null; - dataGridSettings.controller._selectedIndex = -1; - for (final DataRowBase dataRow in dataGridSettings.rowGenerator.items) { - if (dataRow.isSelectedRow) { - dataRow.isSelectedRow = false; - } - } - - _clearCurrentCell(dataGridSettings); - } - - void _clearCurrentCell(_DataGridSettings dataGridSettings) { - dataGridSettings.currentCell._removeCurrentCell(dataGridSettings); - } - - void _refreshSelection() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - _removeUnWantedDataGridRows(dataGridSettings); - final DataGridRow? _selectedRow = _selectedRows.selectedRow.isNotEmpty - ? _selectedRows.selectedRow.last - : null; - final int _recordIndex = _selectedRow == null - ? -1 - : dataGridSettings.source._effectiveRows.indexOf(_selectedRow); - dataGridSettings.controller._selectedIndex = _recordIndex; - dataGridSettings.controller._selectedRow = _selectedRow; - } - - void _removeUnWantedDataGridRows(_DataGridSettings dataGridSettings) { - final List duplicateSelectedRows = - _selectedRows.selectedRow.toList(); - for (final DataGridRow selectedRow in duplicateSelectedRows) { - final int rowIndex = - dataGridSettings.source._effectiveRows.indexOf(selectedRow); - if (rowIndex.isNegative) { - _selectedRows.selectedRow.remove(selectedRow); - dataGridSettings.controller._selectedRows.remove(selectedRow); - } - } - } - - void _addCurrentCell(DataGridRow record, _DataGridSettings dataGridSettings, - {bool isSelectionChanging = false}) { - final int rowIndex = - _SelectionHelper.resolveToRowIndex(dataGridSettings, record); - - if (rowIndex <= _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - return; - } - - if (dataGridSettings.currentCell.columnIndex > 0) { - dataGridSettings.currentCell._moveCurrentCellTo(dataGridSettings, - RowColumnIndex(rowIndex, dataGridSettings.currentCell.columnIndex), - isSelectionChanged: isSelectionChanging); - } else { - final int firstVisibleColumnIndex = - _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); - dataGridSettings.currentCell._moveCurrentCellTo( - dataGridSettings, RowColumnIndex(rowIndex, firstVisibleColumnIndex), - isSelectionChanged: isSelectionChanging); - } - } - - void _onNavigationModeChanged() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.navigationMode == GridNavigationMode.row) { - final RowColumnIndex currentRowColumnIndex = RowColumnIndex( - dataGridSettings.currentCell.rowIndex, - dataGridSettings.currentCell.columnIndex); - _clearCurrentCell(dataGridSettings); - dataGridSettings.currentCell._updateBorderForMultipleSelection( - dataGridSettings, - nextRowColumnIndex: currentRowColumnIndex); - } else { - if (dataGridSettings.selectionMode != SelectionMode.none) { - final DataGridRow? lastRecord = dataGridSettings.controller.selectedRow; - - if (lastRecord == null) { - return; - } - - final RowColumnIndex _currentRowColumnIndex = - _getRowColumnIndexOnModeChanged(dataGridSettings, lastRecord); - - if (_currentRowColumnIndex.rowIndex <= 0) { - return; - } - - // CurrentCell is not drawn when changing the navigation mode at runtime - // We have to update the particular row and column when current cell - // index's are same. - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - if (currentCell.rowIndex == _currentRowColumnIndex.rowIndex && - currentCell.columnIndex == _currentRowColumnIndex.columnIndex) { - currentCell._updateCurrentCell( - dataGridSettings, - _currentRowColumnIndex.rowIndex, - _currentRowColumnIndex.columnIndex, - ); - } else { - currentCell._moveCurrentCellTo( - dataGridSettings, - RowColumnIndex(_currentRowColumnIndex.rowIndex, - _currentRowColumnIndex.columnIndex), - isSelectionChanged: true); - } - } - } - } - - RowColumnIndex _getRowColumnIndexOnModeChanged( - _DataGridSettings dataGridSettings, DataGridRow? lastRecord) { - final int rowIndex = - lastRecord == null && _pressedRowColumnIndex.rowIndex > 0 - ? _pressedRowColumnIndex.rowIndex - : _SelectionHelper.resolveToRowIndex(dataGridSettings, lastRecord!); - - final int columnIndex = _pressedRowColumnIndex.columnIndex != -1 - ? _pressedRowColumnIndex.columnIndex - : _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); - - return RowColumnIndex(rowIndex, columnIndex); - } - - void _onDataSourceChanged() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - _clearSelection(dataGridSettings); - } - - @override - void handleTap(RowColumnIndex rowColumnIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - _pressedRowColumnIndex = rowColumnIndex; - if (dataGridSettings.selectionMode == SelectionMode.none) { - return; - } - - final RowColumnIndex previousRowColumnIndex = RowColumnIndex( - dataGridSettings.currentCell.rowIndex, - dataGridSettings.currentCell.columnIndex); - if (!dataGridSettings.currentCell - ._handlePointerOperation(dataGridSettings, rowColumnIndex)) { - return; - } - - _processSelection(dataGridSettings, rowColumnIndex, previousRowColumnIndex); - } - - void _processSelection( - _DataGridSettings dataGridSettings, - RowColumnIndex nextRowColumnIndex, - RowColumnIndex previousRowColumnIndex) { - // If selectionMode is single. next current cell is going to present in the - // same selected row. - // In this case, we don't update the whole data row. Instead of that - // we have to update next and previous current cell alone and call setState. - // In other selection mode we will update the whole data row and - // it will work. - if (dataGridSettings.selectionMode == SelectionMode.single && - dataGridSettings.navigationMode == GridNavigationMode.cell && - nextRowColumnIndex.rowIndex == previousRowColumnIndex.rowIndex && - nextRowColumnIndex.columnIndex != previousRowColumnIndex.columnIndex) { - notifyListeners(); - return; - } - - _applySelection(nextRowColumnIndex); - } - - @override - void handleDataGridSourceChanges() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - _clearSelectedRows(dataGridSettings); - } - - @override - void onSelectedRowChanged() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - if (dataGridSettings.selectionMode == SelectionMode.none) { - return; - } - - final DataGridRow? newValue = dataGridSettings.controller.selectedRow; - - bool canClearSelections() => - _selectedRows.selectedRow.isNotEmpty && - dataGridSettings.selectionMode != SelectionMode.multiple; - - //If newValue is negative we have clear the whole selection data. - //In multiple case we shouldn't to clear the collection as well - // source properties. - if (newValue == null && canClearSelections()) { - _clearSelectedRow(dataGridSettings); - notifyListeners(); - return; - } - - final int recordIndex = - dataGridSettings.source._effectiveRows.indexOf(newValue!); - final int rowIndex = - _GridIndexResolver.resolveToRowIndex(dataGridSettings, recordIndex); - - if (rowIndex < _GridIndexResolver.getHeaderIndex(dataGridSettings)) { - return; - } - - if (!_selectedRows.contains(newValue)) { - //In multiple case we shouldn't to clear the collection as - // well source properties. - if (canClearSelections()) { - _clearSelectedRow(dataGridSettings); - } - - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - _addCurrentCell(newValue, dataGridSettings, isSelectionChanging: true); - } else { - final int columnIndex = - _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); - final int rowIndex = - _SelectionHelper.resolveToRowIndex(dataGridSettings, newValue); - dataGridSettings.currentCell - ._updateCurrentRowColumnIndex(rowIndex, columnIndex); - } - - _addSelection(newValue, dataGridSettings); - notifyListeners(); - } - } - - @override - void onSelectedIndexChanged() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - if (dataGridSettings.selectionMode == SelectionMode.none) { - return; - } - - final int newValue = dataGridSettings.controller.selectedIndex; - - if (dataGridSettings.source._effectiveRows.isEmpty || - newValue > dataGridSettings.source._effectiveRows.length) { - return; - } - - bool canClearSelections() => - _selectedRows.selectedRow.isNotEmpty && - dataGridSettings.selectionMode != SelectionMode.multiple; - - //If newValue is negative we have to clear the whole selection data. - //In multiple case we shouldn't to clear the collection as - // well source properties. - if (newValue == -1 && canClearSelections()) { - _clearSelectedRow(dataGridSettings); - notifyListeners(); - return; - } - - final DataGridRow? record = - _SelectionHelper.getRecord(dataGridSettings, newValue); - if (record != null && !_selectedRows.contains(record)) { - //In multiple case we shouldn't to clear the collection as - // well source properties. - if (canClearSelections()) { - _clearSelectedRow(dataGridSettings); - } - - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - _addCurrentCell(record, dataGridSettings, isSelectionChanging: true); - } else { - final int rowIndex = - _SelectionHelper.resolveToRowIndex(dataGridSettings, record); - final int columnIndex = - _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); - dataGridSettings.currentCell - ._updateCurrentRowColumnIndex(rowIndex, columnIndex); - } - - _addSelection(record, dataGridSettings); - notifyListeners(); - } - } - - @override - void onSelectedRowsChanged() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - - if (dataGridSettings.selectionMode != SelectionMode.multiple || - dataGridSettings.selectionMode == SelectionMode.none) { - return; - } - - final List newValue = - dataGridSettings.controller.selectedRows.toList(growable: false); - - if (newValue.isEmpty) { - _clearSelectedRows(dataGridSettings); - notifyListeners(); - return; - } - - _clearSelectedRows(dataGridSettings); - for (final DataGridRow record in newValue) { - _addSelection(record, dataGridSettings); - } - - if (dataGridSettings.navigationMode == GridNavigationMode.cell && - _selectedRows.selectedRow.isNotEmpty) { - final DataGridRow lastRecord = _selectedRows.selectedRow.last; - _addCurrentCell(lastRecord, dataGridSettings, isSelectionChanging: true); - } else if (dataGridSettings._isDesktop && - dataGridSettings.navigationMode == GridNavigationMode.row) { - final DataGridRow lastRecord = _selectedRows.selectedRow.last; - final int rowIndex = - _SelectionHelper.resolveToRowIndex(dataGridSettings, lastRecord); - dataGridSettings.currentCell._updateBorderForMultipleSelection( - dataGridSettings, - nextRowColumnIndex: RowColumnIndex(rowIndex, -1)); - } - - notifyListeners(); - } - - @override - void onGridSelectionModeChanged() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.selectionMode == SelectionMode.none) { - _clearSelectedRows(dataGridSettings); - _pressedRowColumnIndex = RowColumnIndex(-1, -1); - } else if (dataGridSettings.selectionMode != SelectionMode.none && - dataGridSettings.selectionMode != SelectionMode.multiple) { - DataGridRow? lastRecord = dataGridSettings.controller.selectedRow; - _clearSelection(dataGridSettings); - if (dataGridSettings.navigationMode == GridNavigationMode.cell && - lastRecord != null) { - final RowColumnIndex _currentRowColumnIndex = - _getRowColumnIndexOnModeChanged(dataGridSettings, lastRecord); - - if (_currentRowColumnIndex.rowIndex <= 0) { - return; - } - - lastRecord = dataGridSettings.selectionMode == SelectionMode.single - ? _SelectionHelper.getRecord( - dataGridSettings, - _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, _currentRowColumnIndex.rowIndex)) - : lastRecord; - - dataGridSettings.currentCell._moveCurrentCellTo( - dataGridSettings, - RowColumnIndex(_currentRowColumnIndex.rowIndex, - _currentRowColumnIndex.columnIndex), - isSelectionChanged: true); - } - - if (lastRecord != null) { - _addSelection(lastRecord, dataGridSettings); - } - } else if (dataGridSettings._isDesktop && - dataGridSettings.selectionMode == SelectionMode.multiple) { - final RowColumnIndex currentRowColumnIndex = - RowColumnIndex(dataGridSettings.currentCell.rowIndex, -1); - dataGridSettings.currentCell._updateBorderForMultipleSelection( - dataGridSettings, - nextRowColumnIndex: currentRowColumnIndex); - } - } - - @override - void _onRowColumnChanged(int recordLength, int columnLength) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - - if (currentCell.rowIndex == -1 && currentCell.columnIndex == -1) { - return; - } - - final int rowIndex = _GridIndexResolver.resolveToRecordIndex( - dataGridSettings, currentCell.rowIndex); - if (recordLength > 0 && - rowIndex >= recordLength && - currentCell.rowIndex != -1) { - final int startRowIndex = - _getPreviousRowIndex(dataGridSettings, currentCell.rowIndex); - currentCell._moveCurrentCellTo(dataGridSettings, - RowColumnIndex(startRowIndex, currentCell.columnIndex), - needToUpdateColumn: false); - _refreshSelection(); - } - - final int columnIndex = _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, currentCell.columnIndex); - if (columnLength > 0 && - columnIndex >= columnLength && - currentCell.columnIndex != -1) { - final int startColumnIndex = - _getPreviousColumnIndex(dataGridSettings, currentCell.columnIndex); - currentCell._moveCurrentCellTo(dataGridSettings, - RowColumnIndex(currentCell.rowIndex, startColumnIndex), - needToUpdateColumn: false); - } - } - - @override - void _updateSelectionController( - {bool isSelectionModeChanged = false, - bool isNavigationModeChanged = false, - bool isDataSourceChanged = false}) { - if (isDataSourceChanged) { - _onDataSourceChanged(); - } - - if (isSelectionModeChanged) { - onGridSelectionModeChanged(); - } - - if (isNavigationModeChanged) { - _onNavigationModeChanged(); - } - } - - @override - void _handleSelectionPropertyChanged( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { - switch (propertyName) { - case 'selectedIndex': - onSelectedIndexChanged(); - break; - case 'selectedRow': - onSelectedRowChanged(); - break; - case 'selectedRows': - onSelectedRowsChanged(); - break; - default: - break; - } - } - - //KeyNavigation - @override - void handleKeyEvent(RawKeyEvent keyEvent) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.currentCell.isEditing && - keyEvent.logicalKey != LogicalKeyboardKey.escape) { - if (!dataGridSettings.currentCell._canSubmitCell(dataGridSettings)) { - return; - } - - dataGridSettings.currentCell - ._onCellSubmit(dataGridSettings, cancelCanSubmitCell: true); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.tab) { - _processKeyTab(keyEvent); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.home) { - _processHomeKey(dataGridSettings, keyEvent); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.end) { - _processEndKey(dataGridSettings, keyEvent); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.pageUp) { - _processPageUp(); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.pageDown) { - _processPageDown(); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.arrowUp) { - _processKeyUp(keyEvent); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.arrowDown || - keyEvent.logicalKey == LogicalKeyboardKey.enter) { - _processKeyDown(keyEvent); - if (keyEvent.logicalKey == LogicalKeyboardKey.enter) { - return; - } - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.arrowLeft) { - if (dataGridSettings.textDirection == TextDirection.rtl) { - _processKeyRight(dataGridSettings, keyEvent); - } else { - _processKeyLeft(dataGridSettings, keyEvent); - } - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.arrowRight) { - if (dataGridSettings.textDirection == TextDirection.rtl) { - _processKeyLeft(dataGridSettings, keyEvent); - } else { - _processKeyRight(dataGridSettings, keyEvent); - } - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.keyA) { - if (keyEvent.isControlPressed) { - _processSelectedAll(); - } - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.space) { - _processSpaceKey(); - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.f2) { - if (dataGridSettings.allowEditing && - dataGridSettings.navigationMode == GridNavigationMode.cell) { - final RowColumnIndex rowColumnIndex = RowColumnIndex( - dataGridSettings.currentCell.rowIndex, - dataGridSettings.currentCell.columnIndex); - dataGridSettings.currentCell._onCellBeginEdit( - editingRowColumnIndex: rowColumnIndex, - isProgrammatic: true, - needToResolveIndex: false); - } - } - - if (keyEvent.logicalKey == LogicalKeyboardKey.escape) { - if (dataGridSettings.allowEditing && - dataGridSettings.navigationMode == GridNavigationMode.cell && - dataGridSettings.currentCell.isEditing) { - dataGridSettings.currentCell - ._onCellSubmit(dataGridSettings, isCellCancelEdit: true); - } - } - } - - void _processEndKey( - _DataGridSettings dataGridSettings, RawKeyEvent keyEvent) { - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int lastCellIndex = - _SelectionHelper.getLastCellIndex(dataGridSettings); - final bool needToScrollToMinOrMaxExtent = - dataGridSettings.container.extentWidth > dataGridSettings.viewWidth; - - if (needToScrollToMinOrMaxExtent) { - _SelectionHelper.scrollInViewFromLeft(dataGridSettings, - needToScrollMaxExtent: true); - } - - if (keyEvent.isControlPressed && - keyEvent.logicalKey != LogicalKeyboardKey.arrowRight) { - final int lastRowIndex = - _SelectionHelper.getLastNavigatingRowIndex(dataGridSettings); - _SelectionHelper.scrollInViewFromTop(dataGridSettings, - needToScrollToMaxExtent: true); - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(lastRowIndex, lastCellIndex)); - } else { - _processSelectionAndCurrentCell(dataGridSettings, - RowColumnIndex(currentCell.rowIndex, lastCellIndex)); - } - } - - void _processHomeKey( - _DataGridSettings dataGridSettings, RawKeyEvent keyEvent) { - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int firstCellIndex = - _SelectionHelper.getFirstCellIndex(dataGridSettings); - final bool needToScrollToMinOrMaxExtend = - dataGridSettings.container.extentWidth > dataGridSettings.viewWidth; - - if (needToScrollToMinOrMaxExtend) { - _SelectionHelper.scrollInViewFromRight(dataGridSettings, - needToScrollToMinExtent: true); - } - - if (keyEvent.isControlPressed && - keyEvent.logicalKey != LogicalKeyboardKey.arrowLeft) { - final int firstRowIndex = - _SelectionHelper.getFirstNavigatingRowIndex(dataGridSettings); - _SelectionHelper.scrollInViewFromDown(dataGridSettings, - needToScrollToMinExtent: true); - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(firstRowIndex, firstCellIndex)); - } else { - _processSelectionAndCurrentCell(dataGridSettings, - RowColumnIndex(currentCell.rowIndex, firstCellIndex)); - } - } - - void _processPageDown() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int index = _SelectionHelper.getNextPageIndex(dataGridSettings); - if (currentCell.rowIndex != index && index != -1) { - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(index, currentCell.columnIndex)); - } - } - - void _processPageUp() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int index = _SelectionHelper.getPreviousPageIndex(dataGridSettings); - if (currentCell.rowIndex != index) { - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(index, currentCell.columnIndex)); - } - } - - void _processKeyDown(RawKeyEvent keyEvent) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int nextRowIndex = - _getNextRowIndex(dataGridSettings, currentCell.rowIndex); - final int lastRowIndex = - _SelectionHelper.getLastNavigatingRowIndex(dataGridSettings); - int nextColumnIndex = currentCell.columnIndex; - - if (nextColumnIndex <= 0) { - nextColumnIndex = _SelectionHelper.getFirstCellIndex(dataGridSettings); - } - - if (nextRowIndex > lastRowIndex || currentCell.rowIndex == nextRowIndex) { - return; - } - - if (keyEvent.isControlPressed) { - _SelectionHelper.scrollInViewFromTop(dataGridSettings, - needToScrollToMaxExtent: true); - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(lastRowIndex, nextColumnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); - } else { - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(nextRowIndex, nextColumnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); - } - } - - void _processKeyUp(RawKeyEvent keyEvent) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int previousRowIndex = - _getPreviousRowIndex(dataGridSettings, currentCell.rowIndex); - - if (previousRowIndex == currentCell.rowIndex) { - return; - } - - if (keyEvent.isControlPressed) { - final int firstRowIndex = - _SelectionHelper.getFirstRowIndex(dataGridSettings); - _SelectionHelper.scrollInViewFromDown(dataGridSettings, - needToScrollToMinExtent: true); - _processSelectionAndCurrentCell(dataGridSettings, - RowColumnIndex(firstRowIndex, currentCell.columnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); - } else { - _processSelectionAndCurrentCell(dataGridSettings, - RowColumnIndex(previousRowIndex, currentCell.columnIndex), - isShiftKeyPressed: keyEvent.isShiftPressed); - } - } - - void _processKeyRight( - _DataGridSettings dataGridSettings, RawKeyEvent keyEvent) { - if (dataGridSettings.navigationMode == GridNavigationMode.row) { - return; - } - - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int lastCellIndex = - _SelectionHelper.getLastCellIndex(dataGridSettings); - final int nextCellIndex = - _getNextColumnIndex(dataGridSettings, currentCell.columnIndex); - - if (currentCell.rowIndex <= - _GridIndexResolver.getHeaderIndex(dataGridSettings) || - nextCellIndex > lastCellIndex || - currentCell.columnIndex == nextCellIndex) { - return; - } - - if (keyEvent.isControlPressed) { - if (dataGridSettings.textDirection == TextDirection.rtl) { - _processHomeKey(dataGridSettings, keyEvent); - } else { - _processEndKey(dataGridSettings, keyEvent); - } - } else { - currentCell._processCurrentCell( - dataGridSettings, RowColumnIndex(currentCell.rowIndex, nextCellIndex), - isSelectionChanged: true); - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - notifyListeners(); - } - } - } - - void _processKeyLeft( - _DataGridSettings dataGridSettings, RawKeyEvent keyEvent) { - if (dataGridSettings.navigationMode == GridNavigationMode.row) { - return; - } - - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int previousCellIndex = - _getPreviousColumnIndex(dataGridSettings, currentCell.columnIndex); - - if (currentCell.rowIndex <= - _GridIndexResolver.getHeaderIndex(dataGridSettings) || - previousCellIndex < - _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings)) { - return; - } - - if (keyEvent.isControlPressed) { - if (dataGridSettings.textDirection == TextDirection.rtl) { - _processEndKey(dataGridSettings, keyEvent); - } else { - _processHomeKey(dataGridSettings, keyEvent); - } - } else { - currentCell._processCurrentCell(dataGridSettings, - RowColumnIndex(currentCell.rowIndex, previousCellIndex), - isSelectionChanged: true); - if (dataGridSettings.navigationMode == GridNavigationMode.cell) { - notifyListeners(); - } - } - } - - void _processKeyTab(RawKeyEvent keyEvent) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int lastCellIndex = - _SelectionHelper.getLastCellIndex(dataGridSettings); - int firstCellIndex = _SelectionHelper.getFirstCellIndex(dataGridSettings); - final int firstRowIndex = - _SelectionHelper.getFirstRowIndex(dataGridSettings); - - if (dataGridSettings.navigationMode == GridNavigationMode.row || - (currentCell.rowIndex < 0 && currentCell.columnIndex < 0)) { - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(firstRowIndex, firstCellIndex)); - notifyListeners(); - return; - } - - final bool needToScrollToMinOrMaxExtend = - dataGridSettings.container.extentWidth > dataGridSettings.viewWidth; - - if (keyEvent.isShiftPressed) { - if (currentCell.columnIndex == firstCellIndex && - currentCell.rowIndex == firstRowIndex) { - return; - } - - if (currentCell.columnIndex == firstCellIndex) { - final int previousRowIndex = - _getPreviousRowIndex(dataGridSettings, currentCell.rowIndex); - if (needToScrollToMinOrMaxExtend) { - _SelectionHelper.scrollInViewFromLeft(dataGridSettings, - needToScrollMaxExtent: needToScrollToMinOrMaxExtend); - } - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(previousRowIndex, lastCellIndex)); - } else { - _processKeyLeft(dataGridSettings, keyEvent); - } - } else { - if (currentCell.columnIndex == lastCellIndex) { - final int nextRowIndex = - _getNextRowIndex(dataGridSettings, currentCell.rowIndex); - if (needToScrollToMinOrMaxExtend) { - _SelectionHelper.scrollInViewFromRight(dataGridSettings, - needToScrollToMinExtent: needToScrollToMinOrMaxExtend); - } - - firstCellIndex = (nextRowIndex == currentCell.rowIndex && - lastCellIndex == currentCell.columnIndex) - ? currentCell.columnIndex - : firstCellIndex; - - _processSelectionAndCurrentCell( - dataGridSettings, RowColumnIndex(nextRowIndex, firstCellIndex)); - } else { - _processKeyRight(dataGridSettings, keyEvent); - } - } - } - - void _processSelectedAll() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.selectionMode != SelectionMode.multiple) { - return; - } - - final List addedItems = []; - final List removeItems = []; - if (dataGridSettings.onSelectionChanging != null || - dataGridSettings.onSelectionChanged != null) { - addedItems.addAll(dataGridSettings.source._effectiveRows); - } - - if (_raiseSelectionChanging(oldItems: removeItems, newItems: addedItems)) { - for (final DataGridRow record in dataGridSettings.source._effectiveRows) { - if (!_selectedRows.contains(record)) { - final int rowIndex = - _SelectionHelper.resolveToRowIndex(dataGridSettings, record); - if (rowIndex != -1 && - dataGridSettings.rowGenerator.items - .any((DataRowBase row) => row.rowIndex == rowIndex)) { - _setRowSelection(rowIndex, dataGridSettings, true); - _selectedRows.selectedRow.add(record); - } else { - _selectedRows.selectedRow.add(record); - } - } - } - - dataGridSettings.controller.selectedRows - .addAll(dataGridSettings.source._effectiveRows); - _refreshSelection(); - dataGridSettings.container._isDirty = true; - notifyListeners(); - _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); - } - } - - void _processSpaceKey() { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.selectionMode == SelectionMode.single) { - return; - } - - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - _applySelection( - RowColumnIndex(currentCell.rowIndex, currentCell.columnIndex)); - } - - void _processSelectionAndCurrentCell( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex, - {bool isShiftKeyPressed = false, bool isProgrammatic = false}) { - final RowColumnIndex previousRowColumnIndex = RowColumnIndex( - dataGridSettings.currentCell.rowIndex, - dataGridSettings.currentCell.columnIndex); - if (isProgrammatic) { - dataGridSettings.currentCell - ._moveCurrentCellTo(dataGridSettings, rowColumnIndex); - } else { - dataGridSettings.currentCell - ._processCurrentCell(dataGridSettings, rowColumnIndex); - } - - if (dataGridSettings.selectionMode == SelectionMode.multiple) { - dataGridSettings.currentCell._updateBorderForMultipleSelection( - dataGridSettings, - nextRowColumnIndex: rowColumnIndex, - previousRowColumnIndex: previousRowColumnIndex); - if (isShiftKeyPressed) { - _processSelection( - dataGridSettings, rowColumnIndex, previousRowColumnIndex); - } else { - notifyListeners(); - } - } - - _pressedRowColumnIndex = rowColumnIndex; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart deleted file mode 100644 index c40534247..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selected_row_info.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of datagrid; - -class _SelectedRowCollection { - List selectedRow = []; - - bool contains(DataGridRow rowData) => findRowData(rowData) != null; - - DataGridRow? findRowData(DataGridRow rowData) { - final DataGridRow? selectedItem = selectedRow - .firstWhereOrNull((DataGridRow dataRow) => dataRow == rowData); - return selectedItem; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart deleted file mode 100644 index 1becd6732..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_helper.dart +++ /dev/null @@ -1,510 +0,0 @@ -part of datagrid; - -class _SelectionHelper { - static int resolveToRowIndex( - _DataGridSettings dataGridSettings, DataGridRow record) { - final DataGridRow? rec = dataGridSettings.source._effectiveRows - .firstWhereOrNull((DataGridRow rec) => rec == record); - if (rec == null) { - return -1; - } - - int recordIndex = dataGridSettings.source._effectiveRows.indexOf(rec); - - recordIndex += - _GridIndexResolver.resolveStartIndexBasedOnPosition(dataGridSettings); - - return recordIndex.isNegative ? -1 : recordIndex; - } - - static DataGridRow? getRecord(_DataGridSettings dataGridSettings, int index) { - final DataGridSource source = dataGridSettings.source; - - if (source._effectiveRows.isEmpty || index < 0) { - return null; - } - - final DataGridRow record = source._effectiveRows[index]; - return record; - } - - static int getFirstNavigatingRowIndex(_DataGridSettings dataGridSettings) { - final int index = _SelectionHelper.getFirstRowIndex(dataGridSettings); - const int count = 0; - for (int start = index; start >= 0; start--) { - final List hiddenRowInfo = - dataGridSettings.container.rowHeights.getHidden(start, count); - final bool isHiddenRow = hiddenRowInfo.first as bool; - if (!isHiddenRow) { - return start; - } - } - - return index; - } - - static int getLastNavigatingRowIndex(_DataGridSettings dataGridSettings) { - int lastRowIndex = -1; - const int count = 0; - final int recordCount = _SelectionHelper.getRecordsCount(dataGridSettings); - - if (recordCount == 0) { - return -1; - } - - /// From actual row count we have to reduce the length by 1 to adapt the - /// row index based on SfDataGrid. - /// Eg : Total rowCount is 30, we will start the row index from 0 of header - /// in SfDataGrid. The actual last row index of data grid is 29. - lastRowIndex = dataGridSettings.container.rowCount - 1; - if (dataGridSettings.footer != null) { - lastRowIndex--; - } - for (int start = lastRowIndex; start >= 0; start--) { - final List hiddenRowInfo = - dataGridSettings.container.rowHeights.getHidden(start, count); - final bool isHiddenRow = hiddenRowInfo.first as bool; - if (!isHiddenRow) { - return start; - } - } - - return lastRowIndex; - } - - static int getRecordsCount(_DataGridSettings dataGridSettings) { - if (dataGridSettings.source._effectiveRows.isNotEmpty) { - return dataGridSettings.source._effectiveRows.length; - } else { - return 0; - } - } - - static int getFirstRowIndex(_DataGridSettings dataGridSettings) { - if (_SelectionHelper.getRecordsCount(dataGridSettings) == 0) { - return -1; - } - - final int index = _GridIndexResolver.getHeaderIndex(dataGridSettings) + 1; - return index; - } - - static int getLastCellIndex(_DataGridSettings dataGridSettings) { - final GridColumn? lastColumn = dataGridSettings.columns - .lastWhereOrNull((GridColumn col) => col.visible); - if (lastColumn == null) { - return -1; - } - - return dataGridSettings.columns.indexOf(lastColumn); - } - - static int getFirstCellIndex(_DataGridSettings dataGridSettings) { - final GridColumn? gridColumn = dataGridSettings.columns - .firstWhereOrNull((GridColumn col) => col.visible); - if (gridColumn == null) { - return -1; - } - - final int firstIndex = dataGridSettings.columns.indexOf(gridColumn); - if (firstIndex < 0) { - return firstIndex; - } - - return firstIndex; - } - - static int getLastRowIndex(_DataGridSettings dataGridSettings) { - if (_SelectionHelper.getRecordsCount(dataGridSettings) == 0) { - return -1; - } - - const int count = 0; - - /// From actual row count we have to reduce the length by 1 to adapt the - /// row index based on SfDataGrid. - /// Eg : Total rowCount is 30, we will start the row index from 0 of header - /// in SfDataGrid. The actual last row index of data grid is 29. - int index = dataGridSettings.container.rowCount - 1; - if (dataGridSettings.footer != null) { - index--; - } - for (int start = index; start >= 0; start--) { - final List hiddenRowInfo = - dataGridSettings.container.rowHeights.getHidden(start, count); - final bool isHiddenRow = hiddenRowInfo.first as bool; - if (!isHiddenRow) { - return start; - } - } - - return index; - } - - static int getPreviousPageIndex(_DataGridSettings dataGridSettings) { - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - final int rowIndex = currentCell.rowIndex; - int lastBodyVisibleIndex = -1; - final _VisibleLinesCollection visibleLines = - dataGridSettings.container.scrollRows.getVisibleLines(); - if (visibleLines.lastBodyVisibleIndex < visibleLines.length) { - lastBodyVisibleIndex = - visibleLines[visibleLines.lastBodyVisibleIndex - 1].lineIndex; - } - - int index = - lastBodyVisibleIndex < rowIndex ? lastBodyVisibleIndex : rowIndex; - index = dataGridSettings.container.scrollRows.getPreviousPage(index); - final int firstRowIndex = - _SelectionHelper.getFirstRowIndex(dataGridSettings); - if (index < firstRowIndex || rowIndex < firstRowIndex) { - return firstRowIndex; - } - - return index; - } - - static int getNextPageIndex(_DataGridSettings dataGridSettings) { - final _CurrentCellManager currentCell = dataGridSettings.currentCell; - int rowIndex = currentCell.rowIndex; - if (rowIndex < - _SelectionHelper.getFirstNavigatingRowIndex(dataGridSettings)) { - rowIndex = 0; - } - - final _VisibleLinesCollection visibleLines = - dataGridSettings.container.scrollRows.getVisibleLines(); - int firstBodyVisibleIndex = -1; - if (visibleLines.firstBodyVisibleIndex < visibleLines.length) { - firstBodyVisibleIndex = - visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex; - } - - int index = - firstBodyVisibleIndex > rowIndex ? firstBodyVisibleIndex : rowIndex; - final int lastRowIndex = _SelectionHelper.getLastRowIndex(dataGridSettings); - index = dataGridSettings.container.scrollRows.getNextPage(index); - if (index > _SelectionHelper.getLastNavigatingRowIndex(dataGridSettings) && - currentCell.rowIndex > lastRowIndex) { - return currentCell.rowIndex; - } - - return index = index <= lastRowIndex ? index : lastRowIndex; - } - - static double getVerticalCumulativeDistance( - _DataGridSettings dataGridSettings, int rowIndex) { - double verticalOffset = 0.0; - final int headerRowIndex = - _GridIndexResolver.getHeaderIndex(dataGridSettings); - rowIndex = rowIndex > headerRowIndex ? rowIndex : headerRowIndex + 1; - final _PixelScrollAxis _scrollRows = - dataGridSettings.container.scrollRows as _PixelScrollAxis; - verticalOffset = _scrollRows.distances!.getCumulatedDistanceAt(rowIndex); - return verticalOffset; - } - - static double getHorizontalCumulativeDistance( - _DataGridSettings dataGridSettings, int columnIndex) { - double horizontalOffset = 0.0; - final int firstVisibleColumnIndex = - _GridIndexResolver.resolveToStartColumnIndex(dataGridSettings); - columnIndex = columnIndex < firstVisibleColumnIndex - ? firstVisibleColumnIndex - : columnIndex; - final _PixelScrollAxis _scrollColumns = - dataGridSettings.container.scrollColumns as _PixelScrollAxis; - horizontalOffset = - _scrollColumns.distances!.getCumulatedDistanceAt(columnIndex); - return horizontalOffset; - } - - // ScrollingView Helping API - - static bool needToScrollDown( - _DataGridSettings dataGridSettings, int nextRowIndex) { - final _VisibleLinesCollection visibleLines = - dataGridSettings.container.scrollRows.getVisibleLines(); - if (visibleLines.isEmpty) { - return false; - } - - final _VisibleLineInfo? nextRowInfo = dataGridSettings.container.scrollRows - .getVisibleLineAtLineIndex(nextRowIndex); - return nextRowInfo == null || nextRowInfo.isClipped; - } - - static bool needToScrollUp( - _DataGridSettings dataGridSettings, int previousRowIndex) { - final _VisibleLinesCollection visibleLineCollection = - dataGridSettings.container.scrollRows.getVisibleLines(); - if (visibleLineCollection.isEmpty) { - return false; - } - - final _VisibleLineInfo? previousRowLineInfo = dataGridSettings - .container.scrollRows - .getVisibleLineAtLineIndex(previousRowIndex); - return previousRowLineInfo == null || previousRowLineInfo.isClipped; - } - - static bool needToScrollLeft( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { - final _VisibleLinesCollection visibleLineCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - if (visibleLineCollection.isEmpty) { - return false; - } - - final _VisibleLineInfo? previousCellLineInfo = visibleLineCollection - .getVisibleLineAtLineIndex(rowColumnIndex.columnIndex); - return previousCellLineInfo == null || previousCellLineInfo.isClipped; - } - - static bool needToScrollRight( - _DataGridSettings dataGridSettings, RowColumnIndex rowColumnIndex) { - final _VisibleLinesCollection visibleLineCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - if (visibleLineCollection.isEmpty) { - return false; - } - final _VisibleLineInfo? nextCellLineInfo = visibleLineCollection - .getVisibleLineAtLineIndex(rowColumnIndex.columnIndex); - return nextCellLineInfo == null || nextCellLineInfo.isClipped; - } - - static void scrollInViewFromLeft(_DataGridSettings dataGridSettings, - {int nextCellIndex = -1, bool needToScrollMaxExtent = false}) { - if (dataGridSettings.horizontalScrollController != null) { - final ScrollController horizontalController = - dataGridSettings.horizontalScrollController!; - double measuredHorizontalOffset = 0.0; - - final int lastFrozenColumnIndex = - _GridIndexResolver.getLastFrozenColumnIndex(dataGridSettings); - - final int firstFooterFrozenColumnIndex = - _GridIndexResolver.getStartFooterFrozenColumnIndex(dataGridSettings); - - if (dataGridSettings.frozenColumnsCount > 0 && - lastFrozenColumnIndex + 1 == nextCellIndex) { - measuredHorizontalOffset = - horizontalController.position.minScrollExtent; - } else if (needToScrollMaxExtent || - (dataGridSettings.footerFrozenColumnsCount > 0 && - firstFooterFrozenColumnIndex - 1 == nextCellIndex)) { - measuredHorizontalOffset = - horizontalController.position.maxScrollExtent; - } else { - if (dataGridSettings.currentCell.columnIndex != -1 && - nextCellIndex == dataGridSettings.currentCell.columnIndex + 1) { - final List nextCellIndexHeight = dataGridSettings - .container.columnWidthsProvider - .getSize(nextCellIndex, 0); - final _VisibleLinesCollection visibleInfoCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - final _VisibleLineInfo? nextCellInfo = - visibleInfoCollection.getVisibleLineAtLineIndex(nextCellIndex); - measuredHorizontalOffset = nextCellInfo != null - ? dataGridSettings.textDirection == TextDirection.rtl - ? nextCellInfo.clippedSize - - (~nextCellInfo.clippedOrigin.toInt()) - : nextCellInfo.size - - (nextCellInfo.size - nextCellInfo.clippedCornerExtent) - : nextCellIndexHeight.first as double; - measuredHorizontalOffset = - horizontalController.offset + measuredHorizontalOffset; - } else { - final _VisibleLinesCollection visibleInfoCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - final int firstBodyVisibleLineIndex = - visibleInfoCollection.firstBodyVisibleIndex < - visibleInfoCollection.length - ? visibleInfoCollection[ - visibleInfoCollection.firstBodyVisibleIndex] - .lineIndex - : 0; - if (nextCellIndex < firstBodyVisibleLineIndex) { - scrollInViewFromRight(dataGridSettings, - previousCellIndex: nextCellIndex, - needToScrollToMinExtent: false); - } else { - measuredHorizontalOffset = - _SelectionHelper.getHorizontalCumulativeDistance( - dataGridSettings, nextCellIndex); - measuredHorizontalOffset = - _SfDataGridHelper.resolveHorizontalScrollOffset( - dataGridSettings, measuredHorizontalOffset); - measuredHorizontalOffset = - horizontalController.offset + measuredHorizontalOffset; - } - } - } - - _SfDataGridHelper.scrollHorizontal( - dataGridSettings, measuredHorizontalOffset); - } - } - - static void scrollInViewFromRight(_DataGridSettings dataGridSettings, - {int previousCellIndex = -1, bool needToScrollToMinExtent = false}) { - double measuredHorizontalOffset = 0.0; - - if (dataGridSettings.horizontalScrollController != null) { - final ScrollController horizontalController = - dataGridSettings.horizontalScrollController!; - - final int startingFooterFrozenColumnIndex = - _GridIndexResolver.getStartFooterFrozenColumnIndex(dataGridSettings); - final int lastFrozenColumnIndex = - _GridIndexResolver.getLastFrozenColumnIndex(dataGridSettings); - - if (dataGridSettings.footerFrozenColumnsCount > 0 && - startingFooterFrozenColumnIndex - 1 == previousCellIndex) { - measuredHorizontalOffset = - horizontalController.position.maxScrollExtent; - } else if (needToScrollToMinExtent || - (dataGridSettings.frozenColumnsCount > 0 && - lastFrozenColumnIndex + 1 == previousCellIndex)) { - measuredHorizontalOffset = - horizontalController.position.minScrollExtent; - } else { - if (dataGridSettings.currentCell.columnIndex != -1 && - previousCellIndex == dataGridSettings.currentCell.columnIndex - 1) { - final List previousCellIndexWidth = dataGridSettings - .container.columnWidthsProvider - .getSize(previousCellIndex, 0); - final _VisibleLinesCollection visibleInfoCollection = - _SfDataGridHelper.getVisibleLines(dataGridSettings); - final _VisibleLineInfo? previousCellInfo = visibleInfoCollection - .getVisibleLineAtLineIndex(previousCellIndex); - measuredHorizontalOffset = previousCellInfo != null - ? previousCellInfo.size - - (previousCellInfo.clippedSize - - previousCellInfo.clippedCornerExtent) - : previousCellIndexWidth.first as double; - measuredHorizontalOffset = - horizontalController.offset - measuredHorizontalOffset; - } else { - measuredHorizontalOffset = - _SelectionHelper.getHorizontalCumulativeDistance( - dataGridSettings, previousCellIndex); - measuredHorizontalOffset = - _SfDataGridHelper.resolveHorizontalScrollOffset( - dataGridSettings, measuredHorizontalOffset); - measuredHorizontalOffset = horizontalController.offset - - (horizontalController.offset - measuredHorizontalOffset); - } - } - - _SfDataGridHelper.scrollHorizontal( - dataGridSettings, measuredHorizontalOffset); - } - } - - static void scrollInViewFromTop(_DataGridSettings dataGridSettings, - {int nextRowIndex = -1, bool needToScrollToMaxExtent = false}) { - double measuredVerticalOffset = 0.0; - - if (dataGridSettings.verticalScrollController != null) { - final ScrollController verticalController = - dataGridSettings.verticalScrollController!; - - if (dataGridSettings.frozenRowsCount > 0 && - _GridIndexResolver.getLastFrozenRowIndex(dataGridSettings) + 1 == - nextRowIndex) { - measuredVerticalOffset = verticalController.position.minScrollExtent; - } else if (needToScrollToMaxExtent) { - measuredVerticalOffset = verticalController.position.maxScrollExtent; - } else { - if (dataGridSettings.currentCell.rowIndex != -1 && - nextRowIndex == dataGridSettings.currentCell.rowIndex + 1) { - final List nextRowIndexHeight = dataGridSettings - .container.rowHeightsProvider - .getSize(nextRowIndex, 0); - final _VisibleLineInfo? nextRowInfo = dataGridSettings - .container.scrollRows - .getVisibleLineAtLineIndex(nextRowIndex); - measuredVerticalOffset = nextRowInfo != null - ? nextRowInfo.size - - (nextRowInfo.size - nextRowInfo.clippedCornerExtent) - : nextRowIndexHeight.first as double; - measuredVerticalOffset = - verticalController.offset + measuredVerticalOffset; - } else { - final _VisibleLinesCollection visibleInfoCollection = - dataGridSettings.container.scrollRows.getVisibleLines(); - final int firstBodyVisibleLineIndex = - visibleInfoCollection.firstBodyVisibleIndex < - visibleInfoCollection.length - ? visibleInfoCollection[ - visibleInfoCollection.firstBodyVisibleIndex] - .lineIndex - : 0; - if (nextRowIndex < firstBodyVisibleLineIndex) { - scrollInViewFromDown(dataGridSettings, - previousRowIndex: nextRowIndex, needToScrollToMinExtent: false); - } else { - measuredVerticalOffset = - _SelectionHelper.getVerticalCumulativeDistance( - dataGridSettings, nextRowIndex); - measuredVerticalOffset = - _SfDataGridHelper.resolveVerticalScrollOffset( - dataGridSettings, measuredVerticalOffset); - } - } - } - - _SfDataGridHelper.scrollVertical( - dataGridSettings, measuredVerticalOffset); - } - } - - static void scrollInViewFromDown(_DataGridSettings dataGridSettings, - {int previousRowIndex = -1, bool needToScrollToMinExtent = false}) { - double measuredVerticalOffset = 0.0; - - if (dataGridSettings.verticalScrollController != null) { - final ScrollController verticalController = - dataGridSettings.verticalScrollController!; - - if (dataGridSettings.footerFrozenRowsCount > 0 && - _GridIndexResolver.getStartFooterFrozenRowIndex(dataGridSettings) - - 1 == - previousRowIndex) { - measuredVerticalOffset = verticalController.position.maxScrollExtent; - } else if (needToScrollToMinExtent) { - measuredVerticalOffset = verticalController.position.minScrollExtent; - } else { - if (dataGridSettings.currentCell.rowIndex != -1 && - previousRowIndex == dataGridSettings.currentCell.rowIndex - 1) { - final List previousRowIndexHeight = dataGridSettings - .container.rowHeightsProvider - .getSize(previousRowIndex, 0); - final _VisibleLineInfo? previousRowInfo = dataGridSettings - .container.scrollRows - .getVisibleLineAtLineIndex(previousRowIndex); - measuredVerticalOffset = previousRowInfo != null - ? previousRowInfo.size - - (previousRowInfo.clippedSize - - previousRowInfo.clippedCornerExtent) - : previousRowIndexHeight.first as double; - measuredVerticalOffset = - verticalController.offset - measuredVerticalOffset; - } else { - measuredVerticalOffset = - _SelectionHelper.getVerticalCumulativeDistance( - dataGridSettings, previousRowIndex); - measuredVerticalOffset = - _SfDataGridHelper.resolveVerticalScrollOffset( - dataGridSettings, measuredVerticalOffset); - measuredVerticalOffset = verticalController.offset - - (verticalController.offset - measuredVerticalOffset); - } - } - - _SfDataGridHelper.scrollVertical( - dataGridSettings, measuredVerticalOffset); - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart deleted file mode 100644 index 0a43d6d17..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/selection_controller/selection_manager_base.dart +++ /dev/null @@ -1,183 +0,0 @@ -part of datagrid; - -/// Provides the base functionalities to process the selection in [SfDataGrid]. -class SelectionManagerBase extends ChangeNotifier { - /// Creates the [SelectionManagerBase]. - SelectionManagerBase() { - _selectedRows = _SelectedRowCollection(); - } - - late _SelectedRowCollection _selectedRows; - - late _DataGridStateDetails _dataGridStateDetails; - - /// Processes the selection operation when tap a cell. - void handleTap(RowColumnIndex rowColumnIndex) {} - - /// Processes the selection operation when [SfDataGrid] receives raw keyboard - /// event. - void handleKeyEvent(RawKeyEvent keyEvent) {} - - /// Called when the [SfDataGrid.selectionMode] is changed at run time. - void onGridSelectionModeChanged() {} - - /// Called when the selection is programmatically changed - /// using [SfDataGrid.controller]. - void handleDataGridSourceChanges() {} - - /// Called when the selectedRow property in [SfDataGrid.controller] - /// is changed. - void onSelectedRowChanged() {} - - /// Called when the selectedIndex property in [SfDataGrid.controller] - /// is changed. - void onSelectedIndexChanged() {} - - /// Called when the selectedRows property in [SfDataGrid.controller] - /// is changed. - void onSelectedRowsChanged() {} - - void _onRowColumnChanged(int recordLength, int columnLength) {} - - void _handleSelectionPropertyChanged( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) {} - - void _updateSelectionController( - {bool isSelectionModeChanged = false, - bool isNavigationModeChanged = false, - bool isDataSourceChanged = false}) {} - - bool _raiseSelectionChanging( - {List oldItems = const [], - List newItems = const []}) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.onSelectionChanging == null) { - return true; - } - - return dataGridSettings.onSelectionChanging!(newItems, oldItems); - } - - void _raiseSelectionChanged( - {List oldItems = const [], - List newItems = const []}) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - if (dataGridSettings.onSelectionChanged == null) { - return; - } - - dataGridSettings.onSelectionChanged!(newItems, oldItems); - } - - int _getPreviousColumnIndex( - _DataGridSettings dataGridSettings, int currentColumnIndex) { - final _DataGridSettings dataGridSettings = _dataGridStateDetails(); - int previousCellIndex = currentColumnIndex; - final GridColumn? column = - _getNextGridColumn(dataGridSettings, currentColumnIndex - 1, false); - if (column != null) { - previousCellIndex = _GridIndexResolver.resolveToScrollColumnIndex( - dataGridSettings, dataGridSettings.columns.indexOf(column)); - } - - if (previousCellIndex == currentColumnIndex) { - previousCellIndex = currentColumnIndex - 1; - } - - return previousCellIndex; - } - - int _getPreviousRowIndex( - _DataGridSettings dataGridSettings, int currentRowIndex) { - final int lastRowIndex = - _SelectionHelper.getLastNavigatingRowIndex(dataGridSettings); - if (currentRowIndex > lastRowIndex) { - return lastRowIndex; - } - - final int firstRowIndex = - _SelectionHelper.getFirstNavigatingRowIndex(dataGridSettings); - if (currentRowIndex <= firstRowIndex) { - return firstRowIndex; - } - - int previousIndex = currentRowIndex; - previousIndex = dataGridSettings.container.scrollRows - .getPreviousScrollLineIndex(currentRowIndex); - if (previousIndex == currentRowIndex) { - previousIndex = previousIndex - 1; - } - - return previousIndex; - } - - GridColumn? _getNextGridColumn( - _DataGridSettings dataGridSettings, int columnIndex, bool moveToRight) { - final int resolvedIndex = - _GridIndexResolver.resolveToGridVisibleColumnIndex( - dataGridSettings, columnIndex); - if (resolvedIndex < 0 || resolvedIndex >= dataGridSettings.columns.length) { - return null; - } - - GridColumn? gridColumn = dataGridSettings.columns[resolvedIndex]; - if (!gridColumn.visible || gridColumn._actualWidth == 0.0) { - gridColumn = _getNextGridColumn(dataGridSettings, - moveToRight ? columnIndex + 1 : columnIndex - 1, moveToRight); - } - - return gridColumn; - } - - int _getNextColumnIndex( - _DataGridSettings dataGridSettings, int currentColumnIndex) { - int nextCellIndex = currentColumnIndex; - final int lastCellIndex = - _SelectionHelper.getLastCellIndex(dataGridSettings); - - final GridColumn? column = - _getNextGridColumn(dataGridSettings, currentColumnIndex + 1, true); - if (column != null) { - final int columnIndex = dataGridSettings.columns.indexOf(column); - nextCellIndex = _GridIndexResolver.resolveToScrollColumnIndex( - dataGridSettings, columnIndex); - } - - if (nextCellIndex == currentColumnIndex) { - nextCellIndex = currentColumnIndex + 1; - } - - return nextCellIndex > lastCellIndex ? lastCellIndex : nextCellIndex; - } - - int _getNextRowIndex( - _DataGridSettings dataGridSettings, int currentRowIndex) { - final int lastRowIndex = - _SelectionHelper.getLastNavigatingRowIndex(dataGridSettings); - if (currentRowIndex >= lastRowIndex) { - return lastRowIndex; - } - - final int firstRowIndex = - _SelectionHelper.getFirstNavigatingRowIndex(dataGridSettings); - - if (currentRowIndex < firstRowIndex) { - return firstRowIndex; - } - - if (currentRowIndex >= dataGridSettings.container.scrollRows.lineCount) { - return -1; - } - - int nextIndex = 0; - nextIndex = dataGridSettings.container.scrollRows - .getNextScrollLineIndex(currentRowIndex); - if (nextIndex == currentRowIndex) { - nextIndex = nextIndex + 1; - } - - return nextIndex; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/callbackargs.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/callbackargs.dart similarity index 87% rename from packages/syncfusion_flutter_datagrid/lib/src/control/callbackargs.dart rename to packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/callbackargs.dart index a7e9fbe8b..4672708c4 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/callbackargs.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/callbackargs.dart @@ -1,4 +1,10 @@ -part of datagrid; +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import '../../grid_common/row_column_index.dart'; +import '../runtime/column.dart'; +import 'enums.dart'; /// A base class which provides the details for callbacks that use /// [DataGridCellTapDetails], [DataGridCellDoubleTapDetails] and @@ -11,10 +17,12 @@ part of datagrid; /// [DataGridCellDoubleTapCallback]. /// * [DataGridCellLongPressDetails], which uses /// [DataGridCellLongPressCallback]. +@immutable class DataGridCellDetails { /// Creates a [DataGridCellDetails] with the specified [rowColumnIndex] and /// [column]. - DataGridCellDetails({required this.rowColumnIndex, required this.column}); + const DataGridCellDetails( + {required this.rowColumnIndex, required this.column}); /// The coordinates of the cell in [SfDataGrid]. final RowColumnIndex rowColumnIndex; @@ -28,10 +36,11 @@ class DataGridCellDetails { /// See also: /// /// [DataGridCellTapCallback]. +@immutable class DataGridCellTapDetails extends DataGridCellDetails { /// Creates a [DataGridCellTapDetails] with the specified [rowColumnIndex], /// [column], [globalPosition], [localPosition] and [kind]. - DataGridCellTapDetails( + const DataGridCellTapDetails( {required RowColumnIndex rowColumnIndex, required GridColumn column, required this.globalPosition, @@ -59,15 +68,15 @@ class RowHeightDetails { /// and [rowHeight]. RowHeightDetails(this.rowIndex, this.rowHeight); - late ColumnSizer _columnSizer; - /// An index of a row. - int rowIndex; + final int rowIndex; ///The height of the row at the specified [rowIndex]. ///If you want to change this height, you can return a different /// height from the [SfDataGrid.onQueryRowHeight] callback. - double rowHeight; + final double rowHeight; + + late ColumnSizer _columnSizer; /// Gets the row height to fit the row based on the [DataGridCell.value]. For /// header cells, it considers the [GridColumn.columnName]. @@ -87,7 +96,7 @@ class RowHeightDetails { double getIntrinsicRowHeight(int rowIndex, {bool canIncludeHiddenColumns = false, List excludedColumns = const []}) { - return _columnSizer._getAutoFitRowHeight(rowIndex, + return getAutoFitRowHeight(_columnSizer, rowIndex, canIncludeHiddenColumns: canIncludeHiddenColumns, excludedColumns: excludedColumns); } @@ -98,10 +107,11 @@ class RowHeightDetails { /// See also: /// /// [DataGridCellDoubleTapCallback]. +@immutable class DataGridCellDoubleTapDetails extends DataGridCellDetails { /// Creates a [DataGridCellDoubleTapDetails] with the specified [rowColumnIndex] /// and [column]. - DataGridCellDoubleTapDetails( + const DataGridCellDoubleTapDetails( {required RowColumnIndex rowColumnIndex, required GridColumn column}) : super(rowColumnIndex: rowColumnIndex, column: column); } @@ -111,11 +121,12 @@ class DataGridCellDoubleTapDetails extends DataGridCellDetails { /// See also: /// /// [DataGridCellLongPressCallback]. +@immutable class DataGridCellLongPressDetails extends DataGridCellDetails { /// Creates a [DataGridCellLongPressDetails] with the specified /// [rowColumnIndex], [column], [globalPosition], [localPosition] and /// [velocity]. - DataGridCellLongPressDetails( + const DataGridCellLongPressDetails( {required RowColumnIndex rowColumnIndex, required GridColumn column, required this.globalPosition, @@ -139,6 +150,7 @@ class DataGridCellLongPressDetails extends DataGridCellDetails { /// /// * [DataGridSource.sortedColumns] – which is the collection of /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. +@immutable class SortColumnDetails { /// Creates the [SortColumnDetails] for [SfDataGrid] widget. const SortColumnDetails({required this.name, required this.sortDirection}); @@ -151,9 +163,10 @@ class SortColumnDetails { } /// Holds the arguments for the [SfDataGrid.onSwipeStart] callback. +@immutable class DataGridSwipeStartDetails { /// Creates a [DataGridSwipeStartDetails] class for [SfDataGrid]. - DataGridSwipeStartDetails( + const DataGridSwipeStartDetails( {required this.rowIndex, required this.swipeDirection}); /// An index of a row which is swiped. @@ -164,9 +177,10 @@ class DataGridSwipeStartDetails { } /// Holds the arguments for the [SfDataGrid.onSwipeUpdate] callback. +@immutable class DataGridSwipeUpdateDetails { /// Creates a [DataGridSwipeUpdateDetails] class for [SfDataGrid]. - DataGridSwipeUpdateDetails( + const DataGridSwipeUpdateDetails( {required this.rowIndex, required this.swipeOffset, required this.swipeDirection}); @@ -182,9 +196,10 @@ class DataGridSwipeUpdateDetails { } /// Holds the arguments for the [SfDataGrid.onSwipeEnd] callback. +@immutable class DataGridSwipeEndDetails { /// Creates a [DataGridSwipeEndDetails] class for [SfDataGrid]. - DataGridSwipeEndDetails( + const DataGridSwipeEndDetails( {required this.rowIndex, required this.swipeDirection}); /// An index of a row which is swiped. @@ -195,9 +210,10 @@ class DataGridSwipeEndDetails { } /// Holds the arguments for the [SfDataGrid.onColumnResizeStart] callback. +@immutable class ColumnResizeStartDetails { /// Creates the [ColumnResizeStartDetails] with the specified [column] and [width]. - ColumnResizeStartDetails({required this.column, required this.width}); + const ColumnResizeStartDetails({required this.column, required this.width}); /// A column that is going to be resized. final GridColumn column; @@ -207,9 +223,10 @@ class ColumnResizeStartDetails { } /// Holds the arguments for the [SfDataGrid.onColumnResizeUpdate] callback. +@immutable class ColumnResizeUpdateDetails { /// Creates the [ColumnResizeUpdateDetails] with the specified [column] and [width]. - ColumnResizeUpdateDetails({required this.column, required this.width}); + const ColumnResizeUpdateDetails({required this.column, required this.width}); /// A column that is being resized. final GridColumn column; @@ -219,9 +236,10 @@ class ColumnResizeUpdateDetails { } /// Holds the arguments for the [SfDataGrid.onColumnResizeEnd] callback. +@immutable class ColumnResizeEndDetails { /// Creates the [ColumnResizeEndDetails] with the specified [column] and [width]. - ColumnResizeEndDetails({required this.column, required this.width}); + const ColumnResizeEndDetails({required this.column, required this.width}); /// A column that is resized. final GridColumn column; @@ -229,3 +247,9 @@ class ColumnResizeEndDetails { /// Currently resized width of a column. final double width; } + +/// ToDo +void setColumnSizerInRowHeightDetailsArgs( + RowHeightDetails rowHeightDetails, ColumnSizer columnSizer) { + rowHeightDetails._columnSizer = columnSizer; +} 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 new file mode 100644 index 000000000..11d12cd34 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_configuration.dart @@ -0,0 +1,289 @@ +import 'package:flutter/material.dart' hide SelectionChangedCallback; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../runtime/cell_renderers.dart'; +import '../runtime/column.dart'; +import '../runtime/generator.dart'; +import '../selection/selection_manager.dart'; +import '../sfdatagrid.dart'; +import '../widgets/scrollview_widget.dart'; +import 'enums.dart'; + +/// Signature for the `_DataGridConfiguration` callback. +typedef DataGridStateDetails = DataGridConfiguration Function(); + +/// Holding to core setting of data grid +class DataGridConfiguration { + // late assignable values and non-null + + /// ToDo + late Map cellRenderers; + + /// ToDo + late DataGridSource source; + + /// ToDo + late List columns; + + /// ToDo + late VisualContainerHelper container; + + /// ToDo + late RowGenerator rowGenerator; + + /// ToDo + late ColumnWidthMode columnWidthMode; + + /// ToDo + late ColumnWidthCalculationRange columnWidthCalculationRange; + + /// ToDo + late ColumnSizer columnSizer; + + /// ToDo + late SelectionManagerBase rowSelectionManager; + + /// ToDo + late DataGridController controller; + + /// ToDo + late CurrentCellManager currentCell; + + /// ToDo + late ColumnResizeController columnResizeController; + + /// ToDo + late double viewWidth; + + /// ToDo + late double viewHeight; + + /// ToDo + late ScrollPhysics horizontalScrollPhysics; + + /// ToDo + late ScrollPhysics verticalScrollPhysics; + + /// ToDo + int headerLineCount = 0; + + /// ToDo + int frozenColumnsCount = 0; + + /// ToDo + int footerFrozenColumnsCount = 0; + + /// ToDo + int frozenRowsCount = 0; + + /// ToDo + int footerFrozenRowsCount = 0; + + /// ToDo + int? rowsPerPage; + + /// ToDo + double rowHeight = double.nan; + + /// ToDo + double headerRowHeight = double.nan; + + /// ToDo + double defaultColumnWidth = double.nan; + + /// ToDo + double swipingOffset = 0.0; + + /// ToDo + double refreshIndicatorDisplacement = 40.0; + + /// ToDo + double refreshIndicatorStrokeWidth = 2.0; + + /// ToDo + double swipeMaxOffset = 200.0; + + /// ToDo + double textScaleFactor = 1.0; + + /// ToDo + double footerHeight = 49.0; + + /// ToDo + bool allowSorting = false; + + /// ToDo + bool allowMultiColumnSorting = false; + + /// ToDo + bool isControlKeyPressed = false; + + /// ToDo + bool allowTriStateSorting = false; + + /// ToDo + bool showSortNumbers = false; + + /// ToDo + bool isDesktop = false; + + /// ToDo + bool isScrollbarAlwaysShown = false; + + /// ToDo + bool allowPullToRefresh = false; + + /// ToDo + bool allowSwiping = false; + // This flag is used to restrict the scrolling while updating the swipe offset + // of a row that already swiped in view. + /// ToDo + bool isSwipingApplied = false; + + /// ToDo + bool highlightRowOnHover = true; + + /// ToDo + bool allowColumnsResizing = false; + + /// ToDo + bool allowEditing = false; + + /// ToDo + bool showCheckboxColumn = false; + + /// Todo + bool? headerCheckboxState = false; + + /// Todo + DataGridCheckboxColumnSettings checkboxColumnSettings = + const DataGridCheckboxColumnSettings(); + + /// ToDo + SortingGestureType sortingGestureType = SortingGestureType.tap; + + /// ToDo + GridNavigationMode navigationMode = GridNavigationMode.row; + + /// ToDo + TextDirection textDirection = TextDirection.ltr; + + /// ToDo + GridLinesVisibility gridLinesVisibility = GridLinesVisibility.horizontal; + + /// ToDo + GridLinesVisibility headerGridLinesVisibility = + GridLinesVisibility.horizontal; + + /// ToDo + SelectionMode selectionMode = SelectionMode.none; + + /// ToDo + ColumnResizeMode columnResizeMode = ColumnResizeMode.onResize; + + /// ToDo + ScrollDirection scrollingState = ScrollDirection.idle; + + /// ToDo + EditingGestureType editingGestureType = EditingGestureType.doubleTap; + + /// ToDo + Paint? gridPaint; + + /// ToDo + BoxPainter? boxPainter; + + /// ToDo + FocusNode? dataGridFocusNode; + + /// ToDo + ImageConfiguration? configuration; + + /// ToDo + GlobalKey? refreshIndicatorKey; + + /// ToDo + Animation? swipingAnimation; + + /// ToDo + AnimationController? swipingAnimationController; + + /// ToDo + List stackedHeaderRows = []; + + /// The collection of [TableSummaryRow]. + /// + /// This enables you to show the total or summary for columns i.e Max, Min, + /// Average and Count for whole DataGrid. + List tableSummaryRows = []; + + /// ToDo + VisualDensity visualDensity = VisualDensity.adaptivePlatformDensity; + + /// ToDo + SfDataGridThemeData? dataGridThemeData; + + /// ToDo + ScrollController? verticalScrollController; + + /// ToDo + ScrollController? horizontalScrollController; + + /// ToDo + DataGridSwipeStartCallback? onSwipeStart; + + /// ToDo + DataGridSwipeUpdateCallback? onSwipeUpdate; + + /// ToDo + DataGridSwipeEndCallback? onSwipeEnd; + + /// ToDo + DataGridSwipeActionsBuilder? startSwipeActionsBuilder; + + /// ToDo + DataGridSwipeActionsBuilder? endSwipeActionsBuilder; + + /// ToDo + QueryRowHeightCallback? onQueryRowHeight; + + /// ToDo + SelectionChangingCallback? onSelectionChanging; + + /// ToDo + SelectionChangedCallback? onSelectionChanged; + + /// ToDo + CurrentCellActivatedCallback? onCurrentCellActivated; + + /// ToDo + CurrentCellActivatingCallback? onCurrentCellActivating; + + /// ToDo + DataGridCellTapCallback? onCellTap; + + /// ToDo + DataGridCellDoubleTapCallback? onCellDoubleTap; + + /// ToDo + DataGridCellTapCallback? onCellSecondaryTap; + + /// ToDo + DataGridCellLongPressCallback? onCellLongPress; + + /// ToDo + LoadMoreViewBuilder? loadMoreViewBuilder; + + /// ToDo + ColumnResizeStartCallback? onColumnResizeStart; + + /// ToDo + ColumnResizeUpdateCallback? onColumnResizeUpdate; + + /// ToDo + ColumnResizeEndCallback? onColumnResizeEnd; + + /// ToDo + Widget? footer; +} 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 new file mode 100644 index 000000000..36f58440c --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/datagrid_helper.dart @@ -0,0 +1,873 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart' hide DataCell, DataRow; + +import '../../grid_common/row_column_index.dart'; +import '../../grid_common/scroll_axis.dart'; +import '../../grid_common/visible_line_info.dart'; +import '../runtime/column.dart'; +import '../sfdatagrid.dart'; +import 'datagrid_configuration.dart'; +import 'enums.dart'; +import 'selection_helper.dart' as selection_helper; + +//----------------------------------------------------------------------------// +//------------------DataGridIndex-Resolving-Helpers---------------------------// +//----------------------------------------------------------------------------// + +/// Get the header index in [SfDataGrid]. +int getHeaderIndex(DataGridConfiguration dataGridConfiguration) { + int headerIndex = dataGridConfiguration.headerLineCount - 1; + + // Removes the top table summary rows from the headerLineCount to resolve the + // actual column header row index. + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + headerIndex -= getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.top); + } + + return headerIndex < 0 ? 0 : headerIndex; +} + +/// Helps to find the column index based on [SfDataGrid.columns] count +/// In this we will not include the column like, indent column, row +/// header etc +int resolveToGridVisibleColumnIndex( + DataGridConfiguration dataGridConfiguration, int columnIndex) { + if (columnIndex >= dataGridConfiguration.container.columnCount) { + return -1; + } + + return columnIndex; +} + +/// Helps to resolve the column index based on the [DataGridRowAdapter.cells] +/// count when indent cell, row header, [SfDataGrid.showCheckboxColumn] are enabled. +int resolveToDataGridRowAdapterCellIndex( + DataGridConfiguration dataGridConfiguration, int columnIndex) { + if (columnIndex >= dataGridConfiguration.container.columnCount) { + return -1; + } + + if (columnIndex == 0) { + return columnIndex; + } else { + columnIndex -= dataGridConfiguration.showCheckboxColumn ? 1 : 0; + return columnIndex; + } +} + +/// Help to resolve the record index to [SfDataGrid] position row index. +int resolveToRowIndex( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + if (rowIndex < 0) { + return -1; + } + + rowIndex = rowIndex + resolveStartIndexBasedOnPosition(dataGridConfiguration); + if (rowIndex >= 0 && rowIndex <= dataGridConfiguration.container.rowCount) { + return rowIndex; + } else { + return -1; + } +} + +/// Help to resolve the [SfDataGrid] position row index to record index. +int resolveToRecordIndex( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + if (rowIndex < 0) { + return -1; + } + + if (rowIndex == 0) { + return 0; + } + + rowIndex = rowIndex - resolveStartIndexBasedOnPosition(dataGridConfiguration); + if (rowIndex >= 0 && + rowIndex <= effectiveRows(dataGridConfiguration.source).length - 1) { + return rowIndex; + } else { + return -1; + } +} + +/// Helps to resolve the [SfDataGrid] row column index to record [RowColumnIndex]. +RowColumnIndex resolveToRecordRowColumnIndex( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex) { + final int rowIndex = + resolveToRecordIndex(dataGridConfiguration, rowColumnIndex.rowIndex); + final int columnIndex = resolveToGridVisibleColumnIndex( + dataGridConfiguration, rowColumnIndex.columnIndex); + return RowColumnIndex(rowIndex, columnIndex); +} + +/// Helps to resolve the row and column index according to DataGrid alignment +RowColumnIndex resolveToRowColumnIndex( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex) { + final int rowIndex = + resolveToRowIndex(dataGridConfiguration, rowColumnIndex.rowIndex); + final int columnIndex = resolveToGridVisibleColumnIndex( + dataGridConfiguration, rowColumnIndex.columnIndex); + return RowColumnIndex(rowIndex, columnIndex); +} + +/// Helps to find the exact starting scrolling row index. +int resolveStartIndexBasedOnPosition( + DataGridConfiguration dataGridConfiguration) { + return dataGridConfiguration.headerLineCount; +} + +/// Helps to find the actual starting scrolling column index +/// Its ignore the indent column, row header and provide the actual staring +/// column index +int resolveToStartColumnIndex(DataGridConfiguration dataGridConfiguration) => 0; + +/// Helps to resolve the provided column index based on [SfDataGrid] column +/// order. Its ignore the indent column, row header and provide the exact +/// column index +int resolveToScrollColumnIndex( + DataGridConfiguration dataGridConfiguration, int gridColumnIndex) => + gridColumnIndex; + +/// Get the last index of frozen column in left side view, it will +/// consider the row header,indent column and frozen column +int getLastFrozenColumnIndex(DataGridConfiguration dataGridConfiguration) { + if (dataGridConfiguration.frozenColumnsCount <= 0) { + return -1; + } + + final int startScrollColumnIndex = + dataGridConfiguration.container.frozenColumns - 1; + return startScrollColumnIndex.isFinite ? startScrollColumnIndex : -1; +} + +/// Get the starting index of frozen column in right side view, it +/// will consider the right frozen pane +int getStartFooterFrozenColumnIndex( + DataGridConfiguration dataGridConfiguration) { + final int columnsCount = dataGridConfiguration.container.columnCount; + if (columnsCount <= 0 || + dataGridConfiguration.footerFrozenColumnsCount <= 0) { + return -1; + } + + return columnsCount - dataGridConfiguration.container.footerFrozenColumns; +} + +/// Get the last frozen row index in top of data grid, it +/// will consider the stacked header, header and top frozen pane +int getLastFrozenRowIndex(DataGridConfiguration dataGridConfiguration) { + if (dataGridConfiguration.frozenRowsCount <= 0) { + return -1; + } + + final int rowIndex = dataGridConfiguration.container.frozenRows - 1; + return rowIndex.isFinite ? rowIndex : -1; +} + +/// Get the starting frozen row index in bottom of data grid, it +/// will consider the bottom table summary and bottom frozen pane +int getStartFooterFrozenRowIndex(DataGridConfiguration dataGridConfiguration) { + final int rowCount = dataGridConfiguration.container.rowCount; + if (rowCount <= 0 || dataGridConfiguration.footerFrozenRowsCount <= 0) { + return -1; + } + + final int rowIndex = + rowCount - dataGridConfiguration.container.footerFrozenRows; + return rowIndex.isFinite ? rowIndex : -1; +} + +//---------------------- Footer view helper methods ------------------------// + +/// Checks whether the row is a footer widget row or not. +bool isFooterWidgetRow( + int rowIndex, DataGridConfiguration dataGridConfiguration) { + final int bottomSummariesCount = getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + final int footerIndex = + dataGridConfiguration.container.rowCount - bottomSummariesCount - 1; + return dataGridConfiguration.footer != null && rowIndex == footerIndex; +} + +/// Returns the row index of a footer widget row. +int getFooterViewRowIndex(DataGridConfiguration dataGridConfiguration) { + final int bottomSummaryRowsCount = getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + return dataGridConfiguration.container.rowCount - bottomSummaryRowsCount - 1; +} + +//-------------------- Table summary row helper methods ----------------------// + +/// Checks whether the givem row is a top summary row or not. +bool isTopTableSummaryRow( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + if (dataGridConfiguration.tableSummaryRows.isNotEmpty && + rowIndex < dataGridConfiguration.headerLineCount) { + final int tableSummaryStartIndex = + dataGridConfiguration.stackedHeaderRows.length + 1; + final int tableSummaryEndIndex = tableSummaryStartIndex + + getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.top); + return rowIndex >= tableSummaryStartIndex && + rowIndex < tableSummaryEndIndex; + } + return false; +} + +/// Checks whether the given row is a bottom summary row or not. +bool isBottomTableSummaryRow( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + final int tableSummaryEndIndex = dataGridConfiguration.container.rowCount; + final int tableSummaryStartIndex = tableSummaryEndIndex - + getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + return rowIndex >= tableSummaryStartIndex && + rowIndex < tableSummaryEndIndex; + } + return false; +} + +/// Checks whether the given row index is a table summary row or not. +bool isTableSummaryIndex( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + return isTopTableSummaryRow(dataGridConfiguration, rowIndex) || + isBottomTableSummaryRow(dataGridConfiguration, rowIndex); +} + +/// Checks whether the row index is a start index of a bottom summary rows or not. +int getStartBottomSummaryRowIndex(DataGridConfiguration dataGridConfiguration) { + final int bottomSummariesCount = getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + return dataGridConfiguration.container.rowCount - bottomSummariesCount; +} + +/// Returns the table summary count based on position. +int getTableSummaryCount(DataGridConfiguration dataGridConfiguration, + GridTableSummaryRowPosition position) { + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + return dataGridConfiguration.tableSummaryRows + .where((GridTableSummaryRow row) => row.position == position) + .length; + } + return 0; +} + +/// Returns the title column span to the table summary column. +int getSummaryTitleColumnSpan(DataGridConfiguration dataGridConfiguration, + GridTableSummaryRow tableSummaryRow) { + int titleSpan = dataGridConfiguration.frozenColumnsCount > 0 + ? min(dataGridConfiguration.frozenColumnsCount, + tableSummaryRow.titleColumnSpan) + : tableSummaryRow.titleColumnSpan; + titleSpan = min(titleSpan, dataGridConfiguration.container.columnCount); + return titleSpan; +} + +/// Returns the span count for the table summary column. +int getSummaryColumnSpan(DataGridConfiguration dataGridConfiguration, int index, + RowType rowType, GridTableSummaryRow? tableSummaryRow) { + int span = 0; + final int columnCount = dataGridConfiguration.container.columnCount; + index = resolveToScrollColumnIndex(dataGridConfiguration, index); + if (rowType == RowType.tableSummaryRow) { + if (tableSummaryRow != null) { + final int titleSpan = + getSummaryTitleColumnSpan(dataGridConfiguration, tableSummaryRow); + if (titleSpan > 0 && index < titleSpan) { + span = titleSpan - 1; + } + } + } else { + span = columnCount - 1; + } + return span; +} + +/// Helps to get the table summary row for the given rowIndex from the +/// `SfDataGrid.tableSummaryRows` collection. +GridTableSummaryRow? getTableSummaryRow( + DataGridConfiguration dataGridConfiguration, + int rowIndex, + GridTableSummaryRowPosition position) { + GridTableSummaryRow getSummaryRowByPosition( + GridTableSummaryRowPosition position, int index) { + final List summaryRows = dataGridConfiguration + .tableSummaryRows + .where((GridTableSummaryRow row) => row.position == position) + .toList(); + return summaryRows[index]; + } + + late int currentSummaryRowIndex; + if (position == GridTableSummaryRowPosition.bottom) { + final int startTableSummaryIndex = + dataGridConfiguration.container.rowCount - + getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + currentSummaryRowIndex = rowIndex - startTableSummaryIndex; + } else if (position == GridTableSummaryRowPosition.top) { + final int startTableSummaryIndex = + getHeaderIndex(dataGridConfiguration) + 1; + currentSummaryRowIndex = rowIndex - startTableSummaryIndex; + } + return getSummaryRowByPosition(position, currentSummaryRowIndex); +} + +/// Calculates the summary value for the given summary column based on the +/// summary type. +String getSummaryValue( + GridSummaryColumn summaryColumn, List rows) { + switch (summaryColumn.summaryType) { + case GridSummaryType.sum: + return calculateSum(rows, summaryColumn); + case GridSummaryType.maximum: + return calculateMaximum(rows, summaryColumn); + case GridSummaryType.minimum: + return calculateMinimum(rows, summaryColumn); + case GridSummaryType.average: + return calculateAverage(rows, summaryColumn); + case GridSummaryType.count: + return rows.length.toString(); + } +} + +/// Calculates the sum value for the given summary column. +String calculateSum(List rows, GridSummaryColumn summaryColumn) { + num? sum; + bool isNumericColumn = false; + for (final DataGridRow row in rows) { + final DataGridCell? cell = row.getCells().firstWhereOrNull( + (DataGridCell element) => + element.columnName == summaryColumn.columnName); + if (cell != null && cell.value != null) { + if (!isNumericColumn && cell.value is! num) { + break; + } + + sum ??= 0; + sum += cell.value; + + if (!isNumericColumn) { + isNumericColumn = true; + } + } + } + return sum?.toString() ?? ''; +} + +/// Calculates the minimum value for the given summary column. +String calculateMinimum( + List rows, GridSummaryColumn summaryColumn) { + dynamic minimum; + bool isNumericColumn = false; + for (final DataGridRow row in rows) { + final DataGridCell? cell = row.getCells().firstWhereOrNull( + (DataGridCell element) => + element.columnName == summaryColumn.columnName); + if (cell != null && cell.value != null) { + if (!isNumericColumn && cell.value is! num) { + break; + } + + minimum ??= cell.value; + minimum = min(minimum, cell.value); + + if (!isNumericColumn) { + isNumericColumn = true; + } + } + } + + return minimum?.toString() ?? ''; +} + +/// Calculates the maximum value for the given summary column. +String calculateMaximum( + List rows, GridSummaryColumn summaryColumn) { + dynamic maximum; + bool isNumericColumn = false; + for (final DataGridRow row in rows) { + final DataGridCell? cell = row.getCells().firstWhereOrNull( + (DataGridCell element) => + element.columnName == summaryColumn.columnName); + if (cell != null && cell.value != null) { + if (!isNumericColumn && cell.value is! num) { + break; + } + + maximum ??= cell.value; + maximum = max(maximum, cell.value); + + if (!isNumericColumn) { + isNumericColumn = true; + } + } + } + + return maximum?.toString() ?? ''; +} + +/// Calculates the average value for the given summary column. +String calculateAverage( + List rows, GridSummaryColumn summaryColumn) { + num? sum; + int count = 0; + bool isNumericColumn = false; + for (final DataGridRow row in rows) { + final DataGridCell? cell = row.getCells().firstWhereOrNull( + (DataGridCell element) => + element.columnName == summaryColumn.columnName); + + if (cell != null && cell.value != null) { + if (!isNumericColumn && cell.value is! num) { + break; + } + + sum ??= 0; + sum += cell.value; + count++; + + if (!isNumericColumn) { + isNumericColumn = true; + } + } + } + return sum != null ? (sum / count).toString() : ''; +} + +//--------------------DataGridIndex-Resolving-Helpers-End---------------------// + +//----------------------------------------------------------------------------// +//--------------------- StackedHeader Helper Method --------------------------// +//----------------------------------------------------------------------------// + +/// Helps to get the sequence of spanned cell indexes +List getChildSequence(DataGridConfiguration dataGridConfiguration, + StackedHeaderCell? column, int rowIndex) { + final List childSequenceNo = []; + + if (column != null && column.columnNames.isNotEmpty) { + for (final String child in column.columnNames) { + final List columns = dataGridConfiguration.columns; + for (int i = 0; i < columns.length; ++i) { + if (columns[i].columnName == child) { + childSequenceNo.add(i); + break; + } + } + } + } + return childSequenceNo; +} + +/// Helps to find the total count of row spanned. +int getRowSpan(DataGridConfiguration dataGridConfiguration, int rowIndex, + int columnIndex, bool isStackedHeader, + {String? mappingName, StackedHeaderCell? stackedHeaderCell}) { + int rowSpan = 0; + int startIndex = 0; + int endIndex = 0; + if (isStackedHeader && stackedHeaderCell != null) { + final List> spannedColumns = + getConsecutiveRanges(getChildColumnIndexes(stackedHeaderCell)); + final List? spannedColumn = spannedColumns + .singleWhereOrNull((List element) => element.first == columnIndex); + if (spannedColumn != null) { + startIndex = spannedColumn.reduce(min); + endIndex = startIndex + spannedColumn.length - 1; + } + rowIndex = rowIndex - 1; + } else { + if (rowIndex >= dataGridConfiguration.stackedHeaderRows.length) { + return rowSpan; + } + } + + while (rowIndex >= 0) { + final StackedHeaderRow stackedHeaderRow = + dataGridConfiguration.stackedHeaderRows[rowIndex]; + for (final StackedHeaderCell stackedColumn in stackedHeaderRow.cells) { + if (isStackedHeader) { + final List> columnsRange = + getConsecutiveRanges(getChildColumnIndexes(stackedColumn)); + for (final List column in columnsRange) { + if ((startIndex >= column.first && startIndex <= column.last) || + (endIndex >= column.first && endIndex <= column.last)) { + return rowSpan; + } + } + } else if (stackedColumn.columnNames.isNotEmpty) { + final List children = stackedColumn.columnNames; + for (int child = 0; child < children.length; child++) { + if (children[child] == mappingName) { + return rowSpan; + } + } + } + } + + rowSpan += 1; + rowIndex -= 1; + } + + return rowSpan; +} + +/// Help to resolve the child column indexes to consecutive range +List> getConsecutiveRanges(List columnsIndex) { + int endIndex = 1; + final List> list = >[]; + if (columnsIndex.isEmpty) { + return list; + } + for (int i = 1; i <= columnsIndex.length; i++) { + if (i == columnsIndex.length || + columnsIndex[i] - columnsIndex[i - 1] != 1) { + if (endIndex == 1) { + list.add(columnsIndex.sublist(i - endIndex, (i - endIndex) + 1)); + } else { + list.add(columnsIndex.sublist(i - endIndex, i)); + } + endIndex = 1; + } else { + endIndex++; + } + } + return list; +} + +//------------------------ StackedHeader-Helper-End --------------------------// + +//----------------------------------------------------------------------------// +//--------------------- DataGrid Helper Method -------------------------------// +//----------------------------------------------------------------------------// + +/// Get the visible line based on view size and scroll offset. +/// Based on the [TextDirection] it will return visible lines info. +VisibleLinesCollection getVisibleLines( + DataGridConfiguration dataGridConfiguration) { + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + dataGridConfiguration.container.scrollColumns.markDirty(); + } + + return dataGridConfiguration.container.scrollColumns.getVisibleLines( + dataGridConfiguration.textDirection == TextDirection.rtl); +} + +/// Helps to scroll the [SfDataGrid] vertically. +/// [canAnimate]: decide to apply animation on scrolling or not. +Future scrollVertical( + DataGridConfiguration dataGridConfiguration, double verticalOffset, + [bool canAnimate = false]) async { + final ScrollController? verticalController = + dataGridConfiguration.verticalScrollController; + + if (verticalController == null || !verticalController.hasClients) { + return; + } + + verticalOffset = verticalOffset > verticalController.position.maxScrollExtent + ? verticalController.position.maxScrollExtent + : verticalOffset; + verticalOffset = verticalOffset.isNegative || verticalOffset == 0.0 + ? verticalController.position.minScrollExtent + : verticalOffset; + + if (canAnimate) { + await dataGridConfiguration.verticalScrollController!.animateTo( + verticalOffset, + duration: const Duration(milliseconds: 1000), + curve: Curves.fastOutSlowIn); + } else { + dataGridConfiguration.verticalScrollController!.jumpTo(verticalOffset); + } + dataGridConfiguration.container.updateScrollBars(); +} + +/// Helps to scroll the [SfDataGrid] horizontally. +/// [canAnimate]: decide to apply animation on scrolling or not. +Future scrollHorizontal( + DataGridConfiguration dataGridConfiguration, double horizontalOffset, + [bool canAnimate = false]) async { + final ScrollController? horizontalController = + dataGridConfiguration.horizontalScrollController; + + if (horizontalController == null || !horizontalController.hasClients) { + return; + } + + horizontalOffset = + horizontalOffset > horizontalController.position.maxScrollExtent + ? horizontalController.position.maxScrollExtent + : horizontalOffset; + horizontalOffset = horizontalOffset.isNegative || horizontalOffset == 0.0 + ? horizontalController.position.minScrollExtent + : horizontalOffset; + + if (canAnimate) { + await dataGridConfiguration.horizontalScrollController!.animateTo( + horizontalOffset, + duration: const Duration(milliseconds: 1000), + curve: Curves.fastOutSlowIn); + } else { + dataGridConfiguration.horizontalScrollController!.jumpTo(horizontalOffset); + } + dataGridConfiguration.container.updateScrollBars(); +} + +/// Decide to enable swipe in [SfDataGrid] +bool canSwipeRow(DataGridConfiguration dataGridConfiguration, + DataGridRowSwipeDirection swipeDirection, double swipeOffset) { + if (dataGridConfiguration.container.horizontalOffset == 0) { + if ((dataGridConfiguration.container.extentWidth > + dataGridConfiguration.viewWidth) && + swipeDirection == DataGridRowSwipeDirection.endToStart && + swipeOffset <= 0) { + return false; + } else { + return true; + } + } else if (dataGridConfiguration.container.horizontalOffset == + dataGridConfiguration.container.extentWidth - + dataGridConfiguration.viewWidth) { + if ((dataGridConfiguration.container.extentWidth > + dataGridConfiguration.viewWidth) && + swipeDirection == DataGridRowSwipeDirection.startToEnd && + swipeOffset >= 0) { + return false; + } else { + return true; + } + } else { + return false; + } +} + +/// Decide the swipe direction based on [TextDirection]. +DataGridRowSwipeDirection getSwipeDirection( + DataGridConfiguration dataGridConfiguration, double swipingOffset) { + return swipingOffset >= 0 + ? dataGridConfiguration.textDirection == TextDirection.ltr + ? DataGridRowSwipeDirection.startToEnd + : DataGridRowSwipeDirection.endToStart + : dataGridConfiguration.textDirection == TextDirection.ltr + ? DataGridRowSwipeDirection.endToStart + : DataGridRowSwipeDirection.startToEnd; +} + +/// Helps to get the [DataGridRow] based on respective rowIndex. +DataGridRow getDataGridRow( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + final int recordIndex = resolveToRecordIndex(dataGridConfiguration, rowIndex); + return effectiveRows(dataGridConfiguration.source)[recordIndex]; +} + +/// Helps to get the [DataGridRowAdapter] based on respective [DataGridRow]. +DataGridRowAdapter? getDataGridRowAdapter( + DataGridConfiguration dataGridConfiguration, DataGridRow dataGridRow) { + DataGridRowAdapter buildBlankRow(DataGridRow dataGridRow) { + return DataGridRowAdapter( + cells: dataGridConfiguration.columns + .map( + (GridColumn dataCell) => SizedBox.fromSize(size: Size.zero)) + .toList()); + } + + return dataGridConfiguration.source.buildRow(dataGridRow) ?? + buildBlankRow(dataGridRow); +} + +/// Check the length of two list. +/// If its not satisfies it throw a exception. +bool debugCheckTheLength(DataGridConfiguration dataGridConfiguration, + int columnLength, int cellLength, String message) { + cellLength += dataGridConfiguration.showCheckboxColumn ? 1 : 0; + assert(() { + if (columnLength != cellLength) { + throw FlutterError.fromParts([ + ErrorSummary('$message: is not true'), + ]); + } + return true; + }()); + return true; +} + +/// Return the cumulative distance of frozen top rows. The cumulative distance +/// covered the header, stacked header and freeze pane +double getCumulativeFrozenRowsHeight( + DataGridConfiguration dataGridConfiguration) { + final int topFrozenRowsLength = dataGridConfiguration.container.frozenRows; + double cumulativeFrozenRowsHeight = 0.0; + for (int index = 0; index < topFrozenRowsLength; index++) { + cumulativeFrozenRowsHeight += + dataGridConfiguration.container.rowHeights[index]; + } + return cumulativeFrozenRowsHeight; +} + +/// Return the cumulative distance of frozen bottom rows. +double getCumulativeFooterFrozenRowsHeight( + DataGridConfiguration dataGridConfiguration) { + final int bottomFrozenRowsLength = + dataGridConfiguration.container.footerFrozenRows; + double cumulativeFooterFrozenRowsHeight = 0.0; + for (int index = 0; index < bottomFrozenRowsLength; index++) { + final int rowIndex = dataGridConfiguration.container.rowCount - index; + cumulativeFooterFrozenRowsHeight += + dataGridConfiguration.container.rowHeights[rowIndex]; + } + return cumulativeFooterFrozenRowsHeight; +} + +/// Return the cumulative distance of frozen column on left side. The +/// cumulative distance covered the row header, indent cell, freeze pane +double getCumulativeFrozenColumnsWidth( + DataGridConfiguration dataGridConfiguration) { + final int leftColumnCount = dataGridConfiguration.container.frozenColumns; + double cumulativeFrozenColumnWidth = 0.0; + for (int index = 0; index < leftColumnCount; index++) { + cumulativeFrozenColumnWidth += + dataGridConfiguration.container.columnWidths[index]; + } + return cumulativeFrozenColumnWidth; +} + +/// Return the cumulative distance of frozen right side columns. +double getCumulativeFooterFrozenColumnsWidth( + DataGridConfiguration dataGridConfiguration) { + final int rightColumnCount = + dataGridConfiguration.container.footerFrozenColumns; + double cumulativeFooterFrozenColumnWidth = 0.0; + for (int index = 0; index < rightColumnCount; index++) { + final int columnIndex = dataGridConfiguration.container.columnCount - index; + cumulativeFooterFrozenColumnWidth += + dataGridConfiguration.container.columnWidths[columnIndex]; + } + return cumulativeFooterFrozenColumnWidth; +} + +/// Resolve the cumulative horizontal offset with frozen rows. +double resolveVerticalScrollOffset( + DataGridConfiguration dataGridConfiguration, double verticalOffset) { + final double leftOffset = + getCumulativeFrozenRowsHeight(dataGridConfiguration); + final double rightOffset = + getCumulativeFooterFrozenRowsHeight(dataGridConfiguration); + final double bottomOffset = + dataGridConfiguration.container.extentHeight - rightOffset; + if (verticalOffset >= bottomOffset) { + return dataGridConfiguration + .verticalScrollController!.position.maxScrollExtent; + } + + if (verticalOffset <= leftOffset) { + return dataGridConfiguration + .verticalScrollController!.position.minScrollExtent; + } + + for (int i = 0; i < dataGridConfiguration.container.frozenRows; i++) { + verticalOffset -= dataGridConfiguration.container.rowHeights[i]; + } + + return verticalOffset; +} + +/// Resolve the cumulative horizontal offset with frozen column. +double resolveHorizontalScrollOffset( + DataGridConfiguration dataGridConfiguration, double horizontalOffset) { + final double topOffset = + getCumulativeFrozenColumnsWidth(dataGridConfiguration); + final double bottomOffset = + getCumulativeFooterFrozenColumnsWidth(dataGridConfiguration); + final double rightOffset = + dataGridConfiguration.container.extentWidth - bottomOffset; + if (horizontalOffset >= rightOffset) { + return dataGridConfiguration + .horizontalScrollController!.position.maxScrollExtent; + } + + if (horizontalOffset <= topOffset) { + return dataGridConfiguration + .horizontalScrollController!.position.minScrollExtent; + } + + for (int i = 0; i < dataGridConfiguration.container.frozenColumns; i++) { + horizontalOffset -= dataGridConfiguration.container.columnWidths[i]; + } + + return horizontalOffset; +} + +/// Get the vertical offset with reduction of frozen rows +double getVerticalOffset( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + if (rowIndex < 0) { + return dataGridConfiguration.container.verticalOffset; + } + + final double cumulativeOffset = selection_helper + .getVerticalCumulativeDistance(dataGridConfiguration, rowIndex); + + return resolveVerticalScrollOffset(dataGridConfiguration, cumulativeOffset); +} + +/// Get the vertical offset with reduction of frozen columns +double getHorizontalOffset( + DataGridConfiguration dataGridConfiguration, int columnIndex) { + if (columnIndex < 0) { + return dataGridConfiguration.container.horizontalOffset; + } + + final double cumulativeOffset = selection_helper + .getHorizontalCumulativeDistance(dataGridConfiguration, columnIndex); + + return resolveHorizontalScrollOffset(dataGridConfiguration, cumulativeOffset); +} + +/// Resolve the scroll offset to [DataGridScrollPosition]. +/// It's helps to get the position of rows and column scroll into desired +/// DataGridScrollPosition. +double resolveScrollOffsetToPosition( + DataGridScrollPosition position, + ScrollAxisBase scrollAxisBase, + double measuredScrollOffset, + double viewDimension, + double headerExtent, + double bottomExtent, + double defaultDimension, + double defaultScrollOffset, + int index) { + if (position == DataGridScrollPosition.center) { + measuredScrollOffset = measuredScrollOffset - + ((viewDimension - bottomExtent - headerExtent) / 2) + + (defaultDimension / 2); + } else if (position == DataGridScrollPosition.end) { + measuredScrollOffset = measuredScrollOffset - + (viewDimension - bottomExtent - headerExtent) + + defaultDimension; + } else if (position == DataGridScrollPosition.makeVisible) { + final VisibleLinesCollection visibleLines = + scrollAxisBase.getVisibleLines(); + final int startIndex = + visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex; + final int endIndex = + visibleLines[visibleLines.lastBodyVisibleIndex].lineIndex; + if (index > startIndex && index < endIndex) { + measuredScrollOffset = defaultScrollOffset; + } + if (defaultScrollOffset - measuredScrollOffset < 0) { + measuredScrollOffset = measuredScrollOffset - + (viewDimension - bottomExtent - headerExtent) + + defaultDimension; + } + } + + return measuredScrollOffset; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/enums.dart similarity index 79% rename from packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart rename to packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/enums.dart index 0de2f08e0..8aac5dc89 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/control/helper/enums.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/enums.dart @@ -1,5 +1,3 @@ -part of datagrid; - /// Describes the possible values for row region. /// /// These values are used to update the row region for the row elements @@ -28,6 +26,13 @@ enum RowType { /// Specifies the footer row that displays the footer row data footerRow, + + /// Specifies the SpannedDataRow that displays the table summary value in + /// corresponding columns. + tableSummaryRow, + + /// Specifies the SpannedDataRow that displays the table summary value in row. + tableSummaryCoveredRow, } /// Describes the possible values for cell types. @@ -46,6 +51,12 @@ enum CellType { /// Specifies the stacked header cell. stackedHeaderCell, + + /// Specifies the checkbox cell. + checkboxCell, + + /// Specifies the table summary cell + tableSummaryCell, } /// Determines how border lines should be shown in [SfDataGrid]. @@ -70,10 +81,10 @@ enum ColumnWidthMode { /// Calculates the width of column based on [GridColumn.columnName]. /// - /// The default [TextStyle] of datagrid is considered internally to calculate - /// the auto size. If you want to set your required [TextStyle] for calculation, + /// The default `TextStyle` of datagrid is considered internally to calculate + /// the auto size. If you want to set your required `TextStyle` for calculation, /// you can override the [ColumnSizer.computeHeaderCellWidth] method and pass - /// the required [TextStyle] to base class. + /// the required `TextStyle` to base class. /// /// See also, /// @@ -93,10 +104,10 @@ enum ColumnWidthMode { /// Set the column width by calculating the max size among the cells in column. /// Auto fit calculation will be depending upon the [DataGridCell.value] property. /// - /// The default [TextStyle] of datagrid is considered internally to calculate - /// the auto size. If you want to set your required [TextStyle] for calculation, + /// The default `TextStyle` of datagrid is considered internally to calculate + /// the auto size. If you want to set your required `TextStyle` for calculation, /// you can override the [ColumnSizer.computeCellWidth] method and pass the - /// required [TextStyle] to base class. + /// required `TextStyle` to base class. /// /// See also, /// @@ -141,16 +152,6 @@ enum SelectionMode { multiple } -/// Decides whether the selection should be applied above the cell/row style -/// or both selection and cell or row style should be applied. -enum StylePreference { - /// Selection should be applied above the applied cell or row style. - selection, - - /// Selection and cell or row style should be applied. - styleAndSelection -} - /// Decides whether the navigation in the [SfDataGrid] should be cell wise /// or row wise. enum GridNavigationMode { @@ -226,3 +227,30 @@ enum EditingGestureType { /// Editing is triggered on double tap. doubleTap } + +/// Determines how table summary row should be positioned in [SfDataGrid]. +enum GridTableSummaryRowPosition { + /// Specifies that the table summary row is positioned at the top. + top, + + /// Specifies that the table summary row is positioned at the bottom. + bottom, +} + +/// Determines which summary type should be displayed for summary column. +enum GridSummaryType { + /// Specifies the sum of the values in specific summary column + sum, + + /// Specifies the maximum value of the specific summary column. + maximum, + + /// Specifies the minimum value of the specific summary column. + minimum, + + /// Specifies the average of the summary column. + average, + + /// Specifies the count of rows available in [SfDataGrid]. + count, +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart new file mode 100644 index 000000000..36507f49a --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/helper/selection_helper.dart @@ -0,0 +1,660 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart' hide DataCell, DataRow; + +import '../../grid_common/row_column_index.dart'; +import '../../grid_common/scroll_axis.dart'; +import '../../grid_common/visible_line_info.dart'; +import '../runtime/column.dart'; +import '../selection/selection_manager.dart'; +import '../sfdatagrid.dart'; +import 'datagrid_configuration.dart'; +import 'datagrid_helper.dart' as grid_helper; +import 'enums.dart'; + +/// Get the row index in data grid based provided DataGridRow +int resolveToRowIndex( + DataGridConfiguration dataGridConfiguration, DataGridRow record) { + final DataGridRow? rec = effectiveRows(dataGridConfiguration.source) + .firstWhereOrNull((DataGridRow rec) => rec == record); + if (rec == null) { + return -1; + } + + int recordIndex = effectiveRows(dataGridConfiguration.source).indexOf(rec); + + recordIndex += + grid_helper.resolveStartIndexBasedOnPosition(dataGridConfiguration); + + return recordIndex.isNegative ? -1 : recordIndex; +} + +/// Helps to get the DataGridRow based on provided row index +DataGridRow? getRecord(DataGridConfiguration dataGridConfiguration, int index) { + // Restricts the index if it doesn't exist in the `DataGridSource.rows` range. + if (effectiveRows(dataGridConfiguration.source).isEmpty || + index < 0 || + index >= effectiveRows(dataGridConfiguration.source).length) { + return null; + } + + final DataGridRow record = effectiveRows(dataGridConfiguration.source)[index]; + return record; +} + +/// Helps to get the total rows count +int getRecordsCount(DataGridConfiguration dataGridConfiguration) { + if (effectiveRows(dataGridConfiguration.source).isNotEmpty) { + return effectiveRows(dataGridConfiguration.source).length; + } else { + return 0; + } +} + +/// Helps to get the first navigating row index consider with hidden row +/// in data grid +int getFirstNavigatingRowIndex(DataGridConfiguration dataGridConfiguration) { + final int index = getFirstRowIndex(dataGridConfiguration); + const int count = 0; + for (int start = index; start >= 0; start--) { + final List hiddenRowInfo = + dataGridConfiguration.container.rowHeights.getHidden(start, count); + final bool isHiddenRow = hiddenRowInfo.first as bool; + if (!isHiddenRow) { + return start; + } + } + + return index; +} + +/// Helps to get the last navigating row index consider with hidden row, footer +/// row +int getLastNavigatingRowIndex(DataGridConfiguration dataGridConfiguration) { + int lastRowIndex = -1; + const int count = 0; + final int recordCount = getRecordsCount(dataGridConfiguration); + + if (recordCount == 0) { + return -1; + } + + /// From actual row count we have to reduce the length by 1 to adapt the + /// row index based on SfDataGrid. + /// Eg : Total rowCount is 30, we will start the row index from 0 of header + /// in SfDataGrid. The actual last row index of data grid is 29. + lastRowIndex = dataGridConfiguration.container.rowCount - 1; + if (dataGridConfiguration.footer != null) { + lastRowIndex--; + } + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + lastRowIndex -= grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + } + + for (int start = lastRowIndex; start >= 0; start--) { + final List hiddenRowInfo = + dataGridConfiguration.container.rowHeights.getHidden(start, count); + final bool isHiddenRow = hiddenRowInfo.first as bool; + if (!isHiddenRow) { + return start; + } + } + + return lastRowIndex; +} + +/// Help to get the first row index in data grid +int getFirstRowIndex(DataGridConfiguration dataGridConfiguration) { + if (getRecordsCount(dataGridConfiguration) == 0) { + return -1; + } + + int index = grid_helper.getHeaderIndex(dataGridConfiguration) + 1; + + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + index += grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.top); + } + + return index; +} + +/// Help to get the last row index in data grid +int getLastRowIndex(DataGridConfiguration dataGridConfiguration) { + if (getRecordsCount(dataGridConfiguration) == 0) { + return -1; + } + + const int count = 0; + + /// From actual row count we have to reduce the length by 1 to adapt the + /// row index based on SfDataGrid. + /// Eg : Total rowCount is 30, we will start the row index from 0 of header + /// in SfDataGrid. The actual last row index of data grid is 29. + int index = dataGridConfiguration.container.rowCount - 1; + if (dataGridConfiguration.footer != null) { + index--; + } + + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + index -= grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + } + + for (int start = index; start >= 0; start--) { + final List hiddenRowInfo = + dataGridConfiguration.container.rowHeights.getHidden(start, count); + final bool isHiddenRow = hiddenRowInfo.first as bool; + if (!isHiddenRow) { + return start; + } + } + + return index; +} + +/// Help to get the first column index in row +int getFirstCellIndex(DataGridConfiguration dataGridConfiguration) { + final GridColumn? gridColumn = dataGridConfiguration.columns + .firstWhereOrNull((GridColumn col) => col.visible); + if (gridColumn == null) { + return -1; + } + + final int firstIndex = dataGridConfiguration.columns.indexOf(gridColumn); + if (firstIndex < 0) { + return firstIndex; + } + + return firstIndex; +} + +/// Help to get the last column index in row +int getLastCellIndex(DataGridConfiguration dataGridConfiguration) { + final GridColumn? lastColumn = dataGridConfiguration.columns + .lastWhereOrNull((GridColumn col) => col.visible); + if (lastColumn == null) { + return -1; + } + + return dataGridConfiguration.columns.indexOf(lastColumn); +} + +/// Helps to get the previous row index on PageUp key +int getPreviousPageIndex(DataGridConfiguration dataGridConfiguration) { + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int rowIndex = currentCell.rowIndex; + int lastBodyVisibleIndex = -1; + final VisibleLinesCollection visibleLines = + dataGridConfiguration.container.scrollRows.getVisibleLines(); + if (visibleLines.lastBodyVisibleIndex < visibleLines.length) { + lastBodyVisibleIndex = + visibleLines[visibleLines.lastBodyVisibleIndex - 1].lineIndex; + } + + int index = lastBodyVisibleIndex < rowIndex ? lastBodyVisibleIndex : rowIndex; + index = dataGridConfiguration.container.scrollRows.getPreviousPage(index); + final int firstRowIndex = getFirstRowIndex(dataGridConfiguration); + if (index < firstRowIndex || rowIndex < firstRowIndex) { + return firstRowIndex; + } + + return index; +} + +/// Helps to get the next row index on PageDown key +int getNextPageIndex(DataGridConfiguration dataGridConfiguration) { + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + int rowIndex = currentCell.rowIndex; + if (rowIndex < getFirstNavigatingRowIndex(dataGridConfiguration)) { + rowIndex = 0; + } + + final VisibleLinesCollection visibleLines = + dataGridConfiguration.container.scrollRows.getVisibleLines(); + int firstBodyVisibleIndex = -1; + if (visibleLines.firstBodyVisibleIndex < visibleLines.length) { + firstBodyVisibleIndex = + visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex; + } + + int index = + firstBodyVisibleIndex > rowIndex ? firstBodyVisibleIndex : rowIndex; + final int lastRowIndex = getLastRowIndex(dataGridConfiguration); + index = dataGridConfiguration.container.scrollRows.getNextPage(index); + if (index > getLastNavigatingRowIndex(dataGridConfiguration) && + currentCell.rowIndex > lastRowIndex) { + return currentCell.rowIndex; + } + + return index = index <= lastRowIndex ? index : lastRowIndex; +} + +/// Help to get the previous cell index from current cell index. +int getPreviousColumnIndex( + DataGridConfiguration dataGridConfiguration, int currentColumnIndex) { + int previousCellIndex = currentColumnIndex; + final GridColumn? column = + getNextGridColumn(dataGridConfiguration, currentColumnIndex - 1, false); + if (column != null) { + previousCellIndex = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, dataGridConfiguration.columns.indexOf(column)); + } + + if (previousCellIndex == currentColumnIndex) { + previousCellIndex = currentColumnIndex - 1; + } + + return previousCellIndex; +} + +/// Help to get the next cell index from current cell index. +int getNextColumnIndex( + DataGridConfiguration dataGridConfiguration, int currentColumnIndex) { + int nextCellIndex = currentColumnIndex; + final int lastCellIndex = getLastCellIndex(dataGridConfiguration); + + final GridColumn? column = + getNextGridColumn(dataGridConfiguration, currentColumnIndex + 1, true); + if (column != null) { + final int columnIndex = dataGridConfiguration.columns.indexOf(column); + nextCellIndex = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, columnIndex); + } + + if (nextCellIndex == currentColumnIndex) { + nextCellIndex = currentColumnIndex + 1; + } + + return nextCellIndex > lastCellIndex ? lastCellIndex : nextCellIndex; +} + +/// Help to get the previous row index from current cell index. +int getPreviousRowIndex( + DataGridConfiguration dataGridConfiguration, int currentRowIndex) { + final int lastRowIndex = getLastNavigatingRowIndex(dataGridConfiguration); + if (currentRowIndex > lastRowIndex) { + return lastRowIndex; + } + + final int firstRowIndex = getFirstNavigatingRowIndex(dataGridConfiguration); + if (currentRowIndex <= firstRowIndex) { + return firstRowIndex; + } + + int previousIndex = currentRowIndex; + previousIndex = dataGridConfiguration.container.scrollRows + .getPreviousScrollLineIndex(currentRowIndex); + if (previousIndex == currentRowIndex) { + previousIndex = previousIndex - 1; + } + + return previousIndex; +} + +/// Help to get the next row index from current cell index. +int getNextRowIndex( + DataGridConfiguration dataGridConfiguration, int currentRowIndex) { + final int lastRowIndex = getLastNavigatingRowIndex(dataGridConfiguration); + if (currentRowIndex >= lastRowIndex) { + return lastRowIndex; + } + + final int firstRowIndex = getFirstNavigatingRowIndex(dataGridConfiguration); + + if (currentRowIndex < firstRowIndex) { + return firstRowIndex; + } + + if (currentRowIndex >= dataGridConfiguration.container.scrollRows.lineCount) { + return -1; + } + + int nextIndex = 0; + nextIndex = dataGridConfiguration.container.scrollRows + .getNextScrollLineIndex(currentRowIndex); + if (nextIndex == currentRowIndex) { + nextIndex = nextIndex + 1; + } + + return nextIndex; +} + +/// Helps to get the next and previous grid column based on provided column +/// index +/// moveToRight: Consider to check on next column else it will check the +/// previous column +GridColumn? getNextGridColumn(DataGridConfiguration dataGridConfiguration, + int columnIndex, bool moveToRight) { + final int resolvedIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, columnIndex); + if (resolvedIndex < 0 || + resolvedIndex >= dataGridConfiguration.columns.length) { + return null; + } + + GridColumn? gridColumn = dataGridConfiguration.columns[resolvedIndex]; + if (!gridColumn.visible || gridColumn.actualWidth == 0.0) { + gridColumn = getNextGridColumn(dataGridConfiguration, + moveToRight ? columnIndex + 1 : columnIndex - 1, moveToRight); + } + + return gridColumn; +} + +/// Helps to get the cumulative distance of provided row index +double getVerticalCumulativeDistance( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + double verticalOffset = 0.0; + final int headerRowIndex = grid_helper.getHeaderIndex(dataGridConfiguration); + rowIndex = rowIndex > headerRowIndex ? rowIndex : headerRowIndex + 1; + final PixelScrollAxis _scrollRows = + dataGridConfiguration.container.scrollRows as PixelScrollAxis; + verticalOffset = _scrollRows.distances!.getCumulatedDistanceAt(rowIndex); + return verticalOffset; +} + +/// Helps to get the cumulative distance of provided column index +double getHorizontalCumulativeDistance( + DataGridConfiguration dataGridConfiguration, int columnIndex) { + double horizontalOffset = 0.0; + final int firstVisibleColumnIndex = + grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + columnIndex = columnIndex < firstVisibleColumnIndex + ? firstVisibleColumnIndex + : columnIndex; + final PixelScrollAxis _scrollColumns = + dataGridConfiguration.container.scrollColumns as PixelScrollAxis; + horizontalOffset = + _scrollColumns.distances!.getCumulatedDistanceAt(columnIndex); + return horizontalOffset; +} + +// ScrollingView Helping API + +/// Decide to scroll toward bottom or not based on next row index +/// If next row index not present in view it will return true, else it will +/// return false +bool needToScrollDown( + DataGridConfiguration dataGridConfiguration, int nextRowIndex) { + final VisibleLinesCollection visibleLines = + dataGridConfiguration.container.scrollRows.getVisibleLines(); + if (visibleLines.isEmpty) { + return false; + } + + final VisibleLineInfo? nextRowInfo = dataGridConfiguration + .container.scrollRows + .getVisibleLineAtLineIndex(nextRowIndex); + return nextRowInfo == null || nextRowInfo.isClipped; +} + +/// Decide to scroll toward top or not based on previous row index +/// If previous row index not present in view it will return true, else it will +/// return false +bool needToScrollUp( + DataGridConfiguration dataGridConfiguration, int previousRowIndex) { + final VisibleLinesCollection visibleLineCollection = + dataGridConfiguration.container.scrollRows.getVisibleLines(); + if (visibleLineCollection.isEmpty) { + return false; + } + + final VisibleLineInfo? previousRowLineInfo = dataGridConfiguration + .container.scrollRows + .getVisibleLineAtLineIndex(previousRowIndex); + return previousRowLineInfo == null || previousRowLineInfo.isClipped; +} + +/// Decide to scroll toward left or not based on previous cell index +/// If previous cell index not present in view it will return true, else it will +/// return false +bool needToScrollLeft(DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex) { + final VisibleLinesCollection visibleLineCollection = + grid_helper.getVisibleLines(dataGridConfiguration); + if (visibleLineCollection.isEmpty) { + return false; + } + + final VisibleLineInfo? previousCellLineInfo = visibleLineCollection + .getVisibleLineAtLineIndex(rowColumnIndex.columnIndex); + return previousCellLineInfo == null || previousCellLineInfo.isClipped; +} + +/// Decide to scroll toward right or not based on previous cell index +/// If next cell index not present in view it will return true, else it will +/// return false +bool needToScrollRight(DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex) { + final VisibleLinesCollection visibleLineCollection = + grid_helper.getVisibleLines(dataGridConfiguration); + if (visibleLineCollection.isEmpty) { + return false; + } + final VisibleLineInfo? nextCellLineInfo = visibleLineCollection + .getVisibleLineAtLineIndex(rowColumnIndex.columnIndex); + return nextCellLineInfo == null || nextCellLineInfo.isClipped; +} + +/// Perform to scroll the view to left +void scrollInViewFromLeft(DataGridConfiguration dataGridConfiguration, + {int nextCellIndex = -1, bool needToScrollMaxExtent = false}) { + if (dataGridConfiguration.horizontalScrollController != null) { + final ScrollController horizontalController = + dataGridConfiguration.horizontalScrollController!; + double measuredHorizontalOffset = 0.0; + + final int lastFrozenColumnIndex = + grid_helper.getLastFrozenColumnIndex(dataGridConfiguration); + + final int firstFooterFrozenColumnIndex = + grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration); + + if (dataGridConfiguration.frozenColumnsCount > 0 && + lastFrozenColumnIndex + 1 == nextCellIndex) { + measuredHorizontalOffset = horizontalController.position.minScrollExtent; + } else if (needToScrollMaxExtent || + (dataGridConfiguration.footerFrozenColumnsCount > 0 && + firstFooterFrozenColumnIndex - 1 == nextCellIndex)) { + measuredHorizontalOffset = horizontalController.position.maxScrollExtent; + } else { + if (dataGridConfiguration.currentCell.columnIndex != -1 && + nextCellIndex == dataGridConfiguration.currentCell.columnIndex + 1) { + final List nextCellIndexHeight = dataGridConfiguration + .container.columnWidthsProvider + .getSize(nextCellIndex, 0); + final VisibleLinesCollection visibleInfoCollection = + grid_helper.getVisibleLines(dataGridConfiguration); + final VisibleLineInfo? nextCellInfo = + visibleInfoCollection.getVisibleLineAtLineIndex(nextCellIndex); + measuredHorizontalOffset = nextCellInfo != null + ? dataGridConfiguration.textDirection == TextDirection.rtl + ? nextCellInfo.clippedSize - + (~nextCellInfo.clippedOrigin.toInt()) + : nextCellInfo.size - + (nextCellInfo.size - nextCellInfo.clippedCornerExtent) + : nextCellIndexHeight.first as double; + measuredHorizontalOffset = + horizontalController.offset + measuredHorizontalOffset; + } else { + final VisibleLinesCollection visibleInfoCollection = + grid_helper.getVisibleLines(dataGridConfiguration); + final int firstBodyVisibleLineIndex = visibleInfoCollection + .firstBodyVisibleIndex < + visibleInfoCollection.length + ? visibleInfoCollection[visibleInfoCollection.firstBodyVisibleIndex] + .lineIndex + : 0; + if (nextCellIndex < firstBodyVisibleLineIndex) { + scrollInViewFromRight(dataGridConfiguration, + previousCellIndex: nextCellIndex, needToScrollToMinExtent: false); + } else { + measuredHorizontalOffset = getHorizontalCumulativeDistance( + dataGridConfiguration, nextCellIndex); + measuredHorizontalOffset = grid_helper.resolveHorizontalScrollOffset( + dataGridConfiguration, measuredHorizontalOffset); + measuredHorizontalOffset = + horizontalController.offset + measuredHorizontalOffset; + } + } + } + + grid_helper.scrollHorizontal( + dataGridConfiguration, measuredHorizontalOffset); + } +} + +/// Perform to scroll the view to right +void scrollInViewFromRight(DataGridConfiguration dataGridConfiguration, + {int previousCellIndex = -1, bool needToScrollToMinExtent = false}) { + double measuredHorizontalOffset = 0.0; + + if (dataGridConfiguration.horizontalScrollController != null) { + final ScrollController horizontalController = + dataGridConfiguration.horizontalScrollController!; + + final int startingFooterFrozenColumnIndex = + grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration); + final int lastFrozenColumnIndex = + grid_helper.getLastFrozenColumnIndex(dataGridConfiguration); + + if (dataGridConfiguration.footerFrozenColumnsCount > 0 && + startingFooterFrozenColumnIndex - 1 == previousCellIndex) { + measuredHorizontalOffset = horizontalController.position.maxScrollExtent; + } else if (needToScrollToMinExtent || + (dataGridConfiguration.frozenColumnsCount > 0 && + lastFrozenColumnIndex + 1 == previousCellIndex)) { + measuredHorizontalOffset = horizontalController.position.minScrollExtent; + } else { + if (dataGridConfiguration.currentCell.columnIndex != -1 && + previousCellIndex == + dataGridConfiguration.currentCell.columnIndex - 1) { + final List previousCellIndexWidth = dataGridConfiguration + .container.columnWidthsProvider + .getSize(previousCellIndex, 0); + final VisibleLinesCollection visibleInfoCollection = + grid_helper.getVisibleLines(dataGridConfiguration); + final VisibleLineInfo? previousCellInfo = + visibleInfoCollection.getVisibleLineAtLineIndex(previousCellIndex); + measuredHorizontalOffset = previousCellInfo != null + ? previousCellInfo.size - + (previousCellInfo.clippedSize - + previousCellInfo.clippedCornerExtent) + : previousCellIndexWidth.first as double; + measuredHorizontalOffset = + horizontalController.offset - measuredHorizontalOffset; + } else { + measuredHorizontalOffset = getHorizontalCumulativeDistance( + dataGridConfiguration, previousCellIndex); + measuredHorizontalOffset = grid_helper.resolveHorizontalScrollOffset( + dataGridConfiguration, measuredHorizontalOffset); + measuredHorizontalOffset = horizontalController.offset - + (horizontalController.offset - measuredHorizontalOffset); + } + } + + grid_helper.scrollHorizontal( + dataGridConfiguration, measuredHorizontalOffset); + } +} + +/// Perform to scroll the view to top +void scrollInViewFromTop(DataGridConfiguration dataGridConfiguration, + {int nextRowIndex = -1, bool needToScrollToMaxExtent = false}) { + double measuredVerticalOffset = 0.0; + + if (dataGridConfiguration.verticalScrollController != null) { + final ScrollController verticalController = + dataGridConfiguration.verticalScrollController!; + + if (dataGridConfiguration.frozenRowsCount > 0 && + grid_helper.getLastFrozenRowIndex(dataGridConfiguration) + 1 == + nextRowIndex) { + measuredVerticalOffset = verticalController.position.minScrollExtent; + } else if (needToScrollToMaxExtent) { + measuredVerticalOffset = verticalController.position.maxScrollExtent; + } else { + if (dataGridConfiguration.currentCell.rowIndex != -1 && + nextRowIndex == dataGridConfiguration.currentCell.rowIndex + 1) { + final List nextRowIndexHeight = dataGridConfiguration + .container.rowHeightsProvider + .getSize(nextRowIndex, 0); + final VisibleLineInfo? nextRowInfo = dataGridConfiguration + .container.scrollRows + .getVisibleLineAtLineIndex(nextRowIndex); + measuredVerticalOffset = nextRowInfo != null + ? nextRowInfo.size - + (nextRowInfo.size - nextRowInfo.clippedCornerExtent) + : nextRowIndexHeight.first as double; + measuredVerticalOffset = + verticalController.offset + measuredVerticalOffset; + } else { + final VisibleLinesCollection visibleInfoCollection = + dataGridConfiguration.container.scrollRows.getVisibleLines(); + final int firstBodyVisibleLineIndex = visibleInfoCollection + .firstBodyVisibleIndex < + visibleInfoCollection.length + ? visibleInfoCollection[visibleInfoCollection.firstBodyVisibleIndex] + .lineIndex + : 0; + if (nextRowIndex < firstBodyVisibleLineIndex) { + scrollInViewFromDown(dataGridConfiguration, + previousRowIndex: nextRowIndex, needToScrollToMinExtent: false); + } else { + measuredVerticalOffset = getVerticalCumulativeDistance( + dataGridConfiguration, nextRowIndex); + measuredVerticalOffset = grid_helper.resolveVerticalScrollOffset( + dataGridConfiguration, measuredVerticalOffset); + } + } + } + + grid_helper.scrollVertical(dataGridConfiguration, measuredVerticalOffset); + } +} + +/// Perform to scroll the view to down +void scrollInViewFromDown(DataGridConfiguration dataGridConfiguration, + {int previousRowIndex = -1, bool needToScrollToMinExtent = false}) { + double measuredVerticalOffset = 0.0; + + if (dataGridConfiguration.verticalScrollController != null) { + final ScrollController verticalController = + dataGridConfiguration.verticalScrollController!; + + if (dataGridConfiguration.footerFrozenRowsCount > 0 && + grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration) - 1 == + previousRowIndex) { + measuredVerticalOffset = verticalController.position.maxScrollExtent; + } else if (needToScrollToMinExtent) { + measuredVerticalOffset = verticalController.position.minScrollExtent; + } else { + if (dataGridConfiguration.currentCell.rowIndex != -1 && + previousRowIndex == dataGridConfiguration.currentCell.rowIndex - 1) { + final List previousRowIndexHeight = dataGridConfiguration + .container.rowHeightsProvider + .getSize(previousRowIndex, 0); + final VisibleLineInfo? previousRowInfo = dataGridConfiguration + .container.scrollRows + .getVisibleLineAtLineIndex(previousRowIndex); + measuredVerticalOffset = previousRowInfo != null + ? previousRowInfo.size - + (previousRowInfo.clippedSize - + previousRowInfo.clippedCornerExtent) + : previousRowIndexHeight.first as double; + measuredVerticalOffset = + verticalController.offset - measuredVerticalOffset; + } else { + measuredVerticalOffset = getVerticalCumulativeDistance( + dataGridConfiguration, previousRowIndex); + measuredVerticalOffset = grid_helper.resolveVerticalScrollOffset( + dataGridConfiguration, measuredVerticalOffset); + measuredVerticalOffset = verticalController.offset - + (verticalController.offset - measuredVerticalOffset); + } + } + + grid_helper.scrollVertical(dataGridConfiguration, measuredVerticalOffset); + } +} 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 new file mode 100644 index 000000000..e08ab7ab0 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/cell_renderers.dart @@ -0,0 +1,355 @@ +import 'package:flutter/material.dart' hide DataCell, DataRow; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../../datagrid.dart'; +import '../helper/datagrid_configuration.dart'; +import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/enums.dart'; +import '../selection/selection_manager.dart' as selection_manager; +import '../widgets/cell_widget.dart'; +import 'generator.dart'; + +/// The base class for the cell renderer that used to display the widget. +abstract class GridCellRendererBase { + /// Decide to enable the editing in the renderer. + bool isEditable = true; + + /// ToDo + late DataGridStateDetails _dataGridStateDetails; + + /// Called when the child widgets for the GridCell are prepared. + Widget? onPrepareWidgets(DataCellBase dataCell) { + return null; + } + + /// Called when the style is set for the cell. + void setCellStyle(DataCellBase dataCell) {} +} + +/// A cell renderer which displays the header text in the +/// stacked columns of the stacked header rows. +class GridStackedHeaderCellRenderer + extends GridVirtualizingCellRendererBase { + @override + void onInitializeDisplayWidget(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + final bool isLight = + dataGridConfiguration.dataGridThemeData!.brightness == Brightness.light; + Widget? label = DefaultTextStyle( + style: isLight + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)), + child: dataCell.stackedHeaderCell!.child); + + dataCell.columnElement = GridCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: isLight + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(33, 33, 33, 1), + isDirty: dataGridConfiguration.container.isDirty || dataCell.isDirty, + dataGridStateDetails: _dataGridStateDetails, + child: label, + ); + + label = null; + } +} + +/// A cell renderer which displays the String value in the cell. +/// +/// This renderer is typically used for [GridTextColumn]. +class GridCellTextFieldRenderer + extends GridVirtualizingCellRendererBase { + @override + void setCellStyle(DataCellBase? dataCell) { + if (dataCell != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + dataCell.textStyle = _getCellTextStyle(dataGridConfiguration, dataCell); + super.setCellStyle(dataCell); + } + } + + TextStyle _getCellTextStyle( + DataGridConfiguration dataGridConfiguration, DataCellBase dataCell) { + final DataRowBase? dataRow = dataCell.dataRow; + if (dataRow != null && dataRow.isSelectedRow) { + return dataRow.isHoveredRow + ? dataGridConfiguration.dataGridThemeData!.rowHoverTextStyle + : dataGridConfiguration.dataGridThemeData!.brightness == + Brightness.light + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Color.fromRGBO(0, 0, 0, 0.87)) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + } else { + return dataRow!.isHoveredRow + ? dataGridConfiguration.dataGridThemeData!.rowHoverTextStyle + : dataGridConfiguration.dataGridThemeData!.brightness == + Brightness.light + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + } + } +} + +/// A cell renderer which displays the header text in the columns. +class GridHeaderCellRenderer + extends GridVirtualizingCellRendererBase { + /// Creates the [GridHeaderCellRenderer] for [SfDataGrid] widget. + GridHeaderCellRenderer() { + super.isEditable = false; + } + + @override + void onInitializeDisplayWidget(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + final Widget child = DefaultTextStyle( + key: dataCell.key, + style: dataCell.textStyle!, + child: dataCell.gridColumn!.label); + if (dataGridConfiguration.showCheckboxColumn && dataCell.columnIndex == 0) { + dataCell.columnElement = GridCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: Colors.transparent, + isDirty: dataGridConfiguration.container.isDirty || + dataCell.isDirty || + dataCell.dataRow!.isDirty, + dataGridStateDetails: _dataGridStateDetails, + child: + _getCheckboxHeaderWidget(dataGridConfiguration, dataCell, child)); + } else { + dataCell.columnElement = GridHeaderCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: Colors.transparent, + isDirty: dataGridConfiguration.container.isDirty || + dataCell.isDirty || + dataCell.dataRow!.isDirty, + dataGridStateDetails: _dataGridStateDetails, + child: child); + } + } + + @override + void setCellStyle(DataCellBase dataCell) { + final SfDataGridThemeData themeData = + _dataGridStateDetails().dataGridThemeData!; + TextStyle getDefaultHeaderTextStyle() { + return themeData.brightness == Brightness.light + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + } + + dataCell.textStyle = getDefaultHeaderTextStyle(); + } + + /// Creates a widget which displays label by default. Also, it creates with Checkbox, + /// only when the [DataGridConfiguration.showCheckboxOnHeader] is true. + Widget _getCheckboxHeaderWidget(DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, Widget child) { + final Widget label = Flexible( + child: DefaultTextStyle( + overflow: TextOverflow.ellipsis, + key: dataCell.key, + style: dataCell.textStyle!, + child: child)); + + return Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: dataGridConfiguration + .checkboxColumnSettings.showCheckboxOnHeader, + child: Checkbox( + tristate: true, + value: dataGridConfiguration.headerCheckboxState, + onChanged: (bool? newValue) { + if (dataGridConfiguration.selectionMode == + SelectionMode.multiple) { + selection_manager.handleSelectionFromCheckbox( + dataGridConfiguration, + dataCell, + dataGridConfiguration.headerCheckboxState, + newValue); + } + })), + label + ], + )); + } +} + +/// A base class for cell renderer classes which displays widget in a cell. +abstract class GridVirtualizingCellRendererBase extends GridCellRendererBase { + /// Creates the [GridVirtualizingCellRendererBase] for [SfDataGrid] widget. + GridVirtualizingCellRendererBase(); + + /// Initializes the column element of a [DataCell] + /// + /// object with the given widget and required values. + void onInitializeDisplayWidget(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + // Need to restrict invisible rows (rowIndex == -1) from the item collection. + // Because we generate the `ensureColumns` for all the data rows in the + // `rowGenerator.items` collection not visible rows. + if (dataCell.columnIndex < 0 || dataCell.rowIndex < 0) { + return; + } + + final int index = grid_helper.resolveToDataGridRowAdapterCellIndex( + dataGridConfiguration, dataCell.columnIndex); + final Widget child = dataCell.dataRow!.dataGridRowAdapter!.cells[index]; + + Widget getChild() { + if (dataGridConfiguration.currentCell.isEditing && dataCell.isEditing) { + return dataCell.editingWidget ?? child; + } else { + return child; + } + } + + dataCell.columnElement = GridCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: Colors.transparent, + isDirty: dataGridConfiguration.container.isDirty || + dataCell.isDirty || + dataCell.dataRow!.isDirty, + dataGridStateDetails: _dataGridStateDetails, + child: DefaultTextStyle( + key: dataCell.key, style: dataCell.textStyle!, child: getChild()), + ); + } + + @override + Widget? onPrepareWidgets(DataCellBase dataCell) { + onInitializeDisplayWidget(dataCell); + return dataCell.columnElement; + } +} + +/// A cell renderer which displays the check box column. +class GridCheckboxRenderer + extends GridVirtualizingCellRendererBase { + @override + void onInitializeDisplayWidget(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (dataCell.columnIndex < 0 || dataCell.rowIndex < 0) { + return; + } + + final bool selectionState = dataCell.dataRow!.isSelectedRow; + + dataCell.columnElement = GridCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: + dataGridConfiguration.checkboxColumnSettings.backgroundColor ?? + Colors.transparent, + isDirty: dataGridConfiguration.container.isDirty || + dataCell.isDirty || + dataCell.dataRow!.isDirty, + dataGridStateDetails: _dataGridStateDetails, + child: Checkbox( + value: selectionState, + onChanged: (bool? newValue) { + selection_manager.handleSelectionFromCheckbox( + dataGridConfiguration, dataCell, selectionState, newValue); + })); + } +} + +/// A cell renderer which displays the widgets to the table summary rows. +class GridTableSummaryCellRenderer + extends GridVirtualizingCellRendererBase { + @override + void onInitializeDisplayWidget(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + Widget getSummaryCell() { + Widget? cell; + final GridTableSummaryRow? tableSummaryRow = + dataCell.dataRow!.tableSummaryRow; + if (tableSummaryRow != null && + (dataCell.summaryColumn != null || dataCell.columnSpan > 0)) { + final GridSummaryColumn? summaryColumn = dataCell.summaryColumn; + final RowColumnIndex rowColumnIndex = + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex); + final String title = dataGridConfiguration.source.calculateSummaryValue( + tableSummaryRow, summaryColumn, rowColumnIndex); + + cell = dataGridConfiguration.source.buildTableSummaryCellWidget( + tableSummaryRow, summaryColumn, rowColumnIndex, title); + } + cell ??= Container(); + return cell; + } + + Widget? label = DefaultTextStyle( + style: dataGridConfiguration.dataGridThemeData!.brightness == + Brightness.light + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)), + child: getSummaryCell()); + + dataCell.columnElement = GridCell( + key: dataCell.key!, + dataCell: dataCell, + backgroundColor: Colors.transparent, + dataGridStateDetails: _dataGridStateDetails, + isDirty: dataGridConfiguration.container.isDirty || dataCell.isDirty, + child: label, + ); + + label = null; + } +} + +/// ToDo +void setStateDetailsInCellRendererBase(GridCellRendererBase cellRendererBase, + DataGridStateDetails dataGridStateDetails) { + cellRendererBase._dataGridStateDetails = dataGridStateDetails; +} 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 new file mode 100644 index 000000000..4ed4f0871 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/column.dart @@ -0,0 +1,1728 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_datagrid/src/grid_common/enums.dart'; + +import '../../grid_common/line_size_host.dart'; +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/enums.dart'; +import '../sfdatagrid.dart'; +import 'generator.dart'; + +/// Provides the base functionalities for all the column types in [SfDataGrid]. +class GridColumn { + /// Creates the [GridColumn] for [SfDataGrid] widget. + GridColumn( + {required this.columnName, + required this.label, + this.columnWidthMode = ColumnWidthMode.none, + this.visible = true, + this.allowSorting = true, + this.autoFitPadding = const EdgeInsets.all(16.0), + this.minimumWidth = double.nan, + this.maximumWidth = double.nan, + this.width = double.nan, + this.allowEditing = true}) { + _actualWidth = double.nan; + _autoWidth = double.nan; + } + + late double _autoWidth; + + /// The label of column header. + /// + /// Typically, this will be [Text] widget. You can also set [Icon] + /// (Typically using size 18), or a [Row] with an icon and [Text]. + /// + /// If you want to take the entire space for widget, + /// e.g. when you want to use [Center], you can wrap it with an [Expanded]. + /// + /// The widget will be loaded in text area alone. When sorting is applied, + /// the default sort icon will be loaded along with the widget. + final Widget label; + + /// How the column widths are determined. + /// + /// This takes higher priority than [SfDataGrid.columnWidthMode]. + /// + /// Defaults to [ColumnWidthMode.none] + /// + /// Also refer [ColumnWidthMode] + final ColumnWidthMode columnWidthMode; + + /// The name of a column.The name should be unique. + /// + /// This must not be empty or null. + final String columnName; + + /// The actual display width of the column when auto fitted based on + /// [SfDataGrid.columnWidthMode] or [columnWidthMode]. + /// + /// Defaults to [double.nan] + double get actualWidth => _actualWidth; + late double _actualWidth; + + /// The minimum width of the column. + /// + /// The column width could not be set or auto sized lesser than the + /// [minimumWidth] + /// + /// Defaults to [double.nan] + final double minimumWidth; + + /// The maximum width of the column. + /// + /// The column width could not be set or auto sized greater than the + /// [maximumWidth] + /// + /// Defaults to [double.nan] + final double maximumWidth; + + /// The width of the column. + /// + /// If value is lesser than [minimumWidth], then [minimumWidth] + /// is set to [width]. + /// Otherwise, If value is greater than [maximumWidth], then the + /// [maximumWidth] is set to [width]. + /// + /// Defaults to [double.nan] + final double width; + + /// Whether column should be hidden. + /// + /// Defaults to false. + final bool visible; + + /// Decides whether user can sort the column simply by tapping the column + /// header. + /// + /// Defaults to true. + /// + /// This is applicable only if the [SfDataGrid.allowSorting] is set as true. + /// + /// See also: + /// + /// * [SfDataGrid.allowSorting] – which allows users to sort the columns in + /// [SfDataGrid]. + /// * [DataGridSource.sortedColumns] - which is the collection of + /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. + /// * [DataGridSource.sort] - call this method when you are adding the + /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. + final bool allowSorting; + + /// Decides whether cell should be moved into edit mode based on + /// [SfDataGrid.editingGestureType]. + /// + /// Defaults to false. + /// + /// Editing can be enabled only if the [SfDataGrid.selectionMode] is other + /// than none and [SfDataGrid.navigationMode] is cell. + /// + /// See also, + /// + /// * [DataGridSource.onCellBeginEdit]- This will be triggered when a cell is + /// moved to edit mode. + /// * [DataGridSource.onCellSubmit] – This will be triggered when the cell’s + /// editing is completed. + final bool allowEditing; + + /// The amount of space which should be added with the auto size calculated + /// when you use [SfDataGrid.columnWidthMode] as [ColumnWidthMode.auto] or + /// [ColumnWidthMode.fitByCellValue] or [ColumnWidthMode.fitByColumnName] + /// option. + final EdgeInsets autoFitPadding; +} + +/// A column which displays the values of the string in its cells. +/// +/// This column has all the required APIs to customize the widget [Text] as it +/// displays [Text] for all the cells. +/// +/// ``` dart +/// @override +/// Widget build(BuildContext context) { +/// return SfDataGrid( +/// source: employeeDataSource, +/// columns: [ +/// GridTextColumn(columnName: 'name', label: Text('Name')), +/// GridTextColumn(columnName: 'designation', label: Text('Designation')), +/// ], +/// ); +/// } +/// ``` +@Deprecated('Use GridColumn instead.') +class GridTextColumn extends GridColumn { + /// Creates a String column using [columnName] and [label]. + GridTextColumn({ + required String columnName, + required Widget label, + ColumnWidthMode columnWidthMode = ColumnWidthMode.none, + EdgeInsets autoFitPadding = const EdgeInsets.all(16.0), + bool visible = true, + bool allowSorting = true, + double minimumWidth = double.nan, + double maximumWidth = double.nan, + double width = double.nan, + bool allowEditing = true, + }) : super( + columnName: columnName, + label: label, + columnWidthMode: columnWidthMode, + autoFitPadding: autoFitPadding, + visible: visible, + allowSorting: allowSorting, + minimumWidth: minimumWidth, + maximumWidth: maximumWidth, + width: width, + allowEditing: allowEditing); +} + +/// A column which displays the checkbox column in its cells. +class GridCheckboxColumn extends GridColumn { + /// Creates the [GridCheckboxColumn] for [SfDataGrid] widget. + GridCheckboxColumn({ + required String columnName, + required Widget label, + double width = double.nan, + }) : super( + columnName: columnName, + label: label, + width: width, + ); +} + +/// Contains all the properties of the checkbox column. +/// +/// This settings are applied to checkbox column only if [SfDataGrid.showCheckboxColumn] is `true`. +class DataGridCheckboxColumnSettings { + /// + const DataGridCheckboxColumnSettings({ + this.showCheckboxOnHeader = true, + this.label, + this.width = 50, + this.backgroundColor, + }); + + /// Decides whether [Checkbox] should be displayed in header of column which + /// shows [Checkbox] to select/deselect the rows. + /// + /// Defaults to true. + /// + /// See also, + /// [SfDataGrid.showCheckboxColumn] – This enables you to show [Checkbox] in + /// each row to select/deselect the rows + final bool showCheckboxOnHeader; + + /// The label of column header of checkbox column. + /// + /// Typically, checkbox can be enabled by setting + /// [DataGridCheckboxColumnSettings.showCheckboxOnHeader] as `true`. + final Widget? label; + + /// The width of the column. + /// + /// Defaults to 50. + final double width; + + /// The background color of the checkbox column. + /// + /// You can customize the appearance of the [Checkbox] by using [ThemeData.checkboxTheme] property. + final Color? backgroundColor; +} + +/// Handles the sizing for all the columns in the [SfDataGrid]. +/// +/// You can override any available methods in this class to calculate the +/// column width based on your requirement and set the instance to the +/// [SfDataGrid.columnSizer]. +/// +/// ``` dart +/// class CustomGridColumnSizer extends ColumnSizer { +/// @override +/// double computeHeaderCellWidth(GridColumn column, TextStyle style) { +/// return super.computeHeaderCellWidth(column, style); +/// } +/// +/// @override +/// double computeCellWidth(GridColumn column, DataGridRow row, Object? cellValue, +/// TextStyle textStyle) { +/// return super.computeCellWidth(column, row, cellValue, textStyle); +/// } +/// +/// @override +/// double computeHeaderCellHeight(GridColumn column, TextStyle textStyle) { +/// return super.computeHeaderCellHeight(column, textStyle); +/// } +/// +/// @override +/// double computeCellHeight(GridColumn column, DataGridRow row, +/// Object? cellValue, TextStyle textStyle) { +/// return super.computeCellHeight(column, row, cellValue, textStyle); +/// } +/// } +/// +/// final CustomGridColumnSizer _customGridColumnSizer = CustomGridColumnSizer(); +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfDataGrid( +/// source: _employeeDataSource, +/// columnSizer: _customGridColumnSizer, +/// columnWidthMode: ColumnWidthMode.auto, +/// columns: [ +/// GridColumn(columnName: 'id', label: Text('ID')), +/// GridColumn(columnName: 'name', label: Text('Name')), +/// GridColumn(columnName: 'designation', label: Text('Designation')), +/// GridColumn(columnName: 'salary', label: Text('Salary')), +/// ); +/// } +/// ``` +class ColumnSizer { + /// Creates the [ColumnSizer] for [SfDataGrid] widget. + ColumnSizer() { + _isColumnSizerLoadedInitially = false; + } + + /// ToDo + DataGridStateDetails? _dataGridStateDetails; + + GridColumn? _autoFillColumn; + + bool _isColumnSizerLoadedInitially = false; + + static const double _sortIconWidth = 20.0; + static const double _sortNumberWidth = 18.0; + + void _initialRefresh(double availableWidth) { + final LineSizeCollection lineSizeCollection = + _dataGridStateDetails!().container.columnWidths as LineSizeCollection; + lineSizeCollection.suspendUpdates(); + _refresh(availableWidth); + lineSizeCollection.resumeUpdates(); + } + + void _refresh(double availableWidth) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final bool hasAnySizerColumn = dataGridConfiguration.columns.any( + (GridColumn column) => + (column.columnWidthMode != ColumnWidthMode.none) || + (column.width != double.nan) || + !column.visible); + + final PaddedEditableLineSizeHostBase paddedEditableLineSizeHostBase = + dataGridConfiguration.container.columnWidths; + final LineSizeCollection? lineSizeCollection = + paddedEditableLineSizeHostBase is LineSizeCollection + ? paddedEditableLineSizeHostBase + : null; + + if (lineSizeCollection == null) { + return; + } + + lineSizeCollection.suspendUpdates(); + _ensureColumnVisibility(dataGridConfiguration); + + if (dataGridConfiguration.columnWidthMode != ColumnWidthMode.none || + hasAnySizerColumn) { + _sizerColumnWidth(dataGridConfiguration, availableWidth); + } + dataGridConfiguration.container.updateScrollBars(); + lineSizeCollection.resumeUpdates(); + } + + void _ensureColumnVisibility(DataGridConfiguration dataGridConfiguration) { + for (final GridColumn column in dataGridConfiguration.columns) { + final int index = dataGridConfiguration.columns.indexOf(column); + dataGridConfiguration.container.columnWidths + .setHidden(index, index, !column.visible); + } + // Columns will be auto sized only if Columns doesn't have explicit width + // defined. + _sizerColumnWidth(dataGridConfiguration, 0.0); + } + + void _sizerColumnWidth( + DataGridConfiguration dataGridConfiguration, double viewPortWidth) { + double totalColumnSize = 0.0; + final List calculatedColumns = []; + + _autoFillColumn = _getColumnToFill(dataGridConfiguration); + + // Hide Hidden columns + final List hiddenColumns = dataGridConfiguration.columns + .where((GridColumn column) => !column.visible) + .toList(); + for (final GridColumn column in hiddenColumns) { + final int index = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, dataGridConfiguration.columns.indexOf(column)); + dataGridConfiguration.container.columnWidths + .setHidden(index, index, true); + calculatedColumns.add(column); + } + + // Set width based on Column.Width + final List widthColumns = dataGridConfiguration.columns + .skipWhile((GridColumn column) => !column.visible) + .where((GridColumn column) => !(column.width).isNaN) + .toList(); + for (final GridColumn column in widthColumns) { + totalColumnSize += + _setColumnWidth(dataGridConfiguration, column, column.width); + calculatedColumns.add(column); + } + + // Set width based on fitByCellValue mode + final List fitByCellValueColumns = dataGridConfiguration.columns + .skipWhile((GridColumn column) => !column.visible) + .where((GridColumn column) => + column.columnWidthMode == ColumnWidthMode.fitByCellValue && + column.width.isNaN) + .toList(); + for (final GridColumn column in fitByCellValueColumns) { + if (column._autoWidth.isNaN) { + final double columnWidth = _getWidthBasedOnColumn( + dataGridConfiguration, column, ColumnWidthMode.fitByCellValue); + totalColumnSize += columnWidth; + _setAutoWidth(column, columnWidth); + } else { + totalColumnSize += + _setColumnWidth(dataGridConfiguration, column, column._autoWidth); + } + calculatedColumns.add(column); + } + + // Set width based on fitByColumnName mode + final List fitByColumnNameColumns = dataGridConfiguration + .columns + .skipWhile((GridColumn column) => !column.visible) + .where((GridColumn column) => + column.columnWidthMode == ColumnWidthMode.fitByColumnName && + column.width.isNaN) + .toList(); + for (final GridColumn column in fitByColumnNameColumns) { + totalColumnSize += _getWidthBasedOnColumn( + dataGridConfiguration, column, ColumnWidthMode.fitByColumnName); + calculatedColumns.add(column); + } + + // Set width based on auto and lastColumnFill + List autoColumns = dataGridConfiguration.columns + .where((GridColumn column) => + column.columnWidthMode == ColumnWidthMode.auto && + column.visible && + column.width.isNaN) + .toList(); + + final List lastColumnFill = dataGridConfiguration.columns + .skipWhile((GridColumn column) => calculatedColumns.contains(column)) + .where((GridColumn col) => + col.columnWidthMode == ColumnWidthMode.lastColumnFill && + !_isLastFillColum(col)) + .toList(); + + autoColumns = (autoColumns + lastColumnFill).toSet().toList(); + + for (final GridColumn column in autoColumns) { + if (column._autoWidth.isNaN) { + final double columnWidth = _getWidthBasedOnColumn( + dataGridConfiguration, column, ColumnWidthMode.auto); + totalColumnSize += columnWidth; + _setAutoWidth(column, columnWidth); + } else { + totalColumnSize += + _setColumnWidth(dataGridConfiguration, column, column._autoWidth); + } + calculatedColumns.add(column); + } + _setWidthBasedOnGrid(dataGridConfiguration, totalColumnSize, + calculatedColumns, viewPortWidth); + _autoFillColumn = null; + } + + GridColumn? _getColumnToFill(DataGridConfiguration dataGridConfiguration) { + final GridColumn? column = dataGridConfiguration.columns.lastWhereOrNull( + (GridColumn c) => + c.visible && + c.width.isNaN && + c.columnWidthMode == ColumnWidthMode.lastColumnFill); + if (column != null) { + return column; + } else { + if (dataGridConfiguration.columnWidthMode == + ColumnWidthMode.lastColumnFill) { + final GridColumn? lastColumn = dataGridConfiguration.columns + .lastWhereOrNull((GridColumn c) => c.visible && c.width.isNaN); + if (lastColumn == null) { + return null; + } + + if (lastColumn.columnWidthMode == ColumnWidthMode.none) { + return lastColumn; + } + } + } + return null; + } + + void _setWidthBasedOnGrid( + DataGridConfiguration dataGridConfiguration, + double totalColumnSize, + List calculatedColumns, + double viewPortWidth) { + for (final GridColumn column in dataGridConfiguration.columns) { + if (calculatedColumns.contains(column) || + column.columnWidthMode == ColumnWidthMode.fill || + _isLastFillColum(column)) { + continue; + } + + switch (dataGridConfiguration.columnWidthMode) { + case ColumnWidthMode.fitByCellValue: + if (column._autoWidth.isNaN) { + final double columnWidth = _getWidthBasedOnColumn( + dataGridConfiguration, column, ColumnWidthMode.fitByCellValue); + totalColumnSize += columnWidth; + _setAutoWidth(column, columnWidth); + } else { + totalColumnSize += _setColumnWidth( + dataGridConfiguration, column, column._autoWidth); + } + calculatedColumns.add(column); + break; + case ColumnWidthMode.fitByColumnName: + totalColumnSize += _getWidthBasedOnColumn( + dataGridConfiguration, column, ColumnWidthMode.fitByColumnName); + calculatedColumns.add(column); + break; + case ColumnWidthMode.auto: + case ColumnWidthMode.lastColumnFill: + if (column._autoWidth.isNaN) { + final double columnWidth = _getWidthBasedOnColumn( + dataGridConfiguration, column, ColumnWidthMode.auto); + totalColumnSize += columnWidth; + _setAutoWidth(column, columnWidth); + } else { + totalColumnSize += _setColumnWidth( + dataGridConfiguration, column, column._autoWidth); + } + calculatedColumns.add(column); + break; + case ColumnWidthMode.none: + if (column.visible) { + totalColumnSize += _setColumnWidth(dataGridConfiguration, column, + dataGridConfiguration.container.columnWidths.defaultLineSize); + calculatedColumns.add(column); + } + break; + default: + break; + } + } + + final List remainingColumns = []; + + for (final GridColumn column in dataGridConfiguration.columns) { + if (!calculatedColumns.contains(column)) { + remainingColumns.add(column); + } + } + + final double remainingColumnWidths = viewPortWidth - totalColumnSize; + + if (remainingColumnWidths > 0 && + (totalColumnSize != 0 || + (totalColumnSize == 0 && remainingColumns.length == 1) || + (dataGridConfiguration.columns.any((GridColumn col) => + col.columnWidthMode == ColumnWidthMode.fill) || + dataGridConfiguration.columnWidthMode == + ColumnWidthMode.fill))) { + _setFillWidth( + dataGridConfiguration, remainingColumnWidths, remainingColumns); + } else { + _setRemainingColumnsWidth(dataGridConfiguration, remainingColumns); + } + } + + double _getWidthBasedOnColumn(DataGridConfiguration dataGridConfiguration, + GridColumn column, ColumnWidthMode columnWidthMode) { + double width = 0.0; + switch (columnWidthMode) { + case ColumnWidthMode.fitByCellValue: + width = _calculateAllCellsExceptHeaderWidth(column); + break; + case ColumnWidthMode.fitByColumnName: + width = _calculateColumnHeaderWidth(column); + break; + case ColumnWidthMode.auto: + width = _calculateAllCellsWidth(column); + break; + default: + return width; + } + return _setColumnWidth(dataGridConfiguration, column, width); + } + + double _calculateAllCellsWidth(GridColumn column) { + final double headerWidth = + _calculateColumnHeaderWidth(column, setWidth: false); + final double cellWidth = + _calculateAllCellsExceptHeaderWidth(column, setWidth: false); + return _getColumnWidth(column, max(cellWidth, headerWidth)); + } + + double _calculateColumnHeaderWidth(GridColumn column, + {bool setWidth = true}) { + final double width = + _getHeaderCellWidth(column) + _getSortIconWidth(column); + _updateSetWidth(setWidth, column, width); + return width; + } + + double _calculateAllCellsExceptHeaderWidth(GridColumn column, + {bool setWidth = true}) { + final double width = _calculateCellWidth(column); + _updateSetWidth(setWidth, column, width); + return width; + } + + void _updateSetWidth(bool setWidth, GridColumn column, double columnWidth) { + if (setWidth) { + column._actualWidth = columnWidth; + } + } + + double _calculateCellWidth(GridColumn column) { + double autoFitWidth = 0.0; + int startRowIndex, endRowIndex; + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + + if (dataGridConfiguration.source.rows.isEmpty) { + return double.nan; + } + + switch (dataGridConfiguration.columnWidthCalculationRange) { + case ColumnWidthCalculationRange.allRows: + startRowIndex = 0; + endRowIndex = dataGridConfiguration.source.rows.length - 1; + break; + case ColumnWidthCalculationRange.visibleRows: + final VisibleLinesCollection visibleLines = + dataGridConfiguration.container.scrollRows.getVisibleLines( + dataGridConfiguration.textDirection == TextDirection.rtl); + startRowIndex = + visibleLines.firstBodyVisibleIndex <= visibleLines.length - 1 + ? visibleLines.firstBodyVisibleIndex + : 0; + endRowIndex = visibleLines.lastBodyVisibleIndex; + break; + } + + for (int rowIndex = startRowIndex; rowIndex <= endRowIndex; rowIndex++) { + autoFitWidth = max(_getCellWidth(column, rowIndex), autoFitWidth); + } + + return autoFitWidth; + } + + double _getHeaderCellWidth(GridColumn column) { + return computeHeaderCellWidth( + column, _getDefaultTextStyle(_dataGridStateDetails!(), true)); + } + + double _getCellWidth(GridColumn column, int rowIndex) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration) || + grid_helper.isTableSummaryIndex(dataGridConfiguration, rowIndex)) { + return 0.0; + } + + late DataGridRow dataGridRow; + switch (dataGridConfiguration.columnWidthCalculationRange) { + case ColumnWidthCalculationRange.allRows: + dataGridRow = effectiveRows(dataGridConfiguration.source)[rowIndex]; + break; + case ColumnWidthCalculationRange.visibleRows: + dataGridRow = + grid_helper.getDataGridRow(dataGridConfiguration, rowIndex); + break; + } + return _measureCellWidth( + _getCellValue(dataGridRow, column), column, dataGridRow); + } + + Object? _getCellValue(DataGridRow dataGridRow, GridColumn column) { + return dataGridRow + .getCells() + .firstWhereOrNull( + (DataGridCell cell) => cell.columnName == column.columnName) + ?.value; + } + + void _setFillWidth(DataGridConfiguration dataGridConfiguration, + double remainingColumnWidth, List remainingColumns) { + final List removedColumns = []; + final List columns = remainingColumns; + double totalRemainingFillValue = remainingColumnWidth; + + double removedWidth = 0; + GridColumn? fillColumn; + bool isRemoved; + while (columns.isNotEmpty) { + isRemoved = false; + removedWidth = 0; + final double fillWidth = + (totalRemainingFillValue / columns.length).floorToDouble(); + final GridColumn column = columns.first; + if (column == _autoFillColumn && + (column.columnWidthMode == ColumnWidthMode.lastColumnFill || + dataGridConfiguration.columnWidthMode == + ColumnWidthMode.lastColumnFill)) { + columns.remove(column); + fillColumn = column; + continue; + } + + final double computedWidth = + _setColumnWidth(dataGridConfiguration, column, fillWidth); + if (fillWidth != computedWidth && fillWidth > 0) { + isRemoved = true; + columns.remove(column); + for (final GridColumn removedColumn in removedColumns) { + if (!columns.contains(removedColumn)) { + removedWidth += removedColumn._actualWidth; + columns.add(removedColumn); + } + } + removedColumns.clear(); + totalRemainingFillValue += removedWidth; + } + + column._actualWidth = computedWidth; + totalRemainingFillValue -= computedWidth; + if (!isRemoved) { + columns.remove(column); + if (!removedColumns.contains(column)) { + removedColumns.add(column); + } + } + } + + if (fillColumn != null) { + double columnWidth = 0.0; + if (fillColumn._autoWidth.isNaN) { + _setAutoWidth(fillColumn, columnWidth); + } else { + columnWidth = fillColumn._autoWidth; + } + + _setColumnWidth(dataGridConfiguration, fillColumn, + max(totalRemainingFillValue, columnWidth)); + } + } + + void _setRemainingColumnsWidth(DataGridConfiguration dataGridConfiguration, + List remainingColumns) { + for (final GridColumn column in remainingColumns) { + if (_isLastFillColum(column) || + !_isFillColumn(dataGridConfiguration, column)) { + _setColumnWidth(dataGridConfiguration, column, + dataGridConfiguration.container.columnWidths.defaultLineSize); + } + } + } + + bool _isFillColumn( + DataGridConfiguration dataGridConfiguration, GridColumn column) { + if (!column.width.isNaN) { + return false; + } else { + return column.columnWidthMode == ColumnWidthMode.none + ? dataGridConfiguration.columnWidthMode == ColumnWidthMode.fill + : column.columnWidthMode == ColumnWidthMode.fill; + } + } + + bool _isLastFillColum(GridColumn column) => column == _autoFillColumn; + + void _setAutoWidth(GridColumn? column, double width) { + if (column != null) { + column._autoWidth = width; + } + } + + void _resetAutoCalculation() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + for (final GridColumn column in dataGridConfiguration.columns) { + column._autoWidth = double.nan; + } + } + + double _getSortIconWidth(GridColumn column) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + double width = 0.0; + if (column.allowSorting && dataGridConfiguration.allowSorting) { + width += _sortIconWidth; + if (dataGridConfiguration.allowMultiColumnSorting && + dataGridConfiguration.showSortNumbers) { + width += _sortNumberWidth; + } + } + return width; + } + + double _setColumnWidth(DataGridConfiguration dataGridConfiguration, + GridColumn column, double columnWidth) { + final int columnIndex = dataGridConfiguration.columns.indexOf(column); + final double width = _getColumnWidth(column, columnWidth); + column._actualWidth = width; + dataGridConfiguration.container.columnWidths[columnIndex] = + column._actualWidth; + return width; + } + + double _getColumnWidth(GridColumn column, double columnWidth) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final int columnIndex = dataGridConfiguration.columns.indexOf(column); + if (column.width < column._actualWidth) { + return columnWidth; + } + + final double width = + dataGridConfiguration.container.columnWidths[columnIndex]; + return _checkWidthConstraints(column, columnWidth, width); + } + + double _checkWidthConstraints( + GridColumn column, double width, double columnWidth) { + if (!column.minimumWidth.isNaN || !column.maximumWidth.isNaN) { + if (!column.maximumWidth.isNaN) { + if (!width.isNaN && column.maximumWidth > width) { + columnWidth = width; + } else { + columnWidth = column.maximumWidth; + } + } + + if (!column.minimumWidth.isNaN) { + if (!width.isNaN && column.minimumWidth < width) { + if (width > column.maximumWidth) { + columnWidth = column.maximumWidth; + } else { + columnWidth = width; + } + } else { + columnWidth = column.minimumWidth; + } + } + } else { + if (!width.isNaN) { + columnWidth = width; + } + } + return columnWidth; + } + + /// Calculates the width of the header cell based on the [GridColumn.columnName]. + /// You can override this method to perform the custom calculation for height. + /// + /// If you want to calculate the width based on different [TextStyle], you can + /// override this method and call the super method with the required [TextStyle]. + /// Set the custom [ColumnSizer] to [SfDataGrid.columnSizer] property. + /// + /// ``` dart + ///class CustomColumnSizer extends ColumnSizer { + /// @override + /// double computeHeaderCellWidth(GridColumn column, + /// TextStyle textStyle) { + /// TextStyle style = textStyle; + /// if (column.columnName == 'Name') + /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); + /// return super.computeHeaderCellWidth(column, style); + /// } + ///} + ///``` + /// + /// The auto size is calculated based on default [TextStyle] of the datagrid. + @protected + double computeHeaderCellWidth(GridColumn column, TextStyle style) { + return _calculateTextSize( + column: column, + textStyle: style, + width: double.infinity, + value: column.columnName, + rowIndex: grid_helper.getHeaderIndex(_dataGridStateDetails!()), + ).width.roundToDouble(); + } + + /// Calculates the width of the cell based on the [DataGridCell.value]. You + /// can override this method to perform the custom calculation for width. + /// + /// If you want to calculate the width based on different [TextStyle], you can + /// override this method and return the super method with the required [TextStyle]. + /// Set the custom [ColumnSizer] to [SfDataGrid.columnSizer] property. + /// + /// The following example show how to pass the different TextStyle for width calculation, + /// + /// ``` dart + ///class CustomColumnSizer extends ColumnSizer { + /// @override + /// double computeCellWidth(GridColumn column, DataGridRow row, Object cellValue, + /// TextStyle textStyle) { + /// TextStyle style = textStyle; + /// if (column.columnName == 'Name') + /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); + /// return super.computeCellWidth(column, row, cellValue, style); + /// } + ///} + ///``` + /// + /// The auto size is calculated based on default [TextStyle] of the datagrid. + @protected + double computeCellWidth(GridColumn column, DataGridRow row, Object? cellValue, + TextStyle textStyle) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final int rowIndex = grid_helper.resolveToRowIndex(dataGridConfiguration, + effectiveRows(dataGridConfiguration.source).indexOf(row)); + + return _calculateTextSize( + column: column, + value: cellValue, + rowIndex: rowIndex, + textStyle: textStyle, + width: double.infinity) + .width + .roundToDouble(); + } + + /// Calculates the height of the header cell based on the [GridColumn.columnName]. + /// + /// ``` dart + ///class CustomColumnSizer extends ColumnSizer { + /// @override + /// double computeHeaderCellHeight(GridColumn column, + /// TextStyle textStyle) { + /// TextStyle style = textStyle; + /// if (column.columnName == 'Name') + /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); + /// return super.computeHeaderCellHeight(column, style); + /// } + ///} + ///``` + /// + /// The auto size is calculated based on default [TextStyle] of the datagrid. + @protected + double computeHeaderCellHeight(GridColumn column, TextStyle textStyle) { + return _measureCellHeight( + column, + grid_helper.getHeaderIndex(_dataGridStateDetails!()), + column.columnName, + textStyle); + } + + /// Calculates the height of the cell based on the [DataGridCell.value]. + /// You can override this method to perform the custom calculation for hight. + /// + /// If you want to calculate the width based on different [TextStyle], you can + /// override this method and call the super method with the required [TextStyle]. + /// Set the custom [ColumnSizer] to [SfDataGrid.columnSizer] property. + /// + /// ``` dart + ///class CustomColumnSizer extends ColumnSizer { + /// @override + /// double computeCellHeight(GridColumn column, DataGridRow row, Object cellValue, + /// TextStyle textStyle) { + /// TextStyle style = textStyle; + /// if (column.columnName == 'Name') + /// style = TextStyle(fontSize: 20, fontStyle: FontStyle.italic); + /// return super.computeCellWidth(column, row, cellValue, style); + /// } + ///} + ///``` + /// + /// The auto size is calculated based on default [TextStyle] of the datagrid. + @protected + double computeCellHeight(GridColumn column, DataGridRow row, + Object? cellValue, TextStyle textStyle) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final int rowIndex = grid_helper.resolveToRowIndex(dataGridConfiguration, + effectiveRows(dataGridConfiguration.source).indexOf(row)); + return _measureCellHeight(column, rowIndex, cellValue, textStyle); + } + + double _getAutoFitRowHeight(int rowIndex, + {bool canIncludeHiddenColumns = false, + List excludedColumns = const []}) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + double autoFitHeight = 0.0; + if (dataGridConfiguration.stackedHeaderRows.isNotEmpty && + rowIndex <= dataGridConfiguration.stackedHeaderRows.length - 1) { + return dataGridConfiguration.headerRowHeight; + } + + if (grid_helper.isTableSummaryIndex(dataGridConfiguration, rowIndex)) { + return dataGridConfiguration.rowHeight; + } + + for (int index = 0; index < dataGridConfiguration.columns.length; index++) { + final GridColumn column = dataGridConfiguration.columns[index]; + if ((!column.visible && !canIncludeHiddenColumns) || + excludedColumns.contains(column.columnName)) { + continue; + } + + autoFitHeight = max(_getRowHeight(column, rowIndex), autoFitHeight); + } + return autoFitHeight; + } + + double _getRowHeight(GridColumn column, int rowIndex) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (rowIndex == grid_helper.getHeaderIndex(dataGridConfiguration)) { + return computeHeaderCellHeight( + column, _getDefaultTextStyle(dataGridConfiguration, true)); + } else { + final DataGridRow row = + grid_helper.getDataGridRow(dataGridConfiguration, rowIndex); + return computeCellHeight(column, row, _getCellValue(row, column), + _getDefaultTextStyle(dataGridConfiguration, false)); + } + } + + double _measureCellWidth( + Object? cellValue, GridColumn column, DataGridRow dataGridRow) { + return computeCellWidth(column, dataGridRow, cellValue, + _getDefaultTextStyle(_dataGridStateDetails!(), false)); + } + + double _measureCellHeight( + GridColumn column, int rowIndex, Object? cellValue, TextStyle textStyle) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final int columnIndex = dataGridConfiguration.columns.indexOf(column); + double columnWidth = !column.visible || column.width == 0.0 + ? dataGridConfiguration.defaultColumnWidth + : dataGridConfiguration.container.columnWidths[columnIndex]; + + final double strokeWidth = _getGridLineStrokeWidth( + rowIndex: rowIndex, dataGridConfiguration: dataGridConfiguration) + .width; + + 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. + columnWidth -= _getSortIconWidth(column) + horizontalPadding + strokeWidth; + + return _calculateTextSize( + column: column, + value: cellValue, + width: columnWidth, + textStyle: textStyle, + rowIndex: rowIndex, + ).height.roundToDouble(); + } + + TextStyle _getDefaultTextStyle( + DataGridConfiguration dataGridConfiguration, bool isHeader) { + final bool isLight = + dataGridConfiguration.dataGridThemeData!.brightness == Brightness.light; + if (isHeader) { + return isLight + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w500, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + } else { + return isLight + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + } + } + + Size _getGridLineStrokeWidth( + {required int rowIndex, + required DataGridConfiguration dataGridConfiguration}) { + final double strokeWidth = + dataGridConfiguration.dataGridThemeData!.gridLineStrokeWidth; + + final GridLinesVisibility gridLinesVisibility = + rowIndex <= grid_helper.getHeaderIndex(dataGridConfiguration) + ? dataGridConfiguration.headerGridLinesVisibility + : dataGridConfiguration.gridLinesVisibility; + + switch (gridLinesVisibility) { + case GridLinesVisibility.none: + return Size.zero; + case GridLinesVisibility.both: + return Size(strokeWidth, strokeWidth); + case GridLinesVisibility.vertical: + return Size(strokeWidth, 0); + case GridLinesVisibility.horizontal: + return Size(0, strokeWidth); + } + } + + Size _calculateTextSize({ + required Object? value, + required int rowIndex, + required double width, + required GridColumn column, + required TextStyle textStyle, + }) { + late Size textSize; + + // TextPainter's maxWidth should not be less than or equal to its minWidth. + // So, We have restricted the behavior by providing a default width to 10.0 + // when it reached the limit While resizing the column. + width = max(width, 10.0); + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + + final Size strokeWidthSize = _getGridLineStrokeWidth( + rowIndex: rowIndex, dataGridConfiguration: dataGridConfiguration); + + final TextPainter textPainter = TextPainter( + text: TextSpan(text: value?.toString() ?? '', style: textStyle), + textScaleFactor: dataGridConfiguration.textScaleFactor, + textDirection: dataGridConfiguration.textDirection) + ..layout(maxWidth: width); + + textSize = Size( + textPainter.size.width + + strokeWidthSize.width + + column.autoFitPadding.horizontal, + textPainter.size.height + + strokeWidthSize.height + + column.autoFitPadding.vertical); + + return textSize; + } +} + +/// ToDo +void initialRefresh(ColumnSizer columnSizer, double availableWidth) { + columnSizer._initialRefresh(availableWidth); +} + +/// ToDo +void refreshColumnSizer(ColumnSizer columnSizer, double availableWidth) { + columnSizer._refresh(availableWidth); +} + +/// ToDo +void resetAutoCalculation(ColumnSizer columnSizer) { + columnSizer._resetAutoCalculation(); +} + +/// ToDo +void updateColumnSizerLoadedInitiallyFlag( + ColumnSizer columnSizer, bool isLoaded) { + columnSizer._isColumnSizerLoadedInitially = isLoaded; +} + +/// ToDo +double getSortIconWidth(ColumnSizer columnSizer, GridColumn column) { + return columnSizer._getSortIconWidth(column); +} + +/// ToDo +double getAutoFitRowHeight(ColumnSizer columnSizer, int rowIndex, + {bool canIncludeHiddenColumns = false, + List excludedColumns = const []}) { + return columnSizer._getAutoFitRowHeight(rowIndex, + canIncludeHiddenColumns: canIncludeHiddenColumns, + excludedColumns: excludedColumns); +} + +/// ToDo +void setStateDetailsInColumnSizer( + ColumnSizer columnSizer, DataGridStateDetails dataGridCellDetails) { + columnSizer._dataGridStateDetails = dataGridCellDetails; +} + +/// Checks whether the column sizer is loaded initially or not. +bool isColumnSizerLoadedInitially(ColumnSizer columnSizer) { + return columnSizer._isColumnSizerLoadedInitially; +} + +/// Process column resizing operation in [SfDataGrid]. +class ColumnResizeController { + /// ToDo + ColumnResizeController({required this.dataGridStateDetails}); + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + /// Holds the buffer value for enable column resizing based on the + /// target platform. + double? _hitTestPrecision; + + /// Checks whether any column is currently resizing or not. + bool isResizing = false; + + /// Decides whether the cursor will have to change + /// [SystemMouseCursors.resizeColumn] or not. + bool canSwitchResizeColumnCursor = false; + + /// Determines whether the resizing indicator is enable or not. + bool isResizeIndicatorVisible = false; + + /// To set the resizing column's right border position to the x position of + /// the resizing indicator. + double indicatorPosition = 0.0; + + /// To maintain the resizing cell from tapped position. + DataCellBase? resizingDataCell; + + /// To hold the current resizing line information. + VisibleLineInfo? _resizingLine; + + /// Maintain the temporary value to update the width of the column. + double _resizingColumnWidth = 0.0; + + /// Holds the current resizing grid column. + GridColumn? _currentResizingColumn; + + // * Properties for mobile platform + + /// Checks whether the column resizing is enable by the long press or not in + /// the mobile platform. + bool _canStartResizeInMobile = false; + + /// Checks whether the long press event is completed or not in the + /// current state. + /// Helps to activate the `_canStartResizingInMobile` property. + bool _isLongPressEnabled = false; + + /// Holds the row index of the current resizing header cell. + int rowIndex = 0; + + /// Holds the spanned value of the current resizing header cell. + int rowSpan = 0; + + bool _isHeaderRow(DataRowBase dataRow) => + dataRow.rowType == RowType.headerRow || + dataRow.rowType == RowType.stackedHeaderRow; + + VisibleLineInfo? _getHitTestResult(double dx, + {bool isPressed = false, bool canAllowBuffer = true}) { + if (!isResizing) { + final DataGridConfiguration dataGridConfiguration = + dataGridStateDetails(); + + // Resolve the local dx position. + dx = getXPosition(dataGridConfiguration, dx); + + _resizingLine = _getResizingLine(dx, canAllowBuffer); + + if (_resizingLine != null && + _resizingLine!.lineIndex >= + grid_helper.resolveToStartColumnIndex(dataGridConfiguration)) { + // To ensure the resizing line for the stacked header row. + if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { + if (!_canAllowResizing(dx, resizingDataCell, + canAllowBuffer: canAllowBuffer)) { + return null; + } + } + + if (!isPressed) { + return _resizingLine; + } + + _ensureDataCell(dx, resizingDataCell); + _onResizingStart(); + return _resizingLine; + } else { + return null; + } + } + } + + void _onResizingStart() { + if (_resizingLine != null && !isResizeIndicatorVisible) { + final DataGridConfiguration dataGridConfiguration = + dataGridStateDetails(); + _currentResizingColumn = + dataGridConfiguration.columns[_resizingLine!.lineIndex]; + if (_raiseColumnResizeStart()) { + isResizeIndicatorVisible = true; + indicatorPosition = _getIndicatorPosition(_resizingLine!, + dataGridConfiguration.textDirection == TextDirection.ltr, + isDefault: true); + + _resizingColumnWidth = _resizingLine!.size; + + // Resets the swiping offset. + dataGridConfiguration.container.resetSwipeOffset(); + } + } + } + + void _onResizing(double currentColumnWidth, double indicatorXPosition) { + if (_resizingLine == null || !isResizeIndicatorVisible) { + return; + } + + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + + isResizing = true; + + // Need to update the resizing line to get the line's updated details after + // resizing it. + _resizingLine = dataGridConfiguration.container.scrollColumns + .getVisibleLineAtLineIndex(_resizingLine!.lineIndex, + isRightToLeft: + dataGridConfiguration.textDirection == TextDirection.rtl); + + // Sets indicator position. + indicatorPosition = indicatorXPosition; + + if (dataGridConfiguration.columnResizeMode == ColumnResizeMode.onResize) { + dataGridConfiguration.container.isDirty = true; + if (!_raiseColumnResizeUpdate(currentColumnWidth)) { + return; + } + } else { + // Rebuild to update the resizing indicator alone. + _rebuild(); + } + } + + // * Helper methods + + bool _canAllowResizing(double downX, DataCellBase? dataColumn, + {bool canAllowBuffer = true}) { + if (dataColumn != null && + _resizingLine != null && + dataColumn.dataRow!.rowType == RowType.stackedHeaderRow && + dataColumn.columnSpan > 0) { + final DataGridConfiguration dataGridConfiguration = + dataGridStateDetails(); + late int cellLeft, cellRight, lineIndex; + + final DataCellBase? dataCell = dataColumn.dataRow!.visibleColumns + .firstWhereOrNull((DataCellBase dataCell) { + cellLeft = dataCell.columnIndex; + cellRight = dataCell.columnIndex + dataCell.columnSpan; + lineIndex = _resizingLine!.lineIndex; + + return cellLeft == lineIndex || + cellRight == lineIndex || + (lineIndex > cellLeft && lineIndex < cellRight); + }); + + if (dataCell != null) { + final bool isLTR = + dataGridConfiguration.textDirection == TextDirection.ltr; + cellLeft = dataCell.columnIndex; + cellRight = cellLeft + dataCell.columnSpan; + lineIndex = _resizingLine!.lineIndex; + + final double origin = + isLTR ? _resizingLine!.clippedOrigin : _resizingLine!.corner; + final double corner = + isLTR ? _resizingLine!.corner : _resizingLine!.clippedOrigin; + + if (canAllowBuffer) { + if ((cellLeft == lineIndex && + (corner - downX).abs() <= _hitTestPrecision!) || + (cellRight == lineIndex && + (origin - downX).abs() <= _hitTestPrecision!) || + (lineIndex > cellLeft && lineIndex < cellRight)) { + return false; + } + } else { + _resizingLine = dataGridConfiguration.container.scrollColumns + .getVisibleLineAtLineIndex(cellRight, isRightToLeft: !isLTR); + return _resizingLine != null; + } + } + } + return true; + } + + void _ensureDataCell(double x, DataCellBase? dataCell) { + if (isResizing || isResizeIndicatorVisible || dataCell == null) { + return; + } + + DataCellBase? nearDataCell; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + final bool isLTR = dataGridConfiguration.textDirection == TextDirection.ltr; + + final VisibleLineInfo? visibleLine = dataGridConfiguration + .container.scrollColumns + .getVisibleLineAtLineIndex(dataCell.columnIndex, isRightToLeft: !isLTR); + if (visibleLine != null) { + final bool canCheckNearCell = !(visibleLine.isLastLine && + visibleLine.isClippedCorner && + visibleLine.isClippedOrigin); + final double origin = + isLTR ? visibleLine.clippedOrigin : visibleLine.corner; + final double corner = + isLTR ? visibleLine.corner : visibleLine.clippedOrigin; + + if (canCheckNearCell) { + if (_hitTestPrecision! > (corner - x).abs()) { + nearDataCell = _getDataCell(dataGridConfiguration, dataCell.rowIndex, + dataCell.columnIndex + 1); + } else if (_hitTestPrecision! > (origin - x).abs()) { + nearDataCell = _getDataCell(dataGridConfiguration, dataCell.rowIndex, + dataCell.columnIndex - 1); + } + } + + if (nearDataCell != null && nearDataCell.rowSpan > dataCell.rowSpan) { + resizingDataCell = nearDataCell; + rowIndex = nearDataCell.rowIndex; + rowSpan = nearDataCell.rowSpan; + } + } + } + + DataCellBase? _getDataCell(DataGridConfiguration dataGridConfiguration, + int rowIndex, int columnIndex) { + final DataRowBase? dataRow = dataGridConfiguration + .container.rowGenerator.items + .firstWhereOrNull((DataRowBase element) => + rowIndex >= 0 && element.rowIndex == rowIndex); + if (dataRow == null || dataRow.visibleColumns.isEmpty) { + return null; + } + + return dataRow.visibleColumns.firstWhereOrNull((DataCellBase dataCell) => + columnIndex >= 0 && dataCell.columnIndex == columnIndex); + } + + double _getIndicatorPosition(VisibleLineInfo line, bool isLTR, + {bool isDefault = false, + ScrollController? scrollController, + double? currentColumnWidth}) { + late double indicatorLeft; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (isDefault) { + indicatorLeft = isLTR ? line.corner : line.clippedOrigin; + } else { + // Maintains the indicator on the same position when scrolling happens from the + // `extentBefore` side. + if (dataGridConfiguration.columnResizeMode == ColumnResizeMode.onResize && + ((line.isFooter && scrollController!.position.maxScrollExtent > 0) || + (scrollController!.position.extentBefore > 0 && + scrollController.position.extentAfter == 0))) { + indicatorLeft = isLTR ? line.corner : line.clippedOrigin; + } else { + indicatorLeft = isLTR + ? (line.origin + currentColumnWidth!) + : (line.corner + line.scrollOffset - currentColumnWidth!); + } + } + + // To remove the half of stroke width to show the indicator to the + // center of grid line. + indicatorLeft -= dataGridConfiguration + .dataGridThemeData!.columnResizeIndicatorStrokeWidth / + 2; + + return indicatorLeft; + } + + VisibleLineInfo? _getResizingLine(double dx, bool canAllowBuffer) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + final bool isLTR = dataGridConfiguration.textDirection == TextDirection.ltr; + bool canAllowResizing(VisibleLineInfo? line) { + if (line != null) { + final double origin = isLTR ? line.corner : line.clippedOrigin; + return _hitTestPrecision! > (dx - origin).abs(); + } + return false; + } + + VisibleLineInfo? resizeLine = _getVisibleLineAtPoint(dx, !isLTR); + + if (canAllowBuffer) { + if (canAllowResizing(resizeLine)) { + return resizeLine; + } else { + // Gets the near line based on hitTestPrecision. + resizeLine = _getVisibleLineAtPoint(dx, !isLTR, checkNearLine: true); + return canAllowResizing(resizeLine) ? resizeLine : null; + } + } else { + return resizeLine; + } + } + + VisibleLineInfo? _getVisibleLineAtPoint(double position, bool isRTL, + {bool checkNearLine = false}) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (checkNearLine) { + return dataGridConfiguration.container.scrollColumns.getLineNearCorner( + position, _hitTestPrecision!, CornerSide.both, + isRightToLeft: isRTL); + } else { + return dataGridConfiguration.container.scrollColumns + .getVisibleLineAtPoint(position, true, isRTL); + } + } + + /// Resolves the point of the current local position to get the visibleLine. + double getXPosition( + DataGridConfiguration dataGridConfiguration, double localPosition) { + final ScrollController scrollController = + dataGridConfiguration.horizontalScrollController!; + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + return localPosition - scrollController.offset; + } else { + return localPosition - + (scrollController.position.maxScrollExtent - scrollController.offset); + } + } + + /// Sets the current tapped data cell to the column resizing cell. + void setDataCell(DataCellBase dataCell) { + if (_isHeaderRow(dataCell.dataRow!)) { + if (dataGridStateDetails().allowColumnsResizing && + !isResizing && + !isResizeIndicatorVisible) { + resizingDataCell = dataCell; + rowIndex = dataCell.rowIndex; + rowSpan = dataCell.rowSpan; + } + } + } + + /// Sets the `hitTestPrecision` to the column resizing based on the current + /// platform. + void setHitTestPrecision() { + _hitTestPrecision ??= dataGridStateDetails().isDesktop ? 10.0 : kTouchSlop; + } + + // * Column Resizing callbacks + + bool _raiseColumnResizeStart() { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.onColumnResizeStart != null) { + return dataGridConfiguration.onColumnResizeStart!( + ColumnResizeStartDetails( + column: _currentResizingColumn!, width: _resizingColumnWidth)); + } + return true; + } + + bool _raiseColumnResizeUpdate(double currentColumnWidth) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.onColumnResizeUpdate != null) { + return dataGridConfiguration.onColumnResizeUpdate!( + ColumnResizeUpdateDetails( + column: _currentResizingColumn!, width: currentColumnWidth)); + } + return true; + } + + void _raiseColumnResizeEnd(double currentColumnWidth) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.onColumnResizeEnd != null) { + dataGridConfiguration.onColumnResizeEnd!(ColumnResizeEndDetails( + column: _currentResizingColumn!, width: currentColumnWidth)); + } + } + + // * Pointer Events + + /// Handles the pointer down event for the column resizing. + void onPointerDown(PointerDownEvent event, DataRowBase dataRow) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.isDesktop || _canStartResizeInMobile) { + if (_isHeaderRow(dataRow)) { + // Clears the editing before start resizing a column. + if (dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + } + + final VisibleLineInfo? resizingLine = + _getHitTestResult(event.localPosition.dx, isPressed: true); + + _canStartResizeInMobile = false; + + if (resizingLine != null && isResizeIndicatorVisible) { + if (dataGridConfiguration.isDesktop) { + // Rebuild to activate the indicator to the view after pressed the + // resizing column in desktop and web. + _rebuild(); + } + } else { + // To disable resizing indicator when taping the other area of the + // header and stacked header rows after enabled the indicator by long press. + if (isResizeIndicatorVisible) { + _resetColumnResize(canResetDataCell: false); + _rebuild(); + } + } + } else { + // To disable resizing indicator when taping the data row after enabled + // the indicator by long press. + if (isResizeIndicatorVisible) { + _resetColumnResize(); + _rebuild(); + } + } + } + } + + /// Handles the pointer move event for the column resizing. + void onPointerMove(PointerMoveEvent event, DataRowBase dataRow) { + if (_isHeaderRow(dataRow)) { + final DataGridConfiguration dataGridConfiguration = + dataGridStateDetails(); + // To restricts the update of column width without calling the pointer up + // event after activating the indicator by long press start. + if (!dataGridConfiguration.isDesktop && _isLongPressEnabled) { + return; + } + + if (_resizingLine != null && isResizeIndicatorVisible) { + final ScrollController scrollController = + dataGridConfiguration.horizontalScrollController!; + final bool isLTR = + dataGridConfiguration.textDirection == TextDirection.ltr; + final double currentColumnWidth = _resizingColumnWidth; + + // Updates the column width based on drag delta. + _resizingColumnWidth += + isLTR ? event.localDelta.dx : -event.localDelta.dx; + + // To restricts the width of current resizing column from its minimum and + // maximum width. + _resizingColumnWidth = dataGridConfiguration.columnSizer + ._checkWidthConstraints(_currentResizingColumn!, + _resizingColumnWidth, currentColumnWidth); + + // To avoid resizing of column width after reached zero; + _resizingColumnWidth = max(0.0, _resizingColumnWidth); + + final double indicatorPosition = _getIndicatorPosition( + _resizingLine!, isLTR, + scrollController: scrollController, + currentColumnWidth: _resizingColumnWidth); + + _onResizing(_resizingColumnWidth, indicatorPosition); + } + } + } + + /// Handles the pointer up event for the column resizing. + void onPointerUp(PointerUpEvent event, DataRowBase dataRow) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (!dataGridConfiguration.isDesktop && _isLongPressEnabled) { + _canStartResizeInMobile = true; + _isLongPressEnabled = false; + } + + if (!_canStartResizeInMobile && isResizeIndicatorVisible) { + if (dataGridConfiguration.columnResizeMode == + ColumnResizeMode.onResizeEnd) { + _raiseColumnResizeUpdate(_resizingColumnWidth); + } + + _raiseColumnResizeEnd(_resizingColumnWidth); + _resetColumnResize(); + _rebuild(); + } + } + + /// Handles the pointer enter event for the column resizing. + void onPointerEnter(PointerEnterEvent event, DataRowBase dataRow) { + _ensureCursorVisibility(event.localPosition, dataRow); + } + + /// Handles the pointer hover event for the column resizing. + void onPointerHover(PointerHoverEvent event, DataRowBase dataRow) { + _ensureCursorVisibility(event.localPosition, dataRow); + } + + /// Handles the pointer exit event for the column resizing. + void onPointerExit(PointerExitEvent event, DataRowBase dataRow) { + _ensureCursorVisibility(event.localPosition, dataRow); + } + + /// To activate the column resizing in mobile platform by long press. + void onLongPressStart(LongPressStartDetails details, DataRowBase dataRow) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.allowColumnsResizing && + !dataGridConfiguration.isDesktop) { + if (_isHeaderRow(dataRow)) { + final VisibleLineInfo? resizingLine = _getHitTestResult( + details.localPosition.dx, + isPressed: true, + canAllowBuffer: false); + + if (resizingLine != null && isResizeIndicatorVisible) { + _isLongPressEnabled = true; + // Rebuild to enable the resizing indicator. + _rebuild(); + } + } + } + } + + void _ensureCursorVisibility(Offset localPosition, DataRowBase dataRow) { + // To skip updating the cursor visibility when a column is resizing. + if (isResizing) { + return; + } + + if (!_isHeaderRow(dataRow)) { + canSwitchResizeColumnCursor = false; + return; + } + + canSwitchResizeColumnCursor = _getHitTestResult(localPosition.dx) != null; + } + + void _resetColumnResize({bool canResetDataCell = true}) { + if (canResetDataCell) { + resizingDataCell = null; + } + isResizing = false; + _resizingLine = null; + _currentResizingColumn = null; + isResizeIndicatorVisible = false; + } + + void _rebuild() { + dataGridStateDetails().container.isDirty = true; + notifyDataGridPropertyChangeListeners(dataGridStateDetails().source, + propertyName: 'columnResizing'); + } +} 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 new file mode 100644 index 000000000..819a62986 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/runtime/generator.dart @@ -0,0 +1,1396 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart' hide DataCell, DataRow; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../grid_common/enums.dart'; +import '../../grid_common/row_column_index.dart'; +import '../../grid_common/utility_helper.dart'; +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/enums.dart'; +import '../selection/selection_manager.dart' as selection_manager; +import '../selection/selection_manager.dart'; +import '../sfdatagrid.dart'; +import '../widgets/scrollview_widget.dart'; +import 'cell_renderers.dart'; +import 'column.dart'; + +/// A base class which provides functionalities for [DataCell]. +abstract class DataCellBase { + /// Key is an identifier of [DataCell]. + Key? key; + + /// Decide whether the [DataCell] to visible. + bool isVisible = true; + + /// Decide whether the cell is ensured. when its re-using the [DataCell]. + bool isEnsured = false; + + /// Decides whether [DataCell] is dirty, to refresh it. + bool isDirty = false; + + /// Decides whether the [DataCell] has the currentcell. + bool isCurrentCell = false; + + /// Decide whether the [DataCell] is in edit mode. + bool isEditing = false; + + /// The column index of the [DataCell]. + int columnIndex = -1; + + /// The row index of the [DataCell]. + int rowIndex = -1; + + /// Count of spanned [DataCell]. + int columnSpan = 0; + + /// Count of spanned [DataRow]. + int rowSpan = 0; + + /// Holds the text style of an widget. + TextStyle? textStyle; + + /// Holds the cell type. + /// Eg: GridCell, HeaderCell etc. + CellType? cellType; + + /// Hold the widget going to place in each [DataCell]. + Widget? columnElement; + + /// Holds the editable widget. When [DataCell] enter into edit mode. + Widget? editingWidget; + + /// Holds the renderer based on cell type. + /// Eg: GridHeaderCellRenderer, GridCellTextFieldRenderer etc. + GridCellRendererBase? renderer; + + /// Holds the corresponding [DataRow], the [DataCell] is present in it. + DataRowBase? dataRow; + + /// [GridColumn] which is associated with [DataCell]. + GridColumn? gridColumn; + + /// Hold the spanned [DataCell] details. + StackedHeaderCell? stackedHeaderCell; + + /// Holds the summary column details. + GridSummaryColumn? summaryColumn; + + /// Initialize the columnElement + Widget? _onInitializeColumnElement(bool isInEdit) => null; + + /// Update the columnElement. + void updateColumn() {} + + /// Perform touch interaction on [DataCell] + /// Eg: selection interaction + void onTouchUp() { + if (dataRow != null) { + final DataGridConfiguration dataGridConfiguration = + dataRow!.dataGridStateDetails!(); + if (rowIndex <= grid_helper.getHeaderIndex(dataGridConfiguration) || + grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration) || + grid_helper.isTableSummaryIndex(dataGridConfiguration, rowIndex) || + dataGridConfiguration.selectionMode == SelectionMode.none) { + return; + } + + final RowColumnIndex currentRowColumnIndex = + RowColumnIndex(rowIndex, columnIndex); + dataGridConfiguration.rowSelectionManager + .handleTap(currentRowColumnIndex); + } + } +} + +/// Provides functionality to display the cell. +class DataCell extends DataCellBase { + @override + Widget? _onInitializeColumnElement(bool isInEdit) { + if (cellType != CellType.indentCell) { + if (renderer != null) { + renderer!.setCellStyle(this); + return renderer!.onPrepareWidgets(this); + } else { + return null; + } + } else { + return null; + } + } + + @override + void updateColumn() { + if (renderer == null) { + return; + } + renderer! + ..setCellStyle(this) + ..onPrepareWidgets(this); + } +} + +/// A base class which provides functionalities for [DataRow]. +abstract class DataRowBase { + /// Key is an identifier of [DataRow]. + Key? key; + + /// Hold's the footer view widget. + Widget? footerView; + + /// Decides whether [DataRow] is dirty, to refresh it. + bool isDirty = false; + + /// Decide whether the [DataRow] is ensured. when its re-using. + bool isEnsured = false; + + /// Decide whether the [DataRow] to visible. + bool isVisible = true; + + /// Holds the collection of visible [DataCell]. + List visibleColumns = []; + + /// This flag is used to indicating whether the row is swiped or not. + bool isSwipingRow = false; + + /// Decide whether the any of the [DataCell] present in [DataRow] is in + /// edit mode. + bool isEditing = false; + + /// The row index of the [DataRow]. + int rowIndex = -1; + + /// The type of the [DataRow]. + RowType rowType = RowType.headerRow; + + /// The region of the [DataRow] to identify whether the row is scrollable + RowRegion rowRegion = RowRegion.header; + + /// Decides whether the [DataRow] is currently active + bool isCurrentRow = false; + + /// Decides whether the [DataRow] is hovered. + bool isHoveredRow = false; + + /// Holds the details of row configuration. + DataGridRow? dataGridRow; + + /// Holds the configuration of collection [DataGridCell]. + DataGridRowAdapter? dataGridRowAdapter; + + /// ToDo + DataGridStateDetails? dataGridStateDetails; + + /// Holds the `GridTableSummaryRow` for the current summary row. + GridTableSummaryRow? tableSummaryRow; + + /// Refresh the column elements in [DataRow]. When index changed. + void rowIndexChanged() { + if (rowIndex < 0) { + return; + } + + for (final DataCellBase col in visibleColumns) { + col + ..rowIndex = rowIndex + ..dataRow = this + ..updateColumn(); + } + } + + void _onGenerateVisibleColumns(VisibleLinesCollection visibleColumnLines) {} + + void _initializeDataRow(VisibleLinesCollection visibleColumnLines) { + _onGenerateVisibleColumns(visibleColumnLines); + } + + void _ensureColumns(VisibleLinesCollection visibleColumnLines) {} + + /// ToDo + VisibleLineInfo? getColumnVisibleLineInfo(int index) => + dataGridStateDetails!() + .container + .scrollColumns + .getVisibleLineAtLineIndex(index); + + /// ToDo + VisibleLineInfo? getRowVisibleLineInfo(int index) => dataGridStateDetails!() + .container + .scrollRows + .getVisibleLineAtLineIndex(index); + + /// ToDo + double getColumnWidth(int startIndex, int endIndex, {bool lineNull = false}) { + if (startIndex != endIndex || lineNull) { + final List currentPos = dataGridStateDetails!() + .container + .scrollColumns + .rangeToRegionPoints(startIndex, endIndex, true); + return currentPos[1].length; + } + + final VisibleLineInfo? line = getColumnVisibleLineInfo(startIndex); + if (line == null) { + return 0; + } + + return line.size; + } + + /// ToDo + double getRowHeight(int startIndex, int endIndex) { + if (startIndex != endIndex) { + final List currentPos = dataGridStateDetails!() + .container + .scrollRows + .rangeToRegionPoints(startIndex, endIndex, true); + return currentPos[1].length; + } + + final VisibleLineInfo? line = getRowVisibleLineInfo(startIndex); + if (line == null) { + return 0; + } + + return line.size; + } + + /// Decides whether the [DataRow] is selected. + bool get isSelectedRow => _isSelectedRow; + bool _isSelectedRow = false; + + /// Decides whether the [DataRow] is selected. + set isSelectedRow(bool newValue) { + if (_isSelectedRow != newValue) { + _isSelectedRow = newValue; + for (final DataCellBase dataCell in visibleColumns) { + dataCell + ..isDirty = true + ..updateColumn(); + } + } + } +} + +/// Provides functionality to process the row. +class DataRow extends DataRowBase { + /// Creates [DataCell] for [SfDataGrid] widget. + DataRow(); + + @override + void _onGenerateVisibleColumns(VisibleLinesCollection visibleColumnLines) { + visibleColumns.clear(); + + int startColumnIndex = 0; + int endColumnIndex = -1; + + for (int i = 0; i < 3; i++) { + if (i == 0) { + if (visibleColumnLines.firstBodyVisibleIndex <= 0) { + continue; + } + + startColumnIndex = 0; + endColumnIndex = + visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex - 1] + .lineIndex; + } else if (i == 1) { + if (visibleColumnLines.firstBodyVisibleIndex <= 0 && + visibleColumnLines.lastBodyVisibleIndex < 0) { + continue; + } + + if (visibleColumnLines.length > + visibleColumnLines.firstBodyVisibleIndex) { + startColumnIndex = + visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex] + .lineIndex; + } else { + continue; + } + + endColumnIndex = + visibleColumnLines[visibleColumnLines.lastBodyVisibleIndex] + .lineIndex; + } else { + if (visibleColumnLines.firstFooterVisibleIndex >= + visibleColumnLines.length) { + continue; + } + + startColumnIndex = + visibleColumnLines[visibleColumnLines.firstFooterVisibleIndex] + .lineIndex; + endColumnIndex = + visibleColumnLines[visibleColumnLines.length - 1].lineIndex; + } + + for (int index = startColumnIndex; index <= endColumnIndex; index++) { + DataCellBase? dc = _createColumn(index); + visibleColumns.add(dc); + dc = null; + } + } + } + + @override + void _ensureColumns(VisibleLinesCollection visibleColumnLines) { + // Need to ignore footer view Row. because footer view row doesn't have + // visible columns. + if (rowType == RowType.footerRow) { + return; + } + + for (int i = 0; i < visibleColumns.length; i++) { + visibleColumns[i].isEnsured = false; + } + + int startColumnIndex = 0; + int endColumnIndex = -1; + + for (int i = 0; i < 3; i++) { + if (i == 0) { + if (visibleColumnLines.firstBodyVisibleIndex <= 0) { + continue; + } + + startColumnIndex = 0; + endColumnIndex = + visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex - 1] + .lineIndex; + } else if (i == 1) { + if (visibleColumnLines.firstBodyVisibleIndex <= 0 && + visibleColumnLines.lastBodyVisibleIndex < 0) { + continue; + } + + if (visibleColumnLines.length > + visibleColumnLines.firstBodyVisibleIndex) { + startColumnIndex = + visibleColumnLines[visibleColumnLines.firstBodyVisibleIndex] + .lineIndex; + } else { + continue; + } + + endColumnIndex = + visibleColumnLines[visibleColumnLines.lastBodyVisibleIndex] + .lineIndex; + } else { + if (visibleColumnLines.firstFooterVisibleIndex >= + visibleColumnLines.length) { + continue; + } + + startColumnIndex = + visibleColumnLines[visibleColumnLines.firstFooterVisibleIndex] + .lineIndex; + endColumnIndex = + visibleColumnLines[visibleColumnLines.length - 1].lineIndex; + } + + for (int index = startColumnIndex; index <= endColumnIndex; index++) { + DataCellBase? dc = _indexer(index); + if (dc == null) { + DataCellBase? dataCell = _reUseCell(startColumnIndex, endColumnIndex); + + dataCell ??= visibleColumns.firstWhereOrNull((DataCellBase col) => + col.columnIndex == -1 && col.cellType != CellType.indentCell); + + _updateColumn(dataCell, index); + dataCell = null; + } + + dc ??= visibleColumns + .firstWhereOrNull((DataCellBase col) => col.columnIndex == index); + + if (dc != null) { + if (!dc.isVisible) { + dc.isVisible = true; + } + } else { + dc = _createColumn(index); + visibleColumns.add(dc); + } + + dc.isEnsured = true; + dc = null; + } + } + + for (final DataCellBase col in visibleColumns) { + if (!col.isEnsured || col.columnIndex == -1) { + col.isVisible = false; + } + } + } + + DataCellBase _createColumn(int index) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + final bool canIncrementHeight = rowType == RowType.headerRow && + dataGridConfiguration.stackedHeaderRows.isNotEmpty; + final DataCellBase dc = DataCell() + ..dataRow = this + ..columnIndex = index + ..rowIndex = rowIndex; + dc.key = ObjectKey(dc); + final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, index); + dc.gridColumn = dataGridConfiguration.columns[columnIndex]; + _checkForCurrentCell(dataGridConfiguration, dc); + if (rowIndex == grid_helper.getHeaderIndex(dataGridConfiguration) && + rowIndex >= 0) { + dc + ..renderer = dataGridConfiguration.cellRenderers['ColumnHeader'] + ..cellType = CellType.headerCell; + } else { + if (dataGridConfiguration.showCheckboxColumn && + dc.gridColumn != null && + dc.columnIndex == 0) { + dc.renderer = dataGridConfiguration.cellRenderers['Checkbox']; + dc.cellType = CellType.checkboxCell; + } else { + dc + ..renderer = dataGridConfiguration.cellRenderers['TextField'] + ..cellType = CellType.gridCell; + } + } + if (canIncrementHeight) { + final int rowSpan = grid_helper.getRowSpan( + dataGridConfiguration, dc.dataRow!.rowIndex - 1, index, false, + mappingName: dc.gridColumn!.columnName); + dc.rowSpan = rowSpan; + } + + dc.columnElement = dc._onInitializeColumnElement(false); + return dc; + } + + DataCellBase? _indexer(int index) { + for (final DataCellBase column in visibleColumns) { + if (rowType == RowType.tableSummaryRow || + rowType == RowType.tableSummaryCoveredRow) { + if (index >= column.columnIndex && + index <= column.columnIndex + column.columnSpan) { + return column; + } + } + if (column.columnIndex == index) { + return column; + } + } + + return null; + } + + DataCellBase? _reUseCell(int startColumnIndex, int endColumnIndex) => + visibleColumns.firstWhereOrNull((DataCellBase cell) => + cell.gridColumn != null && + (cell.columnIndex < 0 || + cell.columnIndex < startColumnIndex || + cell.columnIndex > endColumnIndex) && + !cell.isEnsured && + !cell.isEditing); + + void _updateColumn(DataCellBase? dc, int index) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + final bool canIncrementHeight = rowType == RowType.headerRow && + dataGridConfiguration.stackedHeaderRows.isNotEmpty; + if (dc != null) { + if (index < 0 || index >= dataGridConfiguration.container.columnCount) { + dc.isVisible = false; + } else { + dc + ..columnIndex = index + ..rowIndex = rowIndex + ..key = dc.key + ..isVisible = true; + final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, index); + dc.gridColumn = dataGridConfiguration.columns[columnIndex]; + _checkForCurrentCell(dataGridConfiguration, dc); + _updateRenderer(dataGridConfiguration, dc, dc.gridColumn); + if (canIncrementHeight) { + final int rowSpan = grid_helper.getRowSpan( + dataGridConfiguration, dc.dataRow!.rowIndex - 1, index, false, + mappingName: dc.gridColumn!.columnName); + dc.rowSpan = rowSpan; + } else { + dc.rowSpan = 0; + } + dc + ..columnElement = dc._onInitializeColumnElement(false) + ..isEnsured = true; + } + + if (dc.isVisible != true) { + dc.isVisible = true; + } + } else { + dc = _createColumn(index); + visibleColumns.add(dc); + } + } + + void _updateRenderer(DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, GridColumn? column) { + GridCellRendererBase? newRenderer; + if (rowRegion == RowRegion.header && rowType == RowType.headerRow) { + newRenderer = dataGridConfiguration.cellRenderers['ColumnHeader']; + dataCell.cellType = CellType.headerCell; + } else { + if (dataGridConfiguration.showCheckboxColumn && + column != null && + dataCell.columnIndex == 0) { + newRenderer = dataGridConfiguration.cellRenderers['Checkbox']; + dataCell.cellType = CellType.checkboxCell; + } else { + newRenderer = dataGridConfiguration.cellRenderers['TextField']; + dataCell.cellType = CellType.gridCell; + } + } + + dataCell.renderer = newRenderer; + newRenderer = null; + } + + void _checkForCurrentCell( + DataGridConfiguration dataGridConfiguration, DataCellBase dc) { + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + final CurrentCellManager currentCellManager = + dataGridConfiguration.currentCell; + if (currentCellManager.columnIndex != -1 && + currentCellManager.rowIndex != -1 && + currentCellManager.rowIndex == rowIndex && + currentCellManager.columnIndex == dc.columnIndex) { + dc.isCurrentCell = true; + } else { + dc.isCurrentCell = false; + } + } + } +} + +/// Helps to generate the [DataRow] for data grid. +class RowGenerator { + /// ToDo + RowGenerator({required this.dataGridStateDetails}); + + /// Collection of visible [DataRow]'s. + final List items = []; + + DataGridConfiguration get _dataGridConfiguration => dataGridStateDetails(); + + /// ToDo + DataGridStateDetails dataGridStateDetails; + + VisualContainerHelper get _container => _dataGridConfiguration.container; + + /// ToDo + void preGenerateRows(VisibleLinesCollection? visibleRows, + VisibleLinesCollection? visibleColumns) { + if (items.isNotEmpty || _dataGridConfiguration.container.rowCount <= 0) { + return; + } + + if (visibleRows != null && visibleColumns != null) { + for (int i = 0; i < visibleRows.length; i++) { + VisibleLineInfo? line = visibleRows[i]; + late DataRowBase? dr; + switch (line.region) { + case ScrollAxisRegion.header: + dr = _createHeaderRow(line.lineIndex, visibleColumns); + break; + case ScrollAxisRegion.body: + dr = _createDataRow(line.lineIndex, visibleColumns); + break; + case ScrollAxisRegion.footer: + dr = _createFooterRow(line.lineIndex, visibleColumns); + break; + } + + items.add(dr); + dr = null; + line = null; + } + } + } + + /// ToDo + void ensureRows(VisibleLinesCollection visibleRows, + VisibleLinesCollection visibleColumns) { + List? actualStartAndEndIndex = []; + RowRegion? region = RowRegion.header; + + List reUseRows() => items + .where((DataRowBase row) => + (row.rowIndex < 0 || + row.rowIndex < actualStartAndEndIndex![0] || + row.rowIndex > actualStartAndEndIndex[1]) && + !row.isEnsured && + !row.isEditing) + .toList(growable: false); + + for (final DataRowBase row in items) { + row.isEnsured = false; + } + + for (int i = 0; i < 3; i++) { + if (i == 0) { + region = RowRegion.header; + actualStartAndEndIndex = _container.getStartEndIndex(visibleRows, i); + } else if (i == 1) { + region = RowRegion.body; + actualStartAndEndIndex = _container.getStartEndIndex(visibleRows, i); + } else { + region = RowRegion.footer; + actualStartAndEndIndex = _container.getStartEndIndex(visibleRows, i); + } + + for (int index = actualStartAndEndIndex[0]; + index <= actualStartAndEndIndex[1]; + index++) { + DataRowBase? dr = _indexer(index); + if (dr == null) { + List? rows = reUseRows(); + if (rows.isNotEmpty) { + _updateRow(rows, index, region); + rows = null; + } + } + + dr ??= + items.firstWhereOrNull((DataRowBase row) => row.rowIndex == index); + + if (dr != null) { + if (!dr.isVisible) { + dr.isVisible = true; + } + + dr.isEnsured = true; + } else { + switch (region) { + case RowRegion.header: + dr = _createHeaderRow(index, visibleColumns); + break; + case RowRegion.body: + dr = _createDataRow(index, visibleColumns); + break; + case RowRegion.footer: + dr = _createFooterRow(index, visibleColumns); + break; + } + + dr.isEnsured = true; + items.add(dr); + } + + dr = null; + } + } + + for (final DataRowBase row in items) { + if (!row.isEnsured || row.rowIndex == -1) { + row.isVisible = false; + } + } + + actualStartAndEndIndex = null; + region = null; + } + + /// ToDo + void ensureColumns(VisibleLinesCollection visibleColumns) { + for (final DataRowBase row in items) { + row._ensureColumns(visibleColumns); + } + } + + DataRowBase _createDataRow( + int rowIndex, VisibleLinesCollection visibleColumns, + {RowRegion rowRegion = RowRegion.body}) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + DataRowBase dr; + if (grid_helper.isFooterWidgetRow(rowIndex, dataGridConfiguration)) { + dr = DataRow() + ..rowIndex = rowIndex + ..rowRegion = rowRegion + ..rowType = RowType.footerRow + ..dataGridStateDetails = dataGridStateDetails; + dr.key = ObjectKey(dr); + dr.footerView = _buildFooterWidget(dataGridConfiguration, rowIndex); + return dr; + } else { + dr = DataRow() + ..rowIndex = rowIndex + ..rowRegion = rowRegion + ..rowType = RowType.dataRow + ..dataGridStateDetails = dataGridStateDetails; + dr.key = ObjectKey(dr); + dr + ..dataGridRow = + grid_helper.getDataGridRow(dataGridConfiguration, rowIndex) + ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + dataGridConfiguration, dr.dataGridRow!); + assert(grid_helper.debugCheckTheLength( + dataGridConfiguration, + dataGridConfiguration.columns.length, + dr.dataGridRowAdapter!.cells.length, + 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); + _checkForCurrentRow(dr); + _checkForSelection(dr); + dr._initializeDataRow(visibleColumns); + return dr; + } + } + + DataRowBase _createHeaderRow( + int rowIndex, VisibleLinesCollection visibleColumns) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + DataRowBase dr; + if (rowIndex == grid_helper.getHeaderIndex(dataGridConfiguration)) { + dr = DataRow() + ..rowIndex = rowIndex + ..rowRegion = RowRegion.header + ..rowType = RowType.headerRow + ..dataGridStateDetails = dataGridStateDetails; + dr + ..key = ObjectKey(dr) + .._initializeDataRow(visibleColumns); + return dr; + } else if (rowIndex < dataGridConfiguration.stackedHeaderRows.length) { + dr = _SpannedDataRow(); + dr.key = ObjectKey(dr); + dr.rowIndex = rowIndex; + dr.rowRegion = RowRegion.header; + dr.rowType = RowType.stackedHeaderRow; + dr.dataGridStateDetails = dataGridStateDetails; + _createStackedHeaderCell( + dataGridConfiguration.stackedHeaderRows[rowIndex], rowIndex); + dr._initializeDataRow(visibleColumns); + return dr; + } else if (grid_helper.isTopTableSummaryRow( + dataGridConfiguration, rowIndex)) { + dr = _createTableSummaryRow(rowIndex, GridTableSummaryRowPosition.top); + dr + ..key = ObjectKey(dr) + ..rowRegion = RowRegion.header + ..dataGridStateDetails = dataGridStateDetails + .._initializeDataRow(visibleColumns); + return dr; + } else { + return _createDataRow(rowIndex, visibleColumns, + rowRegion: RowRegion.header); + } + } + + void _createStackedHeaderCell(StackedHeaderRow header, int rowIndex) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + for (final StackedHeaderCell column in header.cells) { + final List childSequence = + grid_helper.getChildSequence(dataGridConfiguration, column, rowIndex); + childSequence.sort(); + setChildColumnIndexes(column, childSequence); + } + } + + DataRowBase _createFooterRow( + int rowIndex, VisibleLinesCollection visibleColumns) { + DataRowBase dr; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (grid_helper.isBottomTableSummaryRow(dataGridConfiguration, rowIndex)) { + dr = _createTableSummaryRow(rowIndex, GridTableSummaryRowPosition.bottom); + dr + ..key = ObjectKey(dr) + ..dataGridStateDetails = dataGridStateDetails + ..rowRegion = RowRegion.footer + .._initializeDataRow(visibleColumns); + } else { + dr = + _createDataRow(rowIndex, visibleColumns, rowRegion: RowRegion.footer); + } + return dr; + } + + Widget? _buildFooterWidget( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + if (dataGridConfiguration.footer == null) { + return null; + } + + BoxDecoration drawBorder() { + final SfDataGridThemeData themeData = + dataGridConfiguration.dataGridThemeData!; + final GridLinesVisibility gridLinesVisibility = + dataGridConfiguration.gridLinesVisibility; + + final bool canDrawHorizontalBorder = (gridLinesVisibility == + GridLinesVisibility.horizontal || + gridLinesVisibility == GridLinesVisibility.both) && + grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom) <= + 0; + final bool canDrawVerticalBorder = + gridLinesVisibility == GridLinesVisibility.vertical || + gridLinesVisibility == GridLinesVisibility.both; + + final bool canDrawTopFrozenBorder = + dataGridConfiguration.footerFrozenRowsCount.isFinite && + dataGridConfiguration.footerFrozenRowsCount > 0 && + grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration) == + rowIndex; + + return BoxDecoration( + border: BorderDirectional( + top: canDrawTopFrozenBorder && themeData.frozenPaneElevation <= 0.0 + ? BorderSide( + color: themeData.frozenPaneLineColor, + width: themeData.frozenPaneLineWidth) + : BorderSide.none, + bottom: canDrawHorizontalBorder + ? BorderSide( + color: themeData.gridLineColor, + width: themeData.gridLineStrokeWidth) + : BorderSide.none, + end: canDrawVerticalBorder + ? BorderSide( + color: themeData.gridLineColor, + width: themeData.gridLineStrokeWidth) + : BorderSide.none, + ), + ); + } + + return Container( + decoration: drawBorder(), + clipBehavior: Clip.antiAlias, + child: dataGridConfiguration.footer); + } + + DataRowBase? _indexer(int index) { + for (int i = 0; i < items.length; i++) { + if (items[i].rowIndex == index) { + return items[i]; + } + } + + return null; + } + + void _updateRow(List rows, int index, RowRegion region) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + final VisibleLinesCollection visibleColumns = + grid_helper.getVisibleLines(dataGridConfiguration); + switch (region) { + case RowRegion.header: + _updateHeaderRow( + rows, index, region, visibleColumns, dataGridConfiguration); + break; + case RowRegion.body: + _updateDataRow( + rows, index, region, visibleColumns, dataGridConfiguration); + break; + case RowRegion.footer: + _updateFooterRow( + rows, index, region, visibleColumns, dataGridConfiguration); + break; + } + } + + DataRowBase _createTableSummaryRow( + int rowIndex, GridTableSummaryRowPosition position) { + final GridTableSummaryRow? tableSummaryRow = grid_helper.getTableSummaryRow( + _dataGridConfiguration, rowIndex, position); + final _SpannedDataRow spannedDataRow = _SpannedDataRow() + ..rowIndex = rowIndex + ..tableSummaryRow = tableSummaryRow; + + if (tableSummaryRow != null) { + spannedDataRow.rowType = tableSummaryRow.showSummaryInRow + ? RowType.tableSummaryCoveredRow + : RowType.tableSummaryRow; + } + return spannedDataRow; + } + + void _updateHeaderRow( + List rows, + int index, + RowRegion region, + VisibleLinesCollection visibleColumns, + DataGridConfiguration dataGridConfiguration) { + DataRowBase? dr; + void createHeaderRow() { + dr = _createHeaderRow(index, visibleColumns); + items.add(dr!); + dr = null; + } + + if (index == grid_helper.getHeaderIndex(dataGridConfiguration)) { + dr = rows.firstWhereOrNull( + (DataRowBase row) => row.rowType == RowType.headerRow); + if (dr != null) { + dr! + ..key = dr!.key + ..rowIndex = index + ..rowRegion = region + ..rowType = RowType.headerRow + ..rowIndexChanged(); + dr = null; + } else { + createHeaderRow(); + } + } else if (index < dataGridConfiguration.stackedHeaderRows.length) { + dr = rows.firstWhereOrNull( + (DataRowBase r) => r.rowType == RowType.stackedHeaderRow); + if (dr != null) { + dr! + ..key = dr!.key + ..rowIndex = index + ..rowRegion = region + ..rowType = RowType.stackedHeaderRow + ..rowIndexChanged(); + _createStackedHeaderCell( + dataGridConfiguration.stackedHeaderRows[index], index); + dr!._initializeDataRow( + dataGridConfiguration.container.scrollRows.getVisibleLines()); + } else { + createHeaderRow(); + } + } else if (grid_helper.isTopTableSummaryRow(dataGridConfiguration, index)) { + dr = rows.firstWhereOrNull((DataRowBase r) => + r.rowRegion == region && + (r.rowType == RowType.tableSummaryRow || + r.rowType == RowType.tableSummaryCoveredRow)); + if (dr != null) { + dr! + ..key = dr!.key + ..rowRegion = region + ..rowIndex = index + .._initializeDataRow(visibleColumns); + } else { + createHeaderRow(); + } + } + } + + void _updateDataRow( + List rows, + int index, + RowRegion region, + VisibleLinesCollection visibleColumns, + DataGridConfiguration dataGridConfiguration) { + DataRowBase? row = rows.firstWhereOrNull( + (DataRowBase row) => row is DataRow && row.rowType == RowType.dataRow); + void createDataRow() { + row = _createDataRow(index, visibleColumns); + items.add(row!); + row = null; + } + + if (grid_helper.isFooterWidgetRow(index, dataGridConfiguration)) { + row = rows + .firstWhereOrNull((DataRowBase r) => r.rowType == RowType.footerRow); + if (row != null) { + row! + ..key = row!.key + ..rowIndex = index + ..rowRegion = region + ..rowType = RowType.footerRow; + row!.footerView = _buildFooterWidget(dataGridConfiguration, index); + } else { + createDataRow(); + } + } else if (row != null && row is DataRow) { + if (index < 0 || index >= _container.scrollRows.lineCount) { + row!.isVisible = false; + } else { + row! + ..key = row!.key + ..rowIndex = index + ..rowRegion = region + ..dataGridRow = + grid_helper.getDataGridRow(dataGridConfiguration, index) + ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + dataGridConfiguration, row!.dataGridRow!); + assert(grid_helper.debugCheckTheLength( + dataGridConfiguration, + dataGridConfiguration.columns.length, + row!.dataGridRowAdapter!.cells.length, + 'SfDataGrid.columns.length == DataGridRowAdapter.cells.length')); + _checkForCurrentRow(row!); + _checkForSelection(row!); + row!.rowIndexChanged(); + } + row = null; + } else { + createDataRow(); + } + } + + void _updateFooterRow( + List rows, + int index, + RowRegion region, + VisibleLinesCollection visibleColumns, + DataGridConfiguration dataGridConfiguration) { + DataRowBase? dr; + if (grid_helper.isBottomTableSummaryRow(dataGridConfiguration, index)) { + dr = rows.firstWhereOrNull((DataRowBase r) => + r.rowRegion == region && + (r.rowType == RowType.tableSummaryRow || + r.rowType == RowType.tableSummaryCoveredRow)); + if (dr != null) { + dr + ..key = dr.key + ..rowRegion = region + ..rowIndex = index + .._initializeDataRow(visibleColumns); + } else { + dr = _createFooterRow(index, visibleColumns); + items.add(dr); + dr = null; + } + } else { + _updateDataRow( + rows, index, region, visibleColumns, dataGridConfiguration); + } + } + + /// ToDo + double queryRowHeight(int rowIndex, double height) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + double rowHeight = height; + if (dataGridConfiguration.onQueryRowHeight != null) { + final RowHeightDetails details = RowHeightDetails(rowIndex, height); + setColumnSizerInRowHeightDetailsArgs( + details, dataGridConfiguration.columnSizer); + rowHeight = dataGridConfiguration.onQueryRowHeight!(details); + } + + return rowHeight; + } + + void _checkForSelection(DataRowBase row) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + if (dataGridConfiguration.selectionMode != SelectionMode.none) { + final int recordIndex = + grid_helper.resolveToRecordIndex(dataGridConfiguration, row.rowIndex); + final DataGridRow record = + effectiveRows(dataGridConfiguration.source)[recordIndex]; + row._isSelectedRow = + selection_manager.isSelectedRow(dataGridConfiguration, record); + } + } + + void _checkForCurrentRow(DataRowBase dr) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + final CurrentCellManager currentCellManager = + dataGridConfiguration.currentCell; + + DataCellBase? getDataCell() { + if (dr.visibleColumns.isEmpty) { + return null; + } + + final DataCellBase? dc = dr.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => + dataCell.columnIndex == currentCellManager.columnIndex); + return dc; + } + + if (currentCellManager.rowIndex != -1 && + currentCellManager.columnIndex != -1 && + currentCellManager.rowIndex == dr.rowIndex) { + final DataCellBase? dataCell = getDataCell(); + currentCellManager.setCurrentCellDirty(dr, dataCell, true); + } else if (dr.isCurrentRow) { + final DataCellBase? dataCell = getDataCell(); + currentCellManager.setCurrentCellDirty(dr, dataCell, false); + } else { + dr.isCurrentRow = false; + } + } + } +} + +/// Provides functionality to process the spanned data row. +class _SpannedDataRow extends DataRow { + @override + void _onGenerateVisibleColumns(VisibleLinesCollection visibleColumnLines) { + if (visibleColumnLines.isEmpty) { + return; + } + visibleColumns.clear(); + + _SpannedDataColumn dc; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + + if (rowType == RowType.tableSummaryRow || + rowType == RowType.tableSummaryCoveredRow) { + if (rowType == RowType.tableSummaryRow) { + for (int i = 0; i < dataGridConfiguration.container.columnCount; i++) { + if (!_isEnsuredSpannedCell(i)) { + final int columnSpan = grid_helper.getSummaryColumnSpan( + dataGridConfiguration, i, rowType, tableSummaryRow); + dc = _createSpannedColumn(i, columnSpan, null); + visibleColumns.add(dc); + } + } + } else { + final int columnSpan = grid_helper.getSummaryColumnSpan( + dataGridConfiguration, 0, rowType, tableSummaryRow); + dc = _createSpannedColumn(0, columnSpan, null); + visibleColumns.add(dc); + } + } else if (rowType == RowType.stackedHeaderRow) { + if (rowType == RowType.stackedHeaderRow) { + if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { + final List stackedColumns = + dataGridConfiguration.stackedHeaderRows[rowIndex].cells; + for (final StackedHeaderCell column in stackedColumns) { + final List> columnsSequence = + grid_helper.getConsecutiveRanges(getChildColumnIndexes(column)); + + for (final List columns in columnsSequence) { + dc = _createSpannedColumn( + columns.reduce(min), columns.length - 1, column); + visibleColumns.add(dc); + } + } + } + } + } + } + + int _getRowSpan(int columnIndex, StackedHeaderCell? stackedHeaderCell, + String mappingName) { + int rowSpan = 0; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + if (rowType == RowType.stackedHeaderRow) { + rowSpan = grid_helper.getRowSpan( + dataGridConfiguration, rowIndex, columnIndex, true, + stackedHeaderCell: stackedHeaderCell); + } else if (rowType == RowType.headerRow) { + rowSpan = grid_helper.getRowSpan( + dataGridConfiguration, rowIndex - 1, columnIndex, false, + mappingName: mappingName); + } + return rowSpan; + } + + _SpannedDataColumn _createSpannedCell(_SpannedDataColumn dc, int columnIndex, + int columnSpan, int rowSpan, GridColumn? gridColumn) { + dc + ..dataRow = this + ..columnIndex = columnIndex + ..rowIndex = rowIndex; + dc.key = ObjectKey(dc); + dc + ..columnSpan = columnSpan + ..rowSpan = rowSpan + ..gridColumn = gridColumn + ..isEnsured = true; + return dc; + } + + _SpannedDataColumn _createSpannedColumn( + int index, int columnSpan, StackedHeaderCell? stackedHeaderCell) { + _SpannedDataColumn dc = _SpannedDataColumn(); + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + + if (index >= grid_helper.resolveToStartColumnIndex(dataGridConfiguration)) { + final int startColumnIndex = + grid_helper.resolveToScrollColumnIndex(dataGridConfiguration, index); + final GridColumn gridColumn = + dataGridConfiguration.columns[startColumnIndex]; + final int rowSpan = + _getRowSpan(index, stackedHeaderCell, gridColumn.columnName); + + if (rowType == RowType.stackedHeaderRow) { + dc = _createSpannedCell(dc, index, columnSpan, rowSpan, gridColumn); + dc + ..renderer = dataGridConfiguration.cellRenderers['StackedHeader'] + ..stackedHeaderCell = stackedHeaderCell + ..cellType = CellType.stackedHeaderCell; + } else if (rowType == RowType.tableSummaryRow || + rowType == RowType.tableSummaryCoveredRow) { + dc = _createSpannedCell(dc, index, columnSpan, rowSpan, gridColumn); + dc + ..renderer = dataGridConfiguration.cellRenderers['TableSummary'] + ..cellType = CellType.tableSummaryCell; + + // Sets the summaryColumn to the data cell. + if (tableSummaryRow != null && tableSummaryRow!.columns.isNotEmpty) { + if (rowType == RowType.tableSummaryRow) { + final int titleColumnSpan = grid_helper.getSummaryTitleColumnSpan( + dataGridConfiguration, tableSummaryRow!); + if (dc.columnIndex >= titleColumnSpan) { + dc.summaryColumn = tableSummaryRow!.columns.firstWhereOrNull( + (GridSummaryColumn element) => + element.columnName == gridColumn.columnName); + } + } + } + } + } else { + // To create an indent cell if it exist in the spanned row. + dc = _createSpannedCell(dc, index, columnSpan, 0, null); + } + + dc.columnElement = dc._onInitializeColumnElement(false); + return dc; + } + + void _ensureSpannedColumn( + {required int columnIndex, + required int columnSpan, + StackedHeaderCell? stackedHeaderCell}) { + DataCellBase? dc = _indexer(columnIndex); + + if (dc == null) { + DataCellBase? dataCell = + _reUseCell(columnIndex, columnIndex + columnSpan); + dataCell ??= visibleColumns.firstWhereOrNull((DataCellBase col) => + col.columnIndex == -1 && col.cellType != CellType.indentCell); + + _updateSpannedColumn( + dataCell, columnIndex, columnSpan, stackedHeaderCell); + dataCell = null; + } + + dc ??= visibleColumns + .firstWhereOrNull((DataCellBase col) => col.columnIndex == columnIndex); + + if (dc != null) { + if (!dc.isVisible) { + dc.isVisible = true; + } + } else { + dc = _createSpannedColumn(columnIndex, columnSpan, stackedHeaderCell); + visibleColumns.add(dc); + } + + dc.isEnsured = true; + dc = null; + } + + @override + void _ensureColumns(VisibleLinesCollection visibleColumnLines) { + if (rowIndex == -1) { + return; + } + + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + if (rowType == RowType.stackedHeaderRow && + dataGridConfiguration.stackedHeaderRows.isNotEmpty) { + final StackedHeaderRow stackedHeaderRow = + dataGridConfiguration.stackedHeaderRows[rowIndex]; + final List stackedColumns = stackedHeaderRow.cells; + dataGridConfiguration.rowGenerator + ._createStackedHeaderCell(stackedHeaderRow, rowIndex); + for (final StackedHeaderCell column in stackedColumns) { + final List> columnsSequence = + grid_helper.getConsecutiveRanges(getChildColumnIndexes(column)); + + for (final List columns in columnsSequence) { + final int columnIndex = columns.reduce(min); + final int columnSpan = columns.length - 1; + _ensureSpannedColumn( + columnSpan: columnSpan, + columnIndex: columnIndex, + stackedHeaderCell: column); + } + } + } else { + for (final DataCellBase column in visibleColumns) { + column.isEnsured = false; + } + + for (int i = 0; i < 3; i++) { + final List startAndEndIndex = dataGridConfiguration.container + .getStartEndIndex(visibleColumnLines, i); + + for (int index = startAndEndIndex[0]; + index <= startAndEndIndex[1]; + index++) { + if (!_isEnsuredSpannedCell(index)) { + final int columnSpan = grid_helper.getSummaryColumnSpan( + dataGridConfiguration, index, rowType, tableSummaryRow); + _ensureSpannedColumn(columnIndex: index, columnSpan: columnSpan); + } + } + } + } + + for (final DataCellBase col in visibleColumns) { + if (!col.isEnsured || col.columnIndex == -1) { + col.isVisible = false; + } + } + } + + void _updateSpannedColumn(DataCellBase? dc, int index, int columnSpan, + StackedHeaderCell? stackedHeaderCell) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails!(); + if (dc != null) { + if (index < 0 || index >= dataGridConfiguration.container.columnCount) { + dc.isVisible = false; + } else { + final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, index); + final GridColumn gridColumn = + dataGridConfiguration.columns[columnIndex]; + final int rowSpan = + _getRowSpan(index, stackedHeaderCell, gridColumn.columnName); + + dc + ..columnIndex = index + ..rowIndex = rowIndex + ..columnSpan = columnSpan + ..rowSpan = rowSpan + ..key = dc.key + ..isVisible = true; + dc.gridColumn = gridColumn; + if (rowType == RowType.stackedHeaderRow) { + dc + ..renderer = dataGridConfiguration.cellRenderers['StackedHeader'] + ..stackedHeaderCell = stackedHeaderCell + ..cellType = CellType.stackedHeaderCell; + } + if (rowType == RowType.tableSummaryRow || + rowType == RowType.tableSummaryCoveredRow) { + dc + ..renderer = dataGridConfiguration.cellRenderers['TableSummary'] + ..cellType = CellType.tableSummaryCell; + } + + dc + ..columnElement = dc._onInitializeColumnElement(false) + ..isEnsured = true; + } + + if (dc.isVisible != true) { + dc.isVisible = true; + } + } else { + dc = _createSpannedColumn(index, columnSpan, stackedHeaderCell); + visibleColumns.add(dc); + } + } + + bool _isEnsuredSpannedCell(int index) { + return visibleColumns.any((DataCellBase cell) => + cell.isEnsured && + (index >= cell.columnIndex && + index <= cell.columnIndex + cell.columnSpan)); + } +} + +/// Provides functionality to display the spanned cell. +class _SpannedDataColumn extends DataCell {} 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 new file mode 100644 index 000000000..6bf4aa93a --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/selection/selection_manager.dart @@ -0,0 +1,1923 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart' hide DataCell, DataRow; +import 'package:flutter/services.dart'; + +import '../../grid_common/row_column_index.dart'; +import '../helper/datagrid_configuration.dart'; +import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/enums.dart'; +import '../helper/selection_helper.dart' as selection_helper; +import '../runtime/generator.dart'; +import '../sfdatagrid.dart'; + +/// Provides the base functionalities to process the selection in [SfDataGrid]. +class SelectionManagerBase extends ChangeNotifier { + final List _selectedRows = []; + + /// ToDo + DataGridStateDetails? _dataGridStateDetails; + + /// Processes the selection operation when tap a cell. + void handleTap(RowColumnIndex rowColumnIndex) {} + + /// Processes the selection operation when [SfDataGrid] receives raw keyboard + /// event. + void handleKeyEvent(RawKeyEvent keyEvent) {} + + /// Called when the [SfDataGrid.selectionMode] is changed at run time. + void onGridSelectionModeChanged() {} + + /// Called when the selection is programmatically changed + /// using [SfDataGrid.controller]. + void handleDataGridSourceChanges() {} + + /// Called when the selectedRow property in [SfDataGrid.controller] + /// is changed. + void onSelectedRowChanged() {} + + /// Called when the selectedIndex property in [SfDataGrid.controller] + /// is changed. + void onSelectedIndexChanged() {} + + /// Called when the selectedRows property in [SfDataGrid.controller] + /// is changed. + void onSelectedRowsChanged() {} +} + +/// Processes the row selection operation in [SfDataGrid]. +/// +/// You can override the available methods in this class to customize the +/// selection operation +/// in DataGrid and set the instance to the [SfDataGrid.selectionManager]. +/// +/// The following code shows how to change the Enter key behavior by +/// overriding the handleKeyEvent() method in RowSelectionManager, +/// +/// ``` dart +/// final CustomSelectionManager _customSelectionManager = CustomSelectionManager(); + +/// @override +/// Widget build(BuildContext context){ +/// return MaterialApp( +/// home: Scaffold( +/// body: SfDataGrid( +/// source: _employeeDataSource, +/// columns: [ +/// GridColumn(columnName: 'id', label = Text('ID')), +/// GridColumn(columnName: 'name', label = Text('Name')), +/// GridColumn(columnName: 'designation', label = Text('Designation')), +/// GridColumn(columnName: 'salary', label = Text('Salary')), +/// ], +/// selectionMode: SelectionMode.multiple, +/// navigationMode: GridNavigationMode.cell, +/// selectionManager: _customSelectionManager, +/// )) +/// ); +/// } + +/// class CustomSelectionManager extends RowSelectionManager{ +/// @override +/// void handleKeyEvent(RawKeyEvent keyEvent) { +/// if(keyEvent.logicalKey == LogicalKeyboardKey.enter){ +/// //apply your logic +/// return; +/// } + +/// super.handleKeyEvent(keyEvent); +/// } +/// } +/// ``` +class RowSelectionManager extends SelectionManagerBase { + /// Creates the [RowSelectionManager] for [SfDataGrid] widget. + RowSelectionManager() : super(); + + RowColumnIndex _pressedRowColumnIndex = RowColumnIndex(-1, -1); + + void _applySelection(RowColumnIndex rowColumnIndex) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + + final int recordIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, rowColumnIndex.rowIndex); + DataGridRow? record = + selection_helper.getRecord(dataGridConfiguration, recordIndex); + + final List addedItems = []; + final List removeItems = []; + + if (dataGridConfiguration.selectionMode == SelectionMode.single) { + if (record == null || _selectedRows.contains(record)) { + return; + } + + if (dataGridConfiguration.onSelectionChanging != null || + dataGridConfiguration.onSelectionChanged != null) { + addedItems.add(record); + if (_selectedRows.isNotEmpty) { + removeItems.add(_selectedRows.first); + } + } + + if (_raiseSelectionChanging( + newItems: addedItems, oldItems: removeItems)) { + _clearSelectedRow(dataGridConfiguration); + _addSelection(record, dataGridConfiguration); + notifyListeners(); + _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); + } + //If user, return false in selection changing, and we should need + // to update the current cell. + else if (dataGridConfiguration.navigationMode == + GridNavigationMode.cell) { + notifyListeners(); + } + } else if (dataGridConfiguration.selectionMode == + SelectionMode.singleDeselect) { + if (dataGridConfiguration.onSelectionChanging != null || + dataGridConfiguration.onSelectionChanged != null) { + if (_selectedRows.isNotEmpty) { + removeItems.add(_selectedRows.first); + } + + if (record != null && !_selectedRows.contains(record)) { + addedItems.add(record); + } + } + + if (_raiseSelectionChanging( + newItems: addedItems, oldItems: removeItems)) { + if (record != null && !_selectedRows.contains(record)) { + _clearSelectedRow(dataGridConfiguration); + _addSelection(record, dataGridConfiguration); + } else { + _clearSelectedRow(dataGridConfiguration); + } + + notifyListeners(); + _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); + } + //If user, return false in selection changing, and we should need + // to update the current cell. + else if (dataGridConfiguration.navigationMode == + GridNavigationMode.cell) { + notifyListeners(); + } + } else if (dataGridConfiguration.selectionMode == SelectionMode.multiple) { + if (dataGridConfiguration.onSelectionChanging != null || + dataGridConfiguration.onSelectionChanged != null) { + if (record != null) { + if (!_selectedRows.contains(record)) { + addedItems.add(record); + } else { + removeItems.add(record); + } + } + } + + if (_raiseSelectionChanging( + newItems: addedItems, oldItems: removeItems) && + record != null) { + if (!_selectedRows.contains(record)) { + _addSelection(record, dataGridConfiguration); + } else { + _removeSelection(record, dataGridConfiguration); + } + + notifyListeners(); + _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); + } + //If user, return false in selection changing, and we should need to + // update the current cell. + else if (dataGridConfiguration.navigationMode == + GridNavigationMode.cell) { + notifyListeners(); + } + } + + record = null; + } + + void _addSelection( + DataGridRow? record, DataGridConfiguration dataGridConfiguration) { + if (record != null && !_selectedRows.contains(record)) { + final int rowIndex = + selection_helper.resolveToRowIndex(dataGridConfiguration, record); + if (!_selectedRows.contains(record)) { + _selectedRows.add(record); + dataGridConfiguration.controller.selectedRows.add(record); + _refreshSelection(); + _setRowSelection(rowIndex, dataGridConfiguration, true); + + /// Check box state should be updated when a row is selected. + _updateCheckboxStateOnHeader(dataGridConfiguration); + } + } + } + + void _removeSelection( + DataGridRow? record, DataGridConfiguration dataGridConfiguration) { + if (record != null && _selectedRows.contains(record)) { + final int rowIndex = + selection_helper.resolveToRowIndex(dataGridConfiguration, record); + _selectedRows.remove(record); + dataGridConfiguration.controller.selectedRows.remove(record); + _refreshSelection(); + _setRowSelection(rowIndex, dataGridConfiguration, false); + + /// Check box state should be updated when a row is selected. + _updateCheckboxStateOnHeader(dataGridConfiguration); + } + } + + void _clearSelectedRow(DataGridConfiguration dataGridConfiguration) { + if (_selectedRows.isNotEmpty) { + final DataGridRow selectedItem = _selectedRows.first; + _removeSelection(selectedItem, dataGridConfiguration); + } + } + + void _clearSelectedRows(DataGridConfiguration dataGridConfiguration) { + if (_selectedRows.isNotEmpty) { + for (int i = _selectedRows.length - 1; i >= 0; i--) { + final DataGridRow selectedItem = _selectedRows[i]; + _removeSelection(selectedItem, dataGridConfiguration); + } + } + + _clearSelection(dataGridConfiguration); + } + + void _setRowSelection(int rowIndex, + DataGridConfiguration dataGridConfiguration, bool isRowSelected) { + if (dataGridConfiguration.rowGenerator.items.isEmpty) { + return; + } + + DataRowBase? row = dataGridConfiguration.rowGenerator.items + .firstWhereOrNull((DataRowBase item) => item.rowIndex == rowIndex); + if (row != null && row.rowType == RowType.dataRow) { + row + ..isDirty = true + ..dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + dataGridConfiguration, row.dataGridRow!) + ..isSelectedRow = isRowSelected; + } + + row = null; + } + + void _clearSelection(DataGridConfiguration dataGridConfiguration) { + _selectedRows.clear(); + dataGridConfiguration.controller.selectedRows.clear(); + updateSelectedRow(dataGridConfiguration.controller, null); + updateSelectedIndex(dataGridConfiguration.controller, -1); + for (final DataRowBase dataRow + in dataGridConfiguration.rowGenerator.items) { + if (dataRow.isSelectedRow) { + dataRow.isSelectedRow = false; + } + } + + _clearCurrentCell(dataGridConfiguration); + } + + void _clearCurrentCell(DataGridConfiguration dataGridConfiguration) { + dataGridConfiguration.currentCell._removeCurrentCell(dataGridConfiguration); + } + + void _refreshSelection() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + _removeUnWantedDataGridRows(dataGridConfiguration); + final DataGridRow? _selectedRow = + _selectedRows.isNotEmpty ? _selectedRows.last : null; + final int _recordIndex = _selectedRow == null + ? -1 + : effectiveRows(dataGridConfiguration.source).indexOf(_selectedRow); + updateSelectedRow(dataGridConfiguration.controller, _selectedRow); + updateSelectedIndex(dataGridConfiguration.controller, _recordIndex); + } + + void _removeUnWantedDataGridRows( + DataGridConfiguration dataGridConfiguration) { + final List duplicateSelectedRows = _selectedRows.toList(); + for (final DataGridRow selectedRow in duplicateSelectedRows) { + final int rowIndex = + effectiveRows(dataGridConfiguration.source).indexOf(selectedRow); + if (rowIndex.isNegative) { + _selectedRows.remove(selectedRow); + dataGridConfiguration.controller.selectedRows.remove(selectedRow); + } + } + } + + void _addCurrentCell( + DataGridRow record, DataGridConfiguration dataGridConfiguration, + {bool isSelectionChanging = false}) { + final int rowIndex = + selection_helper.resolveToRowIndex(dataGridConfiguration, record); + + if (rowIndex <= grid_helper.getHeaderIndex(dataGridConfiguration)) { + return; + } + + if (dataGridConfiguration.currentCell.columnIndex > 0) { + dataGridConfiguration.currentCell._moveCurrentCellTo( + dataGridConfiguration, + RowColumnIndex( + rowIndex, dataGridConfiguration.currentCell.columnIndex), + isSelectionChanged: isSelectionChanging); + } else { + final int firstVisibleColumnIndex = + grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + dataGridConfiguration.currentCell._moveCurrentCellTo( + dataGridConfiguration, + RowColumnIndex(rowIndex, firstVisibleColumnIndex), + isSelectionChanged: isSelectionChanging); + } + } + + void _onNavigationModeChanged() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { + final RowColumnIndex currentRowColumnIndex = RowColumnIndex( + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex); + _clearCurrentCell(dataGridConfiguration); + dataGridConfiguration.currentCell._updateBorderForMultipleSelection( + dataGridConfiguration, + nextRowColumnIndex: currentRowColumnIndex); + } else { + if (dataGridConfiguration.selectionMode != SelectionMode.none) { + final DataGridRow? lastRecord = + dataGridConfiguration.controller.selectedRow; + + if (lastRecord == null) { + return; + } + + final RowColumnIndex _currentRowColumnIndex = + _getRowColumnIndexOnModeChanged(dataGridConfiguration, lastRecord); + + if (_currentRowColumnIndex.rowIndex <= 0) { + return; + } + + // CurrentCell is not drawn when changing the navigation mode at runtime + // We have to update the particular row and column when current cell + // index's are same. + final CurrentCellManager currentCell = + dataGridConfiguration.currentCell; + if (currentCell.rowIndex == _currentRowColumnIndex.rowIndex && + currentCell.columnIndex == _currentRowColumnIndex.columnIndex) { + currentCell._updateCurrentCell( + dataGridConfiguration, + _currentRowColumnIndex.rowIndex, + _currentRowColumnIndex.columnIndex, + ); + } else { + currentCell._moveCurrentCellTo( + dataGridConfiguration, + RowColumnIndex(_currentRowColumnIndex.rowIndex, + _currentRowColumnIndex.columnIndex), + isSelectionChanged: true); + } + } + } + } + + RowColumnIndex _getRowColumnIndexOnModeChanged( + DataGridConfiguration dataGridConfiguration, DataGridRow? lastRecord) { + final int rowIndex = + lastRecord == null && _pressedRowColumnIndex.rowIndex > 0 + ? _pressedRowColumnIndex.rowIndex + : selection_helper.resolveToRowIndex( + dataGridConfiguration, lastRecord!); + + final int columnIndex = _pressedRowColumnIndex.columnIndex != -1 + ? _pressedRowColumnIndex.columnIndex + : grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + + return RowColumnIndex(rowIndex, columnIndex); + } + + void _onDataSourceChanged() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + _clearSelection(dataGridConfiguration); + } + + /// When the selection is applied to a row, we should update the state of the check box also. + void _updateCheckboxStateOnHeader( + DataGridConfiguration dataGridConfiguration) { + if (!dataGridConfiguration.showCheckboxColumn || + !dataGridConfiguration.checkboxColumnSettings.showCheckboxOnHeader || + dataGridConfiguration.selectionMode == SelectionMode.none) { + return; + } + + final DataRowBase? headerDataRow = dataGridConfiguration.rowGenerator.items + .firstWhereOrNull( + (DataRowBase dataRow) => dataRow.rowType == RowType.headerRow); + + if (headerDataRow == null) { + if (dataGridConfiguration.controller.selectedRows.length == + dataGridConfiguration.source.rows.length) { + dataGridConfiguration.headerCheckboxState = true; + } else if (dataGridConfiguration.controller.selectedRows.isNotEmpty) { + dataGridConfiguration.headerCheckboxState = null; + } + return; + } + + final DataCellBase? headerDataCell = headerDataRow.visibleColumns + .firstWhereOrNull((DataCellBase cell) => cell.columnIndex == 0); + + if (dataGridConfiguration.controller.selectedRows.length != + effectiveRows(dataGridConfiguration.source).length && + dataGridConfiguration.headerCheckboxState != null) { + dataGridConfiguration.headerCheckboxState = null; + headerDataCell?.updateColumn(); + } else if (_selectedRows.isEmpty && + dataGridConfiguration.headerCheckboxState == null) { + dataGridConfiguration.headerCheckboxState = false; + headerDataCell?.updateColumn(); + } else if (dataGridConfiguration.controller.selectedRows.length == + dataGridConfiguration.source.rows.length && + (dataGridConfiguration.headerCheckboxState == null || + dataGridConfiguration.headerCheckboxState == false)) { + dataGridConfiguration.headerCheckboxState = true; + headerDataCell?.updateColumn(); + } + } + + @override + void handleTap(RowColumnIndex rowColumnIndex) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + _pressedRowColumnIndex = rowColumnIndex; + if (dataGridConfiguration.selectionMode == SelectionMode.none) { + return; + } + + final RowColumnIndex previousRowColumnIndex = RowColumnIndex( + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex); + if (!dataGridConfiguration.currentCell + ._handlePointerOperation(dataGridConfiguration, rowColumnIndex)) { + return; + } + + _processSelection( + dataGridConfiguration, rowColumnIndex, previousRowColumnIndex); + } + + void _processSelection( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex nextRowColumnIndex, + RowColumnIndex previousRowColumnIndex) { + // If selectionMode is single. next current cell is going to present in the + // same selected row. + // In this case, we don't update the whole data row. Instead of that + // we have to update next and previous current cell alone and call setState. + // In other selection mode we will update the whole data row and + // it will work. + if (dataGridConfiguration.selectionMode == SelectionMode.single && + dataGridConfiguration.navigationMode == GridNavigationMode.cell && + nextRowColumnIndex.rowIndex == previousRowColumnIndex.rowIndex && + nextRowColumnIndex.columnIndex != previousRowColumnIndex.columnIndex) { + notifyListeners(); + return; + } + + _applySelection(nextRowColumnIndex); + } + + @override + void handleDataGridSourceChanges() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + _clearSelectedRows(dataGridConfiguration); + } + + @override + void onSelectedRowChanged() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + + if (dataGridConfiguration.selectionMode == SelectionMode.none) { + return; + } + + final DataGridRow? newValue = dataGridConfiguration.controller.selectedRow; + + bool canClearSelections() => + _selectedRows.isNotEmpty && + dataGridConfiguration.selectionMode != SelectionMode.multiple; + + //If newValue is negative we have clear the whole selection data. + //In multiple case we shouldn't to clear the collection as well + // source properties. + if (newValue == null && canClearSelections()) { + _clearSelectedRow(dataGridConfiguration); + notifyListeners(); + return; + } + + final int recordIndex = + effectiveRows(dataGridConfiguration.source).indexOf(newValue!); + final int rowIndex = + grid_helper.resolveToRowIndex(dataGridConfiguration, recordIndex); + + if (rowIndex < grid_helper.getHeaderIndex(dataGridConfiguration)) { + return; + } + + if (!_selectedRows.contains(newValue)) { + //In multiple case we shouldn't to clear the collection as + // well source properties. + if (canClearSelections()) { + _clearSelectedRow(dataGridConfiguration); + } + + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + _addCurrentCell(newValue, dataGridConfiguration, + isSelectionChanging: true); + } else { + final int columnIndex = + grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + final int rowIndex = + selection_helper.resolveToRowIndex(dataGridConfiguration, newValue); + dataGridConfiguration.currentCell + ._updateCurrentRowColumnIndex(rowIndex, columnIndex); + } + + _addSelection(newValue, dataGridConfiguration); + notifyListeners(); + } + } + + @override + void onSelectedIndexChanged() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + + if (dataGridConfiguration.selectionMode == SelectionMode.none) { + return; + } + + final int newValue = dataGridConfiguration.controller.selectedIndex; + if (effectiveRows(dataGridConfiguration.source).isEmpty || + newValue > effectiveRows(dataGridConfiguration.source).length) { + return; + } + + bool canClearSelections() => + _selectedRows.isNotEmpty && + dataGridConfiguration.selectionMode != SelectionMode.multiple; + + //If newValue is negative we have to clear the whole selection data. + //In multiple case we shouldn't to clear the collection as + // well source properties. + if (newValue == -1 && canClearSelections()) { + _clearSelectedRow(dataGridConfiguration); + notifyListeners(); + return; + } + + final DataGridRow? record = + selection_helper.getRecord(dataGridConfiguration, newValue); + if (record != null && !_selectedRows.contains(record)) { + //In multiple case we shouldn't to clear the collection as + // well source properties. + if (canClearSelections()) { + _clearSelectedRow(dataGridConfiguration); + } + + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + _addCurrentCell(record, dataGridConfiguration, + isSelectionChanging: true); + } else { + final int rowIndex = + selection_helper.resolveToRowIndex(dataGridConfiguration, record); + final int columnIndex = + grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + dataGridConfiguration.currentCell + ._updateCurrentRowColumnIndex(rowIndex, columnIndex); + } + + _addSelection(record, dataGridConfiguration); + notifyListeners(); + } + } + + @override + void onSelectedRowsChanged() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + + if (dataGridConfiguration.selectionMode != SelectionMode.multiple || + dataGridConfiguration.selectionMode == SelectionMode.none) { + return; + } + + final List newValue = + dataGridConfiguration.controller.selectedRows.toList(growable: false); + + if (newValue.isEmpty) { + _clearSelectedRows(dataGridConfiguration); + notifyListeners(); + return; + } + + _clearSelectedRows(dataGridConfiguration); + for (final DataGridRow record in newValue) { + _addSelection(record, dataGridConfiguration); + } + + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell && + _selectedRows.isNotEmpty) { + final DataGridRow lastRecord = _selectedRows.last; + _addCurrentCell(lastRecord, dataGridConfiguration, + isSelectionChanging: true); + } else if (dataGridConfiguration.isDesktop && + dataGridConfiguration.navigationMode == GridNavigationMode.row) { + final DataGridRow lastRecord = _selectedRows.last; + final int rowIndex = + selection_helper.resolveToRowIndex(dataGridConfiguration, lastRecord); + dataGridConfiguration.currentCell._updateBorderForMultipleSelection( + dataGridConfiguration, + nextRowColumnIndex: RowColumnIndex(rowIndex, -1)); + } + + notifyListeners(); + } + + @override + void onGridSelectionModeChanged() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.selectionMode == SelectionMode.none) { + dataGridConfiguration.headerCheckboxState = false; + _clearSelectedRows(dataGridConfiguration); + _pressedRowColumnIndex = RowColumnIndex(-1, -1); + } else if (dataGridConfiguration.selectionMode != SelectionMode.none && + dataGridConfiguration.selectionMode != SelectionMode.multiple) { + DataGridRow? lastRecord = dataGridConfiguration.controller.selectedRow; + _clearSelection(dataGridConfiguration); + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell && + lastRecord != null) { + final RowColumnIndex _currentRowColumnIndex = + _getRowColumnIndexOnModeChanged(dataGridConfiguration, lastRecord); + + if (_currentRowColumnIndex.rowIndex <= 0) { + return; + } + + lastRecord = dataGridConfiguration.selectionMode == SelectionMode.single + ? selection_helper.getRecord( + dataGridConfiguration, + grid_helper.resolveToRecordIndex( + dataGridConfiguration, _currentRowColumnIndex.rowIndex)) + : lastRecord; + + dataGridConfiguration.currentCell._moveCurrentCellTo( + dataGridConfiguration, + RowColumnIndex(_currentRowColumnIndex.rowIndex, + _currentRowColumnIndex.columnIndex), + isSelectionChanged: true); + } + + if (lastRecord != null) { + _addSelection(lastRecord, dataGridConfiguration); + } + } else if (dataGridConfiguration.isDesktop && + dataGridConfiguration.selectionMode == SelectionMode.multiple) { + final RowColumnIndex currentRowColumnIndex = + RowColumnIndex(dataGridConfiguration.currentCell.rowIndex, -1); + dataGridConfiguration.currentCell._updateBorderForMultipleSelection( + dataGridConfiguration, + nextRowColumnIndex: currentRowColumnIndex); + } + } + + void _onRowColumnChanged(int recordLength, int columnLength) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + + if (currentCell.rowIndex == -1 && currentCell.columnIndex == -1) { + return; + } + + final int rowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, currentCell.rowIndex); + if (recordLength > 0 && + rowIndex >= recordLength && + currentCell.rowIndex != -1) { + final int startRowIndex = selection_helper.getPreviousRowIndex( + dataGridConfiguration, currentCell.rowIndex); + currentCell._moveCurrentCellTo(dataGridConfiguration, + RowColumnIndex(startRowIndex, currentCell.columnIndex), + needToUpdateColumn: false); + _refreshSelection(); + } + + final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, currentCell.columnIndex); + if (columnLength > 0 && + columnIndex >= columnLength && + currentCell.columnIndex != -1) { + final int startColumnIndex = selection_helper.getPreviousColumnIndex( + dataGridConfiguration, currentCell.columnIndex); + currentCell._moveCurrentCellTo(dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, startColumnIndex), + needToUpdateColumn: false); + } + } + + void _updateSelectionController( + {bool isSelectionModeChanged = false, + bool isNavigationModeChanged = false, + bool isDataSourceChanged = false}) { + if (isDataSourceChanged) { + _onDataSourceChanged(); + } + + if (isSelectionModeChanged) { + onGridSelectionModeChanged(); + } + + if (isNavigationModeChanged) { + _onNavigationModeChanged(); + } + } + + void _handleSelectionPropertyChanged( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + switch (propertyName) { + case 'selectedIndex': + onSelectedIndexChanged(); + break; + case 'selectedRow': + onSelectedRowChanged(); + break; + case 'selectedRows': + onSelectedRowsChanged(); + break; + default: + break; + } + } + + //KeyNavigation + @override + void handleKeyEvent(RawKeyEvent keyEvent) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.currentCell.isEditing && + keyEvent.logicalKey != LogicalKeyboardKey.escape) { + if (!dataGridConfiguration.currentCell + .canSubmitCell(dataGridConfiguration)) { + return; + } + + dataGridConfiguration.currentCell + .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.tab) { + _processKeyTab(keyEvent); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.home) { + _processHomeKey(dataGridConfiguration, keyEvent); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.end) { + _processEndKey(dataGridConfiguration, keyEvent); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.pageUp) { + _processPageUp(); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.pageDown) { + _processPageDown(); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowUp) { + _processKeyUp(keyEvent); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowDown || + keyEvent.logicalKey == LogicalKeyboardKey.enter) { + _processKeyDown(keyEvent); + if (keyEvent.logicalKey == LogicalKeyboardKey.enter) { + return; + } + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowLeft) { + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + _processKeyRight(dataGridConfiguration, keyEvent); + } else { + _processKeyLeft(dataGridConfiguration, keyEvent); + } + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.arrowRight) { + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + _processKeyLeft(dataGridConfiguration, keyEvent); + } else { + _processKeyRight(dataGridConfiguration, keyEvent); + } + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.keyA) { + if (keyEvent.isControlPressed) { + _processSelectedAll(); + } + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.space) { + _processSpaceKey(); + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.f2) { + if (dataGridConfiguration.allowEditing && + dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + final RowColumnIndex rowColumnIndex = RowColumnIndex( + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex); + dataGridConfiguration.currentCell.onCellBeginEdit( + editingRowColumnIndex: rowColumnIndex, + isProgrammatic: true, + needToResolveIndex: false); + } + } + + if (keyEvent.logicalKey == LogicalKeyboardKey.escape) { + if (dataGridConfiguration.allowEditing && + dataGridConfiguration.navigationMode == GridNavigationMode.cell && + dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell + .onCellSubmit(dataGridConfiguration, isCellCancelEdit: true); + } + } + } + + void _processEndKey( + DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int lastCellIndex = + selection_helper.getLastCellIndex(dataGridConfiguration); + final bool needToScrollToMinOrMaxExtent = + dataGridConfiguration.container.extentWidth > + dataGridConfiguration.viewWidth; + + if (needToScrollToMinOrMaxExtent) { + selection_helper.scrollInViewFromLeft(dataGridConfiguration, + needToScrollMaxExtent: true); + } + + if (keyEvent.isControlPressed && + keyEvent.logicalKey != LogicalKeyboardKey.arrowRight) { + final int lastRowIndex = + selection_helper.getLastNavigatingRowIndex(dataGridConfiguration); + selection_helper.scrollInViewFromTop(dataGridConfiguration, + needToScrollToMaxExtent: true); + _processSelectionAndCurrentCell( + dataGridConfiguration, RowColumnIndex(lastRowIndex, lastCellIndex)); + } else { + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, lastCellIndex)); + } + } + + void _processHomeKey( + DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int firstCellIndex = + selection_helper.getFirstCellIndex(dataGridConfiguration); + final bool needToScrollToMinOrMaxExtend = + dataGridConfiguration.container.extentWidth > + dataGridConfiguration.viewWidth; + + if (needToScrollToMinOrMaxExtend) { + selection_helper.scrollInViewFromRight(dataGridConfiguration, + needToScrollToMinExtent: true); + } + + if (keyEvent.isControlPressed && + keyEvent.logicalKey != LogicalKeyboardKey.arrowLeft) { + final int firstRowIndex = + selection_helper.getFirstNavigatingRowIndex(dataGridConfiguration); + selection_helper.scrollInViewFromDown(dataGridConfiguration, + needToScrollToMinExtent: true); + _processSelectionAndCurrentCell( + dataGridConfiguration, RowColumnIndex(firstRowIndex, firstCellIndex)); + } else { + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, firstCellIndex)); + } + } + + void _processPageDown() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int index = selection_helper.getNextPageIndex(dataGridConfiguration); + if (currentCell.rowIndex != index && index != -1) { + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(index, currentCell.columnIndex)); + } + } + + void _processPageUp() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int index = + selection_helper.getPreviousPageIndex(dataGridConfiguration); + if (currentCell.rowIndex != index) { + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(index, currentCell.columnIndex)); + } + } + + void _processKeyDown(RawKeyEvent keyEvent) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int nextRowIndex = selection_helper.getNextRowIndex( + dataGridConfiguration, currentCell.rowIndex); + final int lastRowIndex = + selection_helper.getLastNavigatingRowIndex(dataGridConfiguration); + int nextColumnIndex = currentCell.columnIndex; + + if (nextColumnIndex <= 0) { + nextColumnIndex = + selection_helper.getFirstCellIndex(dataGridConfiguration); + } + + if (nextRowIndex > lastRowIndex || currentCell.rowIndex == nextRowIndex) { + return; + } + + if (keyEvent.isControlPressed) { + selection_helper.scrollInViewFromTop(dataGridConfiguration, + needToScrollToMaxExtent: true); + _processSelectionAndCurrentCell( + dataGridConfiguration, RowColumnIndex(lastRowIndex, nextColumnIndex), + isShiftKeyPressed: keyEvent.isShiftPressed); + } else { + _processSelectionAndCurrentCell( + dataGridConfiguration, RowColumnIndex(nextRowIndex, nextColumnIndex), + isShiftKeyPressed: keyEvent.isShiftPressed); + } + } + + void _processKeyUp(RawKeyEvent keyEvent) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int previousRowIndex = selection_helper.getPreviousRowIndex( + dataGridConfiguration, currentCell.rowIndex); + + if (previousRowIndex == currentCell.rowIndex) { + return; + } + + if (keyEvent.isControlPressed) { + final int firstRowIndex = + selection_helper.getFirstRowIndex(dataGridConfiguration); + selection_helper.scrollInViewFromDown(dataGridConfiguration, + needToScrollToMinExtent: true); + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(firstRowIndex, currentCell.columnIndex), + isShiftKeyPressed: keyEvent.isShiftPressed); + } else { + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(previousRowIndex, currentCell.columnIndex), + isShiftKeyPressed: keyEvent.isShiftPressed); + } + } + + void _processKeyRight( + DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { + return; + } + + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int lastCellIndex = + selection_helper.getLastCellIndex(dataGridConfiguration); + final int nextCellIndex = selection_helper.getNextColumnIndex( + dataGridConfiguration, currentCell.columnIndex); + + if (currentCell.rowIndex <= + grid_helper.getHeaderIndex(dataGridConfiguration) || + nextCellIndex > lastCellIndex || + currentCell.columnIndex == nextCellIndex) { + return; + } + + if (keyEvent.isControlPressed) { + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + _processHomeKey(dataGridConfiguration, keyEvent); + } else { + _processEndKey(dataGridConfiguration, keyEvent); + } + } else { + currentCell._processCurrentCell(dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, nextCellIndex), + isSelectionChanged: true); + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + notifyListeners(); + } + } + } + + void _processKeyLeft( + DataGridConfiguration dataGridConfiguration, RawKeyEvent keyEvent) { + if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { + return; + } + + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int previousCellIndex = selection_helper.getPreviousColumnIndex( + dataGridConfiguration, currentCell.columnIndex); + + if (currentCell.rowIndex <= + grid_helper.getHeaderIndex(dataGridConfiguration) || + previousCellIndex < + grid_helper.resolveToStartColumnIndex(dataGridConfiguration)) { + return; + } + + if (keyEvent.isControlPressed) { + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + _processEndKey(dataGridConfiguration, keyEvent); + } else { + _processHomeKey(dataGridConfiguration, keyEvent); + } + } else { + currentCell._processCurrentCell(dataGridConfiguration, + RowColumnIndex(currentCell.rowIndex, previousCellIndex), + isSelectionChanged: true); + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + notifyListeners(); + } + } + } + + void _processKeyTab(RawKeyEvent keyEvent) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + final int lastCellIndex = + selection_helper.getLastCellIndex(dataGridConfiguration); + int firstCellIndex = + selection_helper.getFirstCellIndex(dataGridConfiguration); + final int firstRowIndex = + selection_helper.getFirstRowIndex(dataGridConfiguration); + + if (dataGridConfiguration.navigationMode == GridNavigationMode.row || + (currentCell.rowIndex < 0 && currentCell.columnIndex < 0)) { + _processSelectionAndCurrentCell( + dataGridConfiguration, RowColumnIndex(firstRowIndex, firstCellIndex)); + notifyListeners(); + return; + } + + final bool needToScrollToMinOrMaxExtend = + dataGridConfiguration.container.extentWidth > + dataGridConfiguration.viewWidth; + + if (keyEvent.isShiftPressed) { + if (currentCell.columnIndex == firstCellIndex && + currentCell.rowIndex == firstRowIndex) { + return; + } + + if (currentCell.columnIndex == firstCellIndex) { + final int previousRowIndex = selection_helper.getPreviousRowIndex( + dataGridConfiguration, currentCell.rowIndex); + if (needToScrollToMinOrMaxExtend) { + selection_helper.scrollInViewFromLeft(dataGridConfiguration, + needToScrollMaxExtent: needToScrollToMinOrMaxExtend); + } + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(previousRowIndex, lastCellIndex)); + } else { + _processKeyLeft(dataGridConfiguration, keyEvent); + } + } else { + if (currentCell.columnIndex == lastCellIndex) { + final int nextRowIndex = selection_helper.getNextRowIndex( + dataGridConfiguration, currentCell.rowIndex); + if (needToScrollToMinOrMaxExtend) { + selection_helper.scrollInViewFromRight(dataGridConfiguration, + needToScrollToMinExtent: needToScrollToMinOrMaxExtend); + } + + firstCellIndex = (nextRowIndex == currentCell.rowIndex && + lastCellIndex == currentCell.columnIndex) + ? currentCell.columnIndex + : firstCellIndex; + + _processSelectionAndCurrentCell(dataGridConfiguration, + RowColumnIndex(nextRowIndex, firstCellIndex)); + } else { + _processKeyRight(dataGridConfiguration, keyEvent); + } + } + } + + void _processSelectedAll() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.selectionMode != SelectionMode.multiple) { + return; + } + + final List addedItems = []; + final List removeItems = []; + if (dataGridConfiguration.onSelectionChanging != null || + dataGridConfiguration.onSelectionChanged != null) { + addedItems.addAll(effectiveRows(dataGridConfiguration.source)); + } + + if (_raiseSelectionChanging(oldItems: removeItems, newItems: addedItems)) { + for (final DataGridRow record + in effectiveRows(dataGridConfiguration.source)) { + if (!_selectedRows.contains(record)) { + final int rowIndex = + selection_helper.resolveToRowIndex(dataGridConfiguration, record); + if (rowIndex != -1 && + dataGridConfiguration.rowGenerator.items + .any((DataRowBase row) => row.rowIndex == rowIndex)) { + _setRowSelection(rowIndex, dataGridConfiguration, true); + _selectedRows.add(record); + } else { + _selectedRows.add(record); + } + } + } + dataGridConfiguration.controller.selectedRows.clear(); + dataGridConfiguration.controller.selectedRows + .addAll(effectiveRows(dataGridConfiguration.source)); + _refreshSelection(); + dataGridConfiguration.container.isDirty = true; + _updateCheckboxStateOnHeader(dataGridConfiguration); + + notifyListeners(); + _raiseSelectionChanged(newItems: addedItems, oldItems: removeItems); + } + } + + void _processSpaceKey() { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.selectionMode == SelectionMode.single) { + return; + } + + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + _applySelection( + RowColumnIndex(currentCell.rowIndex, currentCell.columnIndex)); + } + + void _processSelectionAndCurrentCell( + DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, + {bool isShiftKeyPressed = false, + bool isProgrammatic = false}) { + final RowColumnIndex previousRowColumnIndex = RowColumnIndex( + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex); + if (isProgrammatic) { + dataGridConfiguration.currentCell + ._moveCurrentCellTo(dataGridConfiguration, rowColumnIndex); + } else { + dataGridConfiguration.currentCell + ._processCurrentCell(dataGridConfiguration, rowColumnIndex); + } + + if (dataGridConfiguration.selectionMode == SelectionMode.multiple) { + dataGridConfiguration.currentCell._updateBorderForMultipleSelection( + dataGridConfiguration, + nextRowColumnIndex: rowColumnIndex, + previousRowColumnIndex: previousRowColumnIndex); + if (isShiftKeyPressed) { + _processSelection( + dataGridConfiguration, rowColumnIndex, previousRowColumnIndex); + } else { + notifyListeners(); + } + } + + _pressedRowColumnIndex = rowColumnIndex; + } + + bool _raiseSelectionChanging( + {List oldItems = const [], + List newItems = const []}) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.onSelectionChanging == null) { + return true; + } + + return dataGridConfiguration.onSelectionChanging!(newItems, oldItems); + } + + void _raiseSelectionChanged( + {List oldItems = const [], + List newItems = const []}) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (dataGridConfiguration.onSelectionChanged == null) { + return; + } + + dataGridConfiguration.onSelectionChanged!(newItems, oldItems); + } + + /// Refresh the selection state in header and cell check box when selection + /// gets cleared. + void _refreshCheckboxSelection() { + notifyListeners(); + } +} + +/// ToDo +class CurrentCellManager { + /// ToDo + CurrentCellManager(this.dataGridStateDetails); + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + /// ToDo + int rowIndex = -1; + + /// ToDo + int columnIndex = -1; + + /// Current editing dataCell. + DataCellBase? dataCell; + + /// Indicate the any [DataGridCell] is in editing state. + bool isEditing = false; + + bool _handlePointerOperation(DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex) { + if (dataGridConfiguration.allowSwiping) { + dataGridConfiguration.container.resetSwipeOffset(); + } + final RowColumnIndex previousRowColumnIndex = + RowColumnIndex(rowIndex, columnIndex); + if (!rowColumnIndex.equals(previousRowColumnIndex) && + dataGridConfiguration.navigationMode != GridNavigationMode.row) { + if (!_raiseCurrentCellActivating(rowColumnIndex)) { + return false; + } + _setCurrentCell(dataGridConfiguration, rowColumnIndex.rowIndex, + rowColumnIndex.columnIndex); + _raiseCurrentCellActivated(previousRowColumnIndex); + } else if (dataGridConfiguration.navigationMode == GridNavigationMode.row && + rowIndex != rowColumnIndex.rowIndex) { + _updateCurrentRowColumnIndex( + rowColumnIndex.rowIndex, rowColumnIndex.columnIndex); + _updateBorderForMultipleSelection(dataGridConfiguration, + previousRowColumnIndex: previousRowColumnIndex, + nextRowColumnIndex: rowColumnIndex); + } + + return true; + } + + void _setCurrentCell(DataGridConfiguration dataGridConfiguration, + int rowIndex, int columnIndex, + [bool needToUpdateColumn = true]) { + if (this.rowIndex == rowIndex && this.columnIndex == columnIndex) { + return; + } + + _removeCurrentCell(dataGridConfiguration, needToUpdateColumn); + _updateCurrentRowColumnIndex(rowIndex, columnIndex); + _updateCurrentCell( + dataGridConfiguration, rowIndex, columnIndex, needToUpdateColumn); + } + + void _updateCurrentCell(DataGridConfiguration dataGridConfiguration, + int rowIndex, int columnIndex, + [bool needToUpdateColumn = true]) { + final DataRowBase? dataRowBase = + _getDataRow(dataGridConfiguration, rowIndex); + if (dataRowBase != null && needToUpdateColumn) { + final DataCellBase? dataCellBase = _getDataCell(dataRowBase, columnIndex); + if (dataCellBase != null) { + dataCell = dataCellBase; + setCurrentCellDirty(dataRowBase, dataCellBase, true); + dataCellBase.updateColumn(); + } + } + + updateCurrentCellIndex( + dataGridConfiguration.controller, + grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex))); + } + + void _removeCurrentCell(DataGridConfiguration dataGridConfiguration, + [bool needToUpdateColumn = true]) { + if (rowIndex == -1 && columnIndex == -1) { + return; + } + + final DataRowBase? dataRowBase = + _getDataRow(dataGridConfiguration, rowIndex); + if (dataRowBase != null && needToUpdateColumn) { + final DataCellBase? dataCellBase = _getDataCell(dataRowBase, columnIndex); + if (dataCellBase != null) { + setCurrentCellDirty(dataRowBase, dataCellBase, false); + dataCellBase.updateColumn(); + } + } + + _updateCurrentRowColumnIndex(-1, -1); + updateCurrentCellIndex( + dataGridConfiguration.controller, RowColumnIndex(-1, -1)); + } + + DataRowBase? _getDataRow( + DataGridConfiguration dataGridConfiguration, int rowIndex) { + final List dataRows = dataGridConfiguration.rowGenerator.items; + if (dataRows.isEmpty) { + return null; + } + + return dataRows + .firstWhereOrNull((DataRowBase row) => row.rowIndex == rowIndex); + } + + DataCellBase? _getDataCell(DataRowBase dataRow, int columnIndex) { + if (dataRow.visibleColumns.isEmpty) { + return null; + } + + return dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); + } + + void _updateCurrentRowColumnIndex(int rowIndex, int columnIndex) { + this.rowIndex = rowIndex; + this.columnIndex = columnIndex; + } + + /// ToDo + void setCurrentCellDirty( + DataRowBase? dataRow, DataCellBase? dataCell, bool enableCurrentCell) { + dataCell?.isCurrentCell = enableCurrentCell; + dataCell?.isDirty = true; + dataRow?.isCurrentRow = enableCurrentCell; + dataRow?.isDirty = true; + } + + bool _raiseCurrentCellActivating(RowColumnIndex rowColumnIndex) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.onCurrentCellActivating == null) { + return true; + } + + final RowColumnIndex newRowColumnIndex = grid_helper + .resolveToRecordRowColumnIndex(dataGridConfiguration, rowColumnIndex); + final RowColumnIndex oldRowColumnIndex = + grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex)); + return dataGridConfiguration.onCurrentCellActivating!( + newRowColumnIndex, oldRowColumnIndex); + } + + void _raiseCurrentCellActivated(RowColumnIndex previousRowColumnIndex) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.onCurrentCellActivated == null) { + return; + } + + final RowColumnIndex newRowColumnIndex = + grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex)); + final RowColumnIndex oldRowColumnIndex = + grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, previousRowColumnIndex); + dataGridConfiguration.onCurrentCellActivated!( + newRowColumnIndex, oldRowColumnIndex); + } + + void _moveCurrentCellTo(DataGridConfiguration dataGridConfiguration, + RowColumnIndex nextRowColumnIndex, + {bool isSelectionChanged = false, bool needToUpdateColumn = true}) { + final RowColumnIndex previousRowColumnIndex = RowColumnIndex( + dataGridConfiguration.currentCell.rowIndex, + dataGridConfiguration.currentCell.columnIndex); + + _scrollVertical(dataGridConfiguration, nextRowColumnIndex); + _scrollHorizontal(dataGridConfiguration, nextRowColumnIndex); + + if (dataGridConfiguration.navigationMode == GridNavigationMode.cell) { + _setCurrentCell(dataGridConfiguration, nextRowColumnIndex.rowIndex, + nextRowColumnIndex.columnIndex, needToUpdateColumn); + } else { + _updateCurrentRowColumnIndex( + nextRowColumnIndex.rowIndex, nextRowColumnIndex.columnIndex); + } + + if (dataGridConfiguration.selectionMode != SelectionMode.none && + dataGridConfiguration.selectionMode != SelectionMode.multiple && + !isSelectionChanged) { + final SelectionManagerBase rowSelectionController = + dataGridConfiguration.rowSelectionManager; + if (rowSelectionController is RowSelectionManager) { + rowSelectionController + .._processSelection( + dataGridConfiguration, nextRowColumnIndex, previousRowColumnIndex) + .._pressedRowColumnIndex = nextRowColumnIndex; + } + } + } + + void _processCurrentCell(DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, + {bool isSelectionChanged = false}) { + if (dataGridConfiguration.navigationMode == GridNavigationMode.row) { + _moveCurrentCellTo(dataGridConfiguration, rowColumnIndex, + isSelectionChanged: isSelectionChanged); + return; + } + + if (_raiseCurrentCellActivating(rowColumnIndex)) { + _moveCurrentCellTo(dataGridConfiguration, rowColumnIndex, + isSelectionChanged: isSelectionChanged); + _raiseCurrentCellActivated(rowColumnIndex); + } + } + + void _scrollHorizontal(DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex) { + if (rowColumnIndex.columnIndex < columnIndex) { + if (selection_helper.needToScrollLeft( + dataGridConfiguration, rowColumnIndex)) { + selection_helper.scrollInViewFromRight(dataGridConfiguration, + previousCellIndex: rowColumnIndex.columnIndex); + } + } + + if (rowColumnIndex.columnIndex > columnIndex) { + if (selection_helper.needToScrollRight( + dataGridConfiguration, rowColumnIndex)) { + selection_helper.scrollInViewFromLeft(dataGridConfiguration, + nextCellIndex: rowColumnIndex.columnIndex); + } + } + } + + void _scrollVertical(DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex) { + if (rowColumnIndex.rowIndex < rowIndex) { + if (selection_helper.needToScrollUp( + dataGridConfiguration, rowColumnIndex.rowIndex)) { + selection_helper.scrollInViewFromDown(dataGridConfiguration, + previousRowIndex: rowColumnIndex.rowIndex); + } + } + + if (rowColumnIndex.rowIndex > rowIndex) { + if (selection_helper.needToScrollDown( + dataGridConfiguration, rowColumnIndex.rowIndex)) { + selection_helper.scrollInViewFromTop(dataGridConfiguration, + nextRowIndex: rowColumnIndex.rowIndex); + } + } + } + + void _updateBorderForMultipleSelection( + DataGridConfiguration dataGridConfiguration, + {RowColumnIndex? previousRowColumnIndex, + RowColumnIndex? nextRowColumnIndex}) { + if (dataGridConfiguration.isDesktop && + dataGridConfiguration.navigationMode == GridNavigationMode.row && + dataGridConfiguration.selectionMode == SelectionMode.multiple) { + if (previousRowColumnIndex != null) { + dataGridConfiguration.currentCell + ._getDataRow(dataGridConfiguration, previousRowColumnIndex.rowIndex) + ?.isDirty = true; + } + + if (nextRowColumnIndex != null) { + final int firstVisibleColumnIndex = + grid_helper.resolveToStartColumnIndex(dataGridConfiguration); + _updateCurrentRowColumnIndex( + nextRowColumnIndex.rowIndex >= 0 + ? nextRowColumnIndex.rowIndex + : rowIndex, + nextRowColumnIndex.columnIndex >= 0 + ? nextRowColumnIndex.columnIndex + : firstVisibleColumnIndex); + dataGridConfiguration.currentCell + ._getDataRow( + dataGridConfiguration, + nextRowColumnIndex.rowIndex >= 0 + ? nextRowColumnIndex.rowIndex + : rowIndex) + ?.isDirty = true; + } + } + } + + // ------------------------------Editing------------------------------------- + /// ToDo + void onCellBeginEdit( + {DataCellBase? editingDataCell, + RowColumnIndex? editingRowColumnIndex, + bool isProgrammatic = false, + bool needToResolveIndex = true}) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + + final bool checkEditingIsEnabled = dataGridConfiguration.allowEditing && + dataGridConfiguration.selectionMode != SelectionMode.none && + dataGridConfiguration.navigationMode != GridNavigationMode.row; + + bool checkDataCellIsValidForEditing(DataCellBase? editingDataCell) => + editingDataCell != null && + editingDataCell.gridColumn!.allowEditing && + !editingDataCell.isEditing && + editingDataCell.renderer != null && + editingDataCell.renderer!.isEditable && + editingDataCell.dataRow!.rowType == RowType.dataRow; + + if (!checkEditingIsEnabled || + (!isProgrammatic && !checkDataCellIsValidForEditing(editingDataCell))) { + return; + } + + // Enable the current cell first to start the on programmatic case. + if (isProgrammatic) { + if (editingRowColumnIndex == null || + editingRowColumnIndex.rowIndex.isNegative || + editingRowColumnIndex.columnIndex.isNegative) { + return; + } + + // When editing is initiate from the f2 key, we need not to to resolve + // the editing row column index because its already resolved based on the + // SfDataGrid. + editingRowColumnIndex = needToResolveIndex + ? grid_helper.resolveToRowColumnIndex( + dataGridConfiguration, editingRowColumnIndex) + : editingRowColumnIndex; + + if (editingRowColumnIndex.rowIndex.isNegative || + editingRowColumnIndex.columnIndex.isNegative || + editingRowColumnIndex.columnIndex > + selection_helper.getLastCellIndex(dataGridConfiguration) || + editingRowColumnIndex.rowIndex > + selection_helper.getLastRowIndex(dataGridConfiguration)) { + return; + } + + // If the editing is initiate from f2 key, need not to process the + // handleTap. + if (needToResolveIndex) { + dataGridConfiguration.rowSelectionManager + .handleTap(editingRowColumnIndex); + } else { + // Need to skip the editing when current cell is not in view and we + // process initiate the editing from f2 key. + final DataRowBase? dataRow = + _getDataRow(dataGridConfiguration, editingRowColumnIndex.rowIndex); + if (dataRow != null) { + dataCell = _getDataCell(dataRow, editingRowColumnIndex.columnIndex); + } else { + return; + } + } + + editingDataCell = dataCell; + } + + if (!checkDataCellIsValidForEditing(editingDataCell)) { + return; + } + + editingRowColumnIndex = grid_helper.resolveToRecordRowColumnIndex( + dataGridConfiguration, + RowColumnIndex(editingDataCell!.rowIndex, editingDataCell.columnIndex)); + + if (editingRowColumnIndex.rowIndex.isNegative || + editingRowColumnIndex.columnIndex.isNegative) { + return; + } + + final bool beginEdit = _raiseCellBeginEdit( + dataGridConfiguration, editingRowColumnIndex, editingDataCell); + + if (beginEdit) { + void submitCell() { + onCellSubmit(dataGridConfiguration); + } + + final Widget? child = dataGridConfiguration.source.buildEditWidget( + editingDataCell.dataRow!.dataGridRow!, + editingRowColumnIndex, + editingDataCell.gridColumn!, + submitCell); + + /// If child is null, we will not initiate the editing + if (child != null) { + /// Wrapped the editing widget inside the FocusScope. + /// To bring the focus automatically to editing widget. + /// canRequestFocus need to set true to auto detect the focus + /// User need to set the autoFocus to true in their editable widget. + editingDataCell.editingWidget = + FocusScope(canRequestFocus: true, child: child); + editingDataCell.isEditing = + editingDataCell.dataRow!.isEditing = isEditing = true; + + notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, + rowColumnIndex: editingRowColumnIndex, propertyName: 'editing'); + } + } + } + + bool _raiseCellBeginEdit(DataGridConfiguration dataGridConfiguration, + RowColumnIndex rowColumnIndex, DataCellBase dataCell) { + return dataGridConfiguration.source.onCellBeginEdit( + dataCell.dataRow!.dataGridRow!, rowColumnIndex, dataCell.gridColumn!); + } + + /// Help to end-edit editable widget and refresh the [DataGridCell]. + /// + /// * isCellCancelEdit - Used to avoid the onCellSubmit behaviour and perform + /// the cellCancelEdit behaviour, + /// * Default value is [false]. + /// Case: + /// 1) Keyboard navigation - Escape key + /// + /// * cancelCanCellSubmit - Used to skip the call canCellSubmit. + /// * Default value is [false]. + /// Case: + /// 1) In keyboard navigation we will call the canCellSubmit before the + /// processing the key. So, we need to skip the canCellSubmit second time. so + /// if we pass the cancelCanCellSubmit to false its will skip it. + /// + /// * canRefresh - Used to skip the call notifyListener + /// * Default value is [true]. + /// Case: + /// 1) _onCellSubmit is call from handleDataGridSource we no need to call the + /// _notifyDataGridPropertyChangeListeners to refresh twice.By, set value false + /// it will skip the refreshing. + void onCellSubmit(DataGridConfiguration dataGridConfiguration, + {bool isCellCancelEdit = false, + bool cancelCanSubmitCell = false, + bool canRefresh = true}) { + if (!isEditing) { + return; + } + + final DataRowBase? dataRow = _getEditingRow(dataGridConfiguration); + + if (dataRow == null) { + return; + } + + final DataCellBase? dataCell = _getEditingCell(dataRow); + + if (dataCell == null || !dataCell.isEditing) { + return; + } + + if (isEditing) { + final RowColumnIndex rowColumnIndex = + grid_helper.resolveToRecordRowColumnIndex(dataGridConfiguration, + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex)); + + if (rowColumnIndex.rowIndex.isNegative || + rowColumnIndex.columnIndex.isNegative) { + return; + } + + final DataGridRow dataGridRow = dataCell.dataRow!.dataGridRow!; + + void resetEditing() { + dataCell.editingWidget = null; + dataCell.isDirty = true; + dataCell.isEditing = dataRow.isEditing = isEditing = false; + } + + if (!isCellCancelEdit) { + bool canSubmitCell = false; + + /// Via keyboard navigation we will check the canCellSubmit before + /// moving to other cell or another row. so we need to skip the + /// canCellSubmit method calling once again + if (!cancelCanSubmitCell) { + canSubmitCell = dataGridConfiguration.source + .canSubmitCell(dataGridRow, rowColumnIndex, dataCell.gridColumn!); + } else { + canSubmitCell = true; + } + if (canSubmitCell) { + resetEditing(); + dataGridConfiguration.source + .onCellSubmit(dataGridRow, rowColumnIndex, dataCell.gridColumn!); + } + } else { + resetEditing(); + dataGridConfiguration.source.onCellCancelEdit( + dataGridRow, rowColumnIndex, dataCell.gridColumn!); + } + + if (canRefresh) { + /// Refresh the visible [DataRow]'s on editing the [DataCell] when + /// sorting enabled + if (dataGridConfiguration.allowSorting) { + updateDataSource(dataGridConfiguration.source); + dataGridConfiguration.container + ..updateDataGridRows(dataGridConfiguration) + ..isDirty = true; + } + + notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, + rowColumnIndex: rowColumnIndex, propertyName: 'editing'); + } + + if (dataGridConfiguration.dataGridFocusNode != null && + !dataGridConfiguration.dataGridFocusNode!.hasPrimaryFocus) { + dataGridConfiguration.dataGridFocusNode!.requestFocus(); + } + } + } + + DataRowBase? _getEditingRow(DataGridConfiguration dataGridConfiguration) { + return dataGridConfiguration.rowGenerator.items + .firstWhereOrNull((DataRowBase dataRow) => dataRow.isEditing); + } + + DataCellBase? _getEditingCell(DataRowBase dataRow) { + return dataRow.visibleColumns + .firstWhereOrNull((DataCellBase dataCell) => dataCell.isEditing); + } + + /// ToDo + bool canSubmitCell(DataGridConfiguration dataGridConfiguration) { + final DataRowBase? dataRow = _getEditingRow(dataGridConfiguration); + + if (dataRow == null) { + return false; + } + + final DataCellBase? dataCell = _getEditingCell(dataRow); + + if (dataCell == null || !dataCell.isEditing) { + return false; + } + + final RowColumnIndex rowColumnIndex = + grid_helper.resolveToRecordRowColumnIndex(dataGridConfiguration, + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex)); + + if (rowColumnIndex.rowIndex.isNegative || + rowColumnIndex.columnIndex.isNegative) { + return false; + } + + final DataGridRow dataGridRow = dataCell.dataRow!.dataGridRow!; + + return dataGridConfiguration.source + .canSubmitCell(dataGridRow, rowColumnIndex, dataCell.gridColumn!); + } +} + +/// +void onRowColumnChanged(DataGridConfiguration dataGridConfiguration, + int recordLength, int columnLength) { + if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { + final RowSelectionManager rowSelectionManager = + dataGridConfiguration.rowSelectionManager as RowSelectionManager; + rowSelectionManager._onRowColumnChanged(recordLength, columnLength); + } +} + +/// +void handleSelectionPropertyChanged( + {required DataGridConfiguration dataGridConfiguration, + RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { + final RowSelectionManager rowSelectionManager = + dataGridConfiguration.rowSelectionManager as RowSelectionManager; + rowSelectionManager._handleSelectionPropertyChanged( + propertyName: propertyName, + rowColumnIndex: rowColumnIndex, + recalculateRowHeight: recalculateRowHeight); + } +} + +/// +void updateSelectionController( + {required DataGridConfiguration dataGridConfiguration, + bool isSelectionModeChanged = false, + bool isNavigationModeChanged = false, + bool isDataSourceChanged = false}) { + if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { + final RowSelectionManager rowSelectionManager = + dataGridConfiguration.rowSelectionManager as RowSelectionManager; + rowSelectionManager._updateSelectionController( + isDataSourceChanged: isDataSourceChanged, + isNavigationModeChanged: isNavigationModeChanged, + isSelectionModeChanged: isSelectionModeChanged); + } +} + +/// ToDo +void processSelectionAndCurrentCell( + DataGridConfiguration dataGridConfiguration, RowColumnIndex rowColumnIndex, + {bool isShiftKeyPressed = false, bool isProgrammatic = false}) { + if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { + final RowSelectionManager rowSelectionManager = + dataGridConfiguration.rowSelectionManager as RowSelectionManager; + rowSelectionManager._processSelectionAndCurrentCell( + dataGridConfiguration, rowColumnIndex, + isShiftKeyPressed: isShiftKeyPressed, isProgrammatic: isProgrammatic); + } +} + +/// Need to clarify +bool isSelectedRow( + DataGridConfiguration dataGridConfiguration, DataGridRow dataGridRow) { + if (dataGridConfiguration.rowSelectionManager is RowSelectionManager) { + final RowSelectionManager rowSelectionManager = + dataGridConfiguration.rowSelectionManager as RowSelectionManager; + return rowSelectionManager._selectedRows.contains(dataGridRow); + } + + return false; +} + +/// Set the DataGridStateDetails in SelectionManagerBase +void setStateDetailsInSelectionManagerBase( + SelectionManagerBase selectionManagerBase, + DataGridStateDetails dataGridStateDetails) { + selectionManagerBase._dataGridStateDetails = dataGridStateDetails; +} + +/// Helps to handle the selection from header and grid cell check box +/// interaction. +void handleSelectionFromCheckbox(DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, bool? oldValue, bool? newValue) { + if (dataGridConfiguration.rowSelectionManager is RowSelectionManager && + dataGridConfiguration.selectionMode != SelectionMode.none) { + final RowSelectionManager rowSelectionManager = + dataGridConfiguration.rowSelectionManager as RowSelectionManager; + if (dataGridConfiguration.selectionMode == SelectionMode.single) { + if (dataCell.cellType == CellType.checkboxCell && !oldValue!) { + dataCell.onTouchUp(); + } + } else if (dataGridConfiguration.selectionMode == + SelectionMode.singleDeselect) { + if (dataCell.cellType == CellType.checkboxCell && oldValue != newValue) { + dataCell.onTouchUp(); + } + } else { + if (dataCell.cellType == CellType.headerCell) { + if (oldValue == null || oldValue == false) { + dataGridConfiguration.headerCheckboxState = true; + dataCell.updateColumn(); + rowSelectionManager._processSelectedAll(); + } else if (oldValue) { + dataGridConfiguration.headerCheckboxState = false; + dataCell.updateColumn(); + final List oldSelectedItems = + rowSelectionManager._selectedRows; + if (rowSelectionManager._raiseSelectionChanging( + newItems: [], oldItems: oldSelectedItems)) { + rowSelectionManager._clearSelectedRows(dataGridConfiguration); + rowSelectionManager._refreshCheckboxSelection(); + rowSelectionManager._raiseSelectionChanged( + oldItems: oldSelectedItems, newItems: []); + } + } + } else { + dataCell.onTouchUp(); + } + } + } +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart new file mode 100644 index 000000000..77a93d0d4 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/sfdatagrid.dart @@ -0,0 +1,3817 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../datagrid.dart'; +import '../datapager/sfdatapager.dart'; +import '../grid_common/line_size_host.dart'; +import '../grid_common/row_column_index.dart'; +import '../grid_common/scroll_axis.dart'; +import 'helper/callbackargs.dart'; +import 'helper/datagrid_configuration.dart'; +import 'helper/datagrid_helper.dart' as grid_helper; +import 'helper/enums.dart'; +import 'runtime/cell_renderers.dart'; +import 'runtime/column.dart'; +import 'runtime/generator.dart'; +import 'selection/selection_manager.dart'; +import 'selection/selection_manager.dart' as selection_manager; +import 'widgets/scrollview_widget.dart'; + +/// Signature for [SfDataGrid.onQueryRowHeight] callback +typedef QueryRowHeightCallback = double Function(RowHeightDetails details); + +/// Signature for [SfDataGrid.onSelectionChanging] callback. +typedef SelectionChangingCallback = bool Function( + List addedRows, List removedRows); + +/// Signature for [SfDataGrid.onSelectionChanged] callback. +typedef SelectionChangedCallback = void Function( + List addedRows, List removedRows); + +/// Signature for [SfDataGrid.onCurrentCellActivating] callback. +typedef CurrentCellActivatingCallback = bool Function( + RowColumnIndex newRowColumnIndex, RowColumnIndex oldRowColumnIndex); + +/// Signature for [SfDataGrid.onCurrentCellActivated] callback. +typedef CurrentCellActivatedCallback = void Function( + RowColumnIndex newRowColumnIndex, RowColumnIndex oldRowColumnIndex); + +/// Signature for [SfDataGrid.onCellTap] and [SfDataGrid.onCellSecondaryTap] +/// callbacks. +typedef DataGridCellTapCallback = void Function(DataGridCellTapDetails details); + +/// Signature for [SfDataGrid.onCellDoubleTap] callback. +typedef DataGridCellDoubleTapCallback = void Function( + DataGridCellDoubleTapDetails details); + +/// Signature for [SfDataGrid.onCellLongPress] callback. +typedef DataGridCellLongPressCallback = void Function( + DataGridCellLongPressDetails details); + +/// The signature of [DataGridSource.handleLoadMoreRows] function. +typedef LoadMoreRows = Future Function(); + +/// Signature for the [SfDataGrid.loadMoreViewBuilder] function. +typedef LoadMoreViewBuilder = Widget? Function( + BuildContext context, LoadMoreRows loadMoreRows); + +/// Signature for the [SfDataGrid.onSwipeStart] callback. +typedef DataGridSwipeStartCallback = bool Function( + DataGridSwipeStartDetails swipeStartDetails); + +/// Signature for the [SfDataGrid.onSwipeUpdate] callback. +typedef DataGridSwipeUpdateCallback = bool Function( + DataGridSwipeUpdateDetails swipeUpdateDetails); + +/// Signature for the [SfDataGrid.onSwipeEnd] callback. +typedef DataGridSwipeEndCallback = void Function( + DataGridSwipeEndDetails swipeEndDetails); + +/// Holds the arguments for the [SfDataGrid.startSwipeActionsBuilder] callback. +typedef DataGridSwipeActionsBuilder = Widget? Function( + BuildContext context, DataGridRow dataGridRow, int rowIndex); + +/// The signature of [DataGridSource.canSubmitCell] and +/// [DataGridSource.onCellSubmit] methods. +typedef CellSubmit = void Function(); + +/// Signature for the [SfDataGrid.onColumnResizeStart] callback. +typedef ColumnResizeStartCallback = bool Function( + ColumnResizeStartDetails details); + +/// Signature for the [SfDataGrid.onColumnResizeUpdate] callback. +typedef ColumnResizeUpdateCallback = bool Function( + ColumnResizeUpdateDetails details); + +/// Signature for the [SfDataGrid.onColumnResizeEnd] callback. +typedef ColumnResizeEndCallback = void Function(ColumnResizeEndDetails details); + +/// Signature for the [DataGridSourceChangeNotifier] listener. +typedef _DataGridSourceListener = void Function( + {RowColumnIndex? rowColumnIndex}); + +/// Signature for the [DataGridSourceChangeNotifier] listener. +typedef _DataGridPropertyChangeListener = void Function( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight}); + +/// Row configuration and cell data for a [SfDataGrid]. +/// +/// Return this list of [DataGridRow] objects to [DataGridSource.rows] property. +/// +/// The data for each row can be passed as the cells argument to the +/// constructor of each [DataGridRow] object. +class DataGridRow { + /// ToDo + const DataGridRow({required List cells}) : _cells = cells; + + /// The data for this row. + /// + /// There must be exactly as many cells as there are columns in the + /// [SfDataGrid]. + final List _cells; + + /// Returns the collection of [DataGridCell] which is created for + /// [DataGridRow]. + List getCells() { + return _cells; + } +} + +/// The data for a cell of a [SfDataGrid]. +/// +/// The list of [DataGridCell] objects should be passed as the cells argument +/// to the constructor of each [DataGridRow] object. +@optionalTypeArgs +class DataGridCell { + /// ToDo + const DataGridCell({required this.columnName, required this.value}); + + /// The name of a column + final String columnName; + + /// The value of a cell. + /// + /// Provide value of a cell to perform the sorting for whole data available + /// in datagrid. + final T? value; +} + +/// Row configuration and widget of cell for a [SfDataGrid]. +/// +/// The widget for each cell can be provided in the [DataGridRowAdapter.cells] +/// property. +class DataGridRowAdapter { + /// ToDo + const DataGridRowAdapter({required this.cells, this.key, this.color}); + + /// ToDo + final Key? key; + + /// The color for the row. + final Color? color; + + /// The widget of each cell for this row. + /// + /// There must be exactly as many cells as there are columns in the + /// [SfDataGrid]. + final List cells; +} + +/// Row configuration for stacked header in [SfDataGrid]. The columns for this +/// stacked header row are provided in the [StackedHeaderCell] property of the +/// [StackedHeaderRow] object. +/// +/// See also: +/// +/// [StackedHeaderCell] – which provides the configuration for column in stacked +/// header row. +class StackedHeaderRow { + /// Creates the [StackedHeaderRow] for [SfDataGrid] widget. + StackedHeaderRow({required this.cells}); + + /// The collection of [StackedHeaderCell] in stacked header row. + List cells; +} + +/// Column configuration for stacked header row in `SfDataGrid`. +/// +/// See also: +/// +/// [StackedHeaderRow] – which provides configuration for stacked header row. +class StackedHeaderCell { + /// Creates the [StackedHeaderCell] for [StackedHeaderRow]. + StackedHeaderCell( + {this.text, required this.columnNames, required this.child}) { + _childColumnIndexes = []; + } + + /// The collection of string which is the [GridColumn.columnName] of the + /// columns defined in the [SfDataGrid]. + /// + /// The columns are spanned as a stacked header based on this collection. If + /// the given collection has the sequence of columns which are presented in + /// the [SfDataGrid], those columns will be spanned. Otherwise, stacked header + /// is added for each column which are not in sequence order in regular + /// columns. + final List columnNames; + + /// The widget that represents the data of this cell. + /// + /// Typically, a [Text] widget. + final Widget child; + + /// The text of the stacked header cell while exporting the [SfDataGrid] to + /// Excel or Pdf. + /// + /// This property won’t affect the UI and this is used while exporting the + /// [SfDataGrid]. + /// + /// As widget can’t be exported to Excel or PDF, set this property to give the + /// corresponding text while exporting the [SfDataGrid] to Excel or Pdf. + final String? text; + + late List _childColumnIndexes; +} + +/// Row configuration of table summary in [SfDataGrid]. +/// +/// The summary columns for this table summary row are provided in [columns] +/// property of the [GridTableSummaryRow] object. +/// +///See also, +/// +/// * [GridSummaryColumn] -Which provides the configuration for summary column in +/// table summary row. +/// * [SfDataGrid.tableSummaryRows] – Enables you to add the table summary rows +/// to DataGrid. +class GridTableSummaryRow { + /// Creates the [GridTableSummaryRow] to the [SfDataGrid]. + GridTableSummaryRow( + {this.title, + this.color, + required this.columns, + required this.position, + this.titleColumnSpan = 0, + this.showSummaryInRow = true}) + : assert(titleColumnSpan >= 0); + + /// A string that has the format and summary column information to be + /// displayed in row. + final String? title; + + /// The color of the summary row. + final Color? color; + + /// Indicates how many columns should be spanned for + /// [GridTableSummaryRow.title]. + /// + /// This is applicable only if the [GridTableSummaryRow.showSummaryInRow] + /// is false. + /// + /// If [GridTableSummaryRow.titleColumnSpan] value is greater than 0, then the + /// [GridTableSummaryRow.title] will be rendered along with summary values as + /// defined through summary columns. + final int titleColumnSpan; + + /// Indicates whether the summary value should be displayed in whole row or + /// based on individual columns. + /// + /// Defaults to true. + final bool showSummaryInRow; + + /// The collection of [GridSummaryColumn]. + final List columns; + + /// Indicates the position of the table summary row. + final GridTableSummaryRowPosition position; +} + +/// Column configuration for table summary row. +/// +/// See also, +/// +/// * [GridTableSummaryRow] – Which provides configuration for table summary row. +class GridSummaryColumn { + /// Creates the [GridSummaryColumn] to the [GridTableSummaryRow]. + const GridSummaryColumn( + {required this.name, + required this.columnName, + required this.summaryType}); + + /// Indicates the name of the summary column. + final String name; + + /// Indicates the name of the column. + /// + /// See also, + /// + /// [GridColumn.columnName] – the name of the column. + final String columnName; + + /// Indicates the summary type which should be displayed for column. + final GridSummaryType summaryType; +} + +/// A material design datagrid. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=-ULsEfjxFuY} +/// +/// DataGrid lets you display and manipulate data in a tabular view. It is built +/// from the ground up to achieve the best possible performance even when +/// loading large amounts of data. +/// +/// DataGrid supports different types of column types to populate the columns +/// for different types of data such as int, double, DateTime, String. +/// +/// [source] property enables you to populate the data for the [SfDataGrid]. +/// +/// This sample shows how to populate the data for the [SfDataGrid] and display +/// with four columns: id, name, designation and salary. +/// The columns are defined by four [GridColumn] objects. +/// +/// ``` dart +/// final List _employees = []; +/// final EmployeeDataSource _employeeDataSource = EmployeeDataSource(); +/// +/// @override +/// void initState(){ +/// super.initState(); +/// populateData(); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return SfDataGrid( +/// source: _employeeDataSource, +/// columnWidthMode: ColumnWidthMode.fill, +/// columns: [ +/// GridColumn(columnName: 'id', label: Text('ID')), +/// GridColumn(columnName: 'name', label: Text('Name')), +/// GridColumn(columnName: 'designation', label: Text('Designation')), +/// GridColumn(columnName: 'salary', label: Text('Salary')), +/// ); +/// } +/// +/// void populateData(){ +/// _employees.add(Employee(10001, 'James', 'Project Lead', 10000)); +/// _employees.add(Employee(10002, 'Kathryn', 'Manager', 10000)); +/// _employees.add(Employee(10003, 'Lara', 'Developer', 10000)); +/// _employees.add(Employee(10004, 'Michael', 'Designer', 10000)); +/// _employees.add(Employee(10005, 'Martin', 'Developer', 10000)); +/// _employees.add(Employee(10006, 'Newberry', 'Developer', 15000)); +/// _employees.add(Employee(10007, 'Balnc', 'Developer', 15000)); +/// _employees.add(Employee(10008, 'Perry', 'Developer', 15000)); +/// _employees.add(Employee(10009, 'Gable', 'Developer', 15000)); +/// _employees.add(Employee(10010, 'Grimes', 'Developer', 15000)); +/// } +/// } +/// +/// class Employee { +/// Employee(this.id, this.name, this.designation, this.salary); +/// final int id; +/// final String name; +/// final String designation; +/// final int salary; +/// } +/// +/// class EmployeeDataSource extends DataGridSource { +/// @override +/// List get rows => _employees +/// .map((dataRow) => DataGridRow(cells: [ +/// DataGridCell(columnName: 'id', value: dataRow.id), +/// DataGridCell(columnName: 'name', value: dataRow.name), +/// DataGridCell( +/// columnName: 'designation', value: dataRow.designation), +/// DataGridCell(columnName: 'salary', value: dataRow.salary), +/// ])) +/// .toList(); +/// +/// @override +/// DataGridRowAdapter? buildRow(DataGridRow row) { +/// return DataGridRowAdapter( +/// cells: row.getCells().map((dataCell) { +/// return Text(dataCell.value.toString()); +/// }).toList()); +/// } +/// } +/// +/// ``` +class SfDataGrid extends StatefulWidget { + /// Creates a widget describing a datagrid. + /// + /// The [columns] and [source] argument must be defined and must not be null. + const SfDataGrid( + {required this.source, + required this.columns, + Key? key, + this.rowHeight = double.nan, + this.headerRowHeight = double.nan, + this.defaultColumnWidth = double.nan, + this.gridLinesVisibility = GridLinesVisibility.horizontal, + this.headerGridLinesVisibility = GridLinesVisibility.horizontal, + this.columnWidthMode = ColumnWidthMode.none, + this.columnSizer, + this.columnWidthCalculationRange = + ColumnWidthCalculationRange.visibleRows, + this.selectionMode = SelectionMode.none, + this.navigationMode = GridNavigationMode.row, + this.frozenColumnsCount = 0, + this.footerFrozenColumnsCount = 0, + this.frozenRowsCount = 0, + this.footerFrozenRowsCount = 0, + this.allowSorting = false, + this.allowMultiColumnSorting = false, + this.allowTriStateSorting = false, + this.showSortNumbers = false, + this.sortingGestureType = SortingGestureType.tap, + this.stackedHeaderRows = const [], + this.selectionManager, + this.controller, + this.onQueryRowHeight, + this.onSelectionChanged, + this.onSelectionChanging, + this.onCurrentCellActivating, + this.onCurrentCellActivated, + this.onCellTap, + this.onCellDoubleTap, + this.onCellSecondaryTap, + this.onCellLongPress, + this.isScrollbarAlwaysShown = false, + this.horizontalScrollPhysics = const AlwaysScrollableScrollPhysics(), + this.verticalScrollPhysics = const AlwaysScrollableScrollPhysics(), + this.loadMoreViewBuilder, + this.allowPullToRefresh = false, + this.refreshIndicatorDisplacement = 40.0, + this.refreshIndicatorStrokeWidth = 2.0, + this.allowSwiping = false, + this.swipeMaxOffset = 200.0, + this.horizontalScrollController, + this.verticalScrollController, + this.onSwipeStart, + this.onSwipeUpdate, + this.onSwipeEnd, + this.startSwipeActionsBuilder, + this.endSwipeActionsBuilder, + this.highlightRowOnHover = true, + this.allowColumnsResizing = false, + this.columnResizeMode = ColumnResizeMode.onResize, + this.onColumnResizeStart, + this.onColumnResizeUpdate, + this.onColumnResizeEnd, + this.allowEditing = false, + this.editingGestureType = EditingGestureType.doubleTap, + this.footer, + this.footerHeight = 49.0, + this.showCheckboxColumn = false, + this.checkboxColumnSettings = const DataGridCheckboxColumnSettings(), + this.tableSummaryRows = const [], + this.rowsPerPage}) + : assert(frozenColumnsCount >= 0), + assert(footerFrozenColumnsCount >= 0), + assert(frozenRowsCount >= 0), + assert(footerFrozenRowsCount >= 0), + super(key: key); + + /// The height of each row except the column header. + /// + /// Defaults to 49.0 + final double rowHeight; + + /// The height of the column header row. + /// + /// Defaults to 56.0 + final double headerRowHeight; + + /// The collection of the [GridColumn]. + /// + /// Each column associated with its own renderer and it controls the + /// corresponding column related operations. + /// + /// Defaults to null. + final List columns; + + /// The datasource that provides the data for each row in [SfDataGrid]. Must + /// be non-null. + /// + /// This object is expected to be long-lived, not recreated with each build. + /// + /// Defaults to null + final DataGridSource source; + + /// The width of each column. + /// + /// If the [columnWidthMode] is set for [SfDataGrid] or [GridColumn], or + /// [GridColumn.width] is set, [defaultColumnWidth] will not be considered. + /// + /// Defaults to 90.0 for Android & iOS and 100.0 for Web. + final double defaultColumnWidth; + + /// How the column widths are determined. + /// + /// Defaults to [ColumnWidthMode.none] + /// + /// Also refer [ColumnWidthMode] + final ColumnWidthMode columnWidthMode; + + /// How the row count should be considered when calculating the width of a + /// column. + /// + /// Provides options to consider only visible rows or all the rows which are + /// available in [SfDataGrid]. + /// + /// Defaults to [ColumnWidthCalculationRange.visibleRows] + /// + /// Also refer [ColumnWidthCalculationRange] + final ColumnWidthCalculationRange columnWidthCalculationRange; + + /// The [ColumnSizer] used to control the column width sizing operation of + /// each columns. + /// + /// You can override the available methods and customize the required + /// operations in the custom [ColumnSizer]. + final ColumnSizer? columnSizer; + + /// How the border should be visible. + /// + /// Decides whether vertical, horizontal, both the borders and no borders + /// should be drawn. + /// + /// Defaults to [GridLinesVisibility.horizontal] + /// + /// Also refer [GridLinesVisibility] + final GridLinesVisibility gridLinesVisibility; + + /// How the border should be visible in header cells. + /// + /// Decides whether vertical or horizontal or both the borders or no borders + /// should be drawn. + /// + /// [GridLinesVisibility.horizontal] will be useful if you are using + /// [stackedHeaderRows] to improve the readability. + /// + /// Defaults to [GridLinesVisibility.horizontal] + /// + /// Also refer [GridLinesVisibility]. + /// + /// See also, [gridLinesVisibility] – To set the border for cells other than + /// header cells. + final GridLinesVisibility headerGridLinesVisibility; + + /// Invoked when the row height for each row is queried. + final QueryRowHeightCallback? onQueryRowHeight; + + /// How the rows should be selected. + /// + /// Provides options to select single row or multiple rows. + /// + /// Defaults to [SelectionMode.none]. + /// + /// Also refer [SelectionMode] + final SelectionMode selectionMode; + + /// Invoked when the row is selected. + /// + /// This callback never be called when the [onSelectionChanging] is returned + /// as false. + final SelectionChangedCallback? onSelectionChanged; + + /// Invoked when the row is being selected or being unselected + /// + /// This callback's return type is [bool]. So, if you want to cancel the + /// selection on a row based on the condition, return false. + /// Otherwise, return true. + final SelectionChangingCallback? onSelectionChanging; + + /// The [SelectionManagerBase] used to control the selection operations + /// in [SfDataGrid]. + /// + /// You can override the available methods and customize the required + /// operations in the custom [RowSelectionManager]. + /// + /// Defaults to null + final SelectionManagerBase? selectionManager; + + /// The [DataGridController] used to control the current cell navigation and + /// selection operation. + /// + /// Defaults to null. + /// + /// This object is expected to be long-lived, not recreated with each build. + final DataGridController? controller; + + /// Decides whether the navigation in the [SfDataGrid] should be cell wise + /// or row wise. + final GridNavigationMode navigationMode; + + /// Invoked when the cell is activated. + /// + /// This callback never be called when the [onCurrentCellActivating] is + /// returned as false. + final CurrentCellActivatedCallback? onCurrentCellActivated; + + /// Invoked when the cell is being activated. + /// + /// This callback's return type is [bool]. So, if you want to cancel cell + /// activation based on the condition, return false. Otherwise, + /// return true. + final CurrentCellActivatingCallback? onCurrentCellActivating; + + /// Called when a tap with a cell has occurred. + final DataGridCellTapCallback? onCellTap; + + /// Called when user is tapped a cell with a primary button at the same cell + /// twice in quick succession. + final DataGridCellDoubleTapCallback? onCellDoubleTap; + + /// Called when a long press gesture with a primary button has been + /// recognized for a cell. + final DataGridCellTapCallback? onCellSecondaryTap; + + /// Called when a tap with a cell has occurred with a secondary button. + final DataGridCellLongPressCallback? onCellLongPress; + + /// The number of non-scrolling columns at the left side of [SfDataGrid]. + /// + /// In Right To Left (RTL) mode, this count refers to the number of + /// non-scrolling columns at the right side of [SfDataGrid]. + /// + /// Defaults to 0 + /// + /// See also: + /// + /// * [footerFrozenColumnsCount] + /// * [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the + /// width of the frozen line. + /// * [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the + /// color of the frozen line + final int frozenColumnsCount; + + /// The number of non-scrolling columns at the right side of [SfDataGrid]. + /// + /// In Right To Left (RTL) mode, this count refers to the number of + /// non-scrolling columns at the left side of [SfDataGrid]. + /// + /// Defaults to 0 + /// + /// See also: + /// + /// * [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the + /// width of the frozen line. + /// * [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the + /// color of the frozen line. + final int footerFrozenColumnsCount; + + /// The number of non-scrolling rows at the top of [SfDataGrid]. + /// + /// Defaults to 0 + /// + /// See also: + /// + /// * [footerFrozenRowsCount] + /// * [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the + /// width of the frozen line. + /// * [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the + /// color of the frozen line. + final int frozenRowsCount; + + /// The number of non-scrolling rows at the bottom of [SfDataGrid]. + /// + /// Defaults to 0 + /// + /// See also: + /// + /// * [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the + /// width of the frozen line. + /// * [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the + /// color of the frozen line. + final int footerFrozenRowsCount; + + /// Decides whether user can sort the column simply by tapping the column + /// header. + /// + /// Defaults to false. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfDataGrid( + /// source: _employeeDataSource, + /// allowSorting: true, + /// columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ]); + /// } + /// + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// List get rows => _employees + /// .map((dataRow) => DataGridRow(cells: [ + /// DataGridCell(columnName: 'id', value: dataRow.id), + /// DataGridCell(columnName: 'name', value: dataRow.name), + /// DataGridCell( + /// columnName: 'designation', value: dataRow.designation), + /// DataGridCell(columnName: 'salary', value: dataRow.salary), + /// ])) + /// .toList(); + /// + /// @override + /// DataGridRowAdapter? buildRow(DataGridRow row) { + /// return DataGridRowAdapter( + /// cells: row.getCells().map((dataCell) { + /// return Text(dataCell.value.toString()); + /// }).toList()); + /// } + /// } + /// + /// ``` + /// + /// + /// See also: + /// + /// * [GridColumn.allowSorting] - which allows users to sort the columns in + /// [SfDataGrid]. + /// * [sortingGestureType] – which allows users to sort the column in tap or + /// double tap. + /// * [DataGridSource.sortedColumns] - which is the collection of + /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. + /// * [DataGridSource.sort] - call this method when you are adding the + /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. + final bool allowSorting; + + /// Decides whether user can sort more than one column. + /// + /// Defaults to false. + /// + /// This is applicable only if the [allowSorting] is set as true. + /// + /// See also: + /// + /// * [DataGridSource.sortedColumns] - which is the collection of + /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. + /// * [DataGridSource.sort] - call this method when you are adding the + /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. + final bool allowMultiColumnSorting; + + /// Decides whether user can sort the column in three states: ascending, + /// descending, unsorted. + /// + /// Defaults to false. + /// + /// This is applicable only if the [allowSorting] is set as true. + /// + /// See also: + /// + /// * [DataGridSource.sortedColumns] - which is the collection of + /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. + /// * [DataGridSource.sort] - call this method when you are adding the + /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. + final bool allowTriStateSorting; + + /// Decides whether the sequence number should be displayed on the header cell + /// of sorted column during multi-column sorting. + /// + /// Defaults to false + /// + /// This is applicable only if the [allowSorting] and + /// [allowMultiColumnSorting] are set as true. + /// + /// See also: + /// + /// * [DataGridSource.sortedColumns] - which is the collection of + /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. + /// * [DataGridSource.sort] - call this method when you are adding the + /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. + final bool showSortNumbers; + + /// Decides whether the sorting should be applied on tap or double tap the + /// column header. + /// + /// Default to [SortingGestureType.tap] + /// + /// see also: + /// + /// [allowSorting] + final SortingGestureType sortingGestureType; + + /// The collection of [StackedHeaderRow]. + /// + /// Stacked headers enable you to display headers that span across multiple + /// columns and rows. These rows are displayed above to the regular column + /// headers. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return SfDataGrid(source: _employeeDataSource, columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')) + /// ], stackedHeaderRows: [ + /// StackedHeaderRow(cells: [ + /// StackedHeaderCell( + /// columnNames: ['id', 'name', 'designation', 'salary'], + /// child: Center( + /// child: Text('Order Details'), + /// ), + /// ), + /// ]) + /// ]); + /// } + /// ``` + final List stackedHeaderRows; + + /// Indicates whether the horizontal and vertical scrollbars should always + /// be visible. When false, both the scrollbar will be shown during scrolling + /// and will fade out otherwise. When true, both the scrollbar will always be + /// visible and never fade out. + /// + /// Defaults to false + final bool isScrollbarAlwaysShown; + + /// How the horizontal scroll view should respond to user input. + /// For example, determines how the horizontal scroll view continues to animate after the user stops dragging the scroll view. + /// + /// Defaults to [AlwaysScrollableScrollPhysics]. + final ScrollPhysics horizontalScrollPhysics; + + /// How the vertical scroll view should respond to user input. + /// For example, determines how the vertical scroll view continues to animate after the user stops dragging the scroll view. + /// + /// Defaults to [AlwaysScrollableScrollPhysics]. + final ScrollPhysics verticalScrollPhysics; + + /// A builder that sets the widget to display at the bottom of the datagrid + /// when vertical scrolling reaches the end of the datagrid. + /// + /// You should override [DataGridSource.handleLoadMoreRows] method to load + /// more rows and then notify the datagrid about the changes. The + /// [DataGridSource.handleLoadMoreRows] can be called to load more rows from + /// this builder using `loadMoreRows` function which is passed as a parameter + /// to this builder. + /// + /// ## Infinite scrolling + /// + /// The example below demonstrates infinite scrolling by showing the circular + /// progress indicator until the rows are loaded when vertical scrolling + /// reaches the end of the datagrid, + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Syncfusion Flutter DataGrid')), + /// body: SfDataGrid( + /// source: employeeDataSource, + /// loadMoreViewBuilder: + /// (BuildContext context, LoadMoreRows loadMoreRows) { + /// Future loadRows() async { + /// await loadMoreRows(); + /// return Future.value('Completed'); + /// } + /// + /// return FutureBuilder( + /// initialData: 'loading', + /// future: loadRows(), + /// builder: (context, snapShot) { + /// if (snapShot.data == 'loading') { + /// return Container( + /// height: 98.0, + /// color: Colors.white, + /// width: double.infinity, + /// alignment: Alignment.center, + /// child: CircularProgressIndicator(valueColor: + /// AlwaysStoppedAnimation(Colors.deepPurple))); + /// } else { + /// return SizedBox.fromSize(size: Size.zero); + /// } + /// }, + /// ); + /// }, + /// columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// ## Load more button + /// + /// The example below demonstrates how to show the button when vertical + /// scrolling is reached at the end of the datagrid and display the circular + /// indicator when you tap that button. In the onPressed flatbutton callback, + /// you can call the `loadMoreRows` function to add more rows. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar(title: Text('Syncfusion Flutter DataGrid')), + /// body: SfDataGrid( + /// source: employeeDataSource, + /// loadMoreViewBuilder: + /// (BuildContext context, LoadMoreRows loadMoreRows) { + /// bool showIndicator = false; + /// return StatefulBuilder( + /// builder: (BuildContext context, StateSetter setState) { + /// if (showIndicator) { + /// return Container( + /// height: 98.0, + /// color: Colors.white, + /// width: double.infinity, + /// alignment: Alignment.center, + /// child: CircularProgressIndicator(valueColor: + /// AlwaysStoppedAnimation(Colors.deepPurple))); + /// } else { + /// return Container( + /// height: 98.0, + /// color: Colors.white, + /// width: double.infinity, + /// alignment: Alignment.center, + /// child: Container( + /// height: 36.0, + /// width: 142.0, + /// child: FlatButton( + /// color: Colors.deepPurple, + /// child: Text('LOAD MORE', + /// style: TextStyle(color: Colors.white)), + /// onPressed: () async { + /// if (context is StatefulElement && + /// context.state != null && + /// context.state.mounted) { + /// setState(() { + /// showIndicator = true; + /// }); + /// } + /// await loadMoreRows(); + /// if (context is StatefulElement && + /// context.state != null && + /// context.state.mounted) { + /// setState(() { + /// showIndicator = false; + /// }); + /// } + /// }, + /// ), + /// ), + /// ); + /// } + /// }); + /// }, + /// columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ], + /// ), + /// ); + /// } + /// ``` + final LoadMoreViewBuilder? loadMoreViewBuilder; + + /// Decides whether refresh indicator should be shown when datagrid is pulled + /// down. + /// + /// See also, + /// + /// [DataGridSource.handleRefresh] – This will be called when datagrid + /// is pulled down to refresh the data. + final bool allowPullToRefresh; + + /// The distance from the [SfDataGrid]’s top or bottom edge to where the refresh + /// indicator will settle. During the drag that exposes the refresh indicator, + /// its actual displacement may significantly exceed this value. + /// + /// By default, the value of `refreshIndicatorDisplacement` is 40.0. + final double refreshIndicatorDisplacement; + + /// Defines `strokeWidth` for `RefreshIndicator` used by [SfDataGrid]. + /// + /// By default, the value of `refreshIndicatorStrokeWidth` is 2.0 pixels. + final double refreshIndicatorStrokeWidth; + + /// Decides whether to 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 swipe view will be shown for custom actions. + /// + /// You can show the widgets for left or right swipe view using + /// [SfDataGrid.startSwipeActionsBuilder] and [SfDataGrid.endSwipeActionsBuilder]. + /// + /// See also, + /// + /// * [SfDataGrid.onSwipeStart] + /// * [SfDataGrid.onSwipeUpdate] + /// * [SfDataGrid.onSwipeEnd] + final bool allowSwiping; + + /// Defines the maximum offset in which a row can be swiped. + /// + /// Defaults to 200. + final double swipeMaxOffset; + + /// Controls a horizontal scrolling in DataGrid. + /// + /// You can use addListener method to listen whenever you do the horizontal scrolling. + /// + final ScrollController? horizontalScrollController; + + /// Controls a vertical scrolling in DataGrid. + /// + /// You can use addListener method to listen whenever you do the vertical scrolling. + /// + final ScrollController? verticalScrollController; + + /// Called when row swiping is started. + /// + /// You can disable the swiping for specific row by returning false. + final DataGridSwipeStartCallback? onSwipeStart; + + /// Called when row is being swiped. + /// + /// You can disable the swiping for specific requirement on swiping itself by + /// returning false. + final DataGridSwipeUpdateCallback? onSwipeUpdate; + + /// Called when swiping of a row is ended (i.e. when reaches the max offset). + final DataGridSwipeEndCallback? onSwipeEnd; + + /// A builder that sets the widget for the background view in which a row is + /// swiped in the reading direction (e.g., from left to right in left-to-right + /// languages). + final DataGridSwipeActionsBuilder? startSwipeActionsBuilder; + + /// A builder that sets the widget for the background view in which a row is + /// swiped in the reverse of reading direction (e.g., from right to left in + /// left-to-right languages). + final DataGridSwipeActionsBuilder? endSwipeActionsBuilder; + + /// Decides whether to highlight a row when mouse hovers over it. + /// + /// see also, + /// + /// * [SfDataGridThemeData.rowHoverColor] – This helps you to change row highlighting color on hovering. + /// * [SfDataGridThemeData.rowHoverTextStyle] – This helps you to change the [TextStyle] of row on hovering. + final bool highlightRowOnHover; + + /// Decides whether a column can be resized by the user interactively using a + /// pointer or not. + /// + /// In mobile platforms, resize indicator will be shown on the right side + /// border of the column header when the user long-press a column header. Then, + /// users tap and drag the resizing indicator to perform the column resizing. + /// + /// In web and desktop platforms, resizing can be performed by clicking and dragging the + /// right side (left side in RTL mode) border of a column header. + /// + /// DataGrid does not automatically resize the columns when you perform column + /// resizing. You should maintain the column width collection at the application + /// level and set the column width of corresponding column using the + /// [SfDataGrid.onColumnResizeUpdate] callback. + /// + /// The column width must be set inside the `setState` method to refresh + /// the DataGrid. + /// + /// If you want to disable the column resizing for specific columns, + /// return `false` for the specific columns in [SfDataGrid.onColumnResizeStart]. + /// + /// The following example shows how to set the column width when + /// resizing a column. + /// + /// ```dart + /// Map columnWidths = { + /// 'id': double.nan, + /// 'name': double.nan, + /// 'designation': double.nan, + /// 'salary': double.nan + /// }; + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter DataGrid'), + /// ), + /// body: SfDataGrid( + /// source: employeeDataSource, + /// allowColumnsResizing: true, + /// onColumnResizeUpdate: (details) { + /// setState(() { + /// columnWidths[details.column.columnName] = details.width; + /// }); + /// return true; + /// }, + /// columns: [ + /// GridColumn( + /// columnName: 'id', + /// width: columnWidths['id']!, + /// label: Container( + /// padding: const EdgeInsets.all(16.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'ID', + /// ))), + /// GridColumn( + /// columnName: 'name', + /// width: columnWidths['name']!, + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Name'))), + /// GridColumn( + /// columnName: 'designation', + /// width: columnWidths['designation']!, + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'Designation', + /// overflow: TextOverflow.ellipsis, + /// ))), + /// GridColumn( + /// columnName: 'salary', + /// width: columnWidths['salary']!, + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Salary'))), + /// ], + /// ), + /// ); + /// } + /// ``` + /// + /// Defaults to false. + /// + /// See also, + /// + /// * [SfDataGrid.onColumnResizeStart] + /// * [SfDataGrid.onColumnResizeUpdate] + /// * [SfDataGrid.onColumnResizeEnd] + final bool allowColumnsResizing; + + /// Decides how column should be resized. It can be either along with indicator moves or releasing a pointer. + /// + /// See also [ColumnResizeMode] + final ColumnResizeMode columnResizeMode; + + /// Called when a column is being resized when tapping and dragging the right-side border of the column header. + /// + /// You can return `false` to disable the column resizing. + final ColumnResizeStartCallback? onColumnResizeStart; + + /// Called when a column is resizing when tapping and dragging the right-side border of the column header. + /// + /// You can return `false` to disable the column resizing. + final ColumnResizeUpdateCallback? onColumnResizeUpdate; + + /// Called when a column is resized successfully. + final ColumnResizeEndCallback? onColumnResizeEnd; + + /// Decides whether cell should be moved into edit mode based on + /// [editingGestureType]. + /// + /// Defaults to false. + /// + /// Editing can be enabled only if the [selectionMode] is other than none and + /// [navigationMode] is cell. + /// + /// You can load the required widget on editing using [DataGridSource.buildEditWidget] method. + /// + /// The following example shows how to load the [TextField] for `id` column + /// by overriding the `onCellSubmit` and `buildEditWidget` methods in + /// [DataGridSource] class. + /// + /// ```dart + /// + /// class EmployeeDataSource extends DataGridSource { + /// + /// TextEditingController editingController = TextEditingController(); + /// + /// dynamic newCellValue; + /// + /// /// Creates the employee data source class with required details. + /// EmployeeDataSource({required List employeeData}) { + /// employees = employeeData; + /// _employeeData = employeeData + /// .map((e) => DataGridRow(cells: [ + /// DataGridCell(columnName: 'id', value: e.id), + /// DataGridCell(columnName: 'name', value: e.name), + /// DataGridCell( + /// columnName: 'designation', value: e.designation), + /// DataGridCell(columnName: 'salary', value: e.salary), + /// ])) + /// .toList(); + /// } + /// + /// List _employeeData = []; + /// + /// List employees = []; + /// + /// @override + /// List get rows => _employeeData; + /// + /// @override + /// DataGridRowAdapter buildRow(DataGridRow row) { + /// return DataGridRowAdapter( + /// cells: row.getCells().map((e) { + /// return Container( + /// alignment: (e.columnName == 'id' || e.columnName == 'salary') + /// ? Alignment.centerRight + /// : Alignment.centerLeft, + /// padding: EdgeInsets.all(8.0), + /// child: Text(e.value.toString()), + /// ); + /// }).toList()); + /// } + /// + /// @override + /// Widget? buildEditWidget(DataGridRow dataGridRow, + /// RowColumnIndex rowColumnIndex, GridColumn column, submitCell) { + /// // To set the value for TextField when cell is moved into edit mode. + /// final String displayText = dataGridRow + /// .getCells() + /// .firstWhere((DataGridCell dataGridCell) => + /// dataGridCell.columnName == column.columnName) + /// .value + /// ?.toString() ?? + /// ''; + /// + /// /// Returning the TextField with the numeric keyboard configuration. + /// if (column.columnName == 'id') { + /// return Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.centerRight, + /// child: TextField( + /// autofocus: true, + /// controller: editingController..text = displayText, + /// textAlign: TextAlign.right, + /// decoration: const InputDecoration( + /// contentPadding: EdgeInsets.all(0), + /// border: InputBorder.none, + /// isDense: true), + /// inputFormatters: [ + /// FilteringTextInputFormatter.allow(RegExp('[0-9]')) + /// ], + /// keyboardType: TextInputType.number, + /// onChanged: (String value) { + /// if (value.isNotEmpty) { + /// print(value); + /// newCellValue = int.parse(value); + /// } else { + /// newCellValue = null; + /// } + /// }, + /// onSubmitted: (String value) { + /// /// Call [CellSubmit] callback to fire the canSubmitCell and + /// /// onCellSubmit to commit the new value in single place. + /// submitCell(); + /// }, + /// )); + /// } + /// } + /// + /// @override + /// void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, + /// GridColumn column) { + /// final dynamic oldValue = dataGridRow + /// .getCells() + /// .firstWhereOrNull((DataGridCell dataGridCell) => + /// dataGridCell.columnName == column.columnName) + /// ?.value ?? + /// ''; + /// + /// final int dataRowIndex = rows.indexOf(dataGridRow); + /// + /// if (newCellValue == null || oldValue == newCellValue) { + /// return; + /// } + /// + /// if (column.columnName == 'id') { + /// rows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] = + /// DataGridCell(columnName: 'id', value: newCellValue); + /// + /// // Save the new cell value to model collection also. + /// employees[dataRowIndex].id = newCellValue as int; + /// } + /// + /// // To reset the new cell value after successfully updated to DataGridRow + /// //and underlying mode. + /// newCellValue = null; + /// } + /// } + /// + /// ``` + /// The following example shows how to enable editing and set the + /// [DataGridSource] for [SfDataGrid]. + /// ```dart + /// + /// List employees = []; + /// + /// late EmployeeDataSource employeeDataSource; + /// + /// @override + /// void initState() { + /// super.initState(); + /// employees = getEmployeeData(); + /// employeeDataSource = EmployeeDataSource(employeeData: employees); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter DataGrid'), + /// ), + /// body: SfDataGrid( + /// source: employeeDataSource, + /// allowEditing: true, + /// columnWidthMode: ColumnWidthMode.fill, + /// selectionMode: SelectionMode.single, + /// navigationMode: GridNavigationMode.cell, + /// columns: [ + /// GridColumn( + /// columnName: 'id', + /// label: Container( + /// padding: EdgeInsets.all(16.0), + /// alignment: Alignment.centerRight, + /// child: Text( + /// 'ID', + /// ))), + /// GridColumn( + /// columnName: 'name', + /// label: Container( + /// padding: EdgeInsets.all(8.0), + /// alignment: Alignment.centerLeft, + /// child: Text('Name'))), + /// GridColumn( + /// columnName: 'designation', + /// label: Container( + /// padding: EdgeInsets.all(8.0), + /// alignment: Alignment.centerLeft, + /// child: Text( + /// 'Designation', + /// overflow: TextOverflow.ellipsis, + /// ))), + /// GridColumn( + /// columnName: 'salary', + /// label: Container( + /// padding: EdgeInsets.all(8.0), + /// alignment: Alignment.centerRight, + /// child: Text('Salary'))), + /// ], + /// ), + /// ); + /// } + /// ``` + /// See also, + /// * [GridColumn.allowEditing] – You can enable or disable editing for an + /// individual column. + /// * [DataGridSource.onCellBeginEdit]- This will be triggered when a cell is + /// moved to edit mode. + /// * [DataGridSource.canSubmitCell]- This will be triggered before + /// [DataGridSource.onCellSubmit] method is called. You can use this method + /// if you want to not end the editing based on any criteria. + /// * [DataGridSource.onCellSubmit] – This will be triggered when the cell’s + /// editing is completed. + final bool allowEditing; + + /// Decides whether the editing should be triggered on tap or double tap + /// the cells. + /// + /// Defaults to [EditingGestureType.doubleTap]. + /// + /// See also, + /// * [allowEditing] – This will enable the editing option for cells. + final EditingGestureType editingGestureType; + + /// The widget to show over the bottom of the [SfDataGrid]. + /// + /// This footer will be displayed like normal row and shown below to last row. + /// + /// See also, + /// + /// [SfDataGrid.footerHeight] – This enables you to change the height of the + /// footer. + final Widget? footer; + + /// The height of the footer. + /// + /// Defaults to 49.0. + final double footerHeight; + + /// Decides whether [Checkbox] should be displayed in each row to select or + /// deselect the rows. + /// + /// Defaults to false. + /// + /// If true, [Checkbox] column will be added at the beginning of each row. + /// Rows can be selected only if the [SfDataGrid.selectionMode] is other than + /// none. + /// + /// [SfDataGrid.onSelectionChanging] and [SfDataGrid.onSelectionChanging] + /// callbacks will be called whenever you select the rows using [Checkbox] in + /// each row. + /// + /// See also, + /// [SfDataGrid.checkboxColumnSettings] – Provides the customization options + /// to the checkbox column. + final bool showCheckboxColumn; + + /// Contains all the properties of the checkbox column. + /// + /// This settings are applied to checkbox column, only if + /// [SfDataGrid.showCheckboxColumn] is `true`. + final DataGridCheckboxColumnSettings checkboxColumnSettings; + + /// The collection of [GridTableSummaryRow]. + /// + /// This enables you to show the total or summary for columns i.e Max, Min, + /// Average, and Count for the whole DataGrid. + /// + /// Load the required widget in summary cell by overriding and returning the + /// widget in [DataGridSource.buildTableSummaryCellWidget] method. + /// + /// The following example shows how to display the table summary rows at the top + /// and bottom with different options. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter DataGrid'), + /// ), + /// body: SfDataGrid( + /// source: employeeDataSource, + /// tableSummaryRows: [ + /// GridTableSummaryRow( + /// showSummaryInRow: true, + /// title: 'Total Employee Count: {Count}', + /// columns: [ + /// GridSummaryColumn( + /// name: 'Count', + /// columnName: 'name', + /// summaryType: GridSummaryType.count) + /// ], + /// position: GridTableSummaryRowPosition.top), + /// GridTableSummaryRow( + /// showSummaryInRow: false, + /// columns: [ + /// GridSummaryColumn( + /// name: 'Sum', + /// columnName: 'salary', + /// summaryType: GridSummaryType.sum) + /// ], + /// position: GridTableSummaryRowPosition.bottom) + /// ], + /// columns: [ + /// GridColumn( + /// columnName: 'id', + /// label: Container( + /// padding: EdgeInsets.all(16.0), + /// alignment: Alignment.center, + /// child: Text( + /// 'ID', + /// ))), + /// GridColumn( + /// columnName: 'name', + /// label: Container( + /// padding: EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: Text('Name'))), + /// GridColumn( + /// columnName: 'designation', + /// label: Container( + /// padding: EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: Text( + /// 'Designation', + /// overflow: TextOverflow.ellipsis, + /// ))), + /// GridColumn( + /// columnName: 'salary', + /// label: Container( + /// padding: EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: Text('Salary'))), + /// ], + /// ), + /// ); + /// } + /// + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// Widget? buildTableSummaryCellWidget( + /// GridTableSummaryRow summaryRow, + /// GridSummaryColumn? summaryColumn, + /// RowColumnIndex rowColumnIndex, + /// String summaryValue) { + /// return Container( + /// padding: EdgeInsets.all(16.0), + /// alignment: Alignment.centerLeft, + /// child: Text(summaryValue)); + /// } + /// ``` + final List tableSummaryRows; + + /// The number of rows to show on each page. + /// + /// This property is applicable only if the [SfDataPager] is used to represent + /// the paging functionality. + /// + /// If you set the value as null, the rows per page is automatically decided + /// based on divided value of the number of rows loaded through [DataGridSource.rows] by [SfDataPager.pageCount]. + /// + /// If you want to maintain the rows per page constantly the same, set the required number of rows to this property. + final int? rowsPerPage; + + @override + State createState() => SfDataGridState(); +} + +/// Contains the state for a [SfDataGrid]. This class can be used to +/// programmatically show the refresh indicator, see the [refresh] +/// method. +class SfDataGridState extends State + with SingleTickerProviderStateMixin { + static const double _minWidth = 300.0; + static const double _minHeight = 300.0; + static const double _rowHeight = 49.0; + static const double _headerRowHeight = 56.0; + + late RowGenerator _rowGenerator; + late VisualContainerHelper _container; + late DataGridConfiguration _dataGridConfiguration; + AnimationController? _swipingAnimationController; + + late Map _cellRenderers; + TextDirection _textDirection = TextDirection.ltr; + SfDataGridThemeData? _dataGridThemeData; + DataGridSource? _source; + List? _columns; + SelectionManagerBase? _rowSelectionManager; + DataGridController? _controller; + Animation? _swipingAnimation; + DataGridStateDetails? _dataGridStateDetails; + + @override + void initState() { + _columns = []; + _dataGridConfiguration = DataGridConfiguration(); + _dataGridStateDetails = _onDataGridStateDetailsChanged; + _dataGridConfiguration.gridPaint = Paint(); + + _rowGenerator = RowGenerator(dataGridStateDetails: _dataGridStateDetails!); + _container = VisualContainerHelper( + rowGenerator: _rowGenerator, + dataGridStateDetails: _dataGridStateDetails!); + _swipingAnimationController = AnimationController( + duration: const Duration(milliseconds: 200), vsync: this); + _setUp(); + _updateDataGridStateDetails(); + super.initState(); + } + + void _onDataGridTextDirectionChanged(TextDirection? newTextDirection) { + if (newTextDirection == null || _textDirection == newTextDirection) { + return; + } + + _textDirection = newTextDirection; + _dataGridConfiguration.textDirection = newTextDirection; + _container.needToSetHorizontalOffset = true; + } + + void _onDataGridThemeDataChanged(SfDataGridThemeData? newThemeData) { + if (newThemeData == null || _dataGridThemeData == newThemeData) { + return; + } + + final bool canUpdate = + _dataGridThemeData != null && _dataGridThemeData != newThemeData; + _dataGridThemeData = newThemeData; + _dataGridConfiguration.dataGridThemeData = newThemeData; + _updateDecoration(); + if (canUpdate) { + _container.refreshViewStyle(); + } + } + + void _onDataGridTextScaleFactorChanged(double textScaleFactor) { + if (textScaleFactor == _dataGridConfiguration.textScaleFactor) { + return; + } + + _dataGridConfiguration.textScaleFactor = textScaleFactor; + _dataGridConfiguration.headerRowHeight = widget.headerRowHeight.isNaN + ? (_dataGridConfiguration.textScaleFactor > 1.0) + ? _headerRowHeight * _dataGridConfiguration.textScaleFactor + : _headerRowHeight + : widget.headerRowHeight; + _dataGridConfiguration.rowHeight = widget.rowHeight.isNaN + ? (_dataGridConfiguration.textScaleFactor > 1.0) + ? _rowHeight * _dataGridConfiguration.textScaleFactor + : _rowHeight + : widget.rowHeight; + // Refreshes the default line size, column widths and row heights in initial + // `SfDataGrid` build. So, restricts the codes in initial loading by using + // the `_container.isGridLoaded` property. + if (_container.isGridLoaded) { + if (_dataGridConfiguration.columnWidthMode != ColumnWidthMode.none) { + resetAutoCalculation(_dataGridConfiguration.columnSizer); + if (!_container.isDirty) { + // Refreshes the autofit columns only if don't have explicit width. + refreshColumnSizer(_dataGridConfiguration.columnSizer, 0.0); + } + } + // Refresh header rows height. + _updateHeaderRowHeight(); + // Refresh data rows height. + _container.rowHeights.defaultLineSize = _dataGridConfiguration.rowHeight; + if (!_container.isDirty) { + _container.setRowHeights(); + } + } + } + + void _updateHeaderRowHeight() { + final LineSizeCollection lineSizeCollection = + _container.columnWidths as LineSizeCollection; + lineSizeCollection.suspendUpdates(); + final int headerIndex = grid_helper.getHeaderIndex(_dataGridConfiguration); + if (_container.rowCount > 0) { + for (int i = 0; i <= headerIndex; i++) { + _container.rowHeights[i] = _dataGridConfiguration.headerRowHeight; + } + } + lineSizeCollection.resumeUpdates(); + _container.updateScrollBars(); + } + + void _setUp() { + _initializeDataGridDataSource(); + _initializeCellRendererCollection(); + + //DataGrid Controller + _controller = _dataGridConfiguration.controller = + widget.controller ?? DataGridController() + .._dataGridStateDetails = _dataGridStateDetails; + + _controller!._addDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + + _dataGridConfiguration.verticalScrollController = + widget.verticalScrollController ?? ScrollController(); + _dataGridConfiguration.horizontalScrollController = + widget.horizontalScrollController ?? ScrollController(); + + //AutoFit controller initializing + _dataGridConfiguration.columnSizer = widget.columnSizer ?? ColumnSizer(); + setStateDetailsInColumnSizer( + _dataGridConfiguration.columnSizer, _dataGridStateDetails!); + + //CurrentCell Manager initializing + _dataGridConfiguration.currentCell = + CurrentCellManager(_dataGridStateDetails!); + + //Selection Manager initializing + _rowSelectionManager = _dataGridConfiguration.rowSelectionManager = + widget.selectionManager ?? RowSelectionManager(); + selection_manager.setStateDetailsInSelectionManagerBase( + _rowSelectionManager!, _dataGridStateDetails!); + + _controller! + ._addDataGridPropertyChangeListener(_handleSelectionPropertyChanged); + + _dataGridConfiguration.columnResizeController = + ColumnResizeController(dataGridStateDetails: _dataGridStateDetails!); + + _initializeProperties(); + } + + @protected + void _gridLoaded() { + _container.refreshDefaultLineSize(); + _refreshContainerAndView(); + } + + @protected + void _refreshContainerAndView({bool isDataSourceChanged = false}) { + if (isDataSourceChanged) { + selection_manager.updateSelectionController( + dataGridConfiguration: _dataGridConfiguration, + isDataSourceChanged: isDataSourceChanged); + } + + _ensureSelectionProperties(); + _container + ..refreshHeaderLineCount() + ..updateRowAndColumnCount() + ..isGridLoaded = true; + } + + void _updateVisualDensity(VisualDensity visualDensity) { + if (_dataGridConfiguration.visualDensity == visualDensity) { + return; + } + + final Offset baseDensity = visualDensity.baseSizeAdjustment; + + _dataGridConfiguration + ..visualDensity = visualDensity + ..headerRowHeight += baseDensity.dy + ..rowHeight += baseDensity.dy; + + // Refreshes the default line size, and row heights in initial + // `SfDataGrid` build. So, restricts the codes in initial loading by using + // the `_container.isGridLoaded` property. + if (_container.isGridLoaded) { + // Refresh header rows height. + _updateHeaderRowHeight(); + // Refresh data rows height. + _container.rowHeights.defaultLineSize = _dataGridConfiguration.rowHeight; + } + } + + void _initializeDataGridDataSource() { + if (_source != widget.source) { + _removeDataGridSourceListeners(); + _source = widget.source.._dataGridStateDetails = _dataGridStateDetails; + _addDataGridSourceListeners(); + } + _source?._updateDataSource(); + } + + void _initializeProperties() { + if (!listEquals(_columns, widget.columns)) { + if (_columns != null) { + _columns! + ..clear() + ..addAll(widget.columns); + } + } + + _updateDataGridStateDetails(); + if (_columns!.isNotEmpty) { + /// Need to add check box column, when showCheckboxColumn is true. + _addCheckboxColumn(_dataGridConfiguration); + } + } + + void _initializeCellRendererCollection() { + _cellRenderers = {}; + _cellRenderers['TextField'] = GridCellTextFieldRenderer(); + setStateDetailsInCellRendererBase( + _cellRenderers['TextField']!, _dataGridStateDetails!); + _cellRenderers['ColumnHeader'] = GridHeaderCellRenderer(); + setStateDetailsInCellRendererBase( + _cellRenderers['ColumnHeader']!, _dataGridStateDetails!); + _cellRenderers['StackedHeader'] = GridStackedHeaderCellRenderer(); + setStateDetailsInCellRendererBase( + _cellRenderers['StackedHeader']!, _dataGridStateDetails!); + _cellRenderers['Checkbox'] = GridCheckboxRenderer(); + setStateDetailsInCellRendererBase( + _cellRenderers['Checkbox']!, _dataGridStateDetails!); + _cellRenderers['TableSummary'] = GridTableSummaryCellRenderer(); + setStateDetailsInCellRendererBase( + _cellRenderers['TableSummary']!, _dataGridStateDetails!); + } + + void _processCellUpdate(RowColumnIndex rowColumnIndex) { + if (rowColumnIndex != RowColumnIndex(-1, -1)) { + final int rowIndex = grid_helper.resolveToRowIndex( + _dataGridConfiguration, rowColumnIndex.rowIndex); + final int columnIndex = grid_helper.resolveToScrollColumnIndex( + _dataGridConfiguration, rowColumnIndex.columnIndex); + + final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( + (DataRowBase dataRow) => dataRow.rowIndex == rowIndex); + + if (dataRow == null) { + return; + } + + dataRow.dataGridRow = _source!._effectiveRows[rowColumnIndex.rowIndex]; + dataRow.dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + _dataGridConfiguration, dataRow.dataGridRow!); + + final DataCellBase? dataCell = dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); + + if (dataCell == null) { + return; + } + + setState(() { + _refreshCell(dataCell); + _updateSummaryColumns(columnIndex); + }); + } + } + + void _refreshCell(DataCellBase dataCell) { + dataCell + ..isDirty = true + ..updateColumn(); + } + + void _updateSummaryColumns(int columnIndex) { + final String columnName = + _dataGridConfiguration.columns[columnIndex].columnName; + if (_dataGridConfiguration.tableSummaryRows.isNotEmpty) { + for (final GridTableSummaryRow tableSummaryRow + in _dataGridConfiguration.tableSummaryRows) { + final GridSummaryColumn? summaryColumn = tableSummaryRow.columns + .firstWhereOrNull( + (GridSummaryColumn column) => column.columnName == columnName); + // Returns if the updated cell doesn't exist in the table summary row. + if (summaryColumn == null) { + return; + } + + final DataRowBase? summaryDataRow = _rowGenerator.items + .firstWhereOrNull( + (DataRowBase row) => row.tableSummaryRow == tableSummaryRow); + if (summaryDataRow != null) { + for (final DataCellBase column in summaryDataRow.visibleColumns) { + final int titleColumnSpan = grid_helper.getSummaryTitleColumnSpan( + _dataGridConfiguration, tableSummaryRow); + if (tableSummaryRow.showSummaryInRow || + (titleColumnSpan > 0 && column.columnIndex < titleColumnSpan)) { + if (tableSummaryRow.title != null) { + if (tableSummaryRow.title!.contains(summaryColumn.name)) { + _refreshCell(column); + } + } + } else if (column.summaryColumn != null && + column.summaryColumn!.columnName == columnName) { + _refreshCell(column); + } + } + } + } + } + } + + void _processUpdateDataSource() { + if (_dataGridConfiguration.source._suspendDataPagerUpdate) { + return; + } + setState(() { + // Need to endEdit the editing [DataGridCell] before perform refreshing. + if (_dataGridConfiguration.currentCell.isEditing) { + _dataGridConfiguration.currentCell + .onCellSubmit(_dataGridConfiguration, canRefresh: false); + } + + _initializeDataGridDataSource(); + _dataGridConfiguration.source = _source!; + + if (!listEquals(_columns, widget.columns)) { + if (widget.selectionMode != SelectionMode.none && + widget.navigationMode == GridNavigationMode.cell && + _rowSelectionManager != null) { + selection_manager.onRowColumnChanged( + _dataGridConfiguration, -1, widget.columns.length); + } + + _resetColumn(); + } + + if (widget.selectionMode != SelectionMode.none && + widget.navigationMode == GridNavigationMode.cell && + _rowSelectionManager != null) { + selection_manager.onRowColumnChanged( + _dataGridConfiguration, widget.source._effectiveRows.length, -1); + } + + if (widget.allowSwiping) { + _dataGridConfiguration.container.resetSwipeOffset(); + } + + if (widget.footer != null) { + final DataRowBase? footerRow = _rowGenerator.items.firstWhereOrNull( + (DataRowBase row) => + row.rowType == RowType.footerRow && row.rowIndex >= 0); + if (footerRow != null) { + // Need to reset the old footer row height in rowHeights collection. + _container.rowHeights[footerRow.rowIndex] = + _dataGridConfiguration.rowHeight; + } + } + + _container + ..updateRowAndColumnCount() + ..refreshView() + ..isDirty = true; + + // FLUT-3219 Need to refresh the scrolling offsets if the container's + // offsets and the ScrollController's offsets are not identical. + _refreshScrollOffsets(); + }); + if (_dataGridConfiguration.source.shouldRecalculateColumnWidths()) { + resetAutoCalculation(_dataGridConfiguration.columnSizer); + } + + if (_dataGridConfiguration.dataGridFocusNode != null && + !_dataGridConfiguration.dataGridFocusNode!.hasPrimaryFocus) { + _dataGridConfiguration.dataGridFocusNode!.requestFocus(); + } + } + + // Refreshes the scrolling offsets when the container and the scrollController + // offsets are different. + void _refreshScrollOffsets([bool canRefreshHorizontalOffset = false]) { + // Refreshes the vertical offset. + if (_dataGridConfiguration.verticalScrollController != null && + _dataGridConfiguration.verticalScrollController!.hasClients && + _dataGridConfiguration.verticalScrollController!.offset == 0 && + _dataGridConfiguration.container.verticalOffset > 0) { + _dataGridConfiguration.container + ..verticalOffset = 0 + ..verticalScrollBar.value = 0; + } + // Refreshes the horizontal offset. + if (canRefreshHorizontalOffset) { + if (_dataGridConfiguration.horizontalScrollController != null && + _dataGridConfiguration.horizontalScrollController!.hasClients && + _dataGridConfiguration.horizontalScrollController!.offset == 0) { + final double maxScrollExtent = _dataGridConfiguration + .horizontalScrollController!.position.maxScrollExtent; + if (_dataGridConfiguration.textDirection == TextDirection.ltr && + _dataGridConfiguration.container.horizontalOffset > 0.0) { + _dataGridConfiguration.container + ..horizontalOffset = 0 + ..horizontalScrollBar.value = 0; + } else if (_dataGridConfiguration.textDirection == TextDirection.rtl && + _dataGridConfiguration.container.horizontalOffset < + maxScrollExtent) { + _dataGridConfiguration.container + ..horizontalOffset = maxScrollExtent + ..horizontalScrollBar.value = maxScrollExtent; + } + } + } + } + + void _processSorting() { + setState(() { + _container + ..updateRowAndColumnCount() + ..refreshView() + ..isDirty = true; + }); + } + + void _resetColumn({bool clearEditing = true}) { + if (_columns != null) { + _columns! + ..clear() + ..addAll(widget.columns); + _dataGridConfiguration.columns = _columns!; + if (_columns!.isNotEmpty) { + /// Add check box column, while refreshing. + _addCheckboxColumn(_dataGridConfiguration); + } + } + + for (final DataRowBase dataRow in _rowGenerator.items) { + if (!clearEditing && dataRow.isEditing) { + return; + } + + for (final DataCellBase dataCell in dataRow.visibleColumns) { + dataCell.columnIndex = -1; + } + } + + if (_textDirection == TextDirection.rtl) { + _container.needToSetHorizontalOffset = true; + } + _container.needToRefreshColumn = true; + } + + void _handleListeners() { + _processUpdateDataSource(); + } + + void _handleNotifyListeners({RowColumnIndex? rowColumnIndex}) { + if (rowColumnIndex != null) { + _processCellUpdate(rowColumnIndex); + } + if (rowColumnIndex == null) { + _processUpdateDataSource(); + } + } + + void _handleDataGridPropertyChangeListeners( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + if (propertyName == 'refreshRow') { + if (rowColumnIndex != null) { + // Need to endEdit before refreshing the row. + _dataGridConfiguration.currentCell + .onCellSubmit(_dataGridConfiguration, canRefresh: false); + final int rowIndex = grid_helper.resolveToRowIndex( + _dataGridConfiguration, rowColumnIndex.rowIndex); + + final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( + (DataRowBase dataRow) => dataRow.rowIndex == rowIndex); + + if (dataRow == null) { + return; + } + setState(() { + dataRow + ..isDirty = true + ..rowIndexChanged(); + if (recalculateRowHeight) { + _dataGridConfiguration.container.rowHeightManager + .setDirty(rowIndex); + _dataGridConfiguration.container + ..needToRefreshColumn = true + ..setRowHeights(); + } + }); + } + } + + if (rowColumnIndex == null && propertyName == 'Sorting') { + _processSorting(); + } + + if (propertyName == 'hoverOnCell') { + setState(() { + // To rebuild the datagrid on hovering the header cell. isDirtly already + // been set in header cell widget itself + }); + } + + if (propertyName == 'Swiping') { + setState(() { + // Need to end-edit the editing [DataGridCell] before swiping a + // [DataGridRow] or refreshing + _dataGridConfiguration.currentCell + .onCellSubmit(_dataGridConfiguration, canRefresh: false); + _container.isDirty = true; + }); + } + + if (propertyName == 'columnResizing') { + setState(() { + // Rebuild the DataGrid to change the column width and indicator visibility when the column resize is processed. + }); + } + + if (propertyName == 'editing' && rowColumnIndex != null) { + _processCellUpdate(rowColumnIndex); + } + } + + void _updateDataGridStateDetails() { + _dataGridConfiguration + ..source = _source! + ..columns = _columns! + ..textDirection = _textDirection + ..cellRenderers = _cellRenderers + ..container = _container + ..rowGenerator = _rowGenerator + ..visualDensity = _dataGridConfiguration.visualDensity + ..headerLineCount = _container.headerLineCount + ..onQueryRowHeight = widget.onQueryRowHeight + ..dataGridThemeData = _dataGridThemeData + ..gridLinesVisibility = widget.gridLinesVisibility + ..headerGridLinesVisibility = widget.headerGridLinesVisibility + ..columnWidthMode = widget.columnWidthMode + ..columnWidthCalculationRange = widget.columnWidthCalculationRange + ..selectionMode = widget.selectionMode + ..onSelectionChanged = widget.onSelectionChanged + ..onSelectionChanging = widget.onSelectionChanging + ..navigationMode = widget.navigationMode + ..onCurrentCellActivated = widget.onCurrentCellActivated + ..onCurrentCellActivating = widget.onCurrentCellActivating + ..onCellTap = widget.onCellTap + ..onCellDoubleTap = widget.onCellDoubleTap + ..onCellSecondaryTap = widget.onCellSecondaryTap + ..onCellLongPress = widget.onCellLongPress + ..frozenColumnsCount = widget.frozenColumnsCount + ..footerFrozenColumnsCount = widget.footerFrozenColumnsCount + ..frozenRowsCount = widget.frozenRowsCount + ..footerFrozenRowsCount = widget.footerFrozenRowsCount + ..allowSorting = widget.allowSorting + ..allowMultiColumnSorting = widget.allowMultiColumnSorting + ..allowTriStateSorting = widget.allowTriStateSorting + ..sortingGestureType = widget.sortingGestureType + ..showSortNumbers = widget.showSortNumbers + ..isControlKeyPressed = false + ..stackedHeaderRows = widget.stackedHeaderRows + ..isScrollbarAlwaysShown = widget.isScrollbarAlwaysShown + ..horizontalScrollPhysics = widget.horizontalScrollPhysics + ..verticalScrollPhysics = widget.verticalScrollPhysics + ..loadMoreViewBuilder = widget.loadMoreViewBuilder + ..refreshIndicatorDisplacement = widget.refreshIndicatorDisplacement + ..allowPullToRefresh = widget.allowPullToRefresh + ..refreshIndicatorStrokeWidth = widget.refreshIndicatorStrokeWidth + ..allowSwiping = widget.allowSwiping + ..swipeMaxOffset = widget.swipeMaxOffset + ..onSwipeStart = widget.onSwipeStart + ..onSwipeUpdate = widget.onSwipeUpdate + ..onSwipeEnd = widget.onSwipeEnd + ..startSwipeActionsBuilder = widget.startSwipeActionsBuilder + ..endSwipeActionsBuilder = widget.endSwipeActionsBuilder + ..swipingAnimationController ??= _swipingAnimationController + ..swipingAnimation ??= _swipingAnimation + ..highlightRowOnHover = widget.highlightRowOnHover + ..allowColumnsResizing = widget.allowColumnsResizing + ..columnResizeMode = widget.columnResizeMode + ..onColumnResizeStart = widget.onColumnResizeStart + ..onColumnResizeUpdate = widget.onColumnResizeUpdate + ..onColumnResizeEnd = widget.onColumnResizeEnd + ..editingGestureType = widget.editingGestureType + ..allowEditing = widget.allowEditing + ..rowHeight = (widget.rowHeight.isNaN + ? _dataGridConfiguration.rowHeight.isNaN + ? _rowHeight + : _dataGridConfiguration.rowHeight + : widget.rowHeight) + ..headerRowHeight = (widget.headerRowHeight.isNaN + ? _dataGridConfiguration.headerRowHeight.isNaN + ? _headerRowHeight + : _dataGridConfiguration.headerRowHeight + : widget.headerRowHeight) + ..defaultColumnWidth = (widget.defaultColumnWidth.isNaN + ? _dataGridConfiguration.defaultColumnWidth + : widget.defaultColumnWidth) + ..footer = widget.footer + ..footerHeight = widget.footerHeight + ..showCheckboxColumn = widget.showCheckboxColumn + ..checkboxColumnSettings = widget.checkboxColumnSettings + ..tableSummaryRows = widget.tableSummaryRows + ..rowsPerPage = widget.rowsPerPage; + + if (widget.allowPullToRefresh) { + _dataGridConfiguration.refreshIndicatorKey ??= + GlobalKey(); + } + } + + DataGridConfiguration _onDataGridStateDetailsChanged() => + _dataGridConfiguration; + + void _updateProperties(SfDataGrid oldWidget) { + final bool isSourceChanged = widget.source != oldWidget.source; + final bool isDataSourceChanged = + !listEquals(oldWidget.source.rows, widget.source.rows); + final bool isColumnsChanged = + !listEquals(_columns, widget.columns); + final bool isSelectionManagerChanged = + oldWidget.selectionManager != widget.selectionManager || + oldWidget.selectionMode != widget.selectionMode; + final bool isColumnSizerChanged = + oldWidget.columnSizer != widget.columnSizer || + oldWidget.columnWidthMode != widget.columnWidthMode || + oldWidget.columnWidthCalculationRange != + widget.columnWidthCalculationRange; + final bool isDataGridControllerChanged = + oldWidget.controller != widget.controller; + final bool isFrozenColumnPaneChanged = oldWidget.frozenColumnsCount != + widget.frozenColumnsCount || + oldWidget.footerFrozenColumnsCount != widget.footerFrozenColumnsCount; + final bool isFrozenRowPaneChanged = + oldWidget.frozenRowsCount != widget.frozenRowsCount || + oldWidget.footerFrozenRowsCount != widget.footerFrozenRowsCount; + final bool isSortingChanged = oldWidget.allowSorting != widget.allowSorting; + final bool isMultiColumnSortingChanged = + oldWidget.allowMultiColumnSorting != widget.allowMultiColumnSorting; + final bool isShowSortNumbersChanged = + oldWidget.showSortNumbers != widget.showSortNumbers; + final bool isStackedHeaderRowsChanged = !listEquals( + oldWidget.stackedHeaderRows, widget.stackedHeaderRows); + final bool isPullToRefreshPropertiesChanged = + oldWidget.allowPullToRefresh != widget.allowPullToRefresh || + oldWidget.refreshIndicatorDisplacement != + widget.refreshIndicatorDisplacement || + oldWidget.refreshIndicatorStrokeWidth != + widget.refreshIndicatorStrokeWidth; + final bool isSwipingChanged = widget.allowSwiping != oldWidget.allowSwiping; + final bool isMaxSwipeOffsetChanged = + widget.swipeMaxOffset != oldWidget.swipeMaxOffset; + final bool isFooterRowChanged = widget.footer != oldWidget.footer || + widget.footerHeight != oldWidget.footerHeight; + final bool isTableSummaryRowsChanged = + widget.tableSummaryRows != oldWidget.tableSummaryRows; + final bool isRowsPerPageChanged = + widget.rowsPerPage != oldWidget.rowsPerPage; + + if (oldWidget.verticalScrollController != widget.verticalScrollController) { + _dataGridConfiguration.verticalScrollController = + widget.verticalScrollController ?? ScrollController(); + } + + if (oldWidget.horizontalScrollController != + widget.horizontalScrollController) { + _dataGridConfiguration.horizontalScrollController = + widget.horizontalScrollController ?? ScrollController(); + } + + if (!oldWidget.allowColumnsResizing && widget.allowColumnsResizing) { + _dataGridConfiguration.columnResizeController.setHitTestPrecision(); + } + + final bool isEditingChanged = + oldWidget.allowEditing != widget.allowEditing || + oldWidget.editingGestureType != widget.editingGestureType; + + void refreshEditing() { + bool isEditingImpactAPIsChanged = isSourceChanged || + isDataSourceChanged || + oldWidget.stackedHeaderRows.length != widget.stackedHeaderRows.length; + + /// Need to end-edit the editing when sorting re-order the row on + /// refreshing + isEditingImpactAPIsChanged = + (isSortingChanged || isMultiColumnSortingChanged) && + (oldWidget.source.sortedColumns.isNotEmpty || + widget.source.sortedColumns.isNotEmpty || + oldWidget.source.sortedColumns.length != + widget.source.sortedColumns.length); + + if (isEditingChanged || + isEditingImpactAPIsChanged || + isSelectionManagerChanged || + oldWidget.navigationMode != widget.navigationMode) { + isEditingImpactAPIsChanged = isEditingImpactAPIsChanged || + isColumnsChanged || + isStackedHeaderRowsChanged; + + if (_dataGridConfiguration.currentCell.isEditing) { + _dataGridConfiguration.currentCell.onCellSubmit( + _dataGridConfiguration, + canRefresh: !isEditingImpactAPIsChanged); + } + } + } + + void refreshFooterView() { + if (oldWidget.footer != null) { + final DataRowBase? footerRow = _rowGenerator.items.firstWhereOrNull( + (DataRowBase row) => + row.rowType == RowType.footerRow && row.rowIndex >= 0); + if (footerRow != null) { + if (isFooterRowChanged) { + // Need to reset the old footer row height in rowHeights collection. + _container.rowHeights[footerRow.rowIndex] = + _dataGridConfiguration.rowHeight; + // We remove the old footer view widget and recreate it in + // `ScrollViewWidget` when the footer property is changed. Thus updates + // the runtime changes of the footer view widget. + _rowGenerator.items.remove(footerRow); + } else if (isSourceChanged || + isDataSourceChanged || + isStackedHeaderRowsChanged) { + _container.rowHeights[footerRow.rowIndex] = + _dataGridConfiguration.rowHeight; + } + } + } + } + + void refreshTableSummaryRows() { + if (isTableSummaryRowsChanged) { + if (oldWidget.tableSummaryRows.isNotEmpty) { + _container.rowGenerator.items.removeWhere((DataRowBase row) => + row.rowType == RowType.tableSummaryRow || + row.rowType == RowType.tableSummaryCoveredRow); + } + _container.refreshHeaderLineCount(); + } + } + + if (!_dataGridConfiguration.isDesktop && + _dataGridConfiguration.allowColumnsResizing) { + final ColumnResizeController columnResizeController = + _dataGridConfiguration.columnResizeController; + if (!columnResizeController.isResizing && + columnResizeController.isResizeIndicatorVisible) { + columnResizeController.isResizeIndicatorVisible = false; + } + } + + if (isSourceChanged || + isColumnsChanged || + isDataSourceChanged || + isSelectionManagerChanged || + isColumnSizerChanged || + isDataGridControllerChanged || + isFrozenColumnPaneChanged || + isFrozenRowPaneChanged || + isSortingChanged || + isMultiColumnSortingChanged || + isShowSortNumbersChanged || + isStackedHeaderRowsChanged || + isSwipingChanged || + isFooterRowChanged || + isTableSummaryRowsChanged || + oldWidget.rowHeight != widget.rowHeight || + oldWidget.headerRowHeight != widget.headerRowHeight || + oldWidget.defaultColumnWidth != widget.defaultColumnWidth || + oldWidget.navigationMode != widget.navigationMode || + oldWidget.showCheckboxColumn != widget.showCheckboxColumn || + isRowsPerPageChanged) { + // Need to endEdit before refreshing + refreshEditing(); + + if (isSourceChanged) { + _initializeDataGridDataSource(); + } + if (isSortingChanged || isMultiColumnSortingChanged) { + if (!widget.allowSorting) { + _dataGridConfiguration.source + ..sortedColumns.clear() + .._updateDataSource(); + } else if (widget.allowSorting && !widget.allowMultiColumnSorting) { + while (_dataGridConfiguration.source.sortedColumns.length > 1) { + _dataGridConfiguration.source.sortedColumns.removeAt(0); + } + _dataGridConfiguration.source._updateDataSource(); + } + } + + if (isDataGridControllerChanged) { + oldWidget.controller?._removeDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + + _controller = _dataGridConfiguration.controller = + widget.controller ?? _controller!; + _controller!._dataGridStateDetails = _dataGridStateDetails; + + _controller?._addDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + } + + if (oldWidget.columnSizer != widget.columnSizer) { + _dataGridConfiguration.columnSizer = + widget.columnSizer ?? ColumnSizer(); + setStateDetailsInColumnSizer( + _dataGridConfiguration.columnSizer, _dataGridStateDetails!); + } + + _initializeProperties(); + + if (isStackedHeaderRowsChanged || isColumnsChanged) { + _onStackedHeaderRowsPropertyChanged(oldWidget, widget); + } + + refreshFooterView(); + + refreshTableSummaryRows(); + + _container.refreshDefaultLineSize(); + + _updateSelectionController(oldWidget, + isDataGridControlChanged: isDataGridControllerChanged, + isSelectionManagerChanged: isSelectionManagerChanged, + isSourceChanged: isSourceChanged, + isDataSourceChanged: isDataSourceChanged); + + _container.updateRowAndColumnCount(); + + if (isSourceChanged || + isColumnsChanged || + isColumnSizerChanged || + isFrozenColumnPaneChanged || + isSortingChanged || + isStackedHeaderRowsChanged || + oldWidget.showCheckboxColumn != widget.showCheckboxColumn || + widget.allowSorting && isMultiColumnSortingChanged || + widget.allowSorting && + widget.allowMultiColumnSorting && + isShowSortNumbersChanged) { + _resetColumn(clearEditing: false); + if (isColumnSizerChanged) { + resetAutoCalculation(_dataGridConfiguration.columnSizer); + } + } + + if (isSourceChanged || + isDataSourceChanged || + isFrozenRowPaneChanged || + isStackedHeaderRowsChanged || + isSortingChanged || + isTableSummaryRowsChanged || + widget.allowSorting && isMultiColumnSortingChanged || + isRowsPerPageChanged) { + _container.refreshView(clearEditing: false); + } + + if (widget.allowSwiping || + (oldWidget.allowSwiping && !widget.allowSwiping)) { + if (isDataSourceChanged || + isColumnSizerChanged || + isMaxSwipeOffsetChanged || + isFrozenRowPaneChanged || + isFrozenColumnPaneChanged || + (oldWidget.allowSwiping && !widget.allowSwiping || + isRowsPerPageChanged)) { + _container.resetSwipeOffset(); + } + } + + if (isSourceChanged || isDataSourceChanged) { + // FLUT-5404 Need to refresh the scrolling offsets if the container's + // offsets and the ScrollController's offsets are not identical. + _refreshScrollOffsets(true); + } + + _container.isDirty = true; + } else { + if (oldWidget.gridLinesVisibility != widget.gridLinesVisibility || + oldWidget.allowTriStateSorting != widget.allowTriStateSorting || + oldWidget.headerGridLinesVisibility != + widget.headerGridLinesVisibility || + oldWidget.sortingGestureType != widget.sortingGestureType || + isEditingChanged) { + // Need to endEdit before refreshing + if (isEditingChanged && _dataGridConfiguration.currentCell.isEditing) { + _dataGridConfiguration.currentCell + .onCellSubmit(_dataGridConfiguration, canRefresh: false); + } + _initializeProperties(); + _container.isDirty = true; + } else if (isPullToRefreshPropertiesChanged || isMaxSwipeOffsetChanged) { + _initializeProperties(); + } + } + } + + void _handleSelectionPropertyChanged( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + selection_manager.handleSelectionPropertyChanged( + dataGridConfiguration: _dataGridStateDetails!(), + propertyName: propertyName, + rowColumnIndex: rowColumnIndex, + recalculateRowHeight: recalculateRowHeight); + } + + void _updateSelectionController(SfDataGrid oldWidget, + {bool isSelectionManagerChanged = false, + bool isDataGridControlChanged = false, + bool isSourceChanged = false, + bool isDataSourceChanged = false}) { + if (isSourceChanged) { + oldWidget.controller?._removeDataGridPropertyChangeListener( + _handleSelectionPropertyChanged); + widget.controller + ?._addDataGridPropertyChangeListener(_handleSelectionPropertyChanged); + } + + if (isSelectionManagerChanged) { + _rowSelectionManager = _dataGridConfiguration.rowSelectionManager = + widget.selectionManager ?? _rowSelectionManager!; + selection_manager.setStateDetailsInSelectionManagerBase( + _rowSelectionManager!, _dataGridStateDetails!); + } + + if (isSourceChanged) { + _rowSelectionManager!.handleDataGridSourceChanges(); + } + + selection_manager.updateSelectionController( + dataGridConfiguration: _dataGridConfiguration, + isSelectionModeChanged: oldWidget.selectionMode != widget.selectionMode, + isNavigationModeChanged: + oldWidget.navigationMode != widget.navigationMode, + isDataSourceChanged: isDataSourceChanged); + + if (isDataGridControlChanged) { + _ensureSelectionProperties(); + } + } + + void _onStackedHeaderRowsPropertyChanged( + SfDataGrid oldWidget, SfDataGrid widget) { + _container.refreshHeaderLineCount(); + if (oldWidget.stackedHeaderRows.isNotEmpty) { + _rowGenerator.items.removeWhere( + (DataRowBase row) => row.rowType == RowType.stackedHeaderRow); + } + if (widget.onQueryRowHeight != null) { + _container.rowHeightManager.reset(); + } + + // FlUT-3851 Needs to reset the vertical and horizontal offset when both the + // controller's offset and scrollbar's offset are not identical. + if ((oldWidget.stackedHeaderRows.isNotEmpty && + widget.stackedHeaderRows.isEmpty) || + (oldWidget.stackedHeaderRows.isEmpty && + widget.stackedHeaderRows.isNotEmpty)) { + if (_dataGridConfiguration.verticalScrollController!.hasClients && + _dataGridConfiguration.container.verticalOffset > 0) { + _dataGridConfiguration.container.verticalOffset = 0; + _dataGridConfiguration.container.verticalScrollBar.value = 0; + } + if (_dataGridConfiguration.horizontalScrollController!.hasClients && + _dataGridConfiguration.container.horizontalOffset > 0) { + _dataGridConfiguration.container.horizontalOffset = 0; + _dataGridConfiguration.container.horizontalScrollBar.value = 0; + } + } + } + + void _ensureSelectionProperties() { + if (_controller!.selectedRows.isNotEmpty) { + _rowSelectionManager?.onSelectedRowsChanged(); + } + + if (_controller!.selectedRow != null) { + _rowSelectionManager?.onSelectedRowChanged(); + } + + if (_controller!.selectedIndex != -1) { + _rowSelectionManager?.onSelectedIndexChanged(); + } + } + + void _updateBoxPainter() { + if (widget.selectionMode == SelectionMode.multiple && + widget.navigationMode == GridNavigationMode.row) { + _dataGridConfiguration.configuration ??= + createLocalImageConfiguration(context); + if (_dataGridConfiguration.boxPainter == null) { + _updateDecoration(); + } + } + } + + void _updateDecoration() { + final BorderSide borderSide = + BorderSide(color: _dataGridThemeData!.currentCellStyle.borderColor); + final BoxDecoration decoration = BoxDecoration( + border: Border( + bottom: borderSide, + top: borderSide, + left: borderSide, + right: borderSide)); + + _dataGridConfiguration.boxPainter = decoration.createBoxPainter(); + } + + void _addDataGridSourceListeners() { + _source?._addDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + _source?._addDataGridSourceListener(_handleNotifyListeners); + _source?.addListener(_handleListeners); + } + + void _removeDataGridSourceListeners() { + _source?._removeDataGridPropertyChangeListener( + _handleDataGridPropertyChangeListeners); + _source?._removeDataGridSourceListener(_handleNotifyListeners); + _source?.removeListener(_handleListeners); + } + + /// Need to add the check box column, when showCheckboxColumn is true. + void _addCheckboxColumn(DataGridConfiguration dataGridConfiguration) { + if (widget.showCheckboxColumn) { + dataGridConfiguration.columns.insert( + 0, + GridCheckboxColumn( + columnName: '', + label: widget.checkboxColumnSettings.label ?? const SizedBox(), + width: widget.checkboxColumnSettings.width)); + } + } + + /// Show the refresh indicator and call the + /// [DataGridSource.handleRefresh]. + /// + /// To access this method, create the [SfDataGrid] with a + /// [GlobalKey]. + /// + /// The future returned from this method completes when the + /// [DataGridSource.handleRefresh] method’s future completes. + /// + /// By default, if you call this method without any parameter, + /// [RefreshIndicator] will be shown. If you want to disable the + /// [RefreshIndicator] and call the [DataGridSource.handleRefresh] method + /// alone, pass the parameter as `false`. + Future refresh([bool showRefreshIndicator = true]) async { + if (_dataGridConfiguration.allowPullToRefresh && + _dataGridConfiguration.refreshIndicatorKey != null) { + if (showRefreshIndicator) { + await _dataGridConfiguration.refreshIndicatorKey!.currentState?.show(); + } else { + await _dataGridConfiguration.source.handleRefresh(); + } + } + } + + @override + void didChangeDependencies() { + final ThemeData themeData = Theme.of(context); + _dataGridConfiguration.isDesktop = kIsWeb || + themeData.platform == TargetPlatform.macOS || + themeData.platform == TargetPlatform.windows || + themeData.platform == TargetPlatform.linux; + + // Sets column resizing hitTestPrecision based on the platform. + if (_dataGridConfiguration.allowColumnsResizing) { + _dataGridConfiguration.columnResizeController.setHitTestPrecision(); + } + _onDataGridTextDirectionChanged(Directionality.of(context)); + _onDataGridThemeDataChanged(SfDataGridTheme.of(context)); + _onDataGridTextScaleFactorChanged(MediaQuery.textScaleFactorOf(context)); + _updateVisualDensity(themeData.visualDensity); + _dataGridConfiguration.defaultColumnWidth = widget.defaultColumnWidth.isNaN + ? _dataGridConfiguration.isDesktop + ? 100 + : 90 + : widget.defaultColumnWidth; + super.didChangeDependencies(); + } + + @override + void didUpdateWidget(SfDataGrid oldWidget) { + super.didUpdateWidget(oldWidget); + _updateProperties(oldWidget); + } + + @override + Widget build(BuildContext context) { + if (_dataGridConfiguration.isDesktop) { + _updateBoxPainter(); + } + + return LayoutBuilder( + builder: (BuildContext _context, BoxConstraints _constraints) { + final double _measuredHeight = _dataGridConfiguration.viewHeight = + _constraints.maxHeight.isInfinite + ? _minHeight + : _constraints.maxHeight; + final double _measuredWidth = _dataGridConfiguration.viewWidth = + _constraints.maxWidth.isInfinite ? _minWidth : _constraints.maxWidth; + + if (!_container.isGridLoaded) { + _gridLoaded(); + if (_textDirection == TextDirection.rtl) { + _container.needToSetHorizontalOffset = true; + } + _container.isDirty = true; + updateColumnSizerLoadedInitiallyFlag( + _dataGridConfiguration.columnSizer, true); + } + + return ScrollViewWidget( + width: _measuredWidth, + height: _measuredHeight, + dataGridStateDetails: _dataGridStateDetails!, + ); + }); + } + + @override + void dispose() { + _removeDataGridSourceListeners(); + _controller?.removeListener(_handleDataGridPropertyChangeListeners); + _dataGridConfiguration + ..gridPaint = null + ..boxPainter = null + ..configuration = null; + _dataGridThemeData = null; + if (_swipingAnimationController != null) { + _swipingAnimationController!.dispose(); + _swipingAnimationController = null; + } + super.dispose(); + } +} + +/// A datasource for obtaining the row data for the [SfDataGrid]. +/// +/// The following APIs are mandatory to process the data, +/// * [rows] - The number of rows in a datagrid and row selection depends +/// on the [rows]. So, set the collection required for datagrid in +/// [rows]. +/// * [buildRow] - The data needed for the cells is obtained from +/// [buildRow]. +/// +/// Call the [notifyDataSourceListeners] when performing CRUD in the underlying +/// datasource. +/// +/// [DataGridSource] objects are expected to be long-lived, not recreated with +/// each build. +/// ``` dart +/// final List _employees = []; +/// +/// class EmployeeDataSource extends DataGridSource { +/// @override +/// List get rows => _employees +/// .map((dataRow) => DataGridRow(cells: [ +/// DataGridCell(columnName: 'id', value: dataRow.id), +/// DataGridCell(columnName: 'name', value: dataRow.name), +/// DataGridCell( +/// columnName: 'designation', value: dataRow.designation), +/// DataGridCell(columnName: 'salary', value: dataRow.salary), +/// ])) +/// .toList(); +/// +/// @override +/// DataGridRowAdapter? buildRow(DataGridRow row) { +/// return DataGridRowAdapter( +/// cells: row.getCells().map((dataCell) { +/// return Text(dataCell.value.toString()); +/// }).toList()); +/// } +/// } +/// ``` + +abstract class DataGridSource extends DataGridSourceChangeNotifier + with DataPagerDelegate { + /// The collection of rows to display in [SfDataGrid]. + /// + /// This must be non-null, but may be empty. + List get rows => List.empty(); + + /// Called to obtain the widget for each cell of the row. + /// + /// This method will be called for every row that are visible in datagrid’s + /// view port from the collection which is assigned to [DataGridSource.rows] + /// property. + /// + /// Return the widgets in the order in which those should be displayed in + /// each column of a row in [DataGridRowAdapter.cells]. + /// + /// The number of widgets in the collection must be exactly as many cells + /// as [SfDataGrid.columns] in the [SfDataGrid]. + /// + /// This method will be called whenever you call the [notifyListeners] method. + DataGridRowAdapter? buildRow(DataGridRow row); + + /// Return the copy of the [DataGridSource.rows]. + /// It holds the sorted collection if the sorting is applied in DataGrid. + /// + /// Use this property to get the corresponding visible row index to perform the customization + /// such as alternate row color, setting row color based on row index and so on. + List get effectiveRows => _effectiveRows; + + List _effectiveRows = []; + + List _unSortedRows = []; + + /// To hold the certain range of collection to be displayed in the [SfDataPager] page. + List _paginatedRows = []; + + /// Helps to suspend the multiple update on SfDataGrid using with + /// SfDataPager. + bool _suspendDataPagerUpdate = false; + + DataGridStateDetails? _dataGridStateDetails; + + /// Called whenever you call [notifyListeners] or [notifyDataSourceListeners] + /// in the DataGridSource class. If you want to recalculate all columns + /// width (may be when underlying data gets changed), return true. + /// + /// Returning true may impact performance as the column widths are + /// recalculated again (whenever the notifyListeners is called). + /// + /// If you are aware that column widths are going to be same whenever + /// underlying data changes, return 'false' from this method. + /// + /// Note: Column widths will be recalculated automatically whenever a new + /// instance of DataGridSource is assigned to SfDataGrid. + /// ``` dart + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// List get rows => _employees + /// .map((dataRow) => DataGridRow(cells: [ + /// DataGridCell(columnName: 'id', value: dataRow.id), + /// DataGridCell(columnName: 'name', value: dataRow.name), + /// DataGridCell( + /// columnName: 'designation', value: dataRow.designation), + /// DataGridCell(columnName: 'salary', value: dataRow.salary), + /// ])) + /// .toList(); + /// + /// @override + /// bool shouldRecalculateColumnWidths() { + /// return true; + /// } + /// + /// @override + /// DataGridRowAdapter? buildRow(DataGridRow row) { + /// return DataGridRowAdapter( + /// cells: row.getCells().map((dataCell) { + /// return Text(dataCell.value.toString()); + /// }).toList()); + /// } + /// } + /// + /// ``` + @protected + bool shouldRecalculateColumnWidths() { + return false; + } + + /// The collection of [SortColumnDetails] objects to sort the columns in + /// [SfDataGrid]. + /// + /// You can use this property to sort the columns programmatically also. + /// Call [sort] method after you added the column details in [sortedColumns] + /// programmatically. + /// + /// The following example show how to sort the columns on datagrid loading, + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter DataGrid'), + /// ), + /// body: Column( + /// children: [ + /// TextButton( + /// child: Text('Click'), + /// onPressed: () { + /// _employeeDataSource.sortedColumns + /// .add(SortColumnDetails('id', SortDirection.ascending)); + /// _employeeDataSource.sort(); + /// }, + /// ), + /// SfDataGrid( + /// source: _employeeDataSource, + /// allowSorting: true, + /// columns: [ + /// GridColumn(columnName: 'id', label: Text('ID')), + /// GridColumn(columnName: 'name', label: Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + ///``` + /// See also: + /// + /// * [SfDataGrid.allowSorting] – which allows users to sort the columns in + /// [SfDataGrid]. + /// * [GridColumn.allowSorting] - which allows users to sort the corresponding + /// column in [SfDataGrid]. + /// * [DataGridSource.sort] - call this method when you are adding the + /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. + List get sortedColumns => _sortedColumns; + final List _sortedColumns = []; + + /// Called when the sorting is applied to the column. + /// + /// Overriding this method gives complete control over sorting. You can handle + /// the sorting completely in your own way. The rows argument provides the + /// unsorted [DataGridRow] collection. + /// + /// You can apply the sorting to rows argument. DataGrid will render the rows + /// based on the [rows] argument. You don’t need to call [notifyListeners] + /// within this method. However, you must override this method only if you + /// want to write the entire sorting logic by yourself. Otherwise, for custom + /// comparison, you can just override [DataGridSource.compare] method and + /// return the custom sorting order. + /// + /// For most of your cases, the 'compare' method should be sufficient. + /// The [DataGridSource.compare] method can be used to do custom sorting based + /// on the length of the text, case insensitive sorting, and so on. + /// + /// See also, + /// + /// [DataGridSource.compare] – To write the custom sorting for most of the use + /// cases. + @protected + void performSorting(List rows) { + if (sortedColumns.isEmpty) { + return; + } + rows.sort((DataGridRow a, DataGridRow b) { + return _compareValues(sortedColumns, a, b); + }); + } + + /// To update the sorted collection in _paginatedRows, notifyListener should be + /// called instead notifyDataGridPropertyChangeListener. Because, notifyListiner is common for + /// in DataPagerDelegate and DataGridSource will get notified. + void _updateDataPagerOnSorting() { + if (_pageCount > 0 && + _paginatedRows.isNotEmpty && + !_suspendDataPagerUpdate) { + _suspendDataPagerUpdate = true; + notifyListeners(); + _suspendDataPagerUpdate = false; + } + } + + int _compareValues( + List sortedColumns, DataGridRow a, DataGridRow b) { + if (sortedColumns.length > 1) { + for (int i = 0; i < sortedColumns.length; i++) { + final SortColumnDetails sortColumn = sortedColumns[i]; + final int compareResult = compare(a, b, sortColumn); + if (compareResult != 0) { + return compareResult; + } else { + final List remainingSortColumns = sortedColumns + .skipWhile((SortColumnDetails value) => value == sortColumn) + .toList(growable: false); + return _compareValues(remainingSortColumns, a, b); + } + } + } + final SortColumnDetails sortColumn = sortedColumns.last; + return compare(a, b, sortColumn); + } + + /// Called when the sorting is applied for column. This method compares the + /// two objects and returns the order either they are equal, or one is + /// greater than or lesser than the other. + /// + /// You can return the following values, + /// * a negative integer if a is smaller than b, + /// * zero if a is equal to b, and + /// * a positive integer if a is greater than b. + /// + /// You can override this method and do the custom sorting based + /// on your requirement. Here [sortColumn] provides the details about the + /// column which is currently sorted with the sort direction. You can get the + /// currently sorted column and do the custom sorting for specific column. + /// + /// + /// The below example shows how to sort the `name` column based on the case + /// insensitive in ascending or descending order. + /// + /// ```dart + /// class EmployeeDataSource extends DataGridSource { + /// @override + /// List get rows => _employees + /// .map((dataRow) => DataGridRow(cells: [ + /// DataGridCell(columnName: 'id', value: dataRow.id), + /// DataGridCell(columnName: 'name', value: dataRow.name), + /// DataGridCell( + /// columnName: 'designation', value: dataRow.designation), + /// DataGridCell(columnName: 'salary', value: dataRow.salary), + /// ])) + /// .toList(); + /// + /// @override + /// DataGridRowAdapter? buildRow(DataGridRow row) { + /// return DataGridRowAdapter( + /// cells: row.getCells().map((dataCell) { + /// return Text(dataCell.value.toString()); + /// }).toList()); + /// } + /// + /// @override + /// int compare(DataGridRow? a, DataGridRow? b, SortColumnDetails sortColumn) { + /// if (sortColumn.name == 'name') { + /// final String? valueA = a + /// ?.getCells() + /// .firstWhereOrNull((dataCell) => dataCell.columnName == 'name') + /// ?.value; + /// final String? valueB = b + /// ?.getCells() + /// .firstWhereOrNull((dataCell) => dataCell.columnName == 'name') + /// ?.value; + /// + /// if (valueA == null || valueB == null) { + /// return 0; + /// } + /// + /// if (sortColumn.sortDirection == DataGridSortDirection.ascending) { + /// return valueA.toLowerCase().compareTo(valueB.toLowerCase()); + /// } else { + /// return valueB.toLowerCase().compareTo(valueA.toLowerCase()); + /// } + /// } + /// + /// return super.compare(a, b, sortColumn); + /// } + /// + /// ``` + @protected + int compare(DataGridRow? a, DataGridRow? b, SortColumnDetails sortColumn) { + Object? _getCellValue(List? cells, String columnName) { + return cells + ?.firstWhereOrNull( + (DataGridCell element) => element.columnName == columnName) + ?.value; + } + + final Object? valueA = _getCellValue(a?.getCells(), sortColumn.name); + final Object? valueB = _getCellValue(b?.getCells(), sortColumn.name); + return _compareTo(valueA, valueB, sortColumn.sortDirection); + } + + int _compareTo( + dynamic value1, dynamic value2, DataGridSortDirection sortDirection) { + if (sortDirection == DataGridSortDirection.ascending) { + if (value1 == null) { + return -1; + } else if (value2 == null) { + return 1; + } + return value1.compareTo(value2) as int; + } else { + if (value1 == null) { + return 1; + } else if (value2 == null) { + return -1; + } + return value2.compareTo(value1) as int; + } + } + + void _updateDataSource() { + if (sortedColumns.isNotEmpty) { + _unSortedRows = rows.toList(); + _effectiveRows = _unSortedRows; + } else { + _effectiveRows = rows; + } + // Should refresh sorting when the data grid source is updated. + performSorting(_effectiveRows); + + /// Helps to update the sorted collection in _paginatedRows + /// by call the DataPagerDelegate.handlePageChange after sorting. + _updateDataPagerOnSorting(); + } + + /// Call this method when you are adding the [SortColumnDetails] + /// programmatically to the [DataGridSource.sortedColumns]. + /// + /// ```dart + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text('Syncfusion Flutter DataGrid'), + /// ), + /// body: Column( + /// children: [ + /// FlatButton( + /// child: Text('Click'), + /// onPressed: () { + /// _employeeDataSource.sortedColumns + /// .add(SortColumnDetails('id', SortDirection.ascending)); + /// _employeeDataSource.sort(); + /// }, + /// ), + /// SfDataGrid( + /// source: _employeeDataSource, + /// allowSorting: true, + /// columns: [ + /// GridColumn(columnName: 'id', label:Text('ID')), + /// GridColumn(columnName: 'name', label:Text('Name')), + /// GridColumn(columnName: 'designation', label: Text('Designation')), + /// GridColumn(columnName: 'salary', label: Text('Salary')), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + void sort() { + _updateDataSource(); + _notifyDataGridPropertyChangeListeners(propertyName: 'Sorting'); + } + + /// An indexer to retrieve the data from the underlying datasource. If the + /// sorting is applied, the data will be retrieved from the sorted datasource. + DataGridRow operator [](int index) => _effectiveRows[index]; + + /// Called when [LoadMoreRows] function is called from the + /// [SfDataGrid.loadMoreViewBuilder]. + /// + /// Call the [notifyListeners] to refresh the datagrid based on current + /// available rows. + /// + /// See also, + /// + /// [SfDataGrid.loadMoreViewBuilder] - A builder that sets the widget to + /// display at end of the datagrid when end of the datagrid is reached on + /// vertical scrolling. + @protected + Future handleLoadMoreRows() async {} + + /// Called when the `swipe to refresh` is performed in [SfDataGrid]. + /// + /// This method will be called only if the + /// [SfDataGrid.allowPullToRefresh] property returns true. + /// + /// Call the [notifyListeners] to refresh the datagrid based on current + /// available rows. + @protected + Future handleRefresh() async {} + + /// Called to obtain the widget when a cell is moved into edit mode. + /// + /// The following example shows how to override this method and return the + /// widget for specific column. + /// + /// ```dart + /// + /// TextEditingController editingController = TextEditingController(); + /// + /// dynamic newCellValue; + /// + /// @override + /// Widget? buildEditWidget(DataGridRow dataGridRow, + /// RowColumnIndex rowColumnIndex, GridColumn column, + /// CellSubmit submitCell) { + /// // To set the value for TextField when cell is moved into edit mode. + /// final String displayText = dataGridRow + /// .getCells() + /// .firstWhere((DataGridCell dataGridCell) => + /// dataGridCell.columnName == column.columnName) + /// .value + /// ?.toString() ?? + /// ''; + /// + /// /// Returning the TextField with the numeric keyboard configuration. + /// if (column.columnName == 'id') { + /// return Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.centerRight, + /// child: TextField( + /// autofocus: true, + /// controller: editingController..text = displayText, + /// textAlign: TextAlign.right, + /// decoration: const InputDecoration( + /// contentPadding: EdgeInsets.all(0), + /// border: InputBorder.none, + /// isDense: true), + /// inputFormatters: [ + /// FilteringTextInputFormatter.allow(RegExp('[0-9]')) + /// ], + /// keyboardType: TextInputType.number, + /// onChanged: (String value) { + /// if (value.isNotEmpty) { + /// print(value); + /// newCellValue = int.parse(value); + /// } else { + /// newCellValue = null; + /// } + /// }, + /// onSubmitted: (String value) { + /// /// Call [CellSubmit] callback to fire the canSubmitCell and + /// /// onCellSubmit to commit the new value in single place. + /// submitCell(); + /// }, + /// )); + /// } + /// } + /// ``` + /// Call the cellSubmit function whenever you are trying to save the cell + /// values. When you call this method, it will call [canSubmitCell] and + /// [onCellSubmit] methods. So, your usual cell value updation will be done + /// in single place. + Widget? buildEditWidget(DataGridRow dataGridRow, + RowColumnIndex rowColumnIndex, GridColumn column, CellSubmit submitCell) { + return null; + } + + /// Called whenever the cell is moved into edit mode. + /// + /// If you want to disable editing for the cells in specific scenarios, + /// you can return false. + /// + /// [rowColumnIndex] represents the index of row and column which are + /// currently in view not based on the actual index. If you want to get the + /// actual row index even after sorting is applied, you can use + /// `DataGridSource.rows.indexOf` method and pass the [dataGridRow]. It will + /// provide the actual row index from unsorted [DataGridRow] collection. + bool onCellBeginEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, + GridColumn column) { + return true; + } + + /// Called whenever the cell’s editing is completed. + /// + /// Typically, this will be called whenever the [notifyListeners] is called + /// when cell is in editing mode and key navigation is performed to move a + /// cell to another cell from the cell which is currently editing. + /// For eg, Enter key, TAB key and so on. + /// + /// The following example show how to override this method and save the + /// currently edited value for specific column. + /// + /// ``` dart + /// @override + /// void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, + /// GridColumn column) { + /// final dynamic oldValue = dataGridRow + /// .getCells() + /// .firstWhereOrNull((DataGridCell dataGridCell) => + /// dataGridCell.columnName == column.columnName) + /// ?.value ?? + /// ''; + /// + /// final int dataRowIndex = rows.indexOf(dataGridRow); + /// + /// if (newCellValue == null || oldValue == newCellValue) { + /// return; + /// } + /// + /// if (column.columnName == 'id') { + /// rows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] = + /// DataGridCell(columnName: 'id', value: newCellValue); + /// + /// // Save the new cell value to model collection also. + /// employees[dataRowIndex].id = newCellValue as int; + /// } + /// + /// // To reset the new cell value after successfully updated to DataGridRow + /// //and underlying mode. + /// newCellValue = null; + /// } + ///``` + /// This method will never be called when you return false from [onCellBeginEdit]. + void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, + GridColumn column) {} + + /// Called whenever the cell’s editing is completed i.e. prior to + /// [onCellSubmit] method. + /// + /// If you want to restrict the cell from being end its editing, you can + /// return false. Otherwise, return true. [onCellSubmit] will be called only + /// if the [canSubmitCell] returns true. + bool canSubmitCell(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, + GridColumn column) { + return true; + } + + /// Called when you press the [LogicalKeyboardKey.escape] key when + /// the [DataGridCell] on editing to cancel the editing. + void onCellCancelEdit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, + GridColumn column) {} + + @override + Future handlePageChange(int oldPageIndex, int newPageIndex) { + if (effectiveRows.isEmpty || _pageCount == 0) { + _paginatedRows = []; + return Future.value(true); + } + + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + + final int rowsPerPage = dataGridConfiguration.rowsPerPage ?? + (effectiveRows.length / _pageCount).ceil(); + final int _startIndex = newPageIndex * rowsPerPage.toInt(); + int _endIndex = _startIndex + rowsPerPage.toInt(); + + /// Need to calculate endIndex for the last page, when the number of rows is + /// lesser than rowsPerPage. + if (_endIndex > effectiveRows.length) { + _endIndex = effectiveRows.length; + } + + /// Get particular range from the sorted collection. + if (_startIndex < effectiveRows.length && + _endIndex <= effectiveRows.length) { + _paginatedRows = effectiveRows.getRange(_startIndex, _endIndex).toList(); + } else { + _paginatedRows = []; + } + + /// To update the collection in data pager. + notifyListeners(); + + return Future.value(true); + } + + /// Calculates the summary value for the table summary row of a specific column. + /// + /// Override this method to write the custom logic to calculate the custom + /// summary. + /// + /// The `summaryColumn` will be null for the spanned table summary columns. + String calculateSummaryValue(GridTableSummaryRow summaryRow, + GridSummaryColumn? summaryColumn, RowColumnIndex rowColumnIndex) { + final int titleColumnSpan = grid_helper.getSummaryTitleColumnSpan( + _dataGridStateDetails!(), summaryRow); + + if (summaryRow.showSummaryInRow || + (!summaryRow.showSummaryInRow && + titleColumnSpan > 0 && + rowColumnIndex.columnIndex < titleColumnSpan)) { + String title = summaryRow.title ?? ''; + if (summaryRow.title != null) { + for (final GridSummaryColumn cell in summaryRow.columns) { + if (title.contains(cell.name)) { + final String summaryValue = + grid_helper.getSummaryValue(cell, _effectiveRows); + title = title.replaceAll('{${cell.name}}', summaryValue); + } + } + } + return title; + } else { + return grid_helper.getSummaryValue(summaryColumn!, _effectiveRows); + } + } + + /// Called to obtain the widget for each cell of the table summary row. + /// + /// Typically, a [Text] widget. `summaryValue` argument holds the calculated + /// summary value based on [GridSummaryColumn.summaryType]. Use this + /// `summaryValue` argument and display in your required widget. + /// + /// This method will be called for visible cells in table summary rows. + /// + /// The `summaryColumn` will be null for the spanned table summary columns. + Widget? buildTableSummaryCellWidget( + GridTableSummaryRow summaryRow, + GridSummaryColumn? summaryColumn, + RowColumnIndex rowColumnIndex, + String summaryValue) { + return null; + } +} + +/// Controls a [SfDataGrid] widget. +/// +/// This can be used to control the selection and currentcell operations such +/// as programmatically select a row or rows, move the currentcell to +/// required position. +/// +/// DataGrid controllers are typically stored as member variables in [State] +/// objects and are reused in each [State.build]. +class DataGridController extends DataGridSourceChangeNotifier { + /// Creates the [DataGridController] with the [selectedIndex], [selectedRow] + /// and [selectedRows]. + DataGridController( + {int selectedIndex = -1, + DataGridRow? selectedRow, + List selectedRows = const []}) + : _selectedRow = selectedRow, + _selectedIndex = selectedIndex, + _selectedRows = selectedRows.toList() { + _currentCell = RowColumnIndex(-1, -1); + _horizontalOffset = 0.0; + _verticalOffset = 0.0; + } + + DataGridStateDetails? _dataGridStateDetails; + + /// The collection of objects that contains object of corresponding + /// to the selected rows in [SfDataGrid]. + List get selectedRows => _selectedRows; + List _selectedRows = List.empty(); + + /// The collection of objects that contains object of corresponding + /// to the selected rows in [SfDataGrid]. + set selectedRows(List newSelectedRows) { + if (_selectedRows == newSelectedRows) { + return; + } + + _selectedRows = newSelectedRows; + _notifyDataGridPropertyChangeListeners(propertyName: 'selectedRows'); + } + + /// An index of the corresponding selected row. + int get selectedIndex => _selectedIndex; + int _selectedIndex; + + /// An index of the corresponding selected row. + set selectedIndex(int newSelectedIndex) { + if (_selectedIndex == newSelectedIndex) { + return; + } + + _selectedIndex = newSelectedIndex; + _notifyDataGridPropertyChangeListeners(propertyName: 'selectedIndex'); + } + + /// An object of the corresponding selected row. + /// + /// The given object must be given from the underlying datasource of the + /// [SfDataGrid]. + DataGridRow? get selectedRow => _selectedRow; + DataGridRow? _selectedRow; + + /// An object of the corresponding selected row. + /// + /// The given object must be given from the underlying datasource of the + /// [SfDataGrid]. + set selectedRow(DataGridRow? newSelectedRow) { + if (_selectedRow == newSelectedRow) { + return; + } + + _selectedRow = newSelectedRow; + _notifyDataGridPropertyChangeListeners(propertyName: 'selectedRow'); + } + + /// The current scroll offset of the vertical scrollbar. + double get verticalOffset => _verticalOffset; + late double _verticalOffset; + + /// The current scroll offset of the horizontal scrollbar. + double get horizontalOffset => _horizontalOffset; + late double _horizontalOffset; + + ///If the [rowIndex] alone is given, the entire row will be set as dirty. + ///So, data which is displayed in a row will be refreshed. + /// You can call this method when the data is updated in row in + /// underlying datasource. + /// + /// If the `recalculateRowHeight` is set as true along with the [rowIndex], + /// [SfDataGrid.onQueryRowHeight] callback will be called for that row. + /// So, the row height can be reset based on the modified data. + /// This is useful when setting auto row height + /// using [SfDataGrid.onQueryRowHeight] callback. + void refreshRow(int rowIndex, {bool recalculateRowHeight = false}) { + _notifyDataGridPropertyChangeListeners( + rowColumnIndex: RowColumnIndex(rowIndex, -1), + propertyName: 'refreshRow', + recalculateRowHeight: recalculateRowHeight); + } + + /// A cell which is currently active. + /// + /// This is used to identify the currently active cell to process the + /// key navigation. + RowColumnIndex get currentCell => _currentCell; + RowColumnIndex _currentCell = RowColumnIndex.empty; + + /// Moves the currentcell to the specified cell coordinates. + void moveCurrentCellTo(RowColumnIndex rowColumnIndex) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (rowColumnIndex != RowColumnIndex(-1, -1) && + dataGridConfiguration.selectionMode != SelectionMode.none && + dataGridConfiguration.navigationMode != GridNavigationMode.row) { + final int rowIndex = grid_helper.resolveToRowIndex( + dataGridConfiguration, rowColumnIndex.rowIndex); + final int columnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, rowColumnIndex.columnIndex); + // Ignore the scrolling when the row index or column index are in negative + // or invalid. + if (rowIndex.isNegative || columnIndex.isNegative) { + return; + } + final SelectionManagerBase rowSelectionController = + dataGridConfiguration.rowSelectionManager; + if (rowSelectionController is RowSelectionManager) { + selection_manager.processSelectionAndCurrentCell( + dataGridConfiguration, RowColumnIndex(rowIndex, columnIndex), + isProgrammatic: true); + } + } + } + } + + /// Scrolls the [SfDataGrid] to the given row and column index. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + /// + /// Also, you can control the position of a row when it comes to view by + /// passing the [DataGridScrollPosition] as an argument for rowPosition where + /// as you can pass [DataGridScrollPosition] as an argument for columnPosition + /// to control the position of a column. + Future scrollToCell(double rowIndex, double columnIndex, + {bool canAnimate = false, + DataGridScrollPosition rowPosition = DataGridScrollPosition.start, + DataGridScrollPosition columnPosition = + DataGridScrollPosition.start}) async { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + final ScrollAxisBase scrollRows = + dataGridConfiguration.container.scrollRows; + final ScrollAxisBase scrollColumns = + dataGridConfiguration.container.scrollColumns; + + if (rowIndex > dataGridConfiguration.container.rowCount || + columnIndex > scrollColumns.lineCount || + (rowIndex.isNegative && columnIndex.isNegative)) { + return; + } + + final int _rowIndex = grid_helper.resolveToRowIndex( + dataGridConfiguration, rowIndex.toInt()); + final int _columnIndex = grid_helper.resolveToGridVisibleColumnIndex( + dataGridConfiguration, columnIndex.toInt()); + double verticalOffset = + grid_helper.getVerticalOffset(dataGridConfiguration, _rowIndex); + double horizontalOffset = + grid_helper.getHorizontalOffset(dataGridConfiguration, _columnIndex); + + if (dataGridConfiguration.textDirection == TextDirection.rtl && + columnIndex == -1) { + horizontalOffset = dataGridConfiguration.container.extentWidth - + dataGridConfiguration.viewWidth - + horizontalOffset > + 0 + ? dataGridConfiguration.container.extentWidth - + dataGridConfiguration.viewWidth - + horizontalOffset + : 0; + } + + verticalOffset = grid_helper.resolveScrollOffsetToPosition( + rowPosition, + scrollRows, + verticalOffset, + dataGridConfiguration.viewHeight, + scrollRows.headerExtent, + scrollRows.footerExtent, + dataGridConfiguration.rowHeight, + dataGridConfiguration.container.verticalOffset, + _rowIndex); + + horizontalOffset = grid_helper.resolveScrollOffsetToPosition( + columnPosition, + scrollColumns, + horizontalOffset, + dataGridConfiguration.viewWidth, + scrollColumns.headerExtent, + scrollColumns.footerExtent, + dataGridConfiguration.defaultColumnWidth, + dataGridConfiguration.container.horizontalOffset, + _columnIndex); + + grid_helper.scrollVertical( + dataGridConfiguration, verticalOffset, canAnimate); + // Need to add await for the horizontal scrolling alone, to avoid the delay time between vertical and horizontal scrolling. + await grid_helper.scrollHorizontal( + dataGridConfiguration, horizontalOffset, canAnimate); + } + } + + /// Scrolls the [SfDataGrid] to the given index. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + /// + /// Also, you can control the position of a row when it comes to view by passing + /// the [DataGridScrollPosition] as an argument for position. + Future scrollToRow(double rowIndex, + {bool canAnimate = false, + DataGridScrollPosition position = DataGridScrollPosition.start}) async { + return scrollToCell(rowIndex, -1, + canAnimate: canAnimate, rowPosition: position); + } + + /// Scrolls the [SfDataGrid] to the given column index. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + /// + /// Also, you can control the position of a row when it comes to view by passing + /// the [DataGridScrollPosition] as an argument for position. + Future scrollToColumn(double columnIndex, + {bool canAnimate = false, + DataGridScrollPosition position = DataGridScrollPosition.start}) async { + return scrollToCell(-1, columnIndex, + canAnimate: canAnimate, columnPosition: position); + } + + /// Scroll the vertical scrollbar from current position to the given value. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + Future scrollToVerticalOffset(double offset, + {bool canAnimate = false}) async { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridSettings = _dataGridStateDetails!(); + return grid_helper.scrollVertical(dataGridSettings, offset, canAnimate); + } + } + + /// Scroll the horizontal scrollbar from current value to the given value. + /// + /// If you want animation on scrolling, you can pass true as canAnimate argument. + Future scrollToHorizontalOffset(double offset, + {bool canAnimate = false}) async { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridSettings = _dataGridStateDetails!(); + return grid_helper.scrollHorizontal(dataGridSettings, offset, canAnimate); + } + } + + /// Begins the edit to the given [RowColumnIndex] in [SfDataGrid]. + void beginEdit(RowColumnIndex rowColumnIndex) { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (!dataGridConfiguration.allowEditing || + dataGridConfiguration.selectionMode == SelectionMode.none || + dataGridConfiguration.navigationMode == GridNavigationMode.row) { + return; + } + + dataGridConfiguration.currentCell.onCellBeginEdit( + editingRowColumnIndex: rowColumnIndex, isProgrammatic: true); + } + } + + /// Ends the current editing of a cell in [SfDataGrid]. + void endEdit() { + if (_dataGridStateDetails != null) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails!(); + if (!dataGridConfiguration.allowEditing || + dataGridConfiguration.selectionMode == SelectionMode.none || + dataGridConfiguration.navigationMode == GridNavigationMode.row) { + return; + } + + dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + } + } +} + +/// A delegate that provides the row count details and method to listen the +/// page navigation in [SfDataPager]. +/// +/// The following code initializes the data source and controller. +/// +/// ```dart +/// finalListpaginatedDataTable=[]; +/// ``` +/// +/// The following code example shows how to initialize the [DataPagerDelegate]. +/// +/// ```dart +/// class PaginatedDataGridSource extends DataPagerDelegate{ +/// @override +/// Future handlePageChange(int oldPageIndex, int newPageIndex) async { +/// _paginatedData = paginatedDataTable +/// .getRange(startRowIndex, endRowIndex ) +/// .toList(growable: false); +/// notifyListeners(); +/// return true; +/// } +/// } +/// ``` +class DataPagerDelegate { + /// Number of pages to be displayed in the [SfDataPager]. + double _pageCount = 0; + + /// Called when the page is navigated. + /// + /// Return true, if the navigation should be performed. Otherwise, return + /// false to disable the navigation for specific page index. + Future handlePageChange(int oldPageIndex, int newPageIndex) async { + return true; + } +} + +/// ToDO +class DataGridSourceChangeNotifier extends ChangeNotifier { + final ObserverList<_DataGridSourceListener> _dataGridSourceListeners = + ObserverList<_DataGridSourceListener>(); + + void _addDataGridSourceListener(_DataGridSourceListener listener) { + _dataGridSourceListeners.add(listener); + } + + void _removeDataGridSourceListener(_DataGridSourceListener listener) { + _dataGridSourceListeners.remove(listener); + } + + final ObserverList<_DataGridPropertyChangeListener> + _dataGridPropertyChangeListeners = + ObserverList<_DataGridPropertyChangeListener>(); + + void _addDataGridPropertyChangeListener( + _DataGridPropertyChangeListener listener) { + _dataGridPropertyChangeListeners.add(listener); + } + + void _removeDataGridPropertyChangeListener( + _DataGridPropertyChangeListener listener) { + _dataGridPropertyChangeListeners.remove(listener); + } + + @protected + @override + void notifyListeners() { + super.notifyListeners(); + } + + /// Calls all the datagrid source listeners. + /// Call this method whenever the underlying data is added or removed. If the value of the specific cell is updated, call this method with RowColumnIndex argument where it refers the corresponding row and column index of the cell. + @protected + void notifyDataSourceListeners({RowColumnIndex? rowColumnIndex}) { + for (final Function listener in _dataGridSourceListeners) { + listener(rowColumnIndex: rowColumnIndex); + } + } + + /// Call this method whenever the rowColumnIndex, propertyName and recalculateRowHeight of the underlying data are updated internally. + void _notifyDataGridPropertyChangeListeners( + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + for (final Function listener in _dataGridPropertyChangeListeners) { + listener( + rowColumnIndex: rowColumnIndex, + propertyName: propertyName, + recalculateRowHeight: recalculateRowHeight); + } + } +} + +/// Call this method whenever the rowColumnIndex, propertyName and recalculateRowHeight of the +/// underlying data are updated internally. +void notifyDataGridPropertyChangeListeners(DataGridSource source, + {RowColumnIndex? rowColumnIndex, + String? propertyName, + bool recalculateRowHeight = false}) { + source._notifyDataGridPropertyChangeListeners( + rowColumnIndex: rowColumnIndex, + recalculateRowHeight: recalculateRowHeight, + propertyName: propertyName); +} + +/// ToDo +Future handleLoadMoreRows(DataGridSource source) async { + return source.handleLoadMoreRows(); +} + +/// ToDo +Future handleRefresh(DataGridSource source) async { + return source.handleRefresh(); +} + +/// ToDo +void updateDataSource(DataGridSource source) { + source._updateDataSource(); +} + +/// ToDo +List effectiveRows(DataGridSource source) { + if (source._paginatedRows.isNotEmpty && source._pageCount > 0.0) { + return source._paginatedRows; + } else { + return source._effectiveRows; + } +} + +/// Helps to set the page count in DataGridSource. +void setPageCount(DataPagerDelegate delegate, double pageCount) { + final DataGridSource source = delegate as DataGridSource; + final double oldPageCount = source._pageCount; + source._pageCount = pageCount; + + /// If the pageCount is changed during CRUD operation(while removing last page), we should update the + /// current page. + if (oldPageCount != pageCount) { + WidgetsBinding.instance!.addPostFrameCallback((Duration timeStamp) { + source.notifyListeners(); + }); + } + + /// User removes the DataPager in view at runtime, we need to reset the + /// DataPager properties in DataGridSource. + if (source._paginatedRows.isNotEmpty && source._pageCount == 0.0) { + /// We couldn't refresh the view at same time when framework doing an + /// frame refreshing. + WidgetsBinding.instance!.addPostFrameCallback((Duration timeStamp) { + source._paginatedRows = []; + source.notifyDataSourceListeners(); + }); + } +} + +/// ToDo +void updateSelectedIndex(DataGridController controller, int newSelectedIndex) { + controller._selectedIndex = newSelectedIndex; +} + +/// ToDo +void updateSelectedRow( + DataGridController controller, DataGridRow? newSelectedRow) { + controller._selectedRow = newSelectedRow; +} + +/// ToDo +void updateCurrentCellIndex( + DataGridController controller, RowColumnIndex newCurrentCellIndex) { + controller._currentCell = newCurrentCellIndex; +} + +/// ToDo +void updateVerticalOffset(DataGridController controller, double offset) { + controller._verticalOffset = offset; +} + +/// ToDo +void updateHorizontalOffset(DataGridController controller, double offset) { + controller._horizontalOffset = offset; +} + +/// Todo +void setChildColumnIndexes( + StackedHeaderCell stackedHeaderCell, List childSequence) { + stackedHeaderCell._childColumnIndexes = childSequence; +} + +/// Helps to get the child column indexes of the given `StackedHeaderCell`. +List getChildColumnIndexes(StackedHeaderCell stackedHeaderCell) { + return stackedHeaderCell._childColumnIndexes; +} 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 new file mode 100644 index 000000000..4b8570c35 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/cell_widget.dart @@ -0,0 +1,972 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:ui'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; + +import '../../grid_common/row_column_index.dart'; +import '../helper/callbackargs.dart'; +import '../helper/datagrid_configuration.dart'; +import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/enums.dart'; +import '../runtime/column.dart'; +import '../runtime/generator.dart'; +import '../sfdatagrid.dart'; +import 'rendering_widget.dart'; + +/// A widget which displays in the cells. +class GridCell extends StatefulWidget { + /// Creates the [GridCell] for [SfDataGrid] widget. + const GridCell( + {required Key key, + required this.dataCell, + required this.isDirty, + required this.backgroundColor, + required this.child, + required this.dataGridStateDetails}) + : super(key: key); + + /// Holds the information required to display the cell. + final DataCellBase dataCell; + + /// The [child] contained by the [GridCell]. + final Widget child; + + /// The color to paint behind the [child]. + final Color backgroundColor; + + /// Decides whether the [GridCell] should be refreshed when [SfDataGrid] is + /// rebuild. + final bool isDirty; + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + @override + State createState() => _GridCellState(); +} + +class _GridCellState extends State { + late PointerDeviceKind _kind; + Timer? tapTimer; + + DataGridStateDetails get dataGridStateDetails => widget.dataGridStateDetails; + + bool _isDoubleTapEnabled(DataGridConfiguration dataGridConfiguration) => + dataGridConfiguration.onCellDoubleTap != null || + (dataGridConfiguration.allowEditing && + dataGridConfiguration.editingGestureType == + EditingGestureType.doubleTap); + + void _handleOnTapDown(TapDownDetails details) { + _kind = details.kind!; + final DataCellBase dataCell = widget.dataCell; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + + // Clear editing when tap on the stacked header cell. + if (widget.dataCell.cellType == CellType.stackedHeaderCell && + dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + } + + if (_isDoubleTapEnabled(dataGridConfiguration)) { + _handleDoubleTapOnEditing(dataGridConfiguration, dataCell, details); + } + } + + void _handleDoubleTapOnEditing(DataGridConfiguration dataGridConfiguration, + DataCellBase dataCell, TapDownDetails details) { + if (tapTimer != null && tapTimer!.isActive) { + tapTimer!.cancel(); + } else { + tapTimer?.cancel(); + // 190 millisecond to satisfies all desktop touchpad double-tap + // action + tapTimer = Timer(const Duration(milliseconds: 190), () { + if (dataGridConfiguration.allowEditing && dataCell.isEditing) { + tapTimer?.cancel(); + return; + } + _handleOnTapUp( + tapDownDetails: details, + tapUpDetails: null, + dataGridConfiguration: dataGridConfiguration, + dataCell: dataCell, + kind: _kind); + tapTimer?.cancel(); + }); + } + } + + Widget _wrapInsideGestureDetector() { + final DataCellBase dataCell = widget.dataCell; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + // Check the DoubleTap is enabled + // If its enable, we have to ignore the onTapUp and we need to handle both + // tap and double tap in onTap itself to avoid the delay on double tap + // callback + final bool isDoubleTapEnabled = _isDoubleTapEnabled(dataGridConfiguration); + return GestureDetector( + onTapUp: isDoubleTapEnabled + ? null + : (TapUpDetails details) { + _handleOnTapUp( + tapUpDetails: details, + tapDownDetails: null, + dataGridConfiguration: dataGridConfiguration, + dataCell: dataCell, + kind: _kind); + }, + onTapDown: _handleOnTapDown, + onTap: isDoubleTapEnabled + ? () { + if (tapTimer != null && !tapTimer!.isActive) { + _handleOnDoubleTap( + dataCell: dataCell, + dataGridConfiguration: dataGridConfiguration); + } + } + : null, + onTapCancel: () { + if (tapTimer != null && tapTimer!.isActive) { + tapTimer?.cancel(); + } + }, + onSecondaryTapUp: (TapUpDetails details) { + _handleOnSecondaryTapUp( + tapUpDetails: details, + dataGridConfiguration: dataGridConfiguration, + dataCell: dataCell, + kind: _kind); + }, + onSecondaryTapDown: _handleOnTapDown, + child: _wrapInsideContainer(), + ); + } + + Widget _wrapInsideContainer() => Container( + key: widget.key, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + border: _getCellBorder(dataGridStateDetails(), widget.dataCell)), + alignment: Alignment.center, + child: _wrapInsideCellContainer( + dataGridConfiguration: dataGridStateDetails(), + child: widget.child, + dataCell: widget.dataCell, + key: widget.key!, + backgroundColor: widget.backgroundColor, + )); + + @override + Widget build(BuildContext context) { + return GridCellRenderObjectWidget( + key: widget.key, + dataCell: widget.dataCell, + isDirty: widget.isDirty, + child: _wrapInsideGestureDetector(), + dataGridStateDetails: dataGridStateDetails, + ); + } + + @override + void dispose() { + super.dispose(); + if (tapTimer != null) { + tapTimer = null; + } + } +} + +/// A widget which displays in the header cells. +class GridHeaderCell extends StatefulWidget { + /// Creates the [GridHeaderCell] for [SfDataGrid] widget. + const GridHeaderCell( + {required Key key, + required this.dataCell, + required this.backgroundColor, + required this.isDirty, + required this.child, + required this.dataGridStateDetails}) + : super(key: key); + + /// Holds the information required to display the cell. + final DataCellBase dataCell; + + /// The [child] contained by the [GridCell]. + final Widget child; + + /// The color to paint behind the [child]. + final Color backgroundColor; + + /// Decides whether the [GridCell] should be refreshed when [SfDataGrid] is + /// rebuild. + final bool isDirty; + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + @override + State createState() => _GridHeaderCellState(); +} + +class _GridHeaderCellState extends State { + DataGridSortDirection? _sortDirection; + Color _sortIconColor = Colors.transparent; + int _sortNumber = -1; + Color _sortNumberBackgroundColor = Colors.transparent; + Color _sortNumberTextColor = Colors.transparent; + late PointerDeviceKind _kind; + + DataGridStateDetails get dataGridStateDetails => widget.dataGridStateDetails; + + void _handleOnTapUp(TapUpDetails tapUpDetails) { + final DataCellBase dataCell = widget.dataCell; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + // Clear editing when tap on the header cell + _clearEditing(dataGridConfiguration); + if (dataGridConfiguration.onCellTap != null) { + final DataGridCellTapDetails details = DataGridCellTapDetails( + rowColumnIndex: + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, + kind: _kind); + dataGridConfiguration.onCellTap!(details); + } + + dataGridConfiguration.dataGridFocusNode?.requestFocus(); + if (dataGridConfiguration.sortingGestureType == SortingGestureType.tap) { + _sort(dataCell); + } + } + + void _handleOnDoubleTap() { + final DataCellBase dataCell = widget.dataCell; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + // Clear editing when tap on the header cell + _clearEditing(dataGridConfiguration); + if (dataGridConfiguration.onCellDoubleTap != null) { + final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( + rowColumnIndex: + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!); + dataGridConfiguration.onCellDoubleTap!(details); + } + + dataGridConfiguration.dataGridFocusNode?.requestFocus(); + if (dataGridConfiguration.sortingGestureType == + SortingGestureType.doubleTap) { + _sort(dataCell); + } + } + + void _handleOnSecondaryTapUp(TapUpDetails tapUpDetails) { + final DataCellBase dataCell = widget.dataCell; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + // Clear editing when tap on the header cell + _clearEditing(dataGridConfiguration); + if (dataGridConfiguration.onCellSecondaryTap != null) { + final DataGridCellTapDetails details = DataGridCellTapDetails( + rowColumnIndex: + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, + kind: _kind); + dataGridConfiguration.onCellSecondaryTap!(details); + } + } + + void _handleOnTapDown(TapDownDetails details) { + _kind = details.kind!; + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + // Clear editing when tap on the header cell + _clearEditing(dataGridConfiguration); + } + + /// Helps to clear the editing cell when tap on header cells + void _clearEditing(DataGridConfiguration dataGridConfiguration) { + if (dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell.onCellSubmit(dataGridConfiguration); + } + } + + Widget _wrapInsideGestureDetector() { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + return GestureDetector( + onTapUp: dataGridConfiguration.onCellTap != null || + dataGridConfiguration.sortingGestureType == SortingGestureType.tap + ? _handleOnTapUp + : null, + onTapDown: _handleOnTapDown, + onDoubleTap: dataGridConfiguration.onCellDoubleTap != null || + dataGridConfiguration.sortingGestureType == + SortingGestureType.doubleTap + ? _handleOnDoubleTap + : null, + onSecondaryTapUp: dataGridConfiguration.onCellSecondaryTap != null + ? _handleOnSecondaryTapUp + : null, + onSecondaryTapDown: _handleOnTapDown, + child: _wrapInsideContainer(), + ); + } + + Widget _wrapInsideContainer() { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + final GridColumn? column = widget.dataCell.gridColumn; + + Widget checkHeaderCellConstraints(Widget child) { + final double iconWidth = + getSortIconWidth(dataGridConfiguration.columnSizer, column!); + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + if (_sortDirection == null || constraints.maxWidth < iconWidth) { + return child; + } else { + return _getCellWithSortIcon(child); + } + }); + } + + _ensureSortIconVisiblity(column!, dataGridConfiguration); + + return Container( + key: widget.key, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + border: _getCellBorder(dataGridConfiguration, widget.dataCell)), + child: _wrapInsideCellContainer( + dataGridConfiguration: dataGridConfiguration, + child: checkHeaderCellConstraints(widget.child), + dataCell: widget.dataCell, + key: widget.key!, + backgroundColor: widget.backgroundColor, + )); + } + + @override + Widget build(BuildContext context) { + return GridCellRenderObjectWidget( + key: widget.key, + dataCell: widget.dataCell, + isDirty: widget.isDirty, + child: _wrapInsideGestureDetector(), + dataGridStateDetails: dataGridStateDetails, + ); + } + + void _ensureSortIconVisiblity( + GridColumn column, DataGridConfiguration? dataGridConfiguration) { + if (dataGridConfiguration != null) { + final SortColumnDetails? sortColumn = dataGridConfiguration + .source.sortedColumns + .firstWhereOrNull((SortColumnDetails sortColumn) => + sortColumn.name == column.columnName); + if (dataGridConfiguration.source.sortedColumns.isNotEmpty && + sortColumn != null) { + final int sortNumber = + dataGridConfiguration.source.sortedColumns.indexOf(sortColumn) + 1; + final bool isLight = + dataGridConfiguration.dataGridThemeData!.brightness == + Brightness.light; + _sortDirection = sortColumn.sortDirection; + _sortIconColor = dataGridConfiguration.dataGridThemeData!.sortIconColor; + _sortNumberBackgroundColor = + isLight ? Colors.grey[350]! : Colors.grey[700]!; + _sortNumberTextColor = isLight ? Colors.black87 : Colors.white54; + if (dataGridConfiguration.source.sortedColumns.length > 1 && + dataGridConfiguration.showSortNumbers) { + _sortNumber = sortNumber; + } else { + _sortNumber = -1; + } + } else { + _sortDirection = null; + _sortNumber = -1; + } + } + } + + Widget _getCellWithSortIcon(Widget child) { + final List children = []; + + children.add(_SortIcon( + sortDirection: _sortDirection!, + sortIconColor: _sortIconColor, + )); + + if (_sortNumber != -1) { + children.add(_getSortNumber()); + } + + return Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Container(child: child), + ), + Container( + padding: const EdgeInsets.only(left: 4.0, right: 4.0), + child: Center(child: Row(children: children)), + ) + ]); + } + + Widget _getSortNumber() { + return Container( + width: 18, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _sortNumberBackgroundColor, + ), + child: Center( + child: Text(_sortNumber.toString(), + style: TextStyle(fontSize: 12, color: _sortNumberTextColor)), + ), + ); + } + + void _sort(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataCell.dataRow?.rowType == RowType.headerRow && + dataCell.dataRow?.rowIndex == + grid_helper.getHeaderIndex(dataGridConfiguration)) { + _makeSort(dataCell); + } + } + + void _makeSort(DataCellBase dataCell) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + + //End-edit before perform sorting + if (dataGridConfiguration.currentCell.isEditing) { + dataGridConfiguration.currentCell + .onCellSubmit(dataGridConfiguration, canRefresh: false); + } + + final GridColumn column = dataCell.gridColumn!; + + if (column.allowSorting && dataGridConfiguration.allowSorting) { + final String sortColumnName = column.columnName; + final bool allowMultiSort = dataGridConfiguration.isDesktop + ? (dataGridConfiguration.isControlKeyPressed && + dataGridConfiguration.allowMultiColumnSorting) + : dataGridConfiguration.allowMultiColumnSorting; + final DataGridSource source = dataGridConfiguration.source; + + final List sortedColumns = source.sortedColumns; + if (sortedColumns.isNotEmpty && allowMultiSort) { + SortColumnDetails? sortedColumn = sortedColumns.firstWhereOrNull( + (SortColumnDetails sortColumn) => + sortColumn.name == sortColumnName); + if (sortedColumn == null) { + final SortColumnDetails newSortColumn = SortColumnDetails( + name: sortColumnName, + sortDirection: DataGridSortDirection.ascending); + sortedColumns.add(newSortColumn); + } else { + if (sortedColumn.sortDirection == DataGridSortDirection.descending && + dataGridConfiguration.allowTriStateSorting) { + final SortColumnDetails? removedSortColumn = + sortedColumns.firstWhereOrNull((SortColumnDetails sortColumn) => + sortColumn.name == sortColumnName); + sortedColumns.remove(removedSortColumn); + } else { + sortedColumn = SortColumnDetails( + name: sortedColumn.name, + sortDirection: sortedColumn.sortDirection == + DataGridSortDirection.ascending + ? DataGridSortDirection.descending + : DataGridSortDirection.ascending); + final SortColumnDetails? removedSortColumn = + sortedColumns.firstWhereOrNull((SortColumnDetails sortColumn) => + sortColumn.name == sortedColumn!.name); + sortedColumns + ..remove(removedSortColumn) + ..add(sortedColumn); + } + } + } else { + SortColumnDetails? currentSortColumn = sortedColumns.firstWhereOrNull( + (SortColumnDetails sortColumn) => + sortColumn.name == sortColumnName); + if (sortedColumns.isNotEmpty && currentSortColumn != null) { + if (currentSortColumn.sortDirection == + DataGridSortDirection.descending && + dataGridConfiguration.allowTriStateSorting) { + sortedColumns.clear(); + } else { + currentSortColumn = SortColumnDetails( + name: currentSortColumn.name, + sortDirection: currentSortColumn.sortDirection == + DataGridSortDirection.ascending + ? DataGridSortDirection.descending + : DataGridSortDirection.ascending); + sortedColumns + ..clear() + ..add(currentSortColumn); + } + } else { + final SortColumnDetails sortColumn = SortColumnDetails( + name: sortColumnName, + sortDirection: DataGridSortDirection.ascending); + if (sortedColumns.isNotEmpty) { + sortedColumns + ..clear() + ..add(sortColumn); + } else { + sortedColumns.add(sortColumn); + } + } + } + // Refreshes the datagrid source and performs sorting based on + // `DataGridSource.sortedColumns`. + source.sort(); + } + } +} + +class _SortIcon extends StatefulWidget { + const _SortIcon({required this.sortDirection, required this.sortIconColor}); + final DataGridSortDirection sortDirection; + final Color sortIconColor; + @override + _SortIconState createState() => _SortIconState(); +} + +class _SortIconState extends State<_SortIcon> + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _sortingAnimation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(milliseconds: 150), + vsync: this, + ); + _sortingAnimation = Tween(begin: 0.0, end: pi).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeIn)); + if (widget.sortDirection == DataGridSortDirection.descending) { + _animationController.value = 1.0; + } + } + + @override + void didUpdateWidget(_SortIcon oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.sortDirection == DataGridSortDirection.ascending && + widget.sortDirection == DataGridSortDirection.descending) { + _animationController.forward(); + } else if (oldWidget.sortDirection == DataGridSortDirection.descending && + widget.sortDirection == DataGridSortDirection.ascending) { + _animationController.reverse(); + } + } + + @override + Widget build(BuildContext context) { + return Container( + child: AnimatedBuilder( + animation: _animationController, + builder: (BuildContext context, Widget? child) { + return Transform.rotate( + angle: _sortingAnimation.value, + child: Icon(Icons.arrow_upward, + color: widget.sortIconColor, size: 16)); + })); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } +} + +BorderDirectional _getCellBorder( + DataGridConfiguration dataGridConfiguration, DataCellBase dataCell) { + final Color borderColor = + dataGridConfiguration.dataGridThemeData!.gridLineColor; + final double borderWidth = + dataGridConfiguration.dataGridThemeData!.gridLineStrokeWidth; + + final int rowIndex = (dataCell.rowSpan > 0) + ? dataCell.rowIndex - dataCell.rowSpan + : dataCell.rowIndex; + final int columnIndex = dataCell.columnIndex; + final bool isStackedHeaderCell = + dataCell.cellType == CellType.stackedHeaderCell; + final bool isHeaderCell = dataCell.cellType == CellType.headerCell; + final bool isTableSummaryCell = + dataCell.cellType == CellType.tableSummaryCell; + + // To skip bottom border for the top data row of the starting row of bottom table + // summary rows and draw top border for the bottom summary start row instead. + final bool canSkipBottomBorder = grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom) > + 0 && + dataCell.rowIndex == + grid_helper.getStartBottomSummaryRowIndex(dataGridConfiguration) - 1; + + // To draw the top border for the starting row of the bottom table summary row. + final bool canDrawStartBottomSummaryRowTopBorder = isTableSummaryCell && + dataCell.rowIndex == + grid_helper.getStartBottomSummaryRowIndex(dataGridConfiguration); + + final bool canDrawHeaderHorizontalBorder = + (dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.both) && + (isHeaderCell || isStackedHeaderCell); + + final bool canDrawHeaderVerticalBorder = + (dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.vertical || + dataGridConfiguration.headerGridLinesVisibility == + GridLinesVisibility.both) && + (isHeaderCell || isStackedHeaderCell); + + final bool canDrawHorizontalBorder = + (dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both) && + !isHeaderCell && + !isStackedHeaderCell; + + final bool canDrawVerticalBorder = + (dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.vertical || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both) && + !isStackedHeaderCell && + !isTableSummaryCell && + !isHeaderCell; + + // Frozen column and row checking + final bool canDrawBottomFrozenBorder = + dataGridConfiguration.frozenRowsCount.isFinite && + dataGridConfiguration.frozenRowsCount > 0 && + grid_helper.getLastFrozenRowIndex(dataGridConfiguration) == rowIndex; + + final bool canDrawTopFrozenBorder = + dataGridConfiguration.footerFrozenRowsCount.isFinite && + dataGridConfiguration.footerFrozenRowsCount > 0 && + grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration) == + rowIndex; + + final bool canDrawRightFrozenBorder = + dataGridConfiguration.frozenColumnsCount.isFinite && + dataGridConfiguration.frozenColumnsCount > 0 && + grid_helper.getLastFrozenColumnIndex(dataGridConfiguration) == + columnIndex; + + final bool canDrawLeftFrozenBorder = + dataGridConfiguration.footerFrozenColumnsCount.isFinite && + dataGridConfiguration.footerFrozenColumnsCount > 0 && + grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration) == + columnIndex; + + final bool isFrozenPaneElevationApplied = + dataGridConfiguration.dataGridThemeData!.frozenPaneElevation > 0.0; + + final Color frozenPaneLineColor = + dataGridConfiguration.dataGridThemeData!.frozenPaneLineColor; + + final double frozenPaneLineWidth = + dataGridConfiguration.dataGridThemeData!.frozenPaneLineWidth; + + BorderSide _getLeftBorder() { + if ((columnIndex == 0 && + (canDrawVerticalBorder || canDrawHeaderVerticalBorder)) || + canDrawLeftFrozenBorder) { + if (canDrawLeftFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { + return BorderSide( + width: frozenPaneLineWidth, color: frozenPaneLineColor); + } else if (columnIndex > 0 && + ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && + !canDrawLeftFrozenBorder)) { + return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; + } + } else { + return BorderSide.none; + } + } + + BorderSide _getTopBorder() { + if ((rowIndex == 0 && + (canDrawHorizontalBorder || canDrawHeaderHorizontalBorder)) || + canDrawTopFrozenBorder || + canDrawStartBottomSummaryRowTopBorder) { + if (canDrawTopFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { + return BorderSide( + width: frozenPaneLineWidth, color: frozenPaneLineColor); + } else if (canDrawHorizontalBorder && + canDrawStartBottomSummaryRowTopBorder) { + return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; + } + } else { + return BorderSide.none; + } + } + + BorderSide _getRightBorder() { + if (canDrawVerticalBorder || + canDrawHeaderVerticalBorder || + canDrawRightFrozenBorder) { + if (canDrawRightFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { + return BorderSide( + width: frozenPaneLineWidth, color: frozenPaneLineColor); + } else if ((canDrawVerticalBorder || canDrawHeaderVerticalBorder) && + !canDrawRightFrozenBorder) { + return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; + } + } else { + return BorderSide.none; + } + } + + BorderSide _getBottomBorder() { + if (canDrawHorizontalBorder || + canDrawHeaderHorizontalBorder || + canDrawBottomFrozenBorder) { + if (canDrawBottomFrozenBorder && + !isStackedHeaderCell && + !isFrozenPaneElevationApplied) { + return BorderSide( + width: frozenPaneLineWidth, color: frozenPaneLineColor); + } else if (!canDrawBottomFrozenBorder && !canSkipBottomBorder) { + return BorderSide(width: borderWidth, color: borderColor); + } else { + return BorderSide.none; + } + } else { + return BorderSide.none; + } + } + + return BorderDirectional( + start: _getLeftBorder(), + top: _getTopBorder(), + end: _getRightBorder(), + bottom: _getBottomBorder(), + ); +} + +Widget _wrapInsideCellContainer( + {required DataGridConfiguration dataGridConfiguration, + required DataCellBase dataCell, + required Key key, + required Color backgroundColor, + required Widget child}) { + final Color color = + dataGridConfiguration.dataGridThemeData!.currentCellStyle.borderColor; + final double borderWidth = + dataGridConfiguration.dataGridThemeData!.currentCellStyle.borderWidth; + + Border getBorder() { + final bool isCurrentCell = dataCell.isCurrentCell; + return Border( + bottom: isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, + left: isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, + top: isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, + right: isCurrentCell + ? BorderSide(color: color, width: borderWidth) + : BorderSide.none, + ); + } + + double getCellHeight(DataCellBase dataCell, double defaultHeight) { + // Restricts the height calculation to the invisible data cell. + if (!dataCell.isVisible) { + return 0.0; + } + + double height; + if (dataCell.rowSpan > 0) { + height = dataCell.dataRow!.getRowHeight( + dataCell.rowIndex - dataCell.rowSpan, dataCell.rowIndex); + } else { + height = defaultHeight; + } + return height; + } + + double getCellWidth(DataCellBase dataCell, double defaultWidth) { + // Restricts the width calculation to the invisible data cell. + if (!dataCell.isVisible) { + return 0.0; + } + + double width; + if (dataCell.columnSpan > 0) { + width = dataCell.dataRow!.getColumnWidth( + dataCell.columnIndex, dataCell.columnIndex + dataCell.columnSpan); + } else { + width = defaultWidth; + } + return width; + } + + Widget getChild(BoxConstraints constraint) { + final double width = getCellWidth(dataCell, constraint.maxWidth); + final double height = getCellHeight(dataCell, constraint.maxHeight); + + if (dataCell.isCurrentCell) { + return Stack( + children: [ + Container( + width: width, + height: height, + child: child, + color: backgroundColor), + Positioned( + left: 0, + top: 0, + width: width, + height: height, + child: IgnorePointer( + ignoring: true, + child: Container( + key: key, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration(border: getBorder())), + )), + ], + ); + } else { + return Container( + width: width, height: height, child: child, color: backgroundColor); + } + } + + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraint) { + return getChild(constraint); + }); +} + +// Gesture Events + +void _handleOnTapUp( + {required TapUpDetails? tapUpDetails, + required TapDownDetails? tapDownDetails, + required DataCellBase dataCell, + required DataGridConfiguration dataGridConfiguration, + required PointerDeviceKind kind}) { + // End edit the current editing cell if its editing mode is differed + if (dataGridConfiguration.currentCell.isEditing) { + if (dataGridConfiguration.currentCell + .canSubmitCell(dataGridConfiguration)) { + dataGridConfiguration.currentCell + .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + } + } + + if (dataGridConfiguration.onCellTap != null) { + final DataGridCellTapDetails details = DataGridCellTapDetails( + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + globalPosition: tapDownDetails != null + ? tapDownDetails.globalPosition + : tapUpDetails!.globalPosition, + localPosition: tapDownDetails != null + ? tapDownDetails.localPosition + : tapUpDetails!.localPosition, + kind: kind); + dataGridConfiguration.onCellTap!(details); + } + + dataGridConfiguration.dataGridFocusNode?.requestFocus(); + dataCell.onTouchUp(); + + // Init the editing based on the editing mode + if (dataGridConfiguration.editingGestureType == EditingGestureType.tap) { + dataGridConfiguration.currentCell + .onCellBeginEdit(editingDataCell: dataCell); + } +} + +void _handleOnDoubleTap( + {required DataCellBase dataCell, + required DataGridConfiguration dataGridConfiguration}) { + // End edit the current editing cell if its editing mode is differed + if (dataGridConfiguration.currentCell.isEditing) { + if (dataGridConfiguration.currentCell + .canSubmitCell(dataGridConfiguration)) { + dataGridConfiguration.currentCell + .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + } else { + return; + } + } + + if (dataGridConfiguration.onCellDoubleTap != null) { + final DataGridCellDoubleTapDetails details = DataGridCellDoubleTapDetails( + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!); + dataGridConfiguration.onCellDoubleTap!(details); + } + + dataGridConfiguration.dataGridFocusNode?.requestFocus(); + dataCell.onTouchUp(); + + // Init the editing based on the editing mode + if (dataGridConfiguration.editingGestureType == + EditingGestureType.doubleTap) { + dataGridConfiguration.currentCell + .onCellBeginEdit(editingDataCell: dataCell); + } +} + +void _handleOnSecondaryTapUp( + {required TapUpDetails tapUpDetails, + required DataCellBase dataCell, + required DataGridConfiguration dataGridConfiguration, + required PointerDeviceKind kind}) { + // Need to end the editing cell when interacting with other tap gesture + if (dataGridConfiguration.currentCell.isEditing) { + if (dataGridConfiguration.currentCell + .canSubmitCell(dataGridConfiguration)) { + dataGridConfiguration.currentCell + .onCellSubmit(dataGridConfiguration, cancelCanSubmitCell: true); + } else { + return; + } + } + + if (dataGridConfiguration.onCellSecondaryTap != null) { + final DataGridCellTapDetails details = DataGridCellTapDetails( + rowColumnIndex: RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + globalPosition: tapUpDetails.globalPosition, + localPosition: tapUpDetails.localPosition, + kind: kind); + dataGridConfiguration.onCellSecondaryTap!(details); + } +} 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 new file mode 100644 index 000000000..dbc99e5c2 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/rendering_widget.dart @@ -0,0 +1,2079 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; + +import '../../grid_common/row_column_index.dart'; +import '../../grid_common/scroll_axis.dart'; +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/enums.dart'; +import '../runtime/generator.dart'; +import '../sfdatagrid.dart'; +import 'scrollview_widget.dart'; + +/// ToDo +class VisualContainerRenderObjectWidget extends MultiChildRenderObjectWidget { + /// ToDo + VisualContainerRenderObjectWidget({ + required Key? key, + required this.containerSize, + required this.isDirty, + required this.dataGridStateDetails, + required this.children, + }) : super( + key: key, + children: RepaintBoundary.wrapAll(List.from(children))); + + @override + final List children; + + /// ToDo + final Size containerSize; + + /// ToDo + final bool isDirty; + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + @override + _RenderVisualContainer createRenderObject(BuildContext context) => + _RenderVisualContainer( + containerSize: containerSize, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails); + + @override + void updateRenderObject( + BuildContext context, _RenderVisualContainer renderObject) { + super.updateRenderObject(context, renderObject); + renderObject + ..containerSize = containerSize + ..isDirty = isDirty; + } +} + +class _VisualContainerParentData extends ContainerBoxParentData { + _VisualContainerParentData(); + + double width = 0.0; + double height = 0.0; + Rect? rowClipRect; + + void reset() { + width = 0.0; + height = 0.0; + offset = Offset.zero; + rowClipRect = null; + } +} + +class _RenderVisualContainer extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin { + _RenderVisualContainer( + {List? children, + required Size containerSize, + required bool isDirty, + required DataGridStateDetails? dataGridStateDetails}) + : _containerSize = containerSize, + _isDirty = isDirty, + _dataGridStateDetails = dataGridStateDetails! { + addAll(children); + } + + bool get isDirty => _isDirty; + bool _isDirty = false; + + set isDirty(bool newValue) { + _isDirty = newValue; + if (_isDirty) { + markNeedsLayout(); + markNeedsPaint(); + } + } + + Size get containerSize => _containerSize; + Size _containerSize = Size.zero; + + set containerSize(Size newContainerSize) { + if (_containerSize == newContainerSize) { + return; + } + _containerSize = newContainerSize; + markNeedsLayout(); + markNeedsPaint(); + } + + _RenderVirtualizingCellsWidget? _swipeWholeRowElement; + + final DataGridStateDetails _dataGridStateDetails; + + @override + bool get isRepaintBoundary => true; + + @override + void setupParentData(RenderObject child) { + super.setupParentData(child); + if (child.parentData is! _VisualContainerParentData) { + child.parentData = _VisualContainerParentData(); + } + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? child = lastChild; + while (child != null) { + final _VisualContainerParentData childParentData = + child.parentData! as _VisualContainerParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + // Need to ensure whether the child type is _RenderVirtualizingCellsWidget + // or not before accessing it. Because swiping widget's render object won't be + // _RenderVirtualizingCellsWidget. + final RenderBox? wholeRowElement = child; + if (wholeRowElement != null && + wholeRowElement is _RenderVirtualizingCellsWidget && + wholeRowElement.rowClipRect != null && + !wholeRowElement.rowClipRect!.contains(transformed)) { + return false; + } + return child!.hitTest(result, position: transformed); + }, + ); + + if (isHit) { + return true; + } + child = childParentData.previousSibling; + } + return false; + } + + Offset _getSwipingChildOffset(Rect swipeRowRect) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + double dxPosition = 0.0; + final double viewWidth = dataGridConfiguration.viewWidth; + final double maxSwipeOffset = dataGridConfiguration.swipeMaxOffset; + final double extentWidth = dataGridConfiguration.container.extentWidth; + + if (dataGridConfiguration.textDirection == TextDirection.rtl && + viewWidth > extentWidth) { + dxPosition = (dataGridConfiguration.swipingOffset >= 0) + ? viewWidth - extentWidth + : viewWidth - maxSwipeOffset; + } else { + dxPosition = (dataGridConfiguration.swipingOffset >= 0) + ? 0.0 + : extentWidth - maxSwipeOffset; + } + + return Offset(dxPosition, swipeRowRect.top); + } + + @override + void performLayout() { + size = + constraints.constrain(Size(containerSize.width, containerSize.height)); + + void layout( + {required RenderBox child, + required double width, + required double height}) { + child.layout(BoxConstraints.tightFor(width: width, height: height), + parentUsesSize: true); + } + + RenderBox? child = firstChild; + while (child != null) { + final _VisualContainerParentData parentData = + child.parentData! as _VisualContainerParentData; + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + // Need to ensure whether the child type is _RenderVirtualizingCellsWidget + // or not before accessing it. Because swiping widget's render object won't be + // _RenderVirtualizingCellsWidget. + final RenderBox wholeRowElement = child; + if (wholeRowElement is _RenderVirtualizingCellsWidget) { + if (wholeRowElement.dataRow.isVisible) { + final Rect rowRect = wholeRowElement._measureRowRect(size.width); + + parentData + ..width = rowRect.width + ..height = rowRect.height + ..rowClipRect = wholeRowElement.rowClipRect + ..offset = Offset( + (wholeRowElement.dataRow.isSwipingRow && + dataGridConfiguration.swipingOffset != 0.0 && + dataGridConfiguration.swipingAnimation != null) + ? rowRect.left + + dataGridConfiguration.swipingAnimation!.value + : rowRect.left, + rowRect.top); + + if (wholeRowElement.dataRow.isSwipingRow && + dataGridConfiguration.swipingOffset.abs() > 0.0) { + _swipeWholeRowElement = wholeRowElement; + } + + layout( + child: child, width: parentData.width, height: parentData.height); + } else { + child.layout(const BoxConstraints.tightFor(width: 0.0, height: 0.0)); + parentData.reset(); + } + } else { + // We added the swiping widget to the last position of chidren collection. + // So, we can get it diretly from lastChild property. + final RenderBox? swipingWidget = lastChild; + if (swipingWidget != null && _swipeWholeRowElement != null) { + final Rect swipeRowRect = + _swipeWholeRowElement!._measureRowRect(size.width); + + parentData + ..width = dataGridConfiguration.swipeMaxOffset + ..height = swipeRowRect.height + ..offset = _getSwipingChildOffset(swipeRowRect); + + layout( + child: swipingWidget, + width: parentData.width, + height: parentData.height); + } + } + + child = parentData.nextSibling; + } + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderBox? child = firstChild; + while (child != null) { + final _VisualContainerParentData childParentData = + child.parentData! as _VisualContainerParentData; + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + final RenderBox wholeRowElement = child; + if (wholeRowElement is _RenderVirtualizingCellsWidget) { + if (childParentData.width != 0.0 && childParentData.height != 0.0) { + if (childParentData.rowClipRect != null) { + if (wholeRowElement.dataRow.isSwipingRow && + dataGridConfiguration.swipingOffset.abs() > 0.0) { + // We added the swiping widget to the last position of chidren collection. + // So, we can get it diretly from lastChild property. + final RenderBox? swipeWidget = lastChild; + if (swipeWidget != null) { + final _VisualContainerParentData childParentData = + swipeWidget.parentData! as _VisualContainerParentData; + context.paintChild( + swipeWidget, childParentData.offset + offset); + } + } + + context.pushClipRect( + needsCompositing, + childParentData.offset + offset, + childParentData.rowClipRect!, + (PaintingContext context, Offset offset) { + context.paintChild(child!, offset); + }, + clipBehavior: Clip.antiAlias, + ); + } else { + context.paintChild(child, childParentData.offset + offset); + } + } + } + child = childParentData.nextSibling; + } + } +} + +/// ToDo +class VirtualizingCellsRenderObjectWidget extends MultiChildRenderObjectWidget { + /// ToDo + VirtualizingCellsRenderObjectWidget( + {required Key key, + required this.dataRow, + required this.isDirty, + required this.children, + required this.dataGridStateDetails}) + : super( + key: key, + children: RepaintBoundary.wrapAll(List.from(children))); + + @override + final List children; + + /// ToDo + final DataRowBase dataRow; + + /// ToDo + final bool isDirty; + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + @override + _RenderVirtualizingCellsWidget createRenderObject(BuildContext context) => + _RenderVirtualizingCellsWidget( + dataRow: dataRow, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails); + + @override + void updateRenderObject( + BuildContext context, _RenderVirtualizingCellsWidget renderObject) { + super.updateRenderObject(context, renderObject); + renderObject + ..dataRow = dataRow + ..isDirty = isDirty; + } +} + +class _VirtualizingCellWidgetParentData + extends ContainerBoxParentData { + _VirtualizingCellWidgetParentData(); + + double width = 0.0; + double height = 0.0; + Rect? cellClipRect; + + void reset() { + width = 0.0; + height = 0.0; + offset = Offset.zero; + cellClipRect = null; + } +} + +class _RenderVirtualizingCellsWidget extends RenderBox + with + ContainerRenderObjectMixin, + RenderBoxContainerDefaultsMixin + implements + MouseTrackerAnnotation { + _RenderVirtualizingCellsWidget({ + List? children, + required DataRowBase dataRow, + required bool isDirty, + required DataGridStateDetails? dataGridStateDetails, + }) : _dataRow = dataRow, + _isDirty = isDirty, + _dataGridStateDetails = dataGridStateDetails! { + addAll(children); + // Provides the `postAcceptSlopTolerance` to restrict the call of + // long press event from the framework when executing the PointerMoveEvent. + _onLongPressGesture = + LongPressGestureRecognizer(postAcceptSlopTolerance: 10.0) + ..onLongPressStart = _onLongPressStart + ..onLongPressEnd = _onLongPressEnd; + } + + bool get isDirty => _isDirty; + bool _isDirty = false; + + set isDirty(bool newValue) { + _isDirty = newValue; + if (_isDirty) { + markNeedsLayout(); + markNeedsPaint(); + } + + dataRow.isDirty = false; + } + + DataRowBase get dataRow => _dataRow; + DataRowBase _dataRow; + + set dataRow(DataRowBase newValue) { + if (_dataRow == newValue) { + return; + } + + _dataRow = newValue; + markNeedsLayout(); + markNeedsPaint(); + } + + Rect rowRect = Rect.zero; + + Rect? rowClipRect; + + DataGridRowSwipeDirection? swipeDirection; + + /// To handle the long press events. + late LongPressGestureRecognizer _onLongPressGesture; + + // It's helps to find the difference of dy action between on [PointerDownEvent] + // and [PointerMoveEvent] event. + double dy = 0.0; + + /// ToDo + final DataGridStateDetails _dataGridStateDetails; + + Rect _measureRowRect(double width) { + if (dataRow.isVisible) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + final VisualContainerHelper container = dataGridConfiguration.container; + + final VisibleLineInfo? lineInfo = + container.scrollRows.getVisibleLineAtLineIndex(dataRow.rowIndex); + + final double lineSize = lineInfo != null ? lineInfo.size : 0.0; + double origin = (lineInfo != null) ? lineInfo.origin : 0.0; + + origin += container.verticalOffset; + + if (dataRow.rowIndex > + grid_helper.getHeaderIndex(dataGridConfiguration)) { + final double headerRowsHeight = container.scrollRows + .rangeToRegionPoints( + 0, grid_helper.getHeaderIndex(dataGridConfiguration), true)[1] + .length; + origin -= headerRowsHeight; + } + + // Clipping the column when frozen row applied + if (lineInfo != null && + (lineInfo.isClippedBody && lineInfo.isClippedOrigin)) { + final double top = + lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent; + if (dataGridConfiguration.allowSwiping && dataRow.isSwipingRow) { + rowClipRect = _getSwipingRowClipRect( + dataGridConfiguration: dataGridConfiguration, + top: top, + height: lineSize); + } else { + rowClipRect = Rect.fromLTWH(0, top, width, lineSize); + } + } else if (dataGridConfiguration.allowSwiping && dataRow.isSwipingRow) { + rowClipRect = _getSwipingRowClipRect( + dataGridConfiguration: dataGridConfiguration, + top: 0.0, + height: lineSize); + } else { + rowClipRect = null; + } + + rowRect = Rect.fromLTWH(0, origin, width, lineSize); + return rowRect; + } else { + return Rect.zero; + } + } + + Rect? _getSwipingRowClipRect( + {required DataGridConfiguration dataGridConfiguration, + required double top, + required double height}) { + if (dataGridConfiguration.swipingAnimation == null) { + return null; + } + double leftPosition = 0.0; + final double viewWidth = dataGridConfiguration.viewWidth; + final double extentWidth = dataGridConfiguration.container.extentWidth; + final double swipingDelta = dataGridConfiguration.swipingOffset >= 0 + ? dataGridConfiguration.swipingAnimation!.value + : -dataGridConfiguration.swipingAnimation!.value; + + if (dataGridConfiguration.textDirection == TextDirection.rtl && + viewWidth > extentWidth) { + leftPosition = dataGridConfiguration.swipingOffset >= 0 + ? viewWidth - extentWidth + : (viewWidth - extentWidth) + swipingDelta; + } else { + leftPosition = + dataGridConfiguration.swipingOffset >= 0 ? 0 : swipingDelta; + } + return Rect.fromLTWH(leftPosition, top, extentWidth - swipingDelta, height); + } + + Rect _getRowRect(DataGridConfiguration dataGridConfiguration, Offset offset, + {bool isHoveredLayer = false}) { + bool needToSetMaxConstraint() => + dataGridConfiguration.container.extentWidth < + dataGridConfiguration.viewWidth && + dataGridConfiguration.textDirection == TextDirection.rtl; + + if (dataRow.rowType == RowType.footerRow) { + return Rect.fromLTWH( + offset.dx + dataGridConfiguration.container.horizontalOffset, + offset.dy, + dataGridConfiguration.viewWidth, + dataGridConfiguration.footerHeight); + } else { + return Rect.fromLTWH( + needToSetMaxConstraint() + ? constraints.maxWidth - + min(dataGridConfiguration.container.extentWidth, + dataGridConfiguration.viewWidth) - + (offset.dx + dataGridConfiguration.container.horizontalOffset) + : offset.dx + dataGridConfiguration.container.horizontalOffset, + offset.dy, + needToSetMaxConstraint() + ? constraints.maxWidth + : min(dataGridConfiguration.container.extentWidth, + dataGridConfiguration.viewWidth), + (isHoveredLayer && + dataRow.isHoveredRow && + (dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both)) + ? constraints.maxHeight - + dataGridConfiguration.dataGridThemeData!.gridLineStrokeWidth + : constraints.maxHeight); + } + } + + void _drawRowBackground(DataGridConfiguration dataGridConfiguration, + PaintingContext context, Offset offset) { + final Rect rect = _getRowRect(dataGridConfiguration, offset); + Color? backgroundColor; + + Color getDefaultRowBackgroundColor() { + return dataGridConfiguration.dataGridThemeData!.brightness == + Brightness.light + ? const Color.fromRGBO(255, 255, 255, 1) + : const Color.fromRGBO(33, 33, 33, 1); + } + + void drawSpannedRowBackgroundColor(Color backgroundColor) { + final bool isRowSpanned = dataRow.visibleColumns + .any((DataCellBase dataCell) => dataCell.rowSpan > 0); + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + + if (isRowSpanned) { + RenderBox? child = lastChild; + while (child != null && child is _RenderGridCell) { + final _VirtualizingCellWidgetParentData childParentData = + child.parentData! as _VirtualizingCellWidgetParentData; + final DataCellBase dataCell = child.dataCell; + final VisibleLineInfo? lineInfo = + dataRow.getColumnVisibleLineInfo(dataCell.columnIndex); + if (dataCell.rowSpan > 0 && lineInfo != null) { + final Rect? columnRect = child.columnRect; + final Rect? cellClipRect = child.cellClipRect; + final double height = dataRow.getRowHeight( + dataCell.rowIndex - dataCell.rowSpan, dataCell.rowIndex); + Rect cellRect = Rect.zero; + if (cellClipRect != null) { + double left = columnRect!.left; + double width = cellClipRect.width; + if (cellClipRect.left > 0 && columnRect.width <= width) { + left += cellClipRect.left; + width = columnRect.width - cellClipRect.left; + } else if (cellClipRect.left > 0 && width < columnRect.width) { + left += cellClipRect.left; + } + + cellRect = Rect.fromLTWH(left, columnRect.top, width, height); + } else { + cellRect = Rect.fromLTWH( + columnRect!.left, columnRect.top, columnRect.width, height); + } + dataGridConfiguration.gridPaint?.color = backgroundColor; + context.canvas.drawRect(cellRect, dataGridConfiguration.gridPaint!); + } + child = childParentData.previousSibling; + } + } + } + + if (dataGridConfiguration.gridPaint != null) { + dataGridConfiguration.gridPaint!.style = PaintingStyle.fill; + + if (dataRow.rowRegion == RowRegion.header && + dataRow.rowType == RowType.headerRow || + dataRow.rowType == RowType.stackedHeaderRow) { + backgroundColor = dataGridConfiguration.dataGridThemeData!.headerColor; + drawSpannedRowBackgroundColor(backgroundColor); + } else if (dataRow.rowType == RowType.footerRow) { + backgroundColor = getDefaultRowBackgroundColor(); + } else if (dataRow.rowType == RowType.tableSummaryRow || + dataRow.rowType == RowType.tableSummaryCoveredRow) { + backgroundColor = dataRow.tableSummaryRow?.color; + } else { + /// Need to check the rowStyle Please look the previous version and + /// selection preference + backgroundColor = dataRow.isSelectedRow + ? dataGridConfiguration.dataGridThemeData!.selectionColor + : dataRow.dataGridRowAdapter!.color; + } + + // Default theme color are common for both the HeaderBackgroundColor and + // CellBackgroundColor, so we have checked commonly at outside of the + // condition + backgroundColor ??= getDefaultRowBackgroundColor(); + + dataGridConfiguration.gridPaint?.color = backgroundColor; + context.canvas.drawRect(rect, dataGridConfiguration.gridPaint!); + } + } + + void _drawCurrentRowBorder(PaintingContext context, Offset offset) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + if (dataGridConfiguration.boxPainter != null && + dataGridConfiguration.selectionMode == SelectionMode.multiple && + dataGridConfiguration.navigationMode == GridNavigationMode.row && + dataGridConfiguration.currentCell.rowIndex == dataRow.rowIndex) { + bool needToSetMaxConstraint() => + dataGridConfiguration.container.extentWidth < + dataGridConfiguration.viewWidth && + dataGridConfiguration.textDirection == TextDirection.rtl; + + const double stokeWidth = 1; + final int origin = (stokeWidth / 2 + + dataGridConfiguration.dataGridThemeData!.gridLineStrokeWidth) + .ceil(); + + final Rect rowRect = _getRowRect(dataGridConfiguration, offset); + final double maxWidth = needToSetMaxConstraint() + ? rowRect.width - rowRect.left + : rowRect.right - rowRect.left; + + final bool isHorizontalGridLinesEnabled = + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.both || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal; + + dataGridConfiguration.boxPainter!.paint( + context.canvas, + Offset(rowRect.left + origin, rowRect.top + (origin / 2)), + dataGridConfiguration.configuration!.copyWith( + size: Size( + maxWidth - (origin * 2), + constraints.maxHeight - + (origin * (isHorizontalGridLinesEnabled ? 1.5 : 1))))); + } + } + + @override + void setupParentData(RenderObject child) { + super.setupParentData(child); + if (child.parentData != null && + child.parentData != _VirtualizingCellWidgetParentData()) { + child.parentData = _VirtualizingCellWidgetParentData(); + } + } + + void _handleSwipingListener() { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, + propertyName: 'Swiping'); + } + + @override + void attach(PipelineOwner owner) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + dataGridConfiguration.swipingAnimationController + ?.addListener(_handleSwipingListener); + super.attach(owner); + } + + @override + void detach() { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + dataGridConfiguration.swipingAnimationController + ?.removeListener(_handleSwipingListener); + + super.detach(); + } + + @override + bool get isRepaintBoundary => true; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? child = lastChild; + while (child != null) { + final _VirtualizingCellWidgetParentData childParentData = + child.parentData! as _VirtualizingCellWidgetParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + if (child is _RenderGridCell && + child.cellClipRect != null && + !child.cellClipRect!.contains(transformed)) { + return false; + } + + return child!.hitTest(result, position: transformed); + }, + ); + + if (isHit) { + return true; + } + child = childParentData.previousSibling; + } + return false; + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + final bool isRowSpanned = dataRow.visibleColumns + .any((DataCellBase dataCell) => dataCell.rowSpan > 0); + + if (isRowSpanned) { + RenderBox? child = lastChild; + while (child != null) { + final _VirtualizingCellWidgetParentData childParentData = + child.parentData! as _VirtualizingCellWidgetParentData; + if (child is _RenderGridCell && + child.columnRect != null && + child.columnRect!.contains(position)) { + // Need to resolve the position when dataCell has row span. + if (child.dataCell.rowSpan > 0) { + // Send the position manually to the `hitTestChildren` to avoid the + // restrcition of spanned row hit test from the framework side. + // Because the spanned row `dy` position starts from the negative value. + if (hitTestChildren(result, position: position)) { + // If the position exists in the any child, add the position to the + // result object like the flutter framework. + result.add(BoxHitTestEntry(this, position)); + return true; + } + return false; + } + return super.hitTest(result, position: position); + } + child = childParentData.previousSibling; + } + } + + return super.hitTest(result, position: position); + } + + void _updateSwipingAnimation(DataGridConfiguration dataGridConfiguration) { + dataGridConfiguration.swipingAnimation = Tween( + begin: 0.0, + end: dataGridConfiguration.swipingOffset.sign >= 0 + ? dataGridConfiguration.swipeMaxOffset + : -dataGridConfiguration.swipeMaxOffset) + .animate(dataGridConfiguration.swipingAnimationController!); + } + + void _handleSwipeStart( + PointerDownEvent event, DataGridConfiguration dataGridConfiguration) { + // Need to reset the swiping and scrolling state to default when pointer + // up and touch again + dataGridConfiguration.isSwipingApplied = false; + dataGridConfiguration.scrollingState = ScrollDirection.idle; + swipeDirection = null; + + // Need to check whether tap action placed on another [DataGridRow] + // instead of swiped [DataGridRow]. + // + // If its tapped on the same swiped [DataGridRow], we don't do anything. + // If it's tapped on different [DataGridRow] or scrolled, we need to end + // the swiping. + final DataRowBase? swipedRow = dataGridConfiguration.rowGenerator.items + .firstWhereOrNull((DataRowBase row) => + row.isSwipingRow && dataGridConfiguration.swipingOffset.abs() > 0); + + if (swipedRow != null && swipedRow.rowIndex != dataRow.rowIndex) { + dataGridConfiguration.container + .resetSwipeOffset(swipedRow: swipedRow, canUpdate: true); + dataGridConfiguration.swipingOffset = event.localDelta.dx; + dy = event.localDelta.dy; + } + } + + void _handleSwipeUpdate( + PointerMoveEvent event, DataGridConfiguration dataGridConfiguration) { + final double currentSwipingDelta = + dataGridConfiguration.swipingOffset + event.localDelta.dx; + dy = dy - event.localDelta.dy; + + // If it's fling or scrolled, we have to ignore the swiping action + if (!dataGridConfiguration.isSwipingApplied && + (dataGridConfiguration.scrollingState == ScrollDirection.forward || + dy.abs() > 3)) { + dataGridConfiguration.isSwipingApplied = false; + return; + } + + final ScrollController horizontalController = + dataGridConfiguration.horizontalScrollController!; + + /// Swipe must to happen when it's reach the max and min scroll extend. + if (currentSwipingDelta > 2) { + if (dataGridConfiguration.container.horizontalOffset == + horizontalController.position.minScrollExtent && + swipeDirection == null) { + swipeDirection = grid_helper.getSwipeDirection( + dataGridConfiguration, currentSwipingDelta); + // Resricted the continuous swiping of both directions by dragging. + } else if (swipeDirection == DataGridRowSwipeDirection.endToStart) { + swipeDirection = null; + } + } else if (currentSwipingDelta < -2) { + if (dataGridConfiguration.container.horizontalOffset == + horizontalController.position.maxScrollExtent && + swipeDirection == null) { + swipeDirection = grid_helper.getSwipeDirection( + dataGridConfiguration, currentSwipingDelta); + // Resricted the continuous swiping of both directions by dragging. + } else if (swipeDirection == DataGridRowSwipeDirection.startToEnd) { + swipeDirection = null; + } + } + + if (swipeDirection != null && + grid_helper.canSwipeRow( + dataGridConfiguration, swipeDirection!, currentSwipingDelta)) { + bool canStartSwiping = true; + final double oldSwipingDelta = dataGridConfiguration.swipingOffset; + final int rowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, dataRow.rowIndex); + + // Need to skip the [onSwipeStart] callback when swiping is applied. + if (dataGridConfiguration.onSwipeStart != null && + !dataGridConfiguration.isSwipingApplied) { + final DataGridSwipeStartDetails swipeStartDetails = + DataGridSwipeStartDetails( + rowIndex: rowIndex, swipeDirection: swipeDirection!); + canStartSwiping = + dataGridConfiguration.onSwipeStart!(swipeStartDetails); + } + + if (canStartSwiping) { + dataGridConfiguration.isSwipingApplied = true; + if (dataGridConfiguration.onSwipeUpdate != null) { + final DataGridSwipeUpdateDetails swipeUpdateDetails = + DataGridSwipeUpdateDetails( + rowIndex: rowIndex, + swipeDirection: swipeDirection!, + swipeOffset: currentSwipingDelta); + canStartSwiping = + dataGridConfiguration.onSwipeUpdate!(swipeUpdateDetails); + } + + if (!canStartSwiping) { + return; + } + + if (dataGridConfiguration.swipingAnimationController!.isAnimating) { + return; + } + + if (currentSwipingDelta >= dataGridConfiguration.swipeMaxOffset) { + dataGridConfiguration.swipingOffset = + dataGridConfiguration.swipeMaxOffset; + } else { + dataGridConfiguration.swipingOffset += event.localDelta.dx; + } + + dataRow.isSwipingRow = true; + + if (oldSwipingDelta.sign != currentSwipingDelta.sign) { + _updateSwipingAnimation(dataGridConfiguration); + } + if (!dataGridConfiguration.swipingAnimationController!.isAnimating) { + dataGridConfiguration.swipingAnimationController!.value = + dataGridConfiguration.swipingOffset.abs() / + dataGridConfiguration.swipeMaxOffset; + } + } else { + dataGridConfiguration.container.resetSwipeOffset(canUpdate: true); + } + } + } + + void _handleSwipeEnd( + PointerUpEvent event, DataGridConfiguration dataGridConfiguration) { + void _onSwipeEnd() { + if (dataGridConfiguration.onSwipeEnd != null) { + final int rowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, dataRow.rowIndex); + final DataGridRowSwipeDirection swipeDirection = + grid_helper.getSwipeDirection( + dataGridConfiguration, dataGridConfiguration.swipingOffset); + final DataGridSwipeEndDetails swipeEndDetails = DataGridSwipeEndDetails( + rowIndex: rowIndex, swipeDirection: swipeDirection); + dataGridConfiguration.onSwipeEnd!(swipeEndDetails); + } + } + + if (dataRow.isSwipingRow) { + if (dataGridConfiguration.swipingAnimationController!.isAnimating) { + return; + } + + dataGridConfiguration.isSwipingApplied = false; + if (dataGridConfiguration.swipingOffset.abs() > + dataGridConfiguration.swipeMaxOffset / 2) { + dataGridConfiguration.swipingOffset = + dataGridConfiguration.swipingOffset >= 0 + ? dataGridConfiguration.swipeMaxOffset + : -dataGridConfiguration.swipeMaxOffset; + dataGridConfiguration.swipingAnimationController! + .forward() + .then((_) => _onSwipeEnd()); + } else { + if (dataGridConfiguration.swipingOffset.abs() < + dataGridConfiguration.swipeMaxOffset) { + dataGridConfiguration.swipingAnimationController!.reverse().then((_) { + _onSwipeEnd(); + dataGridConfiguration.container + .resetSwipeOffset(swipedRow: dataRow); + }); + } + } + } + + dy = 0.0; + dataGridConfiguration.scrollingState = ScrollDirection.idle; + } + + void _handleSwiping(PointerEvent event) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (dataGridConfiguration.allowSwiping && + dataRow.rowType == RowType.dataRow) { + if (event is PointerDownEvent) { + _handleSwipeStart(event, dataGridConfiguration); + } + if (event is PointerMoveEvent) { + _handleSwipeUpdate(event, dataGridConfiguration); + } + if (event is PointerUpEvent) { + _handleSwipeEnd(event, dataGridConfiguration); + } + } + } + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + super.handleEvent(event, entry); + _handleSwiping(event); + + _handleColumnResizing(event); + + // Handles the all the datagrid long press events here commonly. + if (event is PointerDownEvent) { + _onLongPressGesture.addPointer(event); + } + } + + void _handleColumnResizing(PointerEvent event) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + if (dataGridConfiguration.allowColumnsResizing) { + if (event is PointerHoverEvent) { + dataGridConfiguration.columnResizeController + .onPointerHover(event, dataRow); + } + if (event is PointerDownEvent) { + dataGridConfiguration.columnResizeController + .onPointerDown(event, dataRow); + } + if (event is PointerMoveEvent) { + dataGridConfiguration.columnResizeController + .onPointerMove(event, dataRow); + } + if (event is PointerUpEvent) { + dataGridConfiguration.columnResizeController + .onPointerUp(event, dataRow); + } + } + } + + @override + void performLayout() { + void _layout( + {required RenderBox child, + required double width, + required double height}) { + child.layout(BoxConstraints.tightFor(width: width, height: height), + parentUsesSize: true); + } + + RenderBox? child = firstChild; + while (child != null) { + final _VirtualizingCellWidgetParentData _parentData = + child.parentData! as _VirtualizingCellWidgetParentData; + if (dataRow.isVisible && + child is _RenderGridCell && + child.dataCell.isVisible) { + final Rect columnRect = + child._measureColumnRect(constraints.maxHeight)!; + size = constraints.constrain(Size(columnRect.width, columnRect.height)); + _parentData + ..width = columnRect.width + ..height = columnRect.height + ..cellClipRect = child.cellClipRect; + _layout( + child: child, width: _parentData.width, height: _parentData.height); + _parentData.offset = Offset(columnRect.left, columnRect.top); + } else { + if (dataRow.rowType == RowType.footerRow) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + final Rect cellRect = Rect.fromLTWH( + dataGridConfiguration.container.horizontalOffset, + 0.0, + dataGridConfiguration.viewWidth, + dataGridConfiguration.footerHeight); + + size = constraints.constrain(Size(cellRect.width, cellRect.height)); + _parentData + ..width = cellRect.width + ..height = cellRect.height + ..offset = Offset(cellRect.left, cellRect.top); + _layout( + child: child, + width: _parentData.width, + height: _parentData.height); + } else { + size = constraints.constrain(Size.zero); + child.layout(const BoxConstraints.tightFor(width: 0, height: 0)); + _parentData.reset(); + } + } + child = _parentData.nextSibling; + } + } + + void _drawRowHoverBackground(DataGridConfiguration dataGridConfiguration, + PaintingContext context, Offset offset) { + if (dataGridConfiguration.isDesktop && + dataGridConfiguration.highlightRowOnHover && + dataRow.isHoveredRow) { + dataGridConfiguration.gridPaint?.color = + dataGridConfiguration.dataGridThemeData!.rowHoverColor; + context.canvas.drawRect( + _getRowRect(dataGridConfiguration, offset, isHoveredLayer: true), + dataGridConfiguration.gridPaint!); + } + } + + void _drawTableSummaryRowBorder(DataGridConfiguration dataGridConfiguration, + PaintingContext context, Offset offset) { + Rect getRowRect() { + final double extentWidth = dataGridConfiguration.container.extentWidth; + final Rect rect = _getRowRect(dataGridConfiguration, offset); + + if (dataRow.rowType == RowType.tableSummaryRow) { + final double left = (extentWidth < dataGridConfiguration.viewWidth && + dataGridConfiguration.textDirection == TextDirection.rtl) + ? constraints.maxWidth - + min(extentWidth, dataGridConfiguration.viewWidth) - + offset.dx + : offset.dx; + return Rect.fromLTWH(left, rect.top, extentWidth, rect.height); + } else { + return Rect.fromLTWH(rect.left, rect.top, + min(extentWidth, dataGridConfiguration.viewWidth), rect.height); + } + } + + if (dataGridConfiguration.gridLinesVisibility == GridLinesVisibility.none || + dataGridConfiguration.gridLinesVisibility == + GridLinesVisibility.horizontal) { + return; + } + + if (dataRow.rowType == RowType.tableSummaryRow || + dataRow.rowType == RowType.tableSummaryCoveredRow) { + final BorderSide border = BorderSide( + width: dataGridConfiguration.dataGridThemeData!.gridLineStrokeWidth, + color: dataGridConfiguration.dataGridThemeData!.gridLineColor); + + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + paintBorder(context.canvas, getRowRect(), right: border); + } else { + paintBorder(context.canvas, getRowRect(), left: border); + } + } + } + + @override + void paint(PaintingContext context, Offset offset) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + // Remove the below method if the mentioned report has resolved + // form framework side + // https://github.com/flutter/flutter/issues/29702 + _drawRowBackground(dataGridConfiguration, context, offset); + + _drawRowHoverBackground(dataGridConfiguration, context, offset); + + RenderBox? child = firstChild; + while (child != null) { + final _VirtualizingCellWidgetParentData childParentData = + child.parentData! as _VirtualizingCellWidgetParentData; + if (childParentData.width != 0.0 && childParentData.height != 0.0) { + if (childParentData.cellClipRect != null) { + context.pushClipRect( + needsCompositing, + childParentData.offset + offset, + childParentData.cellClipRect!, + (PaintingContext context, Offset offset) { + context.paintChild(child!, offset); + }, + clipBehavior: Clip.antiAlias, + ); + } else { + context.paintChild(child, childParentData.offset + offset); + } + } + child = childParentData.nextSibling; + } + + // To draw the right border to the table summary row. + _drawTableSummaryRowBorder(dataGridConfiguration, context, offset); + + if (dataGridConfiguration.isDesktop) { + _drawCurrentRowBorder(context, offset); + } + } + + @override + MouseCursor get cursor { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + return dataGridConfiguration + .columnResizeController.canSwitchResizeColumnCursor + ? SystemMouseCursors.resizeColumn + : SystemMouseCursors.basic; + } + + @override + PointerEnterEventListener? get onEnter => _onPointerEnter; + + @override + PointerExitEventListener? get onExit => _onPointerExit; + + @override + bool get validForMouseTracker { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + return (dataGridConfiguration.highlightRowOnHover && + dataRow.rowType == RowType.dataRow) || + (dataGridConfiguration.allowColumnsResizing && + (dataRow.rowType == RowType.stackedHeaderRow || + dataRow.rowType == RowType.headerRow)); + } + + // To handle long press start event. + void _onLongPressStart(LongPressStartDetails details) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + dataGridConfiguration.columnResizeController + .onLongPressStart(details, dataRow); + } + + // To handle long press end event. + void _onLongPressEnd(LongPressEndDetails details) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + if (dataRow.rowType == RowType.headerRow || + dataRow.rowType == RowType.stackedHeaderRow || + dataRow.rowType == RowType.dataRow) { + final double position = dataGridConfiguration.columnResizeController + .getXPosition(dataGridConfiguration, details.localPosition.dx); + final VisibleLineInfo? resizingLine = + dataGridConfiguration.container.scrollColumns.getVisibleLineAtPoint( + position, + false, + dataGridConfiguration.textDirection == TextDirection.rtl); + + if (resizingLine != null) { + DataCellBase? dataCell; + + if (dataRow.rowType == RowType.stackedHeaderRow) { + dataCell = + dataRow.visibleColumns.firstWhereOrNull((DataCellBase dataCell) { + final int cellLeft = dataCell.columnIndex; + final int cellRight = dataCell.columnIndex + dataCell.columnSpan; + + return cellLeft == resizingLine.lineIndex || + cellRight == resizingLine.lineIndex || + (resizingLine.lineIndex > cellLeft && + resizingLine.lineIndex < cellRight); + }); + } else { + dataCell = dataRow.visibleColumns.firstWhereOrNull( + (DataCellBase element) => + element.columnIndex == resizingLine.lineIndex); + } + + if (dataCell != null) { + if (dataGridConfiguration.currentCell.isEditing) { + if (dataCell.cellType == CellType.headerCell) { + // Clear editing when tap on the header cell + dataGridConfiguration.currentCell + .onCellSubmit(dataGridConfiguration); + } else if (dataCell.cellType == CellType.gridCell) { + // Clear editing when tap on the grid cell + if (dataGridConfiguration.currentCell + .canSubmitCell(dataGridConfiguration)) { + dataGridConfiguration.currentCell.onCellSubmit( + dataGridConfiguration, + cancelCanSubmitCell: true); + } + } + } + + if (dataGridConfiguration.onCellLongPress != null) { + final DataGridCellLongPressDetails longPressDetails = + DataGridCellLongPressDetails( + rowColumnIndex: + RowColumnIndex(dataCell.rowIndex, dataCell.columnIndex), + column: dataCell.gridColumn!, + globalPosition: details.globalPosition, + localPosition: details.localPosition, + velocity: details.velocity); + dataGridConfiguration.onCellLongPress!(longPressDetails); + } + } + } + } + } + + void _onPointerEnter(PointerEnterEvent event) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + if (dataGridConfiguration.allowColumnsResizing) { + dataGridConfiguration.columnResizeController + .onPointerEnter(event, dataRow); + } + + // Restricts the row hovering when resizing the column. + if (dataGridConfiguration.columnResizeController.isResizeIndicatorVisible) { + return; + } + + if (dataGridConfiguration.highlightRowOnHover && + dataGridConfiguration.isDesktop && + dataRow.rowType == RowType.dataRow) { + dataRow.isHoveredRow = true; + + final TextStyle rowStyle = + dataGridConfiguration.dataGridThemeData!.brightness == + Brightness.light + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + if (dataGridConfiguration.dataGridThemeData!.rowHoverTextStyle != + rowStyle) { + dataRow.rowIndexChanged(); + notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, + propertyName: 'hoverOnCell'); + } + markNeedsPaint(); + } + } + + void _onPointerExit(PointerExitEvent event) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + + if (dataGridConfiguration.allowColumnsResizing) { + dataGridConfiguration.columnResizeController + .onPointerExit(event, dataRow); + } + + if (dataRow.isHoveredRow && + dataGridConfiguration.highlightRowOnHover && + dataGridConfiguration.isDesktop && + dataRow.rowType == RowType.dataRow) { + dataRow.isHoveredRow = false; + + final TextStyle rowStyle = + dataGridConfiguration.dataGridThemeData!.brightness == + Brightness.light + ? const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Colors.black87) + : const TextStyle( + fontFamily: 'Roboto', + fontWeight: FontWeight.w400, + fontSize: 14, + color: Color.fromRGBO(255, 255, 255, 1)); + if (dataGridConfiguration.dataGridThemeData!.rowHoverTextStyle != + rowStyle) { + dataRow.rowIndexChanged(); + notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, + propertyName: 'hoverOnCell'); + } + markNeedsPaint(); + } + } +} + +/// ToDo +class GridCellRenderObjectWidget extends SingleChildRenderObjectWidget { + /// ToDo + GridCellRenderObjectWidget({ + required Key? key, + required this.dataCell, + required this.isDirty, + required this.child, + required this.dataGridStateDetails, + }) : super(key: key, child: RepaintBoundary.wrap(child, 0)); + + @override + final Widget child; + + /// ToDo + final DataCellBase dataCell; + + /// ToDo + final bool isDirty; + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + @override + _RenderGridCell createRenderObject(BuildContext context) => _RenderGridCell( + dataCell: dataCell, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails); + + @override + void updateRenderObject(BuildContext context, _RenderGridCell renderObject) { + super.updateRenderObject(context, renderObject); + renderObject + ..isDirty = isDirty + ..dataCell = dataCell; + } +} + +class _RenderGridCell extends RenderBox + with RenderObjectWithChildMixin + implements MouseTrackerAnnotation { + _RenderGridCell( + {RenderBox? child, + required DataCellBase dataCell, + required bool isDirty, + required DataGridStateDetails dataGridStateDetails}) + : _dataCell = dataCell, + _isDirty = isDirty, + _dataGridStateDetails = dataGridStateDetails { + this.child = child; + } + + DataCellBase get dataCell => _dataCell; + DataCellBase _dataCell; + + set dataCell(DataCellBase newDataColumn) { + if (_dataCell == newDataColumn) { + return; + } + + _dataCell = newDataColumn; + markNeedsLayout(); + markNeedsPaint(); + } + + bool get isDirty => _isDirty; + bool _isDirty = false; + + set isDirty(bool newValue) { + _isDirty = newValue; + if (_isDirty) { + markNeedsLayout(); + markNeedsPaint(); + } + + dataCell.isDirty = false; + } + + Rect? columnRect = Rect.zero; + + Rect? cellClipRect; + + bool isHovered = false; + + final DataGridStateDetails _dataGridStateDetails; + + Rect? _measureColumnRect(double rowHeight) { + if (dataCell.dataRow != null && + dataCell.dataRow!.isVisible && + dataCell.isVisible) { + final DataRowBase dataRow = dataCell.dataRow!; + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + final double lineWidth = dataRow.getColumnWidth( + dataCell.columnIndex, dataCell.columnIndex + dataCell.columnSpan); + final double lineHeight = dataRow.getRowHeight( + dataCell.rowIndex - dataCell.rowSpan, dataCell.rowIndex); + + if (dataRow.rowType == RowType.stackedHeaderRow) { + columnRect = _getStackedHeaderCellRect( + dataGridConfiguration, lineWidth, lineHeight); + } else if (dataRow.tableSummaryRow != null && + (dataRow.rowType == RowType.tableSummaryRow || + dataRow.rowType == RowType.tableSummaryCoveredRow)) { + columnRect = _getTableSummaryCellRect( + dataGridConfiguration, lineWidth, lineHeight); + } else { + final VisibleLineInfo? lineInfo = + dataRow.getColumnVisibleLineInfo(dataCell.columnIndex); + final double origin = lineInfo != null ? lineInfo.origin : 0.0; + columnRect = _getCellRect( + dataGridConfiguration, lineInfo, origin, lineWidth, lineHeight); + } + } else { + columnRect = Rect.zero; + } + + return columnRect; + } + + Rect? _getCellRect( + DataGridConfiguration dataGridConfiguration, + VisibleLineInfo? lineInfo, + double origin, + double lineWidth, + double lineHeight) { + final DataRowBase dataRow = dataCell.dataRow!; + final int rowIndex = dataCell.rowIndex; + final int rowSpan = dataCell.rowSpan; + + origin += dataGridConfiguration.container.horizontalOffset; + + // To overcome grid common RightToLeft clipping line creation problem + // instead of handling in grid common source. + if (dataGridConfiguration.textDirection == TextDirection.rtl && + lineInfo != null && + lineInfo.visibleIndex == + grid_helper + .getVisibleLines(dataGridConfiguration) + .firstBodyVisibleIndex) { + origin += lineInfo.scrollOffset; + } + + if (dataCell.cellType != CellType.stackedHeaderCell) { + // Clipping the column when frozen column applied + cellClipRect = + _getCellClipRect(dataGridConfiguration, lineInfo, lineHeight); + } + + final double topPosition = (rowSpan > 0) + ? -dataRow.getRowHeight(rowIndex - rowSpan, rowIndex - 1) + : 0.0; + + columnRect = Rect.fromLTWH(origin, topPosition, lineWidth, lineHeight); + return columnRect; + } + + double _getCellClippedOrigin(DataGridConfiguration dataGridConfiguration, + int startIndex, int endIndex) { + double origin = 0.0; + final ScrollAxisBase scrollColumns = + dataGridConfiguration.container.scrollColumns; + + bool updateOrigin(int index, bool isRTL) { + final VisibleLineInfo? newLine = + scrollColumns.getVisibleLineAtLineIndex(index, isRightToLeft: true); + + if (newLine != null) { + // Set origin to zero when scrolling is disabled in RTL. + if (isRTL && + scrollColumns.footerExtent >= dataGridConfiguration.viewWidth) { + origin = 0.0; + return false; + } + // Set origin only if the line is not footer. + if (!newLine.isFooter) { + origin += isRTL && newLine.isClippedCorner + ? newLine.clippedSize + : newLine.size - newLine.clippedCorner; + return false; + } + } else { + origin += + dataCell.dataRow!.getColumnWidth(index, index, lineNull: true); + } + return true; + } + + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + for (int index = endIndex; index >= startIndex; index--) { + if (!updateOrigin(index, true)) { + break; + } + } + } else { + for (int index = startIndex; index <= endIndex; index++) { + if (!updateOrigin(index, false)) { + break; + } + } + } + return origin; + } + + double _getCellClippedSize(DataGridConfiguration dataGridConfiguration, + int startIndex, int endIndex) { + double clippedSize = 0; + final bool isRTL = dataGridConfiguration.textDirection == TextDirection.rtl; + + for (int index = startIndex; index <= endIndex; index++) { + final VisibleLineInfo? newLine = dataGridConfiguration + .container.scrollColumns + .getVisibleLineAtLineIndex(index, isRightToLeft: isRTL); + + if (newLine != null) { + clippedSize += isRTL && newLine.isClippedCorner + ? newLine.clippedCornerExtent + : newLine.clippedSize; + } + } + return clippedSize; + } + + void _setClipRect(VisibleLineInfo line, double lineHeight) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + // Provides the clippedRect to the spanned summary column when + // frozen column is applied. + if (dataGridConfiguration.frozenColumnsCount > 0 || + dataGridConfiguration.footerFrozenColumnsCount > 0) { + if (!line.isHeader && dataCell.columnSpan > 0) { + final int endIndex = dataCell.columnIndex + dataCell.columnSpan; + final double lineSize = _getCellClippedSize( + dataGridConfiguration, line.lineIndex, endIndex); + final double clipOrigin = _getCellClippedOrigin( + dataGridConfiguration, dataCell.columnIndex, endIndex); + cellClipRect = Rect.fromLTWH(clipOrigin, 0.0, lineSize, lineHeight); + } else { + cellClipRect = + _getCellClipRect(dataGridConfiguration, line, lineHeight); + } + } + } + + Rect _getTableSummaryCellRect(DataGridConfiguration dataGridConfiguration, + double lineWidth, double lineHeight) { + if (dataCell.dataRow!.rowType == RowType.tableSummaryCoveredRow) { + lineWidth = min(dataGridConfiguration.viewWidth, + dataGridConfiguration.container.extentWidth); + double offset = dataGridConfiguration.container.horizontalOffset; + if (dataGridConfiguration.textDirection == TextDirection.rtl && + dataGridConfiguration.viewWidth > + dataGridConfiguration.container.extentWidth) { + offset += dataGridConfiguration.viewWidth - + dataGridConfiguration.container.extentWidth; + } + return Rect.fromLTWH(offset, 0.0, lineWidth, lineHeight); + } else { + double origin = 0.0, clippedWidth = 0.0; + void setOrigin(VisibleLineInfo line) { + origin = line.origin; + if (dataGridConfiguration.textDirection == TextDirection.rtl) { + origin += line.scrollOffset; + clippedWidth += line.size; + } + } + + VisibleLineInfo? getVisibleLineInfo(int index) => + dataCell.dataRow!.getColumnVisibleLineInfo(index); + final VisibleLineInfo? line = getVisibleLineInfo(dataCell.columnIndex); + + if (line == null) { + // Gets the origin for the first visible line in the spanned cell and + // calculates the clipped size. + for (int index = dataCell.columnIndex; + index <= dataCell.columnIndex + dataCell.columnSpan; + index++) { + final VisibleLineInfo? newLine = getVisibleLineInfo(index); + if (newLine != null) { + setOrigin(newLine); + _setClipRect(newLine, lineHeight); + break; + } else { + clippedWidth += + dataCell.dataRow!.getColumnWidth(index, index, lineNull: true); + } + } + } else { + setOrigin(line); + _setClipRect(line, lineHeight); + } + + origin += dataGridConfiguration.container.horizontalOffset; + + origin = dataGridConfiguration.textDirection == TextDirection.rtl + ? (origin + clippedWidth) - lineWidth + : origin - clippedWidth; + + return Rect.fromLTWH(origin, 0.0, lineWidth, lineHeight); + } + } + + Rect? _getStackedHeaderCellRect(DataGridConfiguration dataGridConfiguration, + double lineWidth, double lineHeight) { + final DataRowBase dataRow = dataCell.dataRow!; + final int cellStartIndex = dataCell.columnIndex; + final int columnSpan = dataCell.columnSpan; + final int cellEndIndex = cellStartIndex + columnSpan; + final int frozenColumns = dataGridConfiguration.container.frozenColumns; + final int frozenColumnsCount = dataGridConfiguration.frozenColumnsCount; + final int footerFrozenColumns = + dataGridConfiguration.container.footerFrozenColumns; + final int footerFrozenColumnsCount = + dataGridConfiguration.footerFrozenColumnsCount; + final int columnsLength = dataGridConfiguration.columns.length; + final ScrollAxisBase scrollColumns = + dataGridConfiguration.container.scrollColumns; + Rect? columnRect = Rect.zero; + double? origin; + VisibleLineInfo? lineInfo; + + if (frozenColumns > cellStartIndex && frozenColumns <= cellEndIndex) { + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + for (int index = cellEndIndex; + index >= frozenColumnsCount - 1; + index--) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + final VisibleLineInfo? startLineInfo = + scrollColumns.getVisibleLineAtLineIndex(cellStartIndex); + origin = startLineInfo?.origin; + lineWidth = _getClippedWidth( + dataGridConfiguration, cellStartIndex, cellEndIndex); + break; + } + } + } else { + for (int index = cellEndIndex; index >= cellStartIndex; index--) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + origin = lineInfo.origin < 0 ? 0.0 : lineInfo.origin; + lineWidth = _getClippedWidth( + dataGridConfiguration, cellStartIndex, cellEndIndex); + if (lineInfo.origin < 0) { + lineWidth += lineInfo.origin; + } + break; + } + } + } + } else if (footerFrozenColumns > 0 && + columnsLength - footerFrozenColumnsCount <= cellEndIndex) { + int span = 0; + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + for (int index = cellStartIndex; index <= cellEndIndex; index++) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + if (index == columnsLength - footerFrozenColumns) { + origin = lineInfo.origin; + lineWidth = + dataRow.getColumnWidth(cellStartIndex + span, cellEndIndex); + break; + } else { + origin = lineInfo.clippedOrigin; + lineWidth = _getClippedWidth( + dataGridConfiguration, cellStartIndex, cellEndIndex); + break; + } + } + span += 1; + } + } else { + int span = 0; + for (int index = cellStartIndex; index <= cellEndIndex; index++) { + lineInfo = scrollColumns.getVisibleLineAtLineIndex(index); + if (lineInfo != null) { + final VisibleLineInfo? line = + scrollColumns.getVisibleLineAtLineIndex(cellEndIndex); + if (line != null) { + if (index == columnsLength - footerFrozenColumnsCount) { + origin = line.origin; + lineWidth = + dataRow.getColumnWidth(cellStartIndex + span, cellEndIndex); + break; + } else { + origin = line.clippedOrigin - lineInfo.scrollOffset; + lineWidth = _getClippedWidth( + dataGridConfiguration, cellStartIndex, cellEndIndex); + break; + } + } + } + span += 1; + } + } + } else { + int span = dataCell.columnSpan; + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + for (int index = cellStartIndex; index <= cellEndIndex; index++) { + lineInfo = dataRow.getColumnVisibleLineInfo(index); + if (lineInfo != null) { + origin = lineInfo.origin + + dataRow.getColumnWidth(index, index + span) - + dataRow.getColumnWidth(cellStartIndex, cellEndIndex); + + cellClipRect = _getSpannedCellClipRect(dataGridConfiguration, + dataRow, dataCell, lineHeight, lineWidth); + break; + } + span -= 1; + } + } else { + for (int index = cellEndIndex; index >= cellStartIndex; index--) { + lineInfo = dataRow.getColumnVisibleLineInfo(index); + if (lineInfo != null) { + origin = lineInfo.origin + + dataRow.getColumnWidth(index - span, index) - + dataRow.getColumnWidth(cellStartIndex, cellEndIndex); + + cellClipRect = _getSpannedCellClipRect(dataGridConfiguration, + dataRow, dataCell, lineHeight, lineWidth); + break; + } + span -= 1; + } + } + } + + if (lineInfo != null) { + columnRect = _getCellRect( + dataGridConfiguration, lineInfo, origin!, lineWidth, lineHeight); + } + return columnRect; + } + + double _getClippedWidth(DataGridConfiguration dataGridConfiguration, + int startIndex, int endIndex) { + double clippedWidth = 0; + for (int index = startIndex; index <= endIndex; index++) { + final VisibleLineInfo? newline = + dataCell.dataRow!.getColumnVisibleLineInfo(index); + if (newline != null) { + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + clippedWidth += + newline.isClipped ? newline.clippedSize : newline.size; + } else { + clippedWidth += newline.isClipped + ? newline.clippedCornerExtent > 0 + ? newline.clippedCornerExtent + : newline.clippedSize + : newline.size; + } + } + } + return clippedWidth; + } + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + // To set the current interacted data cell to the column resizing's data cell. + dataGridConfiguration.columnResizeController.setDataCell(dataCell); + // Resets the header cell hovering when resizing the column. + if (dataGridConfiguration.columnResizeController.isResizeIndicatorVisible) { + if (isHovered) { + isHovered = false; + markNeedsPaint(); + } + } + + return super.hitTest(result, position: position); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + if (child == null) { + return false; + } + + final BoxParentData childParentData = child!.parentData! as BoxParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) => + child!.hitTest(result, position: transformed)); + if (isHit) { + return true; + } else { + return false; + } + } + + @override + bool hitTestSelf(Offset position) => true; + + @override + bool get isRepaintBoundary => true; + + @override + void performLayout() { + size = constraints + .constrain(Size(constraints.maxWidth, constraints.maxHeight)); + + if (child != null) { + child!.layout( + BoxConstraints.tightFor( + width: constraints.maxWidth, height: constraints.maxHeight), + parentUsesSize: true); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null) { + // Paints the cell hover color. + _paintHoverColor(context); + + context.paintChild(child!, offset); + } + + super.paint(context, offset); + } + + @override + MouseCursor get cursor { + final DataGridConfiguration dataGridConfiguration = _dataGridStateDetails(); + return dataGridConfiguration + .columnResizeController.canSwitchResizeColumnCursor + ? SystemMouseCursors.resizeColumn + : SystemMouseCursors.basic; + } + + @override + PointerEnterEventListener? get onEnter => _onPointerEnter; + + @override + PointerExitEventListener? get onExit => _onPointerExit; + + void _onPointerEnter(PointerEnterEvent event) { + // Restricts the header cell hovering when resizing the column. + if (_dataGridStateDetails() + .columnResizeController + .isResizeIndicatorVisible) { + return; + } + + isHovered = true; + markNeedsPaint(); + } + + void _onPointerExit(PointerExitEvent event) { + if (isHovered) { + isHovered = false; + markNeedsPaint(); + } + } + + void _paintHoverColor(PaintingContext context) { + if (dataCell.cellType == CellType.headerCell && isHovered) { + final DataGridConfiguration dataGridConfiguration = + _dataGridStateDetails(); + dataGridConfiguration.gridPaint!.color = + dataGridConfiguration.dataGridThemeData!.headerHoverColor; + final Rect cellRect = + Rect.fromLTRB(0, 0, constraints.maxWidth, constraints.maxHeight); + + context.canvas.drawRect(cellRect, dataGridConfiguration.gridPaint!); + } + } + + // Tracking only to the header cell for apply hovering color. + @override + bool get validForMouseTracker => dataCell.cellType == CellType.headerCell; +} + +Rect? _getCellClipRect(DataGridConfiguration dataGridConfiguration, + VisibleLineInfo? lineInfo, double rowHeight) { + // FLUT-1971 Need to check whether the lineInfo is null or not. Because it + // will be null when load empty to the columns collection. + if (lineInfo == null) { + return null; + } + if (lineInfo.isClippedBody && + lineInfo.isClippedOrigin && + lineInfo.isClippedCorner) { + final double left = dataGridConfiguration.textDirection == TextDirection.ltr + ? lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent + : lineInfo.clippedSize; + final double right = + dataGridConfiguration.textDirection == TextDirection.ltr + ? lineInfo.clippedSize + : lineInfo.clippedCornerExtent; + + return Rect.fromLTWH(left, 0.0, right, rowHeight); + } else if (lineInfo.isClippedBody && lineInfo.isClippedOrigin) { + final double left = dataGridConfiguration.textDirection == TextDirection.ltr + ? lineInfo.size - lineInfo.clippedSize - lineInfo.clippedCornerExtent + : 0.0; + final double right = + dataGridConfiguration.textDirection == TextDirection.ltr + ? lineInfo.size + : lineInfo.size - lineInfo.scrollOffset; + + return Rect.fromLTWH(left, 0.0, right, rowHeight); + } else if (lineInfo.isClippedBody && lineInfo.isClippedCorner) { + final double left = dataGridConfiguration.textDirection == TextDirection.ltr + ? 0.0 + : lineInfo.size - (lineInfo.size - lineInfo.clippedSize); + final double right = + dataGridConfiguration.textDirection == TextDirection.ltr + ? lineInfo.clippedSize + : lineInfo.size; + + return Rect.fromLTWH(left, 0.0, right, rowHeight); + } else { + return null; + } +} + +Rect? _getSpannedCellClipRect( + DataGridConfiguration dataGridConfiguration, + DataRowBase dataRow, + DataCellBase dataCell, + double cellHeight, + double cellWidth) { + Rect? clipRect; + int firstVisibleStackedColumnIndex = dataCell.columnIndex; + double lastCellClippedSize = 0.0; + bool isLastCellClippedCorner = false; + bool isLastCellClippedBody = false; + + double getClippedWidth(DataCellBase dataCell, DataRowBase dataRow, + {bool columnsNotInViewWidth = false, bool allCellsClippedWidth = false}) { + final int startIndex = dataCell.columnIndex; + final int endIndex = dataCell.columnIndex + dataCell.columnSpan; + double clippedWidth = 0; + for (int index = startIndex; index <= endIndex; index++) { + final VisibleLineInfo? newline = dataRow.getColumnVisibleLineInfo(index); + if (columnsNotInViewWidth) { + if (newline == null) { + clippedWidth += + dataGridConfiguration.container.scrollColumns.getLineSize(index); + } else { + firstVisibleStackedColumnIndex = index; + break; + } + } + if (allCellsClippedWidth) { + if (newline != null) { + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + clippedWidth += + newline.isClipped ? newline.clippedSize : newline.size; + } else { + clippedWidth += newline.isClipped + ? newline.clippedCornerExtent > 0 + ? newline.clippedCornerExtent + : newline.clippedSize + : newline.size; + } + lastCellClippedSize = newline.clippedSize; + isLastCellClippedCorner = newline.isClippedCorner; + isLastCellClippedBody = newline.isClippedBody; + } + } + } + return clippedWidth; + } + + if (dataGridConfiguration.frozenColumnsCount < 0 || + dataGridConfiguration.footerFrozenColumnsCount < 0) { + return null; + } + + if (dataCell.renderer != null) { + final double columnsNotInViewWidth = + getClippedWidth(dataCell, dataRow, columnsNotInViewWidth: true); + final double clippedWidth = + getClippedWidth(dataCell, dataRow, allCellsClippedWidth: true); + final VisibleLineInfo? visibleLineInfo = + dataRow.getColumnVisibleLineInfo(firstVisibleStackedColumnIndex); + + if (visibleLineInfo != null) { + if (visibleLineInfo.isClippedOrigin && visibleLineInfo.isClippedCorner) { + final double clippedOrigin = columnsNotInViewWidth + + visibleLineInfo.size - + (visibleLineInfo.clippedSize + visibleLineInfo.clippedCornerExtent); + + final double left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? clippedOrigin + : visibleLineInfo.clippedSize; + final double right = + dataGridConfiguration.textDirection == TextDirection.ltr + ? clippedWidth + : visibleLineInfo.clippedCornerExtent; + + clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); + } else if (visibleLineInfo.isClippedOrigin) { + final double clippedOriginLTR = columnsNotInViewWidth + + visibleLineInfo.size - + visibleLineInfo.clippedSize; + final double clippedOriginRTL = + (isLastCellClippedCorner && isLastCellClippedBody) + ? lastCellClippedSize + : 0.0; + + final double left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? clippedOriginLTR + : clippedOriginRTL; + final double right = + dataGridConfiguration.textDirection == TextDirection.ltr + ? clippedWidth + : cellWidth - + (columnsNotInViewWidth + visibleLineInfo.scrollOffset); + + clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); + } else if (isLastCellClippedCorner && isLastCellClippedBody) { + final double left = + dataGridConfiguration.textDirection == TextDirection.ltr + ? columnsNotInViewWidth + : dataCell.columnIndex < firstVisibleStackedColumnIndex + ? 0.0 + : cellWidth - clippedWidth; + final double right = + dataGridConfiguration.textDirection == TextDirection.ltr + ? clippedWidth + : cellWidth; + + clipRect = Rect.fromLTWH(left, 0.0, right, cellHeight); + } else { + if (clippedWidth < cellWidth) { + double left; + if (dataCell.columnIndex < firstVisibleStackedColumnIndex) { + left = dataGridConfiguration.textDirection == TextDirection.ltr + ? cellWidth - clippedWidth + : 0.0; + } else { + left = dataGridConfiguration.textDirection == TextDirection.ltr + ? 0.0 + : cellWidth - clippedWidth; + } + + clipRect = Rect.fromLTWH(left, 0.0, clippedWidth, cellHeight); + } else if (clipRect != null) { + clipRect = null; + } + } + } + } + return clipRect; +} 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 new file mode 100644 index 000000000..332102fe9 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/datagrid_widget/widgets/scrollview_widget.dart @@ -0,0 +1,1994 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide DataCell, DataRow; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../../grid_common/line_size_host.dart'; +import '../../grid_common/scroll_axis.dart'; +import '../../grid_common/scrollbar.dart'; +import '../../grid_common/visible_line_info.dart'; +import '../helper/datagrid_configuration.dart'; +import '../helper/datagrid_helper.dart' as grid_helper; +import '../helper/enums.dart'; +import '../helper/selection_helper.dart' as selection_helper; +import '../runtime/column.dart'; +import '../runtime/generator.dart'; +import '../selection/selection_manager.dart'; +import '../sfdatagrid.dart'; +import 'rendering_widget.dart'; + +/// ToDo +class ScrollViewWidget extends StatefulWidget { + /// ToDo + const ScrollViewWidget( + {required this.width, + required this.height, + required this.dataGridStateDetails}); + + /// ToDo + final double width; + + /// ToDo + final double height; + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + @override + State createState() => _ScrollViewWidgetState(); +} + +class _ScrollViewWidgetState extends State { + ScrollController? _verticalController; + ScrollController? _horizontalController; + FocusNode? _dataGridFocusNode; + double _width = 0.0; + double _height = 0.0; + bool _isScrolling = false; + + // This flag is used to restrict the load more view from being shown for + // every moment when datagrid reached at bottom on vertical scrolling. + bool _isLoadMoreViewLoaded = false; + + @override + void initState() { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + _verticalController = dataGridConfiguration.verticalScrollController; + _verticalController!.addListener(_verticalListener); + _horizontalController = dataGridConfiguration.horizontalScrollController; + _horizontalController!.addListener(_horizontalListener); + _height = widget.height; + _width = widget.width; + + dataGridConfiguration.rowSelectionManager + .addListener(_handleSelectionController); + + if (_dataGridFocusNode == null) { + // [FocusNode.onKey] callback is not firing on key navigation after flutter + // 2.2.0 and its breaking our key navigation on tab, shift + tab, arrow keys. + // So, we have used the focus widget and its need to remove when below + // mentioned issue is resolved on framework end. + //_dataGridFocusNode = FocusNode(onKey: _handleFocusKeyOperation); + _dataGridFocusNode = FocusNode(); + dataGridConfiguration.dataGridFocusNode = _dataGridFocusNode!; + if (dataGridConfiguration.source.sortedColumns.isNotEmpty) { + _dataGridFocusNode!.requestFocus(); + } + } + + super.initState(); + } + + DataGridConfiguration get _dataGridConfiguration => + widget.dataGridStateDetails(); + + VisualContainerHelper get _container => _dataGridConfiguration.container; + + RowGenerator get rowGenerator => _dataGridConfiguration.rowGenerator; + + SelectionManagerBase get _rowSelectionManager => + _dataGridConfiguration.rowSelectionManager; + + void _verticalListener() { + setState(() { + final double newValue = _verticalController!.offset; + _container.verticalOffset = newValue; + _container.setRowHeights(); + _container.resetSwipeOffset(); + _isScrolling = true; + _container.isDirty = true; + _dataGridConfiguration.scrollingState = ScrollDirection.forward; + _isLoadMoreViewLoaded = false; + }); + } + + void _horizontalListener() { + setState(() { + final double newValue = _horizontalController!.offset; + _container.horizontalOffset = newValue; + // Updating the width of all columns initially and inside the + // `ScrollViewWidget` build method when setting the container_isDirty to + // `true`. Thus, Don't necessary to update column widths while horizontal + // scrolling. + // DataGridSettings.columnSizer._refresh(widget.width); + _container.resetSwipeOffset(); + _dataGridConfiguration.scrollingState = ScrollDirection.forward; + if (!_dataGridConfiguration + .columnResizeController.isResizeIndicatorVisible) { + _isScrolling = true; + } + _container.isDirty = true; + }); + } + + void _updateAxis() { + final double width = _width; + final double height = _height; + _container.updateAxis(Size(width, height)); + } + + void _setHorizontalOffset() { + if (_container.needToSetHorizontalOffset) { + _container.horizontalOffset = _horizontalController!.hasClients + ? _horizontalController!.offset + : 0.0; + _container.scrollColumns.markDirty(); + } + + _container.needToSetHorizontalOffset = false; + } + + void _updateColumnSizer() { + final ColumnSizer columnSizer = _dataGridConfiguration.columnSizer; + if (isColumnSizerLoadedInitially(columnSizer)) { + initialRefresh(columnSizer, widget.width); + updateColumnSizerLoadedInitiallyFlag(columnSizer, false); + } else { + refreshColumnSizer(columnSizer, widget.width); + } + } + + void _ensureWidgets() { + if (!_container.isPreGenerator) { + _container._preGenerateItems(); + } else { + if (_container.needToRefreshColumn) { + _ensureItems(true); + _container.needToRefreshColumn = false; + } else { + _ensureItems(false); + } + } + } + + void _ensureItems(bool needToRefresh) { + final VisibleLinesCollection visibleRows = + _container.scrollRows.getVisibleLines(); + final VisibleLinesCollection visibleColumns = + grid_helper.getVisibleLines(widget.dataGridStateDetails()); + + if (_container.isGridLoaded && visibleColumns.isNotEmpty) { + rowGenerator.ensureRows(visibleRows, visibleColumns); + } + + if (needToRefresh) { + if (visibleColumns.isNotEmpty) { + rowGenerator.ensureColumns(visibleColumns); + } + } + } + + Widget _buildScrollView(double extentWidth, double scrollViewHeight, + double extentHeight, Size containerSize) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + Widget scrollView = Scrollbar( + isAlwaysShown: dataGridConfiguration.isScrollbarAlwaysShown, + controller: _verticalController, + child: SingleChildScrollView( + controller: _verticalController, + physics: dataGridConfiguration.isSwipingApplied + ? const NeverScrollableScrollPhysics() + : dataGridConfiguration.verticalScrollPhysics, + child: ConstrainedBox( + constraints: + BoxConstraints(minHeight: min(scrollViewHeight, extentHeight)), + child: Scrollbar( + isAlwaysShown: dataGridConfiguration.isScrollbarAlwaysShown, + controller: _horizontalController, + child: SingleChildScrollView( + controller: _horizontalController, + scrollDirection: Axis.horizontal, + physics: dataGridConfiguration.isSwipingApplied + ? const NeverScrollableScrollPhysics() + : dataGridConfiguration.horizontalScrollPhysics, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: min(_width, extentWidth)), + child: _VisualContainer( + key: const ValueKey('SfDataGrid-VisualContainer'), + isDirty: _container.isDirty, + rowGenerator: rowGenerator, + containerSize: containerSize, + dataGridStateDetails: widget.dataGridStateDetails, + ), + ), + ), + ), + ), + ), + ); + + if (_dataGridConfiguration.allowPullToRefresh) { + scrollView = RefreshIndicator( + child: scrollView, + key: dataGridConfiguration.refreshIndicatorKey, + onRefresh: () => handleRefresh(dataGridConfiguration.source), + strokeWidth: dataGridConfiguration.refreshIndicatorStrokeWidth, + displacement: dataGridConfiguration.refreshIndicatorDisplacement, + ); + } + + return scrollView; + } + + void _addScrollView(List children) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + final double extentWidth = _container.extentWidth; + final double headerRowsHeight = _container.scrollRows + .rangeToRegionPoints( + 0, grid_helper.getHeaderIndex(dataGridConfiguration), true)[1] + .length; + final double extentHeight = _container.extentHeight - headerRowsHeight; + final double scrollViewHeight = _height - headerRowsHeight; + + final Size containerSize = Size( + _canDisableHorizontalScrolling(dataGridConfiguration) + ? _width + : max(_width, extentWidth), + _canDisableVerticalScrolling(dataGridConfiguration) + ? scrollViewHeight + : (extentHeight > scrollViewHeight + ? extentHeight + : scrollViewHeight)); + + final Widget scrollView = _buildScrollView( + extentWidth, scrollViewHeight, extentHeight, containerSize); + + final Positioned wrapScrollView = Positioned.fill( + top: headerRowsHeight, + child: scrollView, + ); + + children.add(wrapScrollView); + } + + void _addHeaderRows(List children) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + + final double containerWidth = + _canDisableHorizontalScrolling(dataGridConfiguration) + ? _width + : max(_width, _container.extentWidth); + + List _buildHeaderRows() { + final List headerRows = []; + + // Adds stacked header rows + if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { + headerRows.addAll(rowGenerator.items + .where((DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == RowRegion.header && + row.rowType == RowType.stackedHeaderRow) + .map((DataRowBase dataRow) => _HeaderCellsWidget( + key: dataRow.key!, + dataRow: dataRow, + isDirty: _container.isDirty || dataRow.isDirty, + dataGridStateDetails: widget.dataGridStateDetails, + )) + .toList(growable: false)); + } + + // Adds column header row + headerRows.addAll(rowGenerator.items + .where((DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == RowRegion.header && + row.rowType == RowType.headerRow) + .map((DataRowBase dataRow) => _HeaderCellsWidget( + key: dataRow.key!, + dataRow: dataRow, + isDirty: _container.isDirty || dataRow.isDirty, + dataGridStateDetails: widget.dataGridStateDetails, + )) + .toList(growable: false)); + + return headerRows; + } + + double getStartX() { + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + return -_container.horizontalOffset; + } else { + if (!_horizontalController!.hasClients || + _horizontalController!.offset <= 0.0 || + _horizontalController!.position.maxScrollExtent <= 0.0 || + _container.extentWidth <= _width) { + return 0.0; + } else if (_horizontalController!.position.maxScrollExtent == + _horizontalController!.offset) { + return -_horizontalController!.position.maxScrollExtent; + } + + late double maxScrollExtent; + if (dataGridConfiguration + .columnResizeController.isResizeIndicatorVisible) { + // In RTL, Resolves the glitching issue of header rows while resizing + // the column by calculating the maxScrollExtent manually. + maxScrollExtent = _container.extentWidth - + _horizontalController!.position.viewportDimension; + } else { + maxScrollExtent = _horizontalController!.position.maxScrollExtent; + } + + return -(maxScrollExtent - _container.horizontalOffset); + } + } + + if (rowGenerator.items.isNotEmpty) { + final List headerRows = _buildHeaderRows(); + for (int i = 0; i < headerRows.length; i++) { + final VisibleLineInfo? lineInfo = + _container.scrollRows.getVisibleLineAtLineIndex(i); + final Positioned header = Positioned.directional( + textDirection: dataGridConfiguration.textDirection, + start: getStartX(), + top: lineInfo?.origin, + height: lineInfo?.size, + // FLUT-1971 Changed the header row widget as extendwidth instead of + // device width to resloved the issue of apply sorting to the + // invisible columns. + width: containerWidth, + child: headerRows[i]); + children.add(header); + } + } + } + + void _addIndicator(List children) { + final ColumnResizeController columnResizeController = + _dataGridConfiguration.columnResizeController; + if (columnResizeController.isResizeIndicatorVisible && + columnResizeController.resizingDataCell != null) { + double top = 0; + + final SfDataGridThemeData? dataGridThemeData = + _dataGridConfiguration.dataGridThemeData; + int rowIndex = columnResizeController.rowIndex; + + if (columnResizeController.rowSpan > 0) { + rowIndex -= columnResizeController.rowSpan; + } + + if (rowIndex > 0) { + top = columnResizeController.resizingDataCell!.dataRow! + .getRowHeight(0, rowIndex - 1); + } + + final Widget indicator = Positioned( + top: top, + left: columnResizeController.indicatorPosition, + // Ignores the `hitTest` of the indicator to resolved the update of the + // cursor visibility when hovering the column resizing indicator. + child: IgnorePointer( + child: _dataGridConfiguration.isDesktop + ? Container( + width: dataGridThemeData!.columnResizeIndicatorStrokeWidth, + height: _container.extentHeight - top, + color: dataGridThemeData.columnResizeIndicatorColor) + : _getResizingCursor( + _dataGridConfiguration, dataGridThemeData!, top)), + ); + children.add(indicator); + } + } + + void _addLoadMoreView(List children) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + Future loadMoreRows() async { + _isLoadMoreViewLoaded = true; + await handleLoadMoreRows(dataGridConfiguration.source); + } + + if (_verticalController!.hasClients && + dataGridConfiguration.loadMoreViewBuilder != null) { + // FLUT-3038 Need to restrict load more view when rows exist within the + // view height. + if ((_verticalController!.position.maxScrollExtent > 0.0) && + (_verticalController!.offset >= + _verticalController!.position.maxScrollExtent) && + !_isLoadMoreViewLoaded) { + final Widget? loadMoreView = + dataGridConfiguration.loadMoreViewBuilder!(context, loadMoreRows); + + if (loadMoreView != null) { + final Alignment loadMoreAlignment = + dataGridConfiguration.textDirection == TextDirection.ltr + ? Alignment.bottomLeft + : Alignment.bottomRight; + + children.add(Positioned( + top: 0.0, + width: _width, + height: _height, + child: Align( + alignment: loadMoreAlignment, + child: loadMoreView, + ))); + } + } + } + } + + void _addFreezePaneLinesElevation(List children) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + final SfDataGridThemeData? dataGridThemeData = + dataGridConfiguration.dataGridThemeData; + if (dataGridThemeData!.frozenPaneElevation <= 0.0 || + dataGridConfiguration.columns.isEmpty || + effectiveRows(dataGridConfiguration.source).isEmpty) { + return; + } + + void drawElevation({ + EdgeInsets? margin, + double? bottom, + double? start, + double? end, + double? top, + Axis? axis, + }) { + final Widget elevationLine = ClipRect( + child: Container( + width: axis == Axis.vertical ? 1 : 0, + height: axis == Axis.horizontal ? 1 : 0, + margin: margin, + decoration: BoxDecoration( + color: const Color(0xFF000000), + boxShadow: [ + BoxShadow( + color: dataGridThemeData.brightness == Brightness.light + ? const Color(0x3D000000) + : const Color(0x3DFFFFFF), + offset: Offset.zero, + spreadRadius: 3.0, + blurRadius: dataGridThemeData.frozenPaneElevation, + ) + ]))); + + children.add(Positioned.directional( + top: top, + end: end, + start: start, + bottom: bottom, + child: elevationLine, + textDirection: dataGridConfiguration.textDirection, + )); + } + + double getTopPosition(DataRowBase columnHeaderRow, int columnIndex) { + double top = 0.0; + if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { + top = columnHeaderRow.getRowHeight( + 0, dataGridConfiguration.stackedHeaderRows.length - 1); + final DataCellBase? dataCell = columnHeaderRow.visibleColumns + .firstWhereOrNull( + (DataCellBase cell) => cell.columnIndex == columnIndex); + // Need to ignore header cell spanned height from the total stacked + // header rows height if it is spanned. + if (dataCell != null && dataCell.rowSpan > 0) { + top -= columnHeaderRow.getRowHeight( + dataCell.rowIndex - dataCell.rowSpan, dataCell.rowIndex - 1); + } + } + return top; + } + + // The field remainingViewPortHeight and remainingViewPortWidth are used to + // restrict the elevation height and width fill in the entire screen when + // extent width and height is smaller than the view size. + final double remainingViewPortHeight = + (dataGridConfiguration.container.extentHeight < _height) + ? _height - dataGridConfiguration.container.extentHeight + : 0.0; + final double remainingViewPortWidth = + (dataGridConfiguration.container.extentWidth < _width) + ? _width - dataGridConfiguration.container.extentWidth + : 0.0; + + final DataRowBase? columnHeaderRow = + dataGridConfiguration.container.rowGenerator.items.firstWhereOrNull( + (DataRowBase row) => row.rowType == RowType.headerRow); + + // Provided the margin to allow shadow only to the corresponding side. + // 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 = dataGridThemeData.frozenPaneElevation + 4.0; + + final int frozenColumnIndex = + grid_helper.getLastFrozenColumnIndex(dataGridConfiguration); + final int footerFrozenColumnIndex = + grid_helper.getStartFooterFrozenColumnIndex(dataGridConfiguration); + final int frozenRowIndex = + grid_helper.getLastFrozenRowIndex(dataGridConfiguration); + final int footerFrozenRowIndex = + grid_helper.getStartFooterFrozenRowIndex(dataGridConfiguration); + + if (columnHeaderRow != null && + frozenColumnIndex >= 0 && + !_canDisableHorizontalScrolling(dataGridConfiguration)) { + final double top = getTopPosition(columnHeaderRow, frozenColumnIndex); + final double left = columnHeaderRow.getColumnWidth( + 0, dataGridConfiguration.frozenColumnsCount - 1); + + drawElevation( + top: top, + start: left, + bottom: remainingViewPortHeight, + axis: Axis.horizontal, + margin: dataGridConfiguration.textDirection == TextDirection.rtl + ? EdgeInsets.only(left: margin) + : EdgeInsets.only(right: margin)); + } + + if (columnHeaderRow != null && + footerFrozenColumnIndex >= 0 && + !_canDisableHorizontalScrolling(dataGridConfiguration)) { + final double top = + getTopPosition(columnHeaderRow, footerFrozenColumnIndex); + final double right = columnHeaderRow.getColumnWidth( + footerFrozenColumnIndex, dataGridConfiguration.container.columnCount); + + drawElevation( + top: top, + bottom: remainingViewPortHeight, + end: right + remainingViewPortWidth, + axis: Axis.horizontal, + margin: dataGridConfiguration.textDirection == TextDirection.rtl + ? EdgeInsets.only(right: margin) + : EdgeInsets.only(left: margin)); + } + + if (columnHeaderRow != null && + frozenRowIndex >= 0 && + !_canDisableVerticalScrolling(dataGridConfiguration)) { + final double top = columnHeaderRow.getRowHeight(0, frozenRowIndex); + + drawElevation( + top: top, + start: 0.0, + end: remainingViewPortWidth, + axis: Axis.vertical, + margin: EdgeInsets.only(bottom: margin)); + } + + if (columnHeaderRow != null && + footerFrozenRowIndex >= 0 && + !_canDisableVerticalScrolling(dataGridConfiguration)) { + final double bottom = columnHeaderRow.getRowHeight( + footerFrozenRowIndex, dataGridConfiguration.container.rowCount); + + drawElevation( + start: 0.0, + end: remainingViewPortWidth, + axis: Axis.vertical, + bottom: bottom + remainingViewPortHeight, + margin: EdgeInsets.only(top: margin)); + } + } + + void _handleSelectionController() { + setState(() { + /* Rebuild the DataGrid when the selection or currentcell is processed. */ + }); + } + + // ------------------------------------------------------------------------- + // Below method and callback are used in [RawKeyboardListener] widget. + // Due to break on [FocusNode.onKey] callback after flutter 2.2.0. + // So, instead of using the [RawKeyboardListener] we replaced with [Focus] + // widget to adapt our all use case on key navigation. Need to remove the Focus + // widget and its related method and callback when the below mentioned github + // issue resolved on framework end. + // [https://github.com/flutter/flutter/issues/83023] + //--------------------------------------------------------------------------- + + // KeyEventResult _handleFocusKeyOperation(FocusNode focusNode, RawKeyEvent e) { + // final DataGridSettings dataGridSettings = DataGridSettings; + // final _CurrentCellManager currentCell = dataGridSettings.currentCell; + // + // KeyEventResult needToMoveFocus() { + // bool canAllowToRemoveFocus(int rowIndex, int columnIndex) => + // (dataGridSettings.navigationMode == GridNavigationMode.cell && + // currentCell.rowIndex == rowIndex && + // currentCell.columnIndex == columnIndex) || + // (dataGridSettings.navigationMode == GridNavigationMode.row && + // currentCell.rowIndex == rowIndex) || + // (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); + // + // if (e.isShiftPressed) { + // final int firstRowIndex = + // selection_helper.getFirstRowIndex(DataGridSettings); + // final int firstCellIndex = + // selection_helper.getFirstCellIndex(DataGridSettings); + // + // if (canAllowToRemoveFocus(firstRowIndex, firstCellIndex)) { + // return KeyEventResult.ignored; + // } else { + // return KeyEventResult.handled; + // } + // } else { + // final int lastRowIndex = + // selection_helper.getLastNavigatingRowIndex(DataGridSettings); + // final int lastCellIndex = + // selection_helper.getLastCellIndex(DataGridSettings); + // + // if (canAllowToRemoveFocus(lastRowIndex, lastCellIndex)) { + // return KeyEventResult.ignored; + // } else { + // return KeyEventResult.handled; + // } + // } + // } + // + // if (e.logicalKey == LogicalKeyboardKey.tab) { + // return needToMoveFocus(); + // } else { + // return _handleKeys(e); + // } + // } + + // KeyEventResult _handleKeyOperation( + // FocusNode focusNode, RawKeyEvent keyEvent) { + // final DataGridSettings dataGridSettings = DataGridSettings; + // final _CurrentCellManager currentCell = dataGridSettings.currentCell; + // + // KeyEventResult needToMoveFocus() { + // bool canAllowToRemoveFocus(int rowIndex, int columnIndex) => + // (dataGridSettings.navigationMode == GridNavigationMode.cell && + // currentCell.rowIndex == rowIndex && + // currentCell.columnIndex == columnIndex) || + // (dataGridSettings.navigationMode == GridNavigationMode.row && + // currentCell.rowIndex == rowIndex) || + // (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); + // + // if (e.isShiftPressed) { + // final int firstRowIndex = + // selection_helper.getFirstRowIndex(DataGridSettings); + // final int firstCellIndex = + // selection_helper.getFirstCellIndex(DataGridSettings); + // + // if (canAllowToRemoveFocus(firstRowIndex, firstCellIndex)) { + // return KeyEventResult.ignored; + // } else { + // return KeyEventResult.handled; + // } + // } else { + // final int lastRowIndex = + // selection_helper.getLastNavigatingRowIndex(DataGridSettings); + // final int lastCellIndex = + // selection_helper.getLastCellIndex(DataGridSettings); + // + // if (canAllowToRemoveFocus(lastRowIndex, lastCellIndex)) { + // return KeyEventResult.ignored; + // } else { + // return KeyEventResult.handled; + // } + // } + // } + // + // if (e.logicalKey == LogicalKeyboardKey.tab) { + // return needToMoveFocus(); + // } else { + // return _handleKeys(e); + // } + // } + + // KeyEventResult _handleKeys(RawKeyEvent keyEvent) { + // final _CurrentCellManager currentCell = DataGridSettings.currentCell; + // if (DataGridSettings.allowEditing && + // currentCell.isEditing && + // !_dataGridFocusNode!.hasPrimaryFocus) { + // if (keyEvent.logicalKey == LogicalKeyboardKey.tab || + // keyEvent.logicalKey == LogicalKeyboardKey.escape || + // keyEvent.logicalKey == LogicalKeyboardKey.arrowDown || + // keyEvent.logicalKey == LogicalKeyboardKey.arrowUp || + // keyEvent.logicalKey == LogicalKeyboardKey.pageUp || + // keyEvent.logicalKey == LogicalKeyboardKey.pageDown) { + // return KeyEventResult.handled; + // } + // } + // + // return _dataGridFocusNode!.hasPrimaryFocus + // ? KeyEventResult.handled + // : KeyEventResult.ignored; + // } + + // -------------------------------------------------------------------------- + + KeyEventResult _handleKeyOperation( + FocusNode focusNode, RawKeyEvent keyEvent) { + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + final CurrentCellManager currentCell = dataGridConfiguration.currentCell; + + void processKeys() { + if (keyEvent.runtimeType == RawKeyDownEvent) { + _rowSelectionManager.handleKeyEvent(keyEvent); + if (keyEvent.isControlPressed) { + dataGridConfiguration.isControlKeyPressed = true; + } + } + if (keyEvent.runtimeType == RawKeyUpEvent) { + if (keyEvent.logicalKey == LogicalKeyboardKey.controlLeft || + keyEvent.logicalKey == LogicalKeyboardKey.controlRight) { + dataGridConfiguration.isControlKeyPressed = false; + } + } + } + + // Move to the next focusable widget when it reach the last and first + // [DataGridCell] on tab & shift + tab key. + KeyEventResult needToMoveFocus() { + bool canAllowToRemoveFocus(int rowIndex, int columnIndex) => + (dataGridConfiguration.navigationMode == GridNavigationMode.cell && + currentCell.rowIndex == rowIndex && + currentCell.columnIndex == columnIndex) || + (dataGridConfiguration.navigationMode == GridNavigationMode.row && + currentCell.rowIndex == rowIndex) || + (!_dataGridFocusNode!.hasPrimaryFocus && currentCell.isEditing); + + if (keyEvent.isShiftPressed) { + final int firstRowIndex = + selection_helper.getFirstRowIndex(dataGridConfiguration); + final int firstCellIndex = + selection_helper.getFirstCellIndex(dataGridConfiguration); + + if (canAllowToRemoveFocus(firstRowIndex, firstCellIndex)) { + return KeyEventResult.ignored; + } else { + return KeyEventResult.handled; + } + } else { + final int lastRowIndex = + selection_helper.getLastNavigatingRowIndex(dataGridConfiguration); + final int lastCellIndex = + selection_helper.getLastCellIndex(dataGridConfiguration); + + if (canAllowToRemoveFocus(lastRowIndex, lastCellIndex)) { + return KeyEventResult.ignored; + } else { + return KeyEventResult.handled; + } + } + } + + if (_dataGridFocusNode!.hasPrimaryFocus) { + if (keyEvent.logicalKey == LogicalKeyboardKey.tab && + needToMoveFocus() != KeyEventResult.handled) { + return KeyEventResult.ignored; + } + + processKeys(); + return KeyEventResult.handled; + } else { + // On Editing, we have to handle below [LogicalKeyboardKey]'s. For, that + // we have return [KeyEventResult.handled] to handle those keys on + // editing. + if (dataGridConfiguration.allowEditing && + currentCell.isEditing && + !_dataGridFocusNode!.hasPrimaryFocus) { + if (keyEvent.logicalKey == LogicalKeyboardKey.tab || + keyEvent.logicalKey == LogicalKeyboardKey.escape || + keyEvent.logicalKey == LogicalKeyboardKey.arrowDown || + keyEvent.logicalKey == LogicalKeyboardKey.arrowUp || + keyEvent.logicalKey == LogicalKeyboardKey.pageUp || + keyEvent.logicalKey == LogicalKeyboardKey.pageDown || + keyEvent.logicalKey == LogicalKeyboardKey.enter) { + processKeys(); + return KeyEventResult.handled; + } + } + + return KeyEventResult.ignored; + } + } + + @override + void didUpdateWidget(ScrollViewWidget oldWidget) { + super.didUpdateWidget(oldWidget); + final DataGridConfiguration dataGridConfiguration = _dataGridConfiguration; + if (oldWidget.width != widget.width || + oldWidget.height != widget.height || + _container.needToSetHorizontalOffset) { + /// Need not to change the height when onScreenKeyboard appears. + /// Cause: If we change the height on editing, editable widget will not move + /// above the onScreenKeyboard on mobile platforms. + final bool needToResizeHeight = !dataGridConfiguration.isDesktop && + dataGridConfiguration.currentCell.isEditing; + + _width = widget.width; + _height = !needToResizeHeight ? widget.height : _height; + _container + ..needToSetHorizontalOffset = true + ..isDirty = true; + if (oldWidget.width != widget.width || + oldWidget.height != widget.height) { + _container.resetSwipeOffset(); + } + // FLUT-2047 Need to mark all visible rows height as dirty when DataGrid + // size is changed if onQueryRowHeight is not null. + if (oldWidget.width != widget.width && + dataGridConfiguration.onQueryRowHeight != null) { + _container.rowHeightManager.reset(); + } + } + + if (_verticalController != dataGridConfiguration.verticalScrollController) { + _verticalController!.removeListener(_verticalListener); + _verticalController = + dataGridConfiguration.verticalScrollController ?? ScrollController(); + _verticalController!.addListener(_verticalListener); + } + + if (_horizontalController != + dataGridConfiguration.horizontalScrollController) { + _horizontalController!.removeListener(_horizontalListener); + _horizontalController = + dataGridConfiguration.horizontalScrollController ?? + ScrollController(); + _horizontalController!.addListener(_horizontalListener); + } + } + + @override + Widget build(BuildContext context) { + if (_container.isDirty && !_isScrolling) { + _updateAxis(); + _updateColumnSizer(); + _container + ..setRowHeights() + ..needToRefreshColumn = true; + } + + if (_container.needToSetHorizontalOffset) { + _setHorizontalOffset(); + } + + if (_container.isDirty) { + _ensureWidgets(); + } + + final List children = []; + + _addHeaderRows(children); + + _addScrollView(children); + + _addFreezePaneLinesElevation(children); + + _addLoadMoreView(children); + + _addIndicator(children); + + _container.isDirty = false; + _isScrolling = false; + + // return RawKeyboardListener( + // focusNode: _dataGridFocusNode!, + // onKey: _handleKeyOperation, + // child: Container( + // height: _height, + // width: _width, + // decoration: const BoxDecoration( + // color: Colors.transparent, + // ), + // clipBehavior: Clip.antiAlias, + // child: DataGridSettings.allowColumnsResizing + // ? _wrapInsideGestureDetector(children) + // : _wrapInsideStack(children))); + + // [FocusNode.onKey] callback is not firing on key navigation after flutter + // 2.2.0 and its breaking our key navigation on tab, shift + tab, arrow keys. + // So, we have used the focus widget. Need to remove [Focus] widget when + // below mentioned issue is resolved on framework end. + // [https://github.com/flutter/flutter/issues/83023] + return Focus( + focusNode: _dataGridFocusNode, + onKey: _handleKeyOperation, + child: Container( + height: _height, + width: _width, + decoration: const BoxDecoration( + color: Colors.transparent, + ), + clipBehavior: Clip.antiAlias, + child: Stack( + fit: StackFit.passthrough, + children: List.from(children)), + ), + ); + } + + @override + void dispose() { + if (_verticalController != null) { + _verticalController! + ..removeListener(_verticalListener) + ..dispose(); + } + + if (_horizontalController != null) { + _horizontalController! + ..removeListener(_horizontalListener) + ..dispose(); + } + + super.dispose(); + } +} + +/// Return the resizing indicator for mobile platform +Widget _getResizingCursor(DataGridConfiguration dataGridConfiguration, + SfDataGridThemeData themeData, double y) { + const double cursorContainerHeight = 16.0; + const double cursorContainerWidth = 16.0; + + final ColumnResizeController columnResizeController = + dataGridConfiguration.columnResizeController; + + final int rowSpan = columnResizeController.rowSpan; + final int rowIndex = columnResizeController.rowIndex; + 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 + if (rowSpan > 0) { + rowHeight = columnResizeController.resizingDataCell!.dataRow! + .getRowHeight(rowIndex - rowSpan, rowIndex); + } + + return Stack( + clipBehavior: Clip.none, + children: [ + Positioned( + child: Container( + width: strokeWidth, + color: indicatorColor, + height: dataGridConfiguration.container.extentHeight - y, + ), + ), + Positioned( + left: (strokeWidth - cursorContainerWidth) / 2, + top: (rowHeight - cursorContainerHeight) / 2, + child: Container( + height: cursorContainerHeight, + width: cursorContainerWidth, + padding: const EdgeInsets.all(.2), + decoration: + BoxDecoration(color: indicatorColor, shape: BoxShape.circle), + child: CustomPaint( + painter: _CustomResizingCursorPainter( + Colors.white, dataGridConfiguration.gridPaint!), + ), + ), + ), + ], + ); +} + +/// Helps to draw the custom resizing cursor for mobile platforms +class _CustomResizingCursorPainter extends CustomPainter { + _CustomResizingCursorPainter(this._arrowColor, this._arrowPaint); + + final Color _arrowColor; + final Paint _arrowPaint; + + @override + void paint(Canvas canvas, Size size) { + // Center point of the width + final double horizontalCenterPoint = size.width / 2; + // Center point of the height + final double verticalCenterPoint = size.height / 2; + // Consider the space between the left and right of arrow. It 1/4 range of + // horizontal center point + final double leftAndRightPadding = horizontalCenterPoint / 4; + // Consider the space between the top and bottom of arrow. It 1/4 range of + // height + final double topAndBottomPadding = size.height / 4; + + Path? path = Path(); + _arrowPaint + ..style = PaintingStyle.fill + ..color = _arrowColor; + + /// Drawing left arrow + path.moveTo(leftAndRightPadding, verticalCenterPoint); + path.lineTo( + horizontalCenterPoint - leftAndRightPadding, leftAndRightPadding); + path.moveTo( + horizontalCenterPoint - leftAndRightPadding, topAndBottomPadding); + path.lineTo(horizontalCenterPoint - leftAndRightPadding, + size.height - topAndBottomPadding); + path.lineTo(leftAndRightPadding, verticalCenterPoint); + path.close(); + canvas.drawPath(path, _arrowPaint); + + /// Drawing right arrow + path.moveTo( + horizontalCenterPoint + topAndBottomPadding, leftAndRightPadding); + path.lineTo(size.width - leftAndRightPadding, verticalCenterPoint); + path.moveTo(size.width - leftAndRightPadding, verticalCenterPoint); + path.lineTo(horizontalCenterPoint + leftAndRightPadding, + size.height - topAndBottomPadding); + path.lineTo(verticalCenterPoint + leftAndRightPadding, topAndBottomPadding); + path.close(); + canvas.drawPath(path, _arrowPaint); + path = null; + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return false; + } +} + +bool _canDisableVerticalScrolling(DataGridConfiguration dataGridConfiguration) { + final VisualContainerHelper container = dataGridConfiguration.container; + return (container.scrollRows.headerExtent + + container.scrollRows.footerExtent) > + dataGridConfiguration.viewHeight; +} + +bool _canDisableHorizontalScrolling( + DataGridConfiguration dataGridConfiguration) { + final VisualContainerHelper container = dataGridConfiguration.container; + return (container.scrollColumns.headerExtent + + container.scrollColumns.footerExtent) > + dataGridConfiguration.viewWidth; +} + +class _VisualContainer extends StatefulWidget { + const _VisualContainer( + {required Key key, + required this.rowGenerator, + required this.containerSize, + required this.isDirty, + required this.dataGridStateDetails}) + : super(key: key); + + final Size containerSize; + final RowGenerator rowGenerator; + final bool isDirty; + final DataGridStateDetails dataGridStateDetails; + + @override + State createState() => _VisualContainerState(); +} + +class _VisualContainerState extends State<_VisualContainer> { + void _addSwipeBackgroundWidget(List children) { + final DataGridConfiguration dataGridConfiguration = + widget.dataGridStateDetails(); + if (dataGridConfiguration.allowSwiping && + dataGridConfiguration.swipingOffset.abs() > 0.0) { + final DataRowBase? swipeRow = widget.rowGenerator.items + .where((DataRowBase row) => + (row.rowRegion == RowRegion.body || + row.rowType == RowType.dataRow) && + row.rowIndex >= 0) + .firstWhereOrNull((DataRowBase row) => row.isSwipingRow); + if (swipeRow != null) { + final DataGridRowSwipeDirection swipeDirection = + grid_helper.getSwipeDirection( + dataGridConfiguration, dataGridConfiguration.swipingOffset); + final int rowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, swipeRow.rowIndex); + + switch (swipeDirection) { + case DataGridRowSwipeDirection.startToEnd: + if (dataGridConfiguration.startSwipeActionsBuilder != null) { + final Widget? startSwipeWidget = + dataGridConfiguration.startSwipeActionsBuilder!( + context, swipeRow.dataGridRow!, rowIndex); + children.add(startSwipeWidget ?? Container()); + } + break; + case DataGridRowSwipeDirection.endToStart: + if (dataGridConfiguration.endSwipeActionsBuilder != null) { + final Widget? endSwipeWidget = + dataGridConfiguration.endSwipeActionsBuilder!( + context, swipeRow.dataGridRow!, rowIndex); + children.add(endSwipeWidget ?? Container()); + } + break; + } + } + } + } + + void _addFooterRow(List children) { + final DataGridConfiguration dataGridConfiguration = + widget.dataGridStateDetails(); + if (dataGridConfiguration.footer != null) { + final DataRowBase? footerRow = widget.rowGenerator.items.firstWhereOrNull( + (DataRowBase row) => + row.rowType == RowType.footerRow && row.rowIndex >= 0); + if (footerRow != null) { + children.add(_VirtualizingCellsWidget( + key: footerRow.key!, + dataRow: footerRow, + isDirty: widget.isDirty || footerRow.isDirty, + dataGridStateDetails: widget.dataGridStateDetails)); + } + } + } + + void _addSummaryRows(List children, RowRegion region, + GridTableSummaryRowPosition position) { + final DataGridConfiguration dataGridConfiguration = + widget.dataGridStateDetails(); + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + children.addAll(widget.rowGenerator.items + .where((DataRowBase row) => + row.rowIndex >= 0 && + row.rowRegion == region && + row.tableSummaryRow != null && + row.tableSummaryRow!.position == position && + (row.rowType == RowType.tableSummaryRow || + row.rowType == RowType.tableSummaryCoveredRow)) + .map((DataRowBase dataRow) => _VirtualizingCellsWidget( + key: dataRow.key!, + dataRow: dataRow, + dataGridStateDetails: widget.dataGridStateDetails, + isDirty: widget.isDirty || dataRow.isDirty, + )) + .toList()); + } + } + + @override + Widget build(BuildContext context) { + final List children = []; + + // Adds top table summary rows. + _addSummaryRows( + children, RowRegion.header, GridTableSummaryRowPosition.top); + + // Need to restrict the layout of the currently unused rows that keep the + // row index to -1 in the `rowGenerator.items` collection. + children.addAll(widget.rowGenerator.items + .where((DataRowBase row) => + row.rowType == RowType.dataRow && row.rowIndex >= 0) + .map((DataRowBase dataRow) => _VirtualizingCellsWidget( + key: dataRow.key!, + dataRow: dataRow, + isDirty: widget.isDirty || dataRow.isDirty, + dataGridStateDetails: widget.dataGridStateDetails, + )) + .toList()); + + _addFooterRow(children); + + // Adds bottom table summary rows. + _addSummaryRows( + children, RowRegion.footer, GridTableSummaryRowPosition.bottom); + + _addSwipeBackgroundWidget(children); + + return VisualContainerRenderObjectWidget( + key: widget.key, + containerSize: widget.containerSize, + isDirty: widget.isDirty, + children: children, + dataGridStateDetails: widget.dataGridStateDetails, + ); + } +} + +class _VirtualizingCellsWidget extends StatefulWidget { + const _VirtualizingCellsWidget( + {required Key key, + required this.dataRow, + required this.isDirty, + required this.dataGridStateDetails}) + : super(key: key); + + final DataRowBase dataRow; + final bool isDirty; + final DataGridStateDetails dataGridStateDetails; + + @override + State createState() => _VirtualizingCellsWidgetState(); +} + +class _VirtualizingCellsWidgetState extends State<_VirtualizingCellsWidget> { + @override + Widget build(BuildContext context) { + final List children = []; + + if (widget.dataRow.rowType == RowType.footerRow) { + if (widget.dataRow.footerView != null) { + children.add(widget.dataRow.footerView!); + } + } else { + // Need to allow only the visible columns to the layout. + final List visibleColumns = widget.dataRow.visibleColumns + .where((DataCellBase cell) => cell.isVisible && cell.columnIndex >= 0) + .map((DataCellBase cell) => cell.columnElement!) + .toList(growable: false); + children.addAll(visibleColumns); + } + + return VirtualizingCellsRenderObjectWidget( + key: widget.key!, + dataRow: widget.dataRow, + isDirty: widget.isDirty, + children: List.from(children), + dataGridStateDetails: widget.dataGridStateDetails, + ); + } +} + +class _HeaderCellsWidget extends _VirtualizingCellsWidget { + const _HeaderCellsWidget( + {required Key key, + required DataRowBase dataRow, + bool isDirty = false, + required DataGridStateDetails dataGridStateDetails}) + : super( + key: key, + dataRow: dataRow, + isDirty: isDirty, + dataGridStateDetails: dataGridStateDetails); +} + +/// ToDo +class VisualContainerHelper { + /// ToDo + VisualContainerHelper( + {required this.rowGenerator, required this.dataGridStateDetails}) { + isDirty = false; + isGridLoaded = false; + needToSetHorizontalOffset = false; + rowHeightsProvider = _onCreateRowHeights(); + columnWidthsProvider = _onCreateColumnWidths(); + } + + /// ToDo + bool isPreGenerator = false; + + /// ToDo + bool needToRefreshColumn = false; + + /// ToDo + int headerLineCount = 1; + + /// ToDo + late bool isDirty; + + /// ToDo + late bool isGridLoaded; + + /// Used to set the horizontal offset for LTR to RTL and vise versa, + late bool needToSetHorizontalOffset; + + /// ToDo + late RowGenerator rowGenerator; + + /// ToDo + late PaddedEditableLineSizeHostBase rowHeightsProvider; + + /// ToDo + late PaddedEditableLineSizeHostBase columnWidthsProvider; + + /// ToDo + _RowHeightManager rowHeightManager = _RowHeightManager(); + + /// ToDo + PaddedEditableLineSizeHostBase get rowHeights => rowHeightsProvider; + + /// ToDo + PaddedEditableLineSizeHostBase get columnWidths => columnWidthsProvider; + + /// ToDo + final DataGridStateDetails dataGridStateDetails; + + /// ToDo + ScrollAxisBase get scrollRows { + _scrollRows ??= + _createScrollAxis(true, verticalScrollBar, rowHeightsProvider); + _scrollRows!.name = 'ScrollRows'; + + return _scrollRows!; + } + + ScrollAxisBase? _scrollRows; + + set scrollRows(ScrollAxisBase newValue) => _scrollRows = newValue; + + /// ToDo + ScrollAxisBase get scrollColumns { + _scrollColumns ??= + _createScrollAxis(true, horizontalScrollBar, columnWidthsProvider); + _scrollColumns!.name = 'ScrollColumns'; + return _scrollColumns!; + } + + ScrollAxisBase? _scrollColumns; + + set scrollColumns(ScrollAxisBase newValue) => _scrollColumns = newValue; + + /// ToDo + ScrollBarBase get horizontalScrollBar => + _horizontalScrollBar ?? (_horizontalScrollBar = ScrollInfo()); + ScrollBarBase? _horizontalScrollBar; + + /// ToDo + ScrollBarBase get verticalScrollBar => + _verticalScrollBar ?? (_verticalScrollBar = ScrollInfo()); + ScrollBarBase? _verticalScrollBar; + + /// ToDo + int get rowCount => rowHeightsProvider.lineCount; + + set rowCount(int newValue) { + if (newValue > rowCount) { + _insertRows(rowCount, newValue - rowCount); + } else if (newValue < rowCount) { + _removeRows(newValue, rowCount - newValue); + } + } + + /// ToDo + int get columnCount => columnWidthsProvider.lineCount; + + set columnCount(int newValue) { + if (newValue > columnCount) { + _insertColumns(columnCount, newValue - columnCount); + } else if (newValue < columnCount) { + _removeColumns(newValue, columnCount - newValue); + } + } + + /// ToDo + int get frozenRows => rowHeightsProvider.headerLineCount; + + set frozenRows(int newValue) { + if (newValue < 0 || frozenRows == newValue) { + return; + } + + rowHeightsProvider.headerLineCount = newValue; + } + + /// ToDo + int get footerFrozenRows => rowHeightsProvider.footerLineCount; + + set footerFrozenRows(int newValue) { + if (newValue < 0 || footerFrozenRows == newValue) { + return; + } + + rowHeightsProvider.footerLineCount = newValue; + } + + /// ToDo + int get frozenColumns => columnWidthsProvider.headerLineCount; + + set frozenColumns(int newValue) { + if (newValue < 0 || frozenColumns == newValue) { + return; + } + + columnWidthsProvider.headerLineCount = newValue; + } + + /// ToDo + int get footerFrozenColumns => columnWidthsProvider.footerLineCount; + + set footerFrozenColumns(int newValue) { + if (newValue < 0 || footerFrozenColumns == newValue) { + return; + } + + columnWidthsProvider.footerLineCount = newValue; + } + + /// ToDo + double get horizontalOffset => + horizontalScrollBar.value - horizontalScrollBar.minimum; + + set horizontalOffset(double newValue) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (dataGridConfiguration.textDirection == TextDirection.ltr) { + horizontalScrollBar.value = newValue + horizontalScrollBar.minimum; + } else { + horizontalScrollBar.value = max(horizontalScrollBar.minimum, + horizontalScrollBar.maximum - horizontalScrollBar.largeChange) - + newValue; + } + updateHorizontalOffset( + dataGridConfiguration.controller, horizontalScrollBar.value); + + needToRefreshColumn = true; + } + + /// ToDo + double get verticalOffset => + verticalScrollBar.value - verticalScrollBar.minimum; + + set verticalOffset(double newValue) { + if (verticalScrollBar.value != (newValue + verticalScrollBar.minimum)) { + final DataGridConfiguration dataGridConfiguration = + dataGridStateDetails(); + verticalScrollBar.value = newValue + verticalScrollBar.minimum; + updateVerticalOffset( + dataGridConfiguration.controller, verticalScrollBar.value); + } + } + + /// ToDo + double get extentWidth { + final PixelScrollAxis _scrollColumns = scrollColumns as PixelScrollAxis; + return _scrollColumns.totalExtent; + } + + /// ToDo + double get extentHeight { + final PixelScrollAxis _scrollRows = scrollRows as PixelScrollAxis; + return _scrollRows.totalExtent; + } + + /// ToDo + void setRowHeights() { + if (dataGridStateDetails().onQueryRowHeight == null) { + return; + } + + final VisibleLinesCollection visibleRows = scrollRows.getVisibleLines(); + + int endIndex = 0; + + if (visibleRows.length <= visibleRows.firstBodyVisibleIndex) { + return; + } + + endIndex = visibleRows[visibleRows.lastBodyVisibleIndex].lineIndex; + + const int headerStart = 0; + + final int headerEnd = scrollRows.headerLineCount - 1; + rowHeightHelper(headerStart, headerEnd, RowRegion.header); + + rowHeightManager.updateRegion(headerStart, headerEnd, RowRegion.header); + + final int footerStart = + visibleRows.length > visibleRows.firstFooterVisibleIndex && + scrollRows.footerLineCount > 0 + ? visibleRows[visibleRows.firstFooterVisibleIndex].lineIndex + : -1; + final int footerEnd = + scrollRows.footerLineCount > 0 ? scrollRows.lineCount - 1 : -1; + + rowHeightHelper(footerStart, footerEnd, RowRegion.footer); + + rowHeightManager.updateRegion(footerStart, footerEnd, RowRegion.footer); + + final double bodyStart = + visibleRows[visibleRows.firstBodyVisibleIndex].origin; + + final double bodyEnd = + visibleRows[visibleRows.firstFooterVisibleIndex - 1].corner; + + final int bodyStartLineIndex = + visibleRows[visibleRows.firstBodyVisibleIndex].lineIndex; + + double current = bodyStart; + int currentEnd = endIndex; + + final LineSizeCollection lineSizeCollection = + rowHeights as LineSizeCollection; + lineSizeCollection.suspendUpdates(); + for (int index = bodyStartLineIndex; + current <= bodyEnd && index < scrollRows.firstFooterLineIndex; + index++) { + double height = rowHeights[index]; + + if (!rowHeightManager.contains(index, RowRegion.body) && + !grid_helper.isFooterWidgetRow(index, dataGridStateDetails())) { + final double rowHeight = rowGenerator.queryRowHeight(index, height); + if (rowHeight != height) { + height = rowHeight; + rowHeights.setRange(index, index, height); + } + } + + current += height; + currentEnd = index; + } + + rowHeightManager.updateRegion( + bodyStartLineIndex, currentEnd, RowRegion.body); + + if (rowHeightManager.dirtyRows.isNotEmpty) { + for (final int index in rowHeightManager.dirtyRows) { + if (index < 0 || index >= rowHeights.lineCount) { + continue; + } + + final double height = rowHeights[index]; + final double rowHeight = rowGenerator.queryRowHeight(index, height); + if (rowHeight != height) { + rowHeights.setRange(index, index, rowHeight); + } + } + + rowHeightManager.dirtyRows.clear(); + } + + lineSizeCollection.resumeUpdates(); + scrollRows.updateScrollBar(false); + } + + /// ToDo + void rowHeightHelper(int startIndex, int endIndex, RowRegion region) { + if (startIndex < 0 || endIndex < 0) { + return; + } + + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + for (int index = startIndex; index <= endIndex; index++) { + if (!rowHeightManager.contains(index, region)) { + final double height = dataGridConfiguration.container.rowHeights[index]; + final double rowHeight = rowGenerator.queryRowHeight(index, height); + if (rowHeight != height) { + rowHeights.setRange(index, index, rowHeight); + + if (region == RowRegion.header && + index == grid_helper.getHeaderIndex(dataGridConfiguration)) { + dataGridConfiguration.headerRowHeight = rowHeight; + } + } + } + } + } + + void _preGenerateItems() { + final VisibleLinesCollection visibleRows = scrollRows.getVisibleLines(); + final VisibleLinesCollection visibleColumns = + grid_helper.getVisibleLines(dataGridStateDetails()); + + if (visibleRows.isNotEmpty && visibleColumns.isNotEmpty) { + rowGenerator.preGenerateRows(visibleRows, visibleColumns); + isPreGenerator = true; + } + } + + PaddedEditableLineSizeHostBase _onCreateRowHeights() { + final LineSizeCollection lineSizeCollection = LineSizeCollection(); + return lineSizeCollection; + } + + PaddedEditableLineSizeHostBase _onCreateColumnWidths() { + final LineSizeCollection lineSizeCollection = LineSizeCollection(); + return lineSizeCollection; + } + + void _insertRows(int insertAtRowIndex, int count) { + rowHeightsProvider.insertLines(insertAtRowIndex, count, null); + } + + void _removeRows(int removeAtRowIndex, int count) { + rowHeightsProvider.removeLines(removeAtRowIndex, count, null); + } + + void _insertColumns(int insertAtColumnIndex, int count) { + columnWidthsProvider.insertLines(insertAtColumnIndex, count, null); + } + + void _removeColumns(int removeAtColumnIndex, int count) { + final LineSizeCollection lineSizeCollection = + columnWidths as LineSizeCollection; + lineSizeCollection.suspendUpdates(); + columnWidthsProvider.removeLines(removeAtColumnIndex, count, null); + lineSizeCollection.resumeUpdates(); + } + + /// ToDo + void updateScrollBars() { + scrollRows.updateScrollBar(false); + scrollColumns.updateScrollBar(false); + } + + /// ToDo + void updateAxis(Size availableSize) { + scrollRows.renderSize = availableSize.height; + scrollColumns.renderSize = availableSize.width; + } + + ScrollAxisBase _createScrollAxis( + bool isPixelScroll, ScrollBarBase scrollBar, LineSizeHostBase lineSizes) { + if (isPixelScroll) { + final Object _lineSizes = lineSizes; + if (lineSizes is DistancesHostBase) { + return PixelScrollAxis.fromPixelScrollAxis( + scrollBar, lineSizes, _lineSizes as DistancesHostBase); + } else { + return PixelScrollAxis.fromPixelScrollAxis(scrollBar, lineSizes, null); + } + } else { + return LineScrollAxis(scrollBar, lineSizes); + } + } + + /// ToDo + VisibleLineInfo? getRowVisibleLineInfo(int index) => + scrollRows.getVisibleLineAtLineIndex(index); + + /// ToDo + List getStartEndIndex(VisibleLinesCollection visibleLines, int region) { + int startIndex = 0; + int endIndex = -1; + switch (region) { + case 0: + if (visibleLines.firstBodyVisibleIndex > 0) { + startIndex = 0; + endIndex = + visibleLines[visibleLines.firstBodyVisibleIndex - 1].lineIndex; + } + break; + case 1: + if ((visibleLines.firstBodyVisibleIndex <= 0 && + visibleLines.lastBodyVisibleIndex < 0) || + visibleLines.length <= visibleLines.firstBodyVisibleIndex) { + return [startIndex, endIndex]; + } else { + startIndex = + visibleLines[visibleLines.firstBodyVisibleIndex].lineIndex; + endIndex = visibleLines[visibleLines.lastBodyVisibleIndex].lineIndex; + } + break; + case 2: + if (visibleLines.firstFooterVisibleIndex < visibleLines.length) { + startIndex = + visibleLines[visibleLines.firstFooterVisibleIndex].lineIndex; + endIndex = visibleLines[visibleLines.length - 1].lineIndex; + } + break; + } + + return [startIndex, endIndex]; + } + + /// ToDo + void refreshDefaultLineSize() { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + rowHeights.defaultLineSize = dataGridConfiguration.rowHeight; + columnWidths.defaultLineSize = dataGridConfiguration.defaultColumnWidth; + } + + /// ToDo + void refreshHeaderLineCount() { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + headerLineCount = 1; + if (dataGridConfiguration.stackedHeaderRows.isNotEmpty) { + headerLineCount += dataGridConfiguration.stackedHeaderRows.length; + } + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + headerLineCount += grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.top); + } + dataGridConfiguration.headerLineCount = headerLineCount; + } + + int _getFooterLineCount(DataGridConfiguration dataGridConfiguration) { + int footerLineCount = 0; + // Add footer row widget to the rows + if (dataGridConfiguration.footer != null) { + footerLineCount++; + } + if (dataGridConfiguration.tableSummaryRows.isNotEmpty) { + // Add bottom summary rows count + footerLineCount += grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + } + return footerLineCount; + } + + /// ToDo + void updateRowAndColumnCount() { + final LineSizeCollection lineSizeCollection = + columnWidths as LineSizeCollection; + lineSizeCollection.suspendUpdates(); + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + _updateColumnCount(dataGridConfiguration); + _updateRowCount(dataGridConfiguration); + if (rowCount > 0) { + for (int i = 0; + i <= grid_helper.getHeaderIndex(dataGridConfiguration); + i++) { + rowHeights[i] = dataGridConfiguration.headerRowHeight; + } + } + + //need to update the indent column width here + lineSizeCollection.resumeUpdates(); + updateScrollBars(); + _updateFreezePaneColumns(dataGridConfiguration); + + rowHeights.lineCount = rowCount; + columnWidths.lineCount = columnCount; + } + + void _updateColumnCount(DataGridConfiguration dataGridConfiguration) { + final int columnCount = dataGridConfiguration.columns.length; + this.columnCount = columnCount; + } + + void _updateRowCount(DataGridConfiguration dataGridConfiguration) { + final LineSizeCollection lineSizeCollection = + rowHeights as LineSizeCollection; + lineSizeCollection.suspendUpdates(); + int _rowCount = 0; + _rowCount = effectiveRows(dataGridConfiguration.source).isNotEmpty + ? effectiveRows(dataGridConfiguration.source).length + : 0; + _rowCount += dataGridConfiguration.headerLineCount; + + _rowCount += _getFooterLineCount(dataGridConfiguration); + + rowCount = _rowCount; + + // Sets footer row height + if (dataGridConfiguration.footer != null) { + rowHeights[grid_helper.getFooterViewRowIndex(dataGridConfiguration)] = + dataGridConfiguration.footerHeight; + } + + _updateFreezePaneRows(dataGridConfiguration); + // FLUT-2047 Need to mark all visible rows height as dirty when + // updating the row count if onQueryRowHeight is not null. + if (dataGridStateDetails().onQueryRowHeight != null) { + rowHeightManager.reset(); + } + //need to reset the hidden state + lineSizeCollection.resumeUpdates(); + //need to check again need to call the updateFreezePaneRows here + _updateFreezePaneRows(dataGridConfiguration); + } + + void _updateFreezePaneColumns(DataGridConfiguration dataGridConfiguration) { + final int frozenColumnCount = grid_helper.resolveToScrollColumnIndex( + dataGridConfiguration, dataGridConfiguration.frozenColumnsCount); + 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; + } + } + + void _updateFreezePaneRows(DataGridConfiguration dataGridConfiguration) { + final int frozenRowCount = grid_helper.resolveToRowIndex( + dataGridConfiguration, dataGridConfiguration.frozenRowsCount); + if (frozenRowCount > 0 && rowCount >= frozenRowCount) { + frozenRows = headerLineCount + dataGridConfiguration.frozenRowsCount; + } else { + frozenRows = headerLineCount; + } + + final int footerFrozenRowsCount = + dataGridConfiguration.footerFrozenRowsCount; + final int bottomTableSummariesCount = grid_helper.getTableSummaryCount( + dataGridConfiguration, GridTableSummaryRowPosition.bottom); + footerFrozenRows = 0; + if (footerFrozenRowsCount > 0 && + rowCount > frozenRows + footerFrozenRowsCount && + footerFrozenRowsCount < rowCount - frozenRowCount) { + footerFrozenRows = footerFrozenRowsCount; + } + + if (bottomTableSummariesCount > 0) { + footerFrozenRows += bottomTableSummariesCount; + } + } + + /// Helps to reset the [DataGridRow] on each [DataRow] to refresh the + /// [SfDataGrid] with editing and sorting is enabled. + /// + /// cause: + /// * Instead of setting -1 to each rows on editing to refresh. + void updateDataGridRows(DataGridConfiguration dataGridConfiguration) { + void resetRowIndex(DataRowBase dataRow) { + if (dataRow.rowType == RowType.dataRow) { + final int resolvedRowIndex = grid_helper.resolveToRecordIndex( + dataGridConfiguration, dataRow.rowIndex); + if (resolvedRowIndex.isNegative) { + return; + } + + dataRow.dataGridRow = + effectiveRows(dataGridConfiguration.source)[resolvedRowIndex]; + dataRow.dataGridRowAdapter = grid_helper.getDataGridRowAdapter( + dataGridConfiguration, dataRow.dataGridRow!); + dataRow.rowIndexChanged(); + } + } + + rowGenerator.items.forEach(resetRowIndex); + } + + /// ToDo + void refreshView({bool clearEditing = true}) { + void resetRowIndex(DataRowBase dataRow) { + if (!clearEditing && dataRow.isEditing) { + return; + } + dataRow.rowIndex = -1; + } + + rowGenerator.items.forEach(resetRowIndex); + } + + /// ToDo + void refreshViewStyle() { + void updateColumn(DataCellBase dataCell) { + dataCell + ..isDirty = true + ..updateColumn(); + } + + for (final DataRowBase dataRow in rowGenerator.items) { + dataRow + ..isDirty = true + ..visibleColumns.forEach(updateColumn); + } + } + + /// ToDo + void resetSwipeOffset({DataRowBase? swipedRow, bool canUpdate = false}) { + final DataGridConfiguration dataGridConfiguration = dataGridStateDetails(); + if (!dataGridConfiguration.allowSwiping) { + return; + } + + swipedRow = swipedRow ?? + dataGridConfiguration.rowGenerator.items + .firstWhereOrNull((DataRowBase row) => row.isSwipingRow); + + if (swipedRow != null) { + swipedRow.isSwipingRow = false; + } + + dataGridConfiguration.swipingOffset = 0.0; + dataGridConfiguration.isSwipingApplied = false; + + if (canUpdate) { + notifyDataGridPropertyChangeListeners(dataGridConfiguration.source, + propertyName: 'Swiping'); + } + } +} + +class _RowHeightManager { + _Range header = _Range(); + _Range body = _Range(); + _Range footer = _Range(); + List dirtyRows = []; + + bool contains(int index, RowRegion region) { + _Range range; + if (region == RowRegion.header) { + range = header; + } else if (region == RowRegion.body) { + range = body; + } else { + range = footer; + } + + if (range.isEmpty()) { + if (dirtyRows.contains(index)) { + dirtyRows.remove(index); + } + + return false; + } + + if (index >= range.start && index <= range.end) { + if (dirtyRows.contains(index)) { + dirtyRows.remove(index); + return false; + } + + return true; + } + + return false; + } + + void setDirty(int rowIndex) { + if (!dirtyRows.contains(rowIndex)) { + dirtyRows.add(rowIndex); + } + } + + _Range getRange(int index) { + if (index == 0) { + return header; + } else if (index == 1) { + return body; + } else { + return footer; + } + } + + void reset() { + header.start = + header.end = body.start = body.end = footer.start = footer.end = -1; + } + + void resetBody() { + body.start = body.end = -1; + } + + void resetFooter() { + footer.start = footer.end = -1; + } + + void updateBody(int index, int count) { + if ((index + count) <= body.start) { + resetBody(); + return; + } else if (index > body.end) { + return; + } else { + body.end = index; + } + } + + void updateRegion(int start, int end, RowRegion region) { + _Range range; + if (region == RowRegion.header) { + range = header; + } else if (region == RowRegion.body) { + range = body; + } else { + range = footer; + } + + range + ..start = start + ..end = end; + } +} + +class _Range { + _Range(); + + int start = -1; + int end = -1; + + bool isEmpty() => start < 0 || end < 0; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart index c88c83a61..cf5994182 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/datapager/sfdatapager.dart @@ -1,4 +1,11 @@ -part of datagrid; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + +import '../datagrid_widget/sfdatagrid.dart'; /// Signature for the [SfDataPager.pageItemBuilder] callback. typedef DataPagerItemBuilderCallback = Widget? Function(String text); @@ -18,61 +25,60 @@ bool _suspendDataPagerUpdate = false; /// A controller for [SfDataPager]. /// -/// The following code initializes the data source and controller. +///The following code initializes the data source and controller. /// /// ```dart /// PaginatedDataGridSource dataGridSource = PaginatedDataGridSource(); /// DataPagerController dataPagerController = DataPagerController(); -/// final List paginatedDataTable = []; +/// finalListpaginatedDataTable=[]; ///``` /// -/// The following code example shows how to  initialize the [SfDataPager] +///The following code example shows how to initialize the [SfDataPager] /// with [SfDataGrid] /// /// ```dart /// -///  @override -///  Widget build(BuildContext context) { -///    return Scaffold( -///      appBar: AppBar( -///        title: const Text('Syncfusion Flutter DataGrid'), -///      ), -///      body: Column( -///        children: [ -///          SfDataGrid( -///            source: _ dataGridSource, -///            columns: [ -///              GridNumericColumn(mappingName: 'id', headerText: 'ID'), -///              GridTextColumn(mappingName: 'name', headerText: 'Name'), -///              GridTextColumn(mappingName: 'designation', headerText: 'Designation'), -///              GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), -///            ], -///          ), -///          SfDataPager( -///            pageCount: paginatedDataTable.length / rowsPerPage, -///            visibleItemsCount: 5, -///            delegate: dataGridSource, -///     controller:dataPagerController -///          ), -///          TextButton( -///            child: Text('Next'), -///           onPressed: { -///              dataPagerController.nextPage(); -///            }, -///          ) -/// -///        ], -///      ), -///    ); -///  } +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar( +/// title: const Text('Syncfusion Flutter DataGrid'), +/// ), +/// body: Column( +/// children: [ +/// SfDataGrid( +/// source: _ dataGridSource, +/// columns: [ +/// GridNumericColumn(mappingName: 'id', headerText: 'ID'), +/// GridTextColumn(mappingName: 'name', headerText: 'Name'), +/// GridTextColumn(mappingName: 'designation', headerText: 'Designation'), +/// GridNumericColumn(mappingName: 'salary', headerText: 'Salary'), +/// ], +/// ), +/// SfDataPager( +/// pageCount: paginatedDataTable.length / rowsPerPage, +/// visibleItemsCount: 5, +/// delegate: dataGridSource, +/// controller:dataPagerController +/// ), +/// TextButton( +/// child:Text('Next'), +/// onPressed:{ +/// dataPagerController.nextPage(); +/// }, +/// ) +/// ], +/// ), +/// ); +/// } /// ``` /// -/// The following code example shows how to initialize the [DataGridSource] to +///The following code example shows how to initialize the [DataGridSource] to /// [SfDataGrid] /// /// ```dart -/// class  PaginatedDataGridSource extends DataGridSource { -/// final List _paginatedData= []; +/// class PaginatedDataGridSource extends DataGridSource { +/// finalList _paginatedData=[]; /// /// int StartRowIndex= 0, endRowIndex = 0, rowsPerPage = 20; /// @@ -95,16 +101,16 @@ bool _suspendDataPagerUpdate = false; /// }).toList()); /// } /// -///  @override -///  Future handlePageChange(int oldPageIndex, int newPageIndex) async { +/// @override +/// Future handlePageChange(int oldPageIndex, int newPageIndex) async { /// startRowIndex = newPageIndex * rowsPerPage; /// endRowIndex = startRowsIndex + rowsPerPage; -///    _paginatedData =  paginatedDataTable -///        .getRange(startRowIndex, endRowIndex) -///        .toList(growable: false); -///    notifyDataSourceListeners(); -///    return true; -///  } +/// _paginatedData = paginatedDataTable +/// .getRange(startRowIndex, endRowIndex) +/// .toList(growable: false); +/// notifyDataSourceListeners(); +/// return true; +/// } /// ///``` /// See also: @@ -144,42 +150,9 @@ class DataPagerController extends _DataPagerChangeNotifier { } } -/// A delegate that provides the row count details and method to listen the -/// page navigation in [SfDataPager]. -/// -/// The following code initializes the data source and controller. -/// -/// ```dart -/// final List paginatedDataTable = []; -/// ``` -/// -///  The following code example shows how to initialize the [DataPagerDelegate]. -/// -/// ```dart -/// class  PaginatedDataGridSource extends DataPagerDelegate{ -///  @override -///  Future handlePageChange(int oldPageIndex, int newPageIndex) async { -///    _paginatedData =  paginatedDataTable -///        .getRange(startRowIndex, endRowIndex ) -///        .toList(growable: false); -///    notifyListeners(); -///    return true; -///  } -/// } -/// ``` -class DataPagerDelegate { - /// Called when the page is navigated. - /// - /// Return true, if the navigation should be performed. Otherwise, return - /// false to disable the navigation for specific page index. - Future handlePageChange(int oldPageIndex, int newPageIndex) async { - return true; - } -} - /// A widget that provides the paging functionality. /// -/// A [SfDataPager] shows [pageCount] number of pages required to display. +/// A [SfDataPager] shows[pageCount] number of pages required to display. /// /// Data is read lazily from a [DataPagerDelegate]. The widget is presented as /// card. @@ -191,9 +164,9 @@ class DataPagerDelegate { /// /// ```dart /// PaginatedDataGridSource dataGridSource = PaginatedDataGridSource(); -/// final List paginatedDataTable = []; +/// finalListpaginatedDataTable=[]; /// ``` -/// The following code example shows how to  initialize the [SfDataPager] +///The following code example shows how to initialize the [SfDataPager] /// with [SfDataGrid] /// /// ```dart @@ -222,7 +195,7 @@ class DataPagerDelegate { /// } /// ``` /// -/// The following code example shows how to initialize the [DataGridSource] +///The following code example shows how to initialize the [DataGridSource] /// to [SfDataGrid] /// /// ```dart @@ -248,15 +221,15 @@ class DataPagerDelegate { /// } /// /// @override -///  Future handlePageChange(int oldPageIndex, int newPageIndex) async { +/// Future handlePageChange(int oldPageIndex, int newPageIndex) async { /// startRowIndex = newPageIndex * rowsPerPage; /// endRowIndex = startRowIndex + rowsPerPage; -///    _paginatedData =  paginatedDataTable -///        .getRange(startRowIndex, endRowIndex) -///        .toList(growable: false); -///    notifyDataSourceListeners(); -///    return true; -///  } +/// _paginatedData = paginatedDataTable +/// .getRange(startRowIndex, endRowIndex) +/// .toList(growable: false); +/// notifyDataSourceListeners(); +/// return true; +/// } /// } /// /// ``` @@ -270,6 +243,15 @@ class SfDataPager extends StatefulWidget { required this.delegate, Key? key, this.direction = Axis.horizontal, + this.itemWidth = 50.0, + this.itemHeight = 50.0, + this.itemPadding = const EdgeInsets.all(5), + this.navigationItemHeight = 50.0, + this.navigationItemWidth = 50.0, + this.firstPageItemVisible = true, + this.lastPageItemVisible = true, + this.nextPageItemVisible = true, + this.previousPageItemVisible = true, this.visibleItemsCount = 5, this.initialPageIndex = 0, this.pageItemBuilder, @@ -277,6 +259,12 @@ class SfDataPager extends StatefulWidget { this.onPageNavigationEnd, this.controller}) : assert(pageCount > 0), + assert(itemHeight > 0 && itemWidth > 0), + assert((firstPageItemVisible || + lastPageItemVisible || + nextPageItemVisible || + previousPageItemVisible) && + (navigationItemHeight > 0 && navigationItemWidth > 0)), super(key: key); /// The number of pages required to display in [SfDataPager]. @@ -289,6 +277,51 @@ class SfDataPager extends StatefulWidget { /// The maximum number of Items to show in view. final int visibleItemsCount; + /// The width of each item except the navigation items such as First, Last, Next and Previous page items. + /// + /// Defaults to 50.0 + final double itemWidth; + + /// The height of each item except the navigation items such as First, Last, Next and Previous page items. + /// + /// Defaults to 50.0 + final double itemHeight; + + /// The padding of each item including navigation items such as First, Last, Next and Previous page items. + /// + /// Defaults to EdgeInsets.all(5.0) + final EdgeInsetsGeometry itemPadding; + + /// Decides whether first page navigation item should be visible. + /// + /// Defaults to true. + final bool firstPageItemVisible; + + /// Decides whether last page navigation item should be visible. + /// + /// Defaults to true. + final bool lastPageItemVisible; + + /// Decides whether previous page navigation item should be visible. + /// + /// Defaults to true. + final bool previousPageItemVisible; + + /// Decides whether next page navigation item should be visible. + /// + /// Defaults to true. + final bool nextPageItemVisible; + + /// The height of navigation items such as First, Last, Next and Previous page items. + /// + /// Defaults to 50.0 + final double navigationItemHeight; + + /// The width of navigation items such as First, Last, Next and Previous page items. + /// + /// Defaults to 50.0 + final double navigationItemWidth; + /// Determines the direction of the data pager whether vertical or /// horizontal. /// @@ -353,11 +386,7 @@ class SfDataPager extends StatefulWidget { } class _SfDataPagerState extends State { - static const double _defaultPageItemWidth = 50.0; - static const double _defaultPageItemHeight = 50.0; - static const EdgeInsets _defaultPageItemPadding = EdgeInsets.all(5); - static const Size _defaultPagerDimension = - Size(300.0, _defaultPageItemHeight); + Size _defaultPagerDimension = Size.zero; static const Size _defaultPagerLabelDimension = Size(200.0, 50.0); static const double _kMobileViewWidthOnWeb = 767.0; @@ -418,6 +447,11 @@ class _SfDataPagerState extends State { @override void initState() { super.initState(); + + /// Set page count in DataGridSource. + _setPageCountInDataGridSource(widget.pageCount); + _defaultPagerDimension = Size(300.0, _getDefaultDimensionHeight()); + _scrollController = ScrollController() ..addListener(_handleScrollPositionChanged); _itemGenerator = _DataPagerItemGenerator(); @@ -469,8 +503,19 @@ class _SfDataPagerState extends State { } } + /// Helps to set or remove the page count in DataGridSource when SfDataPager with + /// SfDataGrid. + void _setPageCountInDataGridSource(double pageCount) { + if (widget.delegate is DataGridSource) { + setPageCount(widget.delegate, pageCount); + } + } + Future _handlePageItemTapped(int index) async { _suspendDataPagerUpdate = true; + if (index > widget.pageCount - 1) { + index -= 1; + } final bool canChange = await _canChangePage(index); if (canChange) { @@ -614,7 +659,7 @@ class _SfDataPagerState extends State { // ScrollController helpers double _getCumulativeSize(int index) { - return index * _getButtonSize(); + return index * _getButtonSize(widget.itemHeight, widget.itemWidth); } int _getNextPageIndex() { @@ -630,7 +675,8 @@ class _SfDataPagerState extends State { double getScrollOffset(int index) { final double origin = _getCumulativeSize(index); final double scrollOffset = _scrollController!.offset; - final double corner = origin + _getButtonSize(); + final double corner = + origin + _getButtonSize(widget.itemHeight, widget.itemWidth); final double currentViewSize = scrollOffset + _scrollViewPortSize; double offset = 0; if (corner > currentViewSize) { @@ -696,11 +742,29 @@ class _SfDataPagerState extends State { } } - double _getButtonSize() { + double _getButtonSize(double height, double width) { if (widget.direction == Axis.vertical) { - return _defaultPageItemHeight; + return height; } else { - return _defaultPageItemWidth; + return width; + } + } + + double _getDefaultDimensionHeight() { + if (widget.direction == Axis.horizontal) { + // In horizontal direction, If itemHeight > navigationItemHeight + // then the item page button height is greater than datapager height. + // So we have to set the larger height as dataPager height. + return widget.itemHeight > widget.navigationItemHeight + ? widget.itemHeight + : widget.navigationItemHeight; + } else { + // In vertical direction, If itemWidth > navigationItemWidth + // then the item page button width is greater than datapager height. + // So we have to set the larger height as dataPager height. + return widget.itemWidth > widget.navigationItemWidth + ? widget.itemWidth + : widget.navigationItemWidth; } } @@ -818,7 +882,8 @@ class _SfDataPagerState extends State { // Ensuring and Generating void _preGenerateItem(double width) { - _itemGenerator.preGenerateItems(0, width ~/ _getButtonSize()); + _itemGenerator.preGenerateItems( + 0, width ~/ _getButtonSize(widget.itemHeight, widget.itemWidth)); } void _ensureItems() { @@ -826,7 +891,8 @@ class _SfDataPagerState extends State { return; } - final double buttonSize = _getButtonSize(); + final double buttonSize = + _getButtonSize(widget.itemHeight, widget.itemWidth); final int startIndex = _scrollController!.offset <= _scrollController!.position.minScrollExtent ? 0 @@ -843,22 +909,49 @@ class _SfDataPagerState extends State { _itemGenerator._items.sort( (_ScrollableDataPagerItem first, _ScrollableDataPagerItem second) => first.index.compareTo(second.index)); + double getDifferenceInSize(double itemSize, double navigationItemSize) { + if (itemSize > navigationItemSize) { + return 0; + } else { + return (navigationItemSize - itemSize) / 2; + } + } for (final _ScrollableDataPagerItem element in _itemGenerator._items) { if (element.visible) { - final double buttonSize = _getButtonSize(); - final double xPos = widget.direction == Axis.horizontal + final double buttonSize = + _getButtonSize(widget.itemHeight, widget.itemWidth); + + double xPos = widget.direction == Axis.horizontal ? _isRTL ? _scrollViewExtent - (element.index * buttonSize) - buttonSize : element.index * buttonSize : 0.0; - final double yPos = widget.direction == Axis.vertical + // In vertical direction, If navigationItemWidth>itemWidth, + // the items will align from starting position in order of aligning at the center + // So we have to align the scrollable item at the center + if (widget.direction == Axis.vertical && + widget.itemWidth != widget.navigationItemWidth) { + xPos += + getDifferenceInSize(widget.itemWidth, widget.navigationItemWidth); + } + + double yPos = widget.direction == Axis.vertical ? element.index * buttonSize : 0.0; - element.elementRect = Rect.fromLTWH( - xPos, yPos, _defaultPageItemWidth, _defaultPageItemHeight); + // In horizontal direction, If navigationItemHeight>itemHeight, + // the items will align from starting position in order of aligning at the center + // So we have to align the scrollable item at the center + if (widget.direction == Axis.horizontal && + widget.itemHeight != widget.navigationItemHeight) { + yPos += getDifferenceInSize( + widget.itemHeight, widget.navigationItemHeight); + } + + element.elementRect = + Rect.fromLTWH(xPos, yPos, widget.itemWidth, widget.itemHeight); } } } @@ -867,7 +960,12 @@ class _SfDataPagerState extends State { // Item builder Widget _buildDataPagerItem( - {_ScrollableDataPagerItem? element, String? type, IconData? iconData}) { + {_ScrollableDataPagerItem? element, + String? type, + IconData? iconData, + double? height, + double? width, + EdgeInsetsGeometry? padding}) { final ThemeData _flutterTheme = Theme.of(context); Widget? pagerItem; Key? pagerItemKey; @@ -924,10 +1022,10 @@ class _SfDataPagerState extends State { return Container( key: pagerItemKey, - width: _defaultPageItemWidth, - height: _defaultPageItemHeight, + width: width, + height: height, child: Padding( - padding: _defaultPageItemPadding, + padding: padding!, child: CustomPaint( key: pagerItemKey, painter: _DataPagerItemBoxPainter( @@ -999,14 +1097,33 @@ class _SfDataPagerState extends State { } //FirstIcon - children.add(_buildDataPagerItem(type: 'First', iconData: getFirstIcon())); + + if (widget.firstPageItemVisible) { + children.add(_buildDataPagerItem( + type: 'First', + iconData: getFirstIcon(), + height: widget.navigationItemHeight, + width: widget.navigationItemWidth, + padding: widget.itemPadding)); + } //PreviousIcon - children.add( - _buildDataPagerItem(type: 'Previous', iconData: getPreviousIcon())); + + if (widget.previousPageItemVisible) { + children.add(_buildDataPagerItem( + type: 'Previous', + iconData: getPreviousIcon(), + height: widget.navigationItemHeight, + width: widget.navigationItemWidth, + padding: widget.itemPadding)); + } //Set headerExtent - _headerExtent = children.isEmpty ? 0.0 : children.length * _getButtonSize(); + _headerExtent = children.isEmpty + ? 0.0 + : children.length * + _getButtonSize( + widget.navigationItemHeight, widget.navigationItemWidth); return children.isEmpty ? null : _getChildrenBasedOnDirection(children); } @@ -1032,22 +1149,44 @@ class _SfDataPagerState extends State { } //NextIcon - children.add(_buildDataPagerItem(type: 'Next', iconData: getNextIcon())); + + if (widget.nextPageItemVisible) { + children.add(_buildDataPagerItem( + type: 'Next', + iconData: getNextIcon(), + height: widget.navigationItemHeight, + width: widget.navigationItemWidth, + padding: widget.itemPadding)); + } //LastIcon - children.add(_buildDataPagerItem(type: 'Last', iconData: getLastIcon())); + + if (widget.lastPageItemVisible) { + children.add(_buildDataPagerItem( + type: 'Last', + iconData: getLastIcon(), + height: widget.navigationItemHeight, + width: widget.navigationItemWidth, + padding: widget.itemPadding)); + } //Set footerExtent - _footerExtent = children.isEmpty ? 0.0 : children.length * _getButtonSize(); + _footerExtent = children.isEmpty + ? 0.0 + : children.length * + _getButtonSize( + widget.navigationItemHeight, widget.navigationItemWidth); return children.isEmpty ? null : _getChildrenBasedOnDirection(children); } // Body Widget _buildBody(BoxConstraints constraint) { + final int _oldPageCount = _pageCount; _pageCount = widget.pageCount.toInt(); - final double buttonSize = _getButtonSize(); + final double buttonSize = + _getButtonSize(widget.itemHeight, widget.itemWidth); _scrollViewExtent = buttonSize * _pageCount; final double size = _getSizeConstraint(constraint); @@ -1074,7 +1213,7 @@ class _SfDataPagerState extends State { // After we scrolled some distance on previous orientation. // Issue: https://github.com/flutter/flutter/issues/60288 // Need to remove once it fixed - _resetScrollOffset(preScrollViewPortSize); + _resetScrollOffset(preScrollViewPortSize, _oldPageCount); if (!_isPregenerateItems) { _preGenerateItem(_scrollViewPortSize); @@ -1094,6 +1233,8 @@ class _SfDataPagerState extends State { height: widget.direction == Axis.horizontal ? _defaultPagerDimension.height : _scrollViewPortSize, + clipBehavior: Clip.antiAlias, + decoration: const BoxDecoration(color: Colors.transparent), child: child); } @@ -1114,21 +1255,34 @@ class _SfDataPagerState extends State { key: element.key, isDirty: _isDirty, element: element, - child: _buildDataPagerItem(element: element), + child: _buildDataPagerItem( + element: element, + height: widget.itemHeight, + width: widget.itemWidth, + padding: widget.itemPadding), )) .toList(growable: false)), ), ); } - void _resetScrollOffset(double previousScrollViewPortSize) { + void _resetScrollOffset( + double previousScrollViewPortSize, int _oldPageCount) { if (_isPregenerateItems && - _isOrientationChanged && _scrollController!.hasClients && _scrollController!.offset > 0.0 && - _scrollViewPortSize > previousScrollViewPortSize) { + ((_isOrientationChanged && + _scrollViewPortSize > previousScrollViewPortSize) || + _oldPageCount != _pageCount)) { WidgetsBinding.instance?.addPostFrameCallback((_) { _handleScrollPositionChanged(); + if (_oldPageCount != _pageCount) { + if (_currentPageIndex > _pageCount - 1) { + _canChangePage(_pageCount - 1); + } else { + _canChangePage(0); + } + } }); } else { _isOrientationChanged = false; @@ -1149,7 +1303,11 @@ class _SfDataPagerState extends State { } final Widget body = _buildBody(constraint); - children.insert(1, body); + if (header == null) { + children.insert(0, body); + } else { + children.insert(1, body); + } return _getChildrenBasedOnDirection(children); } @@ -1249,8 +1407,16 @@ class _SfDataPagerState extends State { isDelegateChanged || oldWidget.pageCount != widget.pageCount || oldWidget.direction != widget.direction || + oldWidget.navigationItemHeight != widget.navigationItemHeight || + oldWidget.navigationItemWidth != widget.navigationItemWidth || + oldWidget.itemWidth != widget.itemWidth || + oldWidget.itemHeight != widget.itemHeight || oldWidget.visibleItemsCount != widget.visibleItemsCount || oldWidget.initialPageIndex != widget.initialPageIndex) { + /// Set page count in DataGridSource. + _setPageCountInDataGridSource(widget.pageCount); + _defaultPagerDimension = Size(300.0, _getDefaultDimensionHeight()); + if (isDelegateChanged) { _removeDelegateListener(oldWidget); _addDelegateListener(); @@ -1318,6 +1484,8 @@ class _SfDataPagerState extends State { ..dispose(); } + /// Helps to remove the pageCount in DataGridSource. + _setPageCountInDataGridSource(0.0); _controller = null; _imageConfiguration = null; _removeDelegateListener(widget); diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart deleted file mode 100644 index 32aeade29..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table.dart +++ /dev/null @@ -1,2295 +0,0 @@ -part of datagrid; - -/// Used by Tree Table to balance the tree with algorithm based -/// on Red - Black tree. -enum _TreeTableNodeColor { - /// TreeTableNodeColoe.red, will represented the Red color. - red, - - /// TreeTableNodeColoe.black, will represented the Black color. - black -} - -/// A branch or leaf in the tree. -abstract class _TreeTableNodeBase { - /// Gets the color to the branch. - _TreeTableNodeColor? get color => _color; - _TreeTableNodeColor? _color; - - /// Sets the color to the branch. - set color(_TreeTableNodeColor? value) { - if (value == _color) { - return; - } - - _color = value; - } - - /// Gets the parent branch. - _TreeTableBranchBase? get parent => _parent; - _TreeTableBranchBase? _parent; - - /// Sets the parent color. - set parent(_TreeTableBranchBase? value) { - if (value == _parent) { - return; - } - - _parent = value; - } - - /// Walk up parent branches and reset counters. - /// - /// * notifyParentRecordSource - _required_ - boolean value - void invalidateCounterBottomUp(bool notifyParentRecordSource); - - /// Walk up parent branches and reset summaries. - /// - /// * notifyParentRecordSource - _required_ - Boolean value - void invalidateSummariesBottomUp(bool notifyParentRecordSource); - - /// Indicates whether leaf is empty. - /// - /// Returns the boolean value indicates whether the leaf is empty - bool isEmpty(); - - /// Indicates whether this is a leaf. - /// - /// Returns the boolean value indicates whether this is a leaf - bool isEntry(); - - ///Gets the number of child nodes (+1 for the current node). - /// - /// Returns the number of child nodes (+1 for the current node). - int getCount(); - - /// Gets the minimum value (of the leftmost leaf) of the branch in - /// a sorted tree. - /// - /// Returns the minimum value (of the leftmost leaf) of the branch in - /// a sorted tree. - Object? getMinimum(); - - /// Gets the position in the tree. - /// - /// Returns the position in the tree. - int getPosition(); - - /// Gets the tree level of this node. - /// - /// Returns Returns the tree level of this node. - int getLevel(); -} - -/// A branch with left and right leaves or branches. -mixin _TreeTableBranchBase on _TreeTableNodeBase { - /// Gets the left node. - _TreeTableNodeBase? get left => _left; - _TreeTableNodeBase? _left; - - /// Sets the left node. - set left(_TreeTableNodeBase? value) { - if (value == _left) { - return; - } - - _left = value; - } - - /// Gets the right node. - _TreeTableNodeBase? get right => _right; - _TreeTableNodeBase? _right; - - /// Sets the right node. - set right(_TreeTableNodeBase? value) { - if (value == _right) { - return; - } - - _right = value; - } - - /// Sets this object's child node Count dirty and - /// marks parent nodes' child node Count dirty. - void invalidateCountBottomUp(); - - /// Sets this object's child node Count dirty and steps - /// through all child branches and marks their child node Count dirty. - void invalidateCountTopDown(); - - /// Sets this object's child node Minimum dirty and - /// marks parent nodes' child node Minimum dirty. - void invalidateMinimumBottomUp(); - - /// Sets this object's child node Minimum dirty and steps - /// through all child branches and marks their child node Minimum dirty. - void invalidateMinimumTopDown(); - - /// The left branch cast to _TreeTableBranchBase. - /// - /// Returns the left branch cast to _TreeTableBranchBase. - _TreeTableBranchBase? getLeftBranch(); - - /// The right branch cast to _TreeTableBranchBase. - /// - /// Returns the right branch cast to _TreeTableBranchBase. - _TreeTableBranchBase? getRightBranch(); - - /// Returns the position in the tree table of the specified child node. - /// - /// * node - _required_ - Tree node - /// - /// Returns the position in the tree. - int getEntryPositionOfChild(_TreeTableNodeBase node); - - /// Sets the left node. - /// - /// Call this method instead of simply setting `Left` property if you want - /// to avoid the round-trip call to check whether the tree is in add-mode - /// or if tree-table is sorted. - /// - /// * value - _required_ - The new node. - /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. - /// * isSortedTree - _required_ - Indicates whether tree-table is sorted. - void setLeft(_TreeTableNodeBase? value, bool inAddMode, bool isSortedTree); - - /// Sets the right node. - /// - /// Call this method instead of simply setting `Right` property if you want - /// to avoid the round-trip call to check whether the tree is in add-mode - /// or if tree-table is sorted. - /// - /// * value - _required_ - The new node. - /// * inAddMode - _required_ - Specifies if tree-table is in add-mode. - void setRight(_TreeTableNodeBase? value, bool inAddMode); -} - -/// A leaf with value and optional sort key. -mixin _TreeTableEntryBase on _TreeTableNodeBase { - /// Gets the value attached to this leaf. - Object? get value => _value; - Object? _value; - - /// Sets the value attached to this leaf - set value(Object? value) { - if (value == _value) { - return; - } - - _value = value; - } - - /// Creates a branch that can hold this entry when new leaves are - /// inserted into the tree. - /// - /// * tree - _required_ - Tree table instance - /// - /// Returns the instance of newly created branch - _TreeTableBranchBase? createBranch(_TreeTable tree); - - /// Gets the sort key of this leaf. - /// - /// Returns the sort key of this leaf. - Object? getSortKey(); -} - -/// A branch or leaf in the tree. -abstract class _TreeTableNode extends _TreeTableNodeBase { - static Object emptyMin = Object(); - - /// Gets the tree this node belongs to. - _TreeTable? get tree => _tree; - _TreeTable? _tree; - - /// Sets the tree this node belongs to. - set tree(_TreeTable? value) { - if (value == _tree) { - return; - } - - _tree = value; - } - - /// Gets the parent branch. - @override - _TreeTableBranchBase? get parent => _parent; - - /// Sets the parent branch. - @override - set parent(_TreeTableBranchBase? value) { - _parent = value; - } - - /// Walks up parent branches and reset counters. - /// - /// * notifyParentRecordSource - _required_ - Boolean value - @override - void invalidateCounterBottomUp(bool notifyParentRecordSource) {} - - /// Walks up parent branches and reset summaries. - /// - /// * notifyParentRecordSource - _required_ - Boolean value - @override - void invalidateSummariesBottomUp(bool notifyParentRecordSource) {} - - /// Indicates whether leaf is empty. - /// - /// Returns the boolean value indicates whether the leaf is empty - @override - bool isEmpty() => getCount() == 0; - - /// Indicates whether this is a leaf. - /// - /// Returns the boolean value indicates whether this is a leaf - @override - bool isEntry(); - - /// Gets the minimum value (of the most-left leaf) of the branch - /// in a sorted tree. - /// - /// Returns the minimum value (of the most-left leaf) of the branch - /// in a sorted tree. - @override - Object? getMinimum() => emptyMin; - - /// Gets the number of child nodes (+1 for the current node). - /// - /// Returns the number of child nodes (+1 for the current node). - @override - int getCount(); - - /// Gets the tree level of this node. - /// - /// Returns the tree level of this node. - @override - int getLevel() { - int level = 0; - if (parent != null) { - level = parent!.getLevel() + 1; - } - - return level; - } - - /// Gets the Debug / text information about the node. - /// - /// Returns the Debug / text information about the node. - String getNodeInfoBase() { - String side = '_'; - if (parent != null) { - side = _MathHelper.referenceEquals(parent!.left, this) ? 'L' : 'R'; - } - - return '${getLevel()} $side this., ${getPosition()} , ${getCount()}'; - } - - /// Gets the position in the tree. - /// - /// Returns the position in the tree. - @override - int getPosition() { - if (parent == null) { - return 0; - } - - return parent!.getEntryPositionOfChild(this); - } - - /// Gets the Debug / text information about the node. - /// - /// Returns the Debug / text information about the node. - @override - String toString() => '$runtimeType ${getNodeInfoBase()}'; -} - -/// A branch in a tree. -class _TreeTableBranch extends _TreeTableNode with _TreeTableBranchBase { - /// Initializes a new instance of the `TreeTableBranch` class. - /// - /// * tree - _required_ - Tree table instance - _TreeTableBranch(_TreeTable tree) { - this.tree = tree; - } - - int entryCount = -1; - Object? _minimum = _TreeTableNode.emptyMin; - - /// Gets the right tree or branch. - @override - _TreeTableNodeBase? get right => _right; - - /// Sets the right tree or branch. - @override - set right(_TreeTableNodeBase? value) { - setRight(value, false); - } - - /// Gets the left leaf or branch. - @override - _TreeTableNodeBase? get left => _left; - - /// Sets the left leaf or branch. - @override - set left(_TreeTableNodeBase? value) { - setLeft(value, false, tree!.sorted); - } - - /// Indicates whether this is a leaf. - /// - /// Returns boolean value - @override - bool isEntry() => false; - - /// Sets this object's child node count dirty and - /// walks up parent nodes and marks their child node count dirty. - @override - void invalidateCountBottomUp() { - entryCount = -1; - if (parent != null && parent!.parent == parent) { - throw Exception(); - } - - if (parent != null) { - parent!.invalidateCountBottomUp(); - } - } - - /// Sets this object's child node count dirty and steps - /// through all child branches and marks their child node count dirty. - @override - void invalidateCountTopDown() { - entryCount = -1; - if (left != null && !left!.isEntry()) { - getLeftBranch()?.invalidateCountTopDown(); - } - - if (right != null && !right!.isEntry()) { - getRightBranch()?.invalidateCountTopDown(); - } - } - - /// Sets this object's child node minimum dirty and - /// marks parent nodes' child node minimum dirty. - @override - void invalidateMinimumBottomUp() { - _minimum = _TreeTableNode.emptyMin; - if (parent != null) { - parent!.invalidateMinimumBottomUp(); - } - } - - /// Sets this object's child node minimum dirty and steps - /// through all child branches and marks their child node minimum dirty. - @override - void invalidateMinimumTopDown() { - if (left != null && !left!.isEntry()) { - getLeftBranch()?.invalidateMinimumTopDown(); - } - if (right != null && !right!.isEntry()) { - getRightBranch()?.invalidateMinimumTopDown(); - } - _minimum = _TreeTableNode.emptyMin; - } - - /// Gets the number of child nodes (+1 for the current node). - /// - /// Returns the number of child nodes (+1 for the current node). - @override - int getCount() { - if (entryCount < 0 && _left != null && _right != null) { - entryCount = _left!.getCount() + _right!.getCount(); - } - - return entryCount; - } - - /// Gets the position in the tree table of the specific child node. - /// - /// * node - _required_ - Tree node - /// - /// Returns the position in the tree table of the specific child node. - @override - int getEntryPositionOfChild(_TreeTableNodeBase node) { - int pos = getPosition(); - if (_MathHelper.referenceEquals(node, right)) { - if (left != null) { - pos += left!.getCount(); - } - } else if (!_MathHelper.referenceEquals(node, left)) { - //throw ArgumentError('must be a child node','node'); - throw ArgumentError('must be a child node'); - } - - return pos; - } - - /// The left node cast to _TreeTableBranchBase. - /// - /// Returns the left node cast to _TreeTableBranchBase. - @override - _TreeTableBranchBase? getLeftBranch() { - if (_left is _TreeTableBranchBase) { - return _left! as _TreeTableBranchBase; - } else { - return null; - } - } - - /// Gets the minimum value (of the most-left leaf) of the branch - /// in a sorted tree. - /// - /// Returns the minimum value (of the most-left leaf) of the branch - /// in a sorted tree. - @override - Object? getMinimum() { - if (_MathHelper.referenceEquals(_TreeTableNode.emptyMin, _minimum)) { - _minimum = _left!.getMinimum(); - } - return _minimum; - } - - /// The right node cast to _TreeTableBranchBase. - /// - /// Returns the right node cast to _TreeTableBranchBase. - @override - _TreeTableBranchBase? getRightBranch() { - if (_right is _TreeTableBranchBase) { - return _right! as _TreeTableBranchBase; - } else { - return null; - } - } - - /// Sets the left node. - /// - /// Call this method instead of simply setting `Left` property if you want - /// to avoid the round-trip call to check whether the tree is in add-mode - /// or if tree-table is sorted. - /// - /// * value - _required_ - The new node. - /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. - /// * isSorted - _required_ - Indicates whether tree-table is sorted. - @override - void setLeft(_TreeTableNodeBase? value, bool inAddMode, bool isSorted) { - if (!_MathHelper.referenceEquals(left, value)) { - if (inAddMode) { - if (_left != null && _left!.parent == this) { - _left!.parent = null; - } - - _left = value; - if (_left != null) { - _left!.parent = this; - } - } else { - final int lc = (_left != null) ? _left!.getCount() : 0; - final int vc = (value != null) ? value.getCount() : 0; - final int entryCountDelta = vc - lc; - if (_left != null && _left!.parent == this) { - _left!.parent = null; - } - - _left = value; - if (_left != null) { - _left!.parent = this; - } - - if (entryCountDelta != 0) { - invalidateCountBottomUp(); - } - - if (isSorted) { - invalidateMinimumBottomUp(); - } - - invalidateCounterBottomUp(false); - invalidateSummariesBottomUp(false); - } - } - } - - /// Sets the right node. - /// - /// Call this method instead of simply setting `Right` property if you want - /// to avoid the round-trip call to check whether the tree is in add-mode - /// or if tree-table is sorted. - /// - /// * value - _required_ - The new node. - /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. - @override - void setRight(_TreeTableNodeBase? value, bool inAddMode) { - if (!_MathHelper.referenceEquals(right, value)) { - if (inAddMode) { - if (_right != null && _right!.parent == this) { - _right!.parent = null; - } - - _right = value; - if (_right != null) { - _right!.parent = this; - } - } else { - final int lc = (_right != null) ? _right!.getCount() : 0; - final int vc = (value != null) ? value.getCount() : 0; - final int entryCountDelta = vc - lc; - if (_right != null && _right!.parent == this) { - _right!.parent = null; - } - - _right = value; - if (_right != null) { - _right!.parent = this; - } - - if (entryCountDelta != 0) { - invalidateCountBottomUp(); - } - - invalidateCounterBottomUp(false); - invalidateSummariesBottomUp(false); - } - } - } -} - -/// A leaf in the tree with value and optional sort key. -class _TreeTableEntry extends _TreeTableNode with _TreeTableEntryBase { - /// Gets the Debug / text information about the node. - /// - /// Returns the Debug / text information about the node. - String getNodeInfo() => - '${getNodeInfoBase()} ${value != null ? value.toString() : 'null'}'; - - /// Indicates whether this is a leaf. - /// - /// Returns the boolean value - @override - bool isEntry() => true; - - /// Creates a branch that can hold this entry when new leaves are inserted - /// into the tree. - /// - /// * tree - _required_ - Tree table instance - /// - /// Returns the instance of newly created branch - - @override - _TreeTableBranchBase? createBranch(_TreeTable tree) => _TreeTableBranch(tree); - - /// Gets the number of child nodes (+1 for the current node). - /// - /// Returns the number of child nodes (+1 for the current node). - @override - int getCount() => 1; - - /// Gets the minimum value (of the most-left leaf) of the branch - /// in a sorted tree. - /// - /// Returns the minimum value (of the most-left leaf) of the branch - /// in a sorted tree. - @override - Object? getMinimum() => getSortKey(); - - /// Gets the sort key of this leaf. - /// - /// Returns the sort key of this leaf. - @override - Object? getSortKey() => value; -} - -/// An empty node. -class _TreeTableEmpty extends _TreeTableNode { - static _TreeTableEmpty empty = _TreeTableEmpty(); - - /// The Debug / text information about the node. - /// - /// Returns the Debug / text information about the node. - String getNodeInfo() => 'Empty'; - - /// The number of child nodes (+1 for the current node). - /// - /// Returns the number of child nodes (+1 for the current node). - @override - int getCount() => 0; - - /// Indicates whether this is a leaf. - /// - /// Returns boolean value - @override - bool isEntry() => true; -} - -/// Tree table interface definition. -class _TreeTableBase extends _ListBase { - _TreeTableBase() { - _isInitializing = false; - } - - /// Gets the comparer value used by sorted trees. - Comparable? get comparer => _comparer; - Comparable? _comparer; - - /// Sets the comparer value used by sorted trees. - set comparer(Comparable? value) { - if (value == _comparer) { - return; - } - - _comparer = value; - } - - _TreeTableNodeBase? get root => _root; - _TreeTableNodeBase? _root; - - /// Gets a value indicating whether this is a sorted tree or not. - bool get sorted => _sorted; - bool _sorted = false; - - /// Gets the root node. - - /// Gets a value indicating whether the tree was initialize or not. - bool get isInitializing => _isInitializing; - late bool _isInitializing; - - /// Optimizes insertion of many elements when tree is initialized - /// for the first time. - void beginInit(); - - /// Ends optimization of insertion of elements when tree is initialized - /// for the first time. - void endInit(); - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - Current item - /// - /// Returns next subsequent entry - _TreeTableEntryBase? getNextEntry(_TreeTableEntryBase current); - - /// Optimized access to a previous entry. - /// - /// * current - _required_ - Current item - /// - /// Returns previous entry - _TreeTableEntryBase? getPreviousEntry(_TreeTableEntryBase current); -} - -/// A tree table. -class _TreeTable extends _TreeTableBase { - /// Initializes a new instance of the `TreeTable` class. - /// - /// * sorted - _required_ - Boolean value - _TreeTable(bool sorted) { - _sorted = sorted; - } - - bool _inAddMode = false; - _TreeTableBranchBase? _lastAddBranch; - _TreeTableEntryBase? _lastFoundEntry; - Object? _lastFoundEntryKey; - bool _lastFoundEntryHighestSmallerValue = false; - int lastIndex = -1; - Object? tag; - - /// Gets the last index leaf. - /// - /// Returns the last index leaf. - _TreeTableEntryBase? get lastIndexLeaf => _lastIndexLeaf; - _TreeTableEntryBase? _lastIndexLeaf; - - /// Sets the last index leaf. - /// - /// Returns the last index leaf. - set lastIndexLeaf(_TreeTableEntryBase? value) { - if (_lastIndexLeaf != value) { - _lastIndexLeaf = value; - } - } - - /// Gets the comparer used by sorted trees. - @override - Comparable? get comparer => _comparer; - - /// Sets the comparer used by sorted trees. - @override - set comparer(Comparable? value) { - _comparer = value; - _sorted = _comparer != null; - } - - /// Gets the number of leaves. - @override - int get count => countBase; - - /// Gets the number of leaves. - int get countBase => getCount(); - - /// Gets a value indicating whether the tree was initialize or not. - @override - bool get isInitializing => _inAddMode; - - /// Gets a value indicating whether the tree is Read-only or not. - @override - bool get isReadOnly => false; - - /// Gets a value indicating whether the nodes can be added or removed. - @override - bool get isFixedSize => false; - - /// Gets a value indicating whether the tree is Synchronized or not. - @override - bool get isSynchronized => false; - - /// Gets a value indicating whether tree is sorted or not. - @override - bool get sorted => _sorted; - - /// Gets the root node. - @override - _TreeTableNodeBase? get root => _root; - - /// Gets an object that can be used to synchronize access to - /// the `ICollection`. - /// - /// Returns - _required_ - an object that can be used to synchronize access - /// to the `ICollection`. - @override - Object? get syncRoot => null; - - /// Appends a node. - /// - /// * value - _required_ - Node value to append. - /// - /// Returns the zero-based collection index at which the value has been added. - int addBase(_TreeTableNodeBase value) { - cacheLastFoundEntry(null, null, false); - lastIndex = -1; - - if (sorted && !_inAddMode) { - return addSorted(value); - } - - if (_root == null) { - // replace root - _root = value; - return 0; - } else { - // add node to most right branch - _TreeTableBranchBase? branch; - _TreeTableNodeBase? current = _lastAddBranch ?? _root; - - while (current != null && !current.isEntry()) { - branch = current as _TreeTableBranchBase; - current = branch.right; - } - - final _TreeTableEntryBase leaf = current! as _TreeTableEntryBase; - - final _TreeTableBranchBase? newBranch = leaf.createBranch(this); - if (newBranch != null) { - newBranch - ..setLeft(leaf, _inAddMode, sorted) - // will set leaf.Parent ... - ..setRight(value, _inAddMode); - } - - if (branch == null) { - _root = newBranch; - } else { - // swap out leafs parent with new node - _replaceNode(branch, current, newBranch, _inAddMode); - if (!(branch.parent == null || - branch.parent?.parent == null || - branch.right != branch.parent?.parent?.right)) { - throw Exception(); - } - - final Object? _left = branch.parent?.left; - if (!(branch.parent == null || - (branch.parent != null && - branch.parent!.left != null && - branch.parent!.left!.isEntry()) || - (_left is _TreeTableBranch && _left.right != branch))) { - throw Exception(); - } - } - - insertFixup(newBranch!, _inAddMode); - - if (value.parent != null && value.parent?.parent != null) { - if (value.parent!.parent?.right == value) { - throw Exception(); - } - } - - _lastAddBranch = newBranch; - - if (_inAddMode) { - return -1; - } else { - return _root!.getCount() - 1; - } - } - } - - /// Adds a node in a sorted tree only if no node with the same value has - /// not been added yet. - /// - /// * key - _required_ - Key needs to be add in the collection - /// * value - _required_ - Node value to add. - /// - /// Returns the instance for the tree - _TreeTableEntryBase? addIfNotExists( - Comparable? key, _TreeTableEntryBase? value) { - if (!sorted) { - throw Exception('This tree is not sorted.'); - } - - cacheLastFoundEntry(null, null, false); - - if (_root == null) { - // replace root - _root = value; - return value; - } else { - // find node - _TreeTableBranchBase? branch; - _TreeTableNodeBase? current = _root!; - int cmp = 0; - final Comparable? comparer = this.comparer; - const bool inAddMode = false; - Comparable? comparableKey = key!; - while (current != null && !current.isEntry()) { - branch = current as _TreeTableBranchBase; - if (comparer != null) { - final _TreeTableNodeBase? tableNodeBase = branch.right; - if (tableNodeBase != null) { - final Object? value = tableNodeBase.getMinimum(); - cmp = key.compareTo(value); - } - } else if (comparableKey is Comparable) { - cmp = comparableKey.compareTo(branch.right?.getMinimum()); - } else { - throw Exception('No Comparer specified.'); - } - - if (cmp == 0) { - current = branch.right; - while (current != null && !current.isEntry()) { - final _TreeTableBranchBase _current = - current as _TreeTableBranchBase; - current = _current.left; - } - - return current! as _TreeTableEntryBase; - } else if (cmp < 0) { - current = branch.left; - } else { - current = branch.right; - } - } - - final _TreeTableEntryBase leaf = current! as _TreeTableEntryBase; - - if (comparer != null) { - cmp = key.compareTo(leaf.getSortKey()); - } else if (value!.getMinimum() is Comparable) { - cmp = comparableKey.compareTo(leaf.getSortKey()); - } - - comparableKey = null; - - if (cmp == 0) { - return leaf; - } - - final _TreeTableBranchBase? newBranch = leaf.createBranch(this); - - if (newBranch != null && cmp < 0) { - newBranch - ..setLeft(value, false, sorted) // will set leaf.Parent ... - ..right = leaf; - } else if (newBranch != null && cmp > 0) { - newBranch - ..setLeft(leaf, false, sorted) // will set leaf.Parent ... - ..right = value; - } - - if (branch == null) { - _root = newBranch; - } else { - _replaceNode(branch, leaf, newBranch, false); - } - - insertFixup(newBranch, inAddMode); - return value; - } - } - - /// Adds a node into a sorted tree. - /// - /// * value - _required_ - Node value to add. - /// - /// Returns the zero-based collection index at which the value has been added. - int addSorted(_TreeTableNodeBase value) { - if (!sorted) { - throw const FormatException('This tree is not sorted.'); - } - - if (_inAddMode) { - return add(value); - } - - cacheLastFoundEntry(null, null, false); - - if (_root == null) { - // replace root - _root = value; - return 0; - } else { - const bool inAddMode = false; - final Comparable? comparer = this.comparer; - - // find node - _TreeTableBranchBase? branch; - _TreeTableNodeBase? current = _root; - int count = 0; - int? cmp = 0; - - while (current != null && !current.isEntry()) { - branch = current as _TreeTableBranchBase; - if (comparer != null) { - final dynamic minimum = value.getMinimum(); - final dynamic right = branch.right!.getMinimum(); - cmp = Comparable.compare(minimum, right); - } else if (value.getMinimum() is Comparable) { - final Object? _minimum = value.getMinimum(); - if (_minimum != null && _minimum is Comparable) { - cmp = _minimum.compareTo(branch.right!.getMinimum()); - } else { - cmp = null; - } - } else { - throw Exception('No Comparer Specified'); - } - - if (cmp != null && cmp <= 0) { - current = branch.left; - } else { - count += branch.left!.getCount(); - current = branch.right; - } - } - - final _TreeTableEntryBase leaf = current! as _TreeTableEntryBase; - if (leaf is _TreeTableEntryBase) {} - - final _TreeTableBranchBase? newBranch = leaf.createBranch(this); - - if (comparer != null) { - final Object? minimum = value.getMinimum(); - final Object? sortKey = leaf.getSortKey(); - if (minimum is Comparable && sortKey is Comparable) { - cmp = Comparable.compare(minimum, sortKey); - } - } else if (value.getMinimum() is Comparable) { - final Object? _minimum = value.getMinimum(); - if (_minimum != null && _minimum is Comparable) { - cmp = _minimum.compareTo(leaf.getSortKey()); - } else { - cmp = null; - } - } - - if (newBranch != null && cmp != null && cmp <= 0) { - newBranch - ..setLeft(value, false, sorted) // will set leaf.Parent ... - ..right = leaf; - } else { - if (newBranch != null) { - newBranch.setLeft(leaf, false, sorted); // will set leaf.Parent ... - count++; - newBranch.right = value; - } - } - - if (branch == null) { - _root = newBranch; - } else { - // swap out leafs parent with new node - _replaceNode(branch, leaf, newBranch, inAddMode); - } - - //Debug.Assert(value.Position == index); - insertFixup(newBranch, inAddMode); - return count; - } - } - - _TreeTableEntryBase? cacheLastFoundEntry( - _TreeTableEntryBase? entry, Object? key, bool highestSmallerValue) { - lastIndex = -1; - _lastFoundEntry = entry; - _lastFoundEntryKey = key; - _lastFoundEntryHighestSmallerValue = highestSmallerValue; - return _lastFoundEntry; - } - - /// Indicates whether the node belongs to this tree. - /// - /// * value - _required_ - Node value to search for. - /// - /// Returns true if node belongs to this tree; false otherwise. - bool containsBase(_TreeTableNodeBase? value) { - if (value == null || _root == null) { - return false; - } - - // search root - while (value!.parent != null) { - value = value.parent!; - } - - return _MathHelper.referenceEquals(value, _root); - } - - /// Copies the elements from this collection into an array. - /// - /// * array - _required_ - The destination array. - /// * index - _required_ - The starting index in the destination array. - void copyToBase(List<_TreeTableNodeBase> array, int index) { - final int count = getCount(); - for (int i = 0; i < count; i++) { - array[i + index] = this[i]!; - } - } - - void deleteFixup(_TreeTableBranchBase? x, bool isLeft) { - const bool inAddMode = false; - while (x != null && - !_MathHelper.referenceEquals(x, _root) && - x._color == _TreeTableNodeColor.black) { - if (isLeft) { - _TreeTableNodeBase? w = x.parent?.right; - if (w != null && w.color == _TreeTableNodeColor.red) { - w.color = _TreeTableNodeColor.black; - x.parent?.color = _TreeTableNodeColor.black; - leftRotate(x.parent!, inAddMode); - if (x.parent != null) { - w = x.parent!.right! as _TreeTableBranchBase; - } - } - - if (w == null) { - return; - } - - if (w is _TreeTableBranchBase && - w.color == _TreeTableNodeColor.black && - (w.left!.isEntry() || - w.getLeftBranch()!.color == _TreeTableNodeColor.black) && - (w.right!.isEntry() || - w.getRightBranch()!.color == _TreeTableNodeColor.black)) { - w.color = _TreeTableNodeColor.red; - if (x.color == _TreeTableNodeColor.red) { - x.color = _TreeTableNodeColor.black; - return; - } else { - isLeft = x.parent!.left == x; - x = x.parent; - } - } else if (w is _TreeTableBranchBase && - w.color == _TreeTableNodeColor.black && - !w.right!.isEntry() && - w.getRightBranch()!.color == _TreeTableNodeColor.red) { - leftRotate(x.parent!, inAddMode); - w.color = x.parent!.color; - x.parent!.color = w.color; - return; - } else if (w is _TreeTableBranchBase && - w.color == _TreeTableNodeColor.black && - !w.left!.isEntry() && - w.getLeftBranch()!.color == _TreeTableNodeColor.red && - (w.right!.isEntry() || - w.getRightBranch()!.color == _TreeTableNodeColor.black)) { - rightRotate(w, inAddMode); - - w.parent!.color = _TreeTableNodeColor.black; - w.color = _TreeTableNodeColor.red; - - leftRotate(x.parent!, inAddMode); - w.color = x.parent!.color; - x.parent!.color = w.color; - return; - } else { - return; - } - } else { - _TreeTableNodeBase? w = x.parent?.left; - if (w != null && w.color == _TreeTableNodeColor.red) { - w.color = _TreeTableNodeColor.black; - x.parent!.color = _TreeTableNodeColor.red; - rightRotate(x.parent, inAddMode); - w = x.parent!.left; - } - - if (w == null) { - return; - } - - if (w is _TreeTableBranchBase && - w.color == _TreeTableNodeColor.black && - (w.left!.isEntry() || - w.getLeftBranch()!.color == _TreeTableNodeColor.black) && - (w.right!.isEntry() || - w.getRightBranch()!.color == _TreeTableNodeColor.black)) { - w.color = _TreeTableNodeColor.red; - if (x.color == _TreeTableNodeColor.red) { - x.color = _TreeTableNodeColor.black; - return; - } else if (x.parent != null) { - isLeft = x.parent!.left == x; - x = x.parent; - } - } else { - if (w is _TreeTableBranchBase && - w.color == _TreeTableNodeColor.black && - !w.right!.isEntry() && - w.getRightBranch()!.color == _TreeTableNodeColor.red) { - final _TreeTableBranchBase xParent = x.parent!; - leftRotate(xParent, inAddMode); - final _TreeTableNodeColor t = w.color!; - w.color = xParent.color; - xParent.color = t; - return; - } else if (w is _TreeTableBranchBase && - w.color == _TreeTableNodeColor.black && - !w.left!.isEntry() && - w.getLeftBranch()!.color == _TreeTableNodeColor.red && - (w.right!.isEntry() || - w.getRightBranch()!.color == _TreeTableNodeColor.black)) { - final _TreeTableBranchBase wParent = w.parent!; - final _TreeTableBranchBase xParent = x.parent!; - rightRotate(w, inAddMode); - - wParent.color = _TreeTableNodeColor.black; - w.color = _TreeTableNodeColor.red; - - leftRotate(x.parent, inAddMode); - w.color = xParent.color; - xParent.color = w.color; - return; - } - } - } - } - - x!.color = _TreeTableNodeColor.black; - } - - /// Finds the node in a sorted tree is just one entry ahead of the - /// node with the specified key. - /// - /// It searches for the largest possible - /// key that is smaller than the specified key. - /// - /// * key - _required_ - The key to search. - /// - /// Returns the node; `NULL` if not found. - _TreeTableEntryBase? findHighestSmallerOrEqualKey(Object key) => - _findKey(key, true); - - /// Finds a node in a sorted tree that matches the specified key. - /// - /// * key - _required_ - The key to search. - /// - /// Returns the node; `NULL` if not found. - _TreeTableEntryBase? findKey(Object key) => _findKey(key, false); - - _TreeTableEntryBase? _findKey(Object? key, bool highestSmallerValue) { - if (!sorted) { - throw Exception('This tree is not sorted.'); - } - - Object? comparableKey = key; - if (root == null) { - // replace root - return null; - } else { - final Comparable? comparer = this.comparer; - int cmp = 0; - - if (_lastFoundEntry != null && - _lastFoundEntryKey != null && - key != null && - _lastFoundEntryHighestSmallerValue == highestSmallerValue) { - if (comparer != null) { - final Object? lastFoundEntry = _lastFoundEntry!.getMinimum(); - if (key is Comparable && lastFoundEntry is Comparable) { - cmp = Comparable.compare(key, lastFoundEntry); - } - } else if (comparableKey != null && comparableKey is Comparable) { - cmp = comparableKey.compareTo(_lastFoundEntry!.getMinimum()); - } - - if (cmp == 0) { - return _lastFoundEntry; - } - } - - // find node - _TreeTableBranchBase branch; - _TreeTableNodeBase current = root!; - - _TreeTableNodeBase? lastLeft; - - while (!current.isEntry()) { - branch = current as _TreeTableBranchBase; - if (comparer != null) { - final Object? minimum = branch.right!.getMinimum(); - if (key is Comparable && minimum is Comparable) { - cmp = Comparable.compare(key, minimum); - } - } else if (comparableKey != null && comparableKey is Comparable) { - cmp = comparableKey.compareTo(branch.right!.getMinimum()); - } else { - throw Exception('No Comparer specified.'); - } - - if (cmp == 0) { - current = branch.right!; - while (!current.isEntry()) { - if (current is _TreeTableBranchBase) { - current = current.left!; - } - } - - return cacheLastFoundEntry( - current as _TreeTableEntryBase, key, highestSmallerValue); - } else if (cmp < 0) { - current = branch.left!; - lastLeft = branch.left; - } else { - current = branch.right!; - } - } - - final _TreeTableEntryBase leaf = current as _TreeTableEntryBase; - - if (comparer != null) { - final Object? sortKey = leaf.getSortKey(); - if (key is Comparable && sortKey is Comparable) { - cmp = Comparable.compare(key, sortKey); - } - } else if (comparableKey != null && comparableKey is Comparable) { - cmp = comparableKey.compareTo(leaf.getSortKey()); - } - - comparableKey = null; - if (cmp == 0) { - return cacheLastFoundEntry(leaf, key, highestSmallerValue); - } - - if (highestSmallerValue) { - if (cmp < 0) { - return cacheLastFoundEntry(leaf, key, highestSmallerValue); - } else if (lastLeft != null) { - current = lastLeft; - while (!current.isEntry()) { - final _TreeTableBranchBase _current = - current as _TreeTableBranchBase; - current = _current.right!; - } - - return cacheLastFoundEntry( - current as _TreeTableEntryBase, key, highestSmallerValue); - } - } - - _lastFoundEntry = null; - return null; - } - } - - /// Inserts a node at the specified index. - /// - /// * index - _required_ - Index value where the node is to be inserted. - /// * value - _required_ - Value of the node to insert. - void insertBase(int index, _TreeTableNodeBase value) { - if (sorted) { - throw Exception('This tree is sorted - use AddSorted instead.'); - } - - final int treeCount = getCount(); - if (index < 0 || index > treeCount) { - throw ArgumentError( - 'index ${index.toString()} must be between 0 and ${treeCount.toString()}'); - } - - if (index == treeCount) { - add(value); - return; - } - - cacheLastFoundEntry(null, null, false); - if (_root == null) { - // replace root - _root = value; - } else { - _TreeTableEntryBase? leaf; - if (lastIndex != -1) { - if (index == lastIndex) { - leaf = lastIndexLeaf; - } else if (index == lastIndex + 1) { - leaf = getNextEntry(lastIndexLeaf); - } - } - - leaf ??= _getEntryAt(index); - final _TreeTableBranchBase? branch = leaf?.parent; - final _TreeTableBranchBase newBranch = leaf!.createBranch(this)!; - newBranch - ..setLeft(value, false, sorted) // will set leaf.Parent ... - ..right = leaf; - - if (branch == null) { - _root = newBranch; - } else { - // swap out leafs parent with new node - _replaceNode(branch, leaf, newBranch, false); - } - - insertFixup(newBranch, _inAddMode); - - if (value.isEntry()) { - _lastIndexLeaf = value as _TreeTableEntryBase; - lastIndex = index; - } else { - _lastIndexLeaf = null; - lastIndex = -1; - } - } - } - - /// Returns the position of a node. - /// - /// * value - _required_ - Node value to look for. - /// - /// Returns Index of the node if found. - int indexOfBase(_TreeTableNodeBase value) { - if (!contains(value)) { - return -1; - } - - return value.getPosition(); - } - - /// Finds a node in a sorted tree. - /// - /// * key - _required_ - Key needs to be find an index - /// - /// Returns the index of the key - int indexOfKey(Object key) { - final _TreeTableEntryBase? entry = findKey(key); - if (entry == null) { - return -1; - } - - return entry.getPosition(); - } - - void insertFixup(_TreeTableBranchBase? x, bool inAddMode) { - // Check Red-Black properties - while (x != null && - x.parent != null && - !_MathHelper.referenceEquals(x, _root) && - x.parent!.color == _TreeTableNodeColor.red && - x.parent!.parent != null) { - // We have a violation - if (x.parent == x.parent!.parent!.left) { - final _TreeTableNodeBase? y = x.parent!.parent?.right; - if (y != null && y.color == _TreeTableNodeColor.red) { - // uncle is red - x.parent!.color = _TreeTableNodeColor.black; - y.color = _TreeTableNodeColor.black; - x.parent!.parent?.color = _TreeTableNodeColor.red; - x = x.parent!.parent; - } else { - // uncle is black - if (x == x.parent!.right) { - // Make x a left child - x = x.parent; - leftRotate(x, inAddMode); - } - - // Recolor and rotate - x!.parent!.color = _TreeTableNodeColor.black; - x.parent!.parent!.color = _TreeTableNodeColor.red; - rightRotate(x.parent!.parent, inAddMode); - } - } else { - // Mirror image of above code - final _TreeTableNodeBase? y = x.parent!.parent?.left; - if (y != null && y.color == _TreeTableNodeColor.red) { - // uncle is red - x.parent!.color = _TreeTableNodeColor.black; - y.color = _TreeTableNodeColor.black; - x.parent!.parent!.color = _TreeTableNodeColor.red; - x = x.parent!.parent; - } else { - // uncle is black - if (x == x.parent!.left) { - x = x.parent; - rightRotate(x, inAddMode); - } - - x!.parent!.color = _TreeTableNodeColor.black; - x.parent!.parent!.color = _TreeTableNodeColor.red; - leftRotate(x.parent!.parent, inAddMode); - } - } - } - - root!.color = _TreeTableNodeColor.black; - } - - /// Gets the number of leaves. - /// - /// Returns the number of leaves. - int getCount() => _root == null ? 0 : _root!.getCount(); - - /// Gets a [TreeTableEnumerator]. - /// - /// Returns a [TreeTableEnumerator]. - _TreeTableEnumerator getEnumeratorBase() => _TreeTableEnumerator(this); - - _TreeTableEntryBase? _getEntryAt(int index) { - final int treeCount = getCount(); - if (index < 0 || index >= treeCount) { - throw ArgumentError( - 'index ${index.toString()} must be between 0 and ${(treeCount - 1).toString()}'); - } - - if (_root == null) { - // replace root - return null; - } else { - if (lastIndex != -1) { - if (index == lastIndex) { - return _lastIndexLeaf; - } else if (index == lastIndex + 1) { - lastIndex++; - return _lastIndexLeaf = getNextEntry(_lastIndexLeaf); - } - } - - // find node - _TreeTableBranchBase branch; - _TreeTableNodeBase? current = _root; - int count = 0; - while (current != null && !current.isEntry()) { - branch = current as _TreeTableBranchBase; - final int leftCount = branch.left!.getCount(); - - if (index < count + leftCount) { - current = branch.left; - } else { - count += branch.left!.getCount(); - current = branch.right; - } - } - - if (current is _TreeTableEntryBase) { - lastIndexLeaf = current; - } - lastIndex = index; - return _lastIndexLeaf; - } - } - - _TreeTableEntryBase? getMostLeftEntry(_TreeTableBranchBase? parent) { - _TreeTableNodeBase? next; - - if (parent == null) { - next = null; - return null; - } else { - next = parent.left; - while (!next!.isEntry()) { - final _TreeTableBranchBase _next = next as _TreeTableBranchBase; - next = _next.left; - } - } - - return next as _TreeTableEntryBase; - } - - _TreeTableNodeBase _getSisterNode( - _TreeTableBranchBase leafsParent, _TreeTableNodeBase node) { - final _TreeTableNodeBase? sisterNode = - _MathHelper.referenceEquals(leafsParent.left!, node) - ? leafsParent.right - : leafsParent.left; - - return sisterNode!; - } - - void leftRotate(_TreeTableBranchBase? x, bool inAddMode) { - if (x == null) { - return; - } - - final _TreeTableBranchBase y = x.right! as _TreeTableBranchBase; - - if (y.left is _TreeTableNodeBase) { - y.setLeft(_TreeTableEmpty.empty, inAddMode, sorted); - x.setRight(y.left, inAddMode); - if (x.parent != null) { - if (_MathHelper.referenceEquals(x, x.parent!.left)) { - x.parent!.setLeft(y, inAddMode, sorted); - } else { - x.parent!.setRight(y, inAddMode); - } - } else { - _root = y; - } - y.setLeft(x, inAddMode, sorted); - } - } - - /// Removes the specified node. - /// - /// * value - _required_ - Node value to look for and remove. - /// - /// Returns the removed value - bool removeBase(_TreeTableNodeBase value) => _remove(value, true); - - /// Used to remove the value from the tree table - /// - /// * value - _required_ - Tree value - /// * resetParent - _required_ - Boolean value - /// - /// Returns the boolean value - bool _remove(_TreeTableNodeBase? value, bool resetParent) { - if (value == null) { - return false; - } - - if (!contains(value)) { - return false; - } - - cacheLastFoundEntry(null, null, false); - - _lastAddBranch = null; - lastIndex = -1; - _lastIndexLeaf = null; - - // root - if (_MathHelper.referenceEquals(value, root)) { - _root = null; - if (resetParent) { - value.parent = null; - } - } else { - final _TreeTableBranchBase? leafsParent = value.parent; - - // get the sister node - final _TreeTableNodeBase sisterNode = _getSisterNode(leafsParent!, value); - - // swap out leaves parent with sister - if (_MathHelper.referenceEquals(leafsParent, _root)) { - _root = sisterNode..parent = null; - } else { - final _TreeTableBranchBase leafsParentParent = leafsParent.parent!; - final bool isLeft = leafsParentParent.left == leafsParent; - _replaceNode(leafsParentParent, leafsParent, sisterNode, false); - - if (leafsParent.color == _TreeTableNodeColor.black) { - leafsParent.parent = leafsParentParent; - deleteFixup(leafsParent, isLeft); - } - } - - if (resetParent) { - value.parent = null; - } - } - - return true; - } - - /// Resets the cache. - void resetCache() { - _lastAddBranch = null; - lastIndex = -1; - _lastIndexLeaf = null; - } - - void _replaceNode(_TreeTableBranchBase? branch, _TreeTableNodeBase? oldNode, - _TreeTableNodeBase? newNode, bool inAddMode) { - // also updates node count. - if (_MathHelper.referenceEquals(branch?.left, oldNode)) { - branch?.setLeft(newNode, inAddMode, sorted); - } else { - branch?.setRight(newNode, inAddMode); - } - } - - void rightRotate(_TreeTableBranchBase? x, bool inAddMode) { - if (x == null) { - return; - } - - final _TreeTableBranchBase y = x.left! as _TreeTableBranchBase; - - final _TreeTableNodeBase yRight = y.right!; - y.setRight(_TreeTableEmpty.empty, - inAddMode); // make sure Parent is not reset later - x.setLeft(yRight, inAddMode, sorted); - if (x.parent != null) { - if (x == x.parent!.right) { - x.parent!.setRight(y, inAddMode); - } else { - x.parent!.setLeft(y, inAddMode, sorted); - } - } else { - _root = y; - } - y.setRight(x, inAddMode); - } - - /// Sets the node at the specified index. - /// - /// * index - _required_ - Index value where the node is to be inserted. - /// * value - _required_ - Value of the node that is to be inserted. - void setNodeAt(int index, _TreeTableNodeBase value) { - final _TreeTableEntryBase? leaf = _getEntryAt(index); - if (_MathHelper.referenceEquals(leaf, _root)) { - _root = value; - } else { - if (leaf != null) { - final _TreeTableBranchBase branch = leaf.parent!; - _replaceNode(branch, leaf, value, false); - } - } - - lastIndex = -1; - } - - /// Indicates whether the node belongs to this tree. - /// - /// * value - _required_ - Node value - /// - /// Returns the boolean value indicating whether the node belongs - /// to this tree. - @override - bool contains(Object value) { - if (value is _TreeTableNodeBase) { - return containsBase(value); - } else { - return false; - } - } - - /// Clears all nodes in the tree. - @override - void clear() { - _root = null; - _lastAddBranch = null; - lastIndex = -1; - _lastIndexLeaf = null; - cacheLastFoundEntry(null, null, false); - } - - /// Adds the specified node to the tree. - /// - /// * value - _required_ - Adding value - /// - /// Returns the zero-based collection index at which the value has been added - @override - int add(Object value) { - if (value is _TreeTableNodeBase) { - return addBase(value); - } else { - return -1; - } - } - - /// Optimizes insertion of many elements when tree is initialized - /// for the first time. - @override - void beginInit() { - _inAddMode = true; - } - - /// Copies the element from this collection into an array. - /// - /// * array - _required_ - The destination array. - /// * index - _required_ - The starting index in the destination array. - @override - void copyTo(List array, int index) { - if (array is List<_TreeTableNodeBase>) { - copyToBase(array, index); - } - } - - /// Ends optimization of insertion of elements when tree is initialized - /// for the first time. - @override - void endInit() { - _inAddMode = false; - - // Fixes issues when GetCount() was called while debugging ... - final Object? branch = _root; - if (branch is _TreeTableBranch && branch.entryCount != -1) { - branch.entryCount = -1; - } - } - - /// Inserts a node at the specified index. - /// - /// * index - _required_ - Position where to insert the value - /// * value - _required_ - Tree value need to be insert - @override - void insert(int index, Object value) { - if (value is _TreeTableNodeBase) { - insertBase(index, value); - } - } - - /// Sets the index of the specified node. - /// - /// * value - _required_ - Tree value - /// - /// Returns the index of the specified node. - @override - int indexOf(Object value) { - if (value is _TreeTableNodeBase) { - return indexOfBase(value); - } else { - return -1; - } - } - - /// Gets an enumerator. - /// - /// Returns an enumerator. - @override - _EnumeratorBase getEnumerator() => getEnumerator(); - - /// Optimized access to a subsequent entry. - /// - /// * current - _required_ - Current item - /// - /// Returns next subsequent entry - @override - _TreeTableEntryBase? getNextEntry(_TreeTableEntryBase? current) { - _TreeTableBranchBase? parent = current?.parent; - _TreeTableNodeBase? next; - - if (parent == null) { - next = null; - return null; - } else { - if (_MathHelper.referenceEquals(current, parent.left)) { - next = parent.right; - } else { - _TreeTableBranchBase? parentParent = parent.parent; - if (parentParent == null) { - return null; - } else { - while (_MathHelper.referenceEquals(parentParent!.right, parent)) { - parent = parentParent; - parentParent = parentParent.parent; - if (parentParent == null) { - return null; - } - } - - next = parentParent.right; - } - } - - while (!next!.isEntry()) { - if (next is _TreeTableBranchBase) { - next = next.left; - } - } - } - - if (next is _TreeTableEntryBase) { - return next; - } else { - return null; - } - } - - /// Optimized access to the previous entry. - /// - /// * current - _required_ - Current item - /// - /// Returns previous entry - @override - _TreeTableEntryBase? getPreviousEntry(_TreeTableEntryBase current) { - _TreeTableBranchBase? parent = current.parent; - _TreeTableNodeBase? prev; - - if (parent == null) { - prev = null; - return null; - } else { - if (_MathHelper.referenceEquals(current, parent.right)) { - prev = parent.left; - } else { - _TreeTableBranchBase? parentParent = parent.parent; - if (parentParent == null) { - return null; - } else { - while (_MathHelper.referenceEquals(parentParent!.left, parent)) { - parent = parentParent; - parentParent = parentParent.parent; - if (parentParent == null) { - return null; - } - } - - prev = parentParent.left; - } - } - - while (!prev!.isEntry()) { - if (prev is _TreeTableBranchBase) { - prev = prev.right; - } - } - } - - if (prev is _TreeTableEntryBase) { - return prev; - } else { - return null; - } - } - - /// Removes the node with the specified value. - /// - /// * value - _required_ - Value needs to be remove - @override - bool remove(Object? value) { - if (value is _TreeTableNodeBase) { - return removeBase(value); - } else { - return false; - } - } - - /// Removes a node at the specified position. - /// - /// * index - _required_ - Index value - @override - void removeAt(int index) { - remove(this[index]); - } - - /// Gets an item at the specified index. - /// - /// * index - _required_ - Index value - /// - /// Returns the item at the specified index. - @override - _TreeTableNodeBase? operator [](int index) => _getEntryAt(index); - - /// Sets an item at the specified index. - @override - void operator []=(int index, Object value) { - if (value is _TreeTableNodeBase) { - setNodeAt(index, value); - } - } - - void lastIndexLeafDisposed() { - lastIndexLeaf = null; - lastIndex = -1; - } -} - -class _TreeTableEnumerator implements _EnumeratorBase { - /// Initializes a new instance of the `TreeTableEnumerator` class. - /// - /// * tree - _required_ - Tree instance - _TreeTableEnumerator(_TreeTableBase tree) { - _tree = tree; - _cursor = null; - if (tree.count > 0 && (tree[0] is _TreeTableNodeBase)) { - _next = tree[0]! as _TreeTableNodeBase; - } - } - - _TreeTableNodeBase? _cursor; - _TreeTableNodeBase? _next; - _TreeTableBase? _tree; - - /// Gets the current enumerator. - Object? get current => currentBase; - - /// Gets the current node. - _TreeTableEntryBase? get currentBase { - if (_cursor is _TreeTableEntryBase) { - return _cursor! as _TreeTableEntryBase; - } else { - return null; - } - } - - /// Indicates whether to move to the next node. - /// - /// Returns a boolean value indicating whether to move to the next node. - @override - bool moveNext() { - if (_next == null) { - return false; - } - - _cursor = _next; - - _TreeTableBranchBase? _parent = _cursor!.parent; - - if (_parent == null) { - _next = null; - return true; - } else { - if (_MathHelper.referenceEquals(_cursor, _parent.left)) { - _next = _parent.right; - } else { - _TreeTableBranchBase? parentParent = _parent.parent; - if (parentParent == null) { - _next = null; - return true; - } else { - while (_MathHelper.referenceEquals(parentParent!.right, _parent)) { - _parent = parentParent; - parentParent = parentParent.parent; - if (parentParent == null) { - _next = null; - return true; - } - } - - _next = parentParent.right; - } - } - - while (!_next!.isEntry()) { - final _TreeTableBranchBase next = _next! as _TreeTableBranchBase; - _next = next.left; - } - } - - return _cursor != null; - } - - /// Resets the enumerator. - @override - void reset() { - _cursor = null; - if (_tree != null && - _tree!.count > 0 && - _tree?[0] != null && - (_tree![0] is _TreeTableNodeBase)) { - _next = _tree![0]! as _TreeTableNodeBase; - } else { - _next = null; - } - } -} - -/// An object that holds an [_TreeTableEntryBase]. -class _TreeTableEntryBaseSource { - /// Gets a reference to the [_TreeTableEntryBase]. - _TreeTableEntryBase? get entry => _entry; - _TreeTableEntryBase? _entry; - - /// Sets a reference to the [_TreeTableEntryBase]. - set entry(_TreeTableEntryBase? value) { - if (value == _entry) { - return; - } - - _entry = value; - } -} - -/// A collection of `_TreeTableEntryBaseSource` objects -/// that are internally using a `ITreeTable`. -class _TreeTableEntrySourceCollection extends _ListBase { - /// Initializes a new instance of the `TreeTableEntrySourceCollection` class. - _TreeTableEntrySourceCollection() { - inner = _TreeTable(false); - } - - late _TreeTableBase inner; - - /// Gets the number of objects in this collection. - @override - int get count => inner.count; - - /// Gets a value indicating whether the [BeginInit] was called or not. - bool get isInitializing => inner.isInitializing; - - /// Gets a value indicating whether the nodes can be added or removed. - @override - bool get isFixedSize => false; - - /// Gets a value indicating whether tree is Read-only or not. - @override - bool get isReadOnly => false; - - /// Gets a value indicating whether the tree is Synchronized or not. - @override - bool get isSynchronized => false; - - /// Appends an object. - /// - /// * value - _required_ - The value of the object to append. - /// - /// Returns an instance for the tree with newly added entry. - int addBase(_TreeTableEntryBaseSource value) { - final _TreeTableEntry entry = _TreeTableEntry()..value = value; - value.entry = entry; - return inner.add(entry); - } - - /// Optimizes insertion of many elements when tree is initialized for the - /// first time. - void beginInit() { - inner.beginInit(); - } - - /// Indicates whether object belongs to this collection. - /// - /// * value - _required_ - The value of the object. - /// - /// Returns `True` if object belongs to the collection. `false` otherwise. - bool containsBase(_TreeTableEntryBaseSource? value) { - if (value == null || value.entry == null) { - return false; - } - - return inner.contains(value.entry!); - } - - /// Copies the contents of the collection to an array. - /// - /// * array - _required_ - Destination array. - /// * index - _required_ - Starting index of the destination array. - void copyToBase(List<_TreeTableEntryBaseSource>? array, int index) { - final int count = inner.count; - for (int n = 0; n < count; n++) { - final Object _n = [n]; - if (_n is _TreeTableEntryBaseSource && array != null) { - array[index + n] = _n; - } - } - } - - /// Ends optimization of insertion of elements when tree is initialized for - /// the first time. - void endInit() { - inner.endInit(); - } - - /// Inserts an object at the specified index. - /// - /// * index - _required_ - Index value where the object is to be inserted. - /// * value - _required_ - Value of the object to insert. - void insertBase(int index, _TreeTableEntryBaseSource? value) { - if (value == null) { - return; - } - - final _TreeTableEntry entry = _TreeTableEntry()..value = value; - value.entry = entry; - inner.insert(index, entry); - } - - /// Returns the position of a object in the collection. - /// * value - _required_ - The value of the object. - /// Returns - _required_ - the position of the object. - int indexOfBase(_TreeTableEntryBaseSource? value) => - (value != null && value.entry != null) ? inner.indexOf(value.entry!) : -1; - - /// Removes a node at the specified index. - /// - /// * index - _required_ - Index value of the node to remove. - void removeAtBase(int index) { - inner.removeAt(index); - } - - /// Removes the object. - /// - /// * value - _required_ - The value of the object to remove. - void removeBase(_TreeTableEntryBaseSource? value) { - if (value == null || value.entry == null) { - return; - } - - inner.remove(value.entry!); - } - - /// Adds the specified object to the collection. - /// - /// * value - _required_ - Value of the object to add. - /// - /// Returns the zero-based collection index at which the value has been added - @override - int add(Object value) { - if (value is _TreeTableEntryBaseSource) { - return addBase(value); - } else { - return -1; - } - } - - /// Clears all nodes in the tree. - @override - void clear() { - inner.clear(); - } - - /// Indicate whether the specified object belongs to this collection. - /// - /// * value - _required_ - Object value to look for. - /// Returns - _required_ - true if object belongs to the collection; - /// false otherwise. - @override - bool contains(Object value) { - if (value is _TreeTableEntryBaseSource) { - return containsBase(value); - } else { - return false; - } - } - - /// Copies elements to destination array. - /// - /// * array - _required_ - Destination array. - /// * index - _required_ - Starting index of the destination array. - @override - void copyTo(List array, int index) { - if (array is List<_TreeTableEntryBaseSource>) { - copyToBase(array, index); - } - } - - /// Returns a strongly typed enumerator. - @override - _TreeTableEntrySourceCollectionEnumerator getEnumerator() => - _TreeTableEntrySourceCollectionEnumerator(this); - - /// Inserts the object at the specified index. - /// - /// * index - _required_ - Index value of the object to insert. - /// * value - _required_ - Value of the object to insert. - @override - void insert(int index, Object? value) { - if (value is _TreeTableEntryBaseSource) { - insertBase(index, value); - } - } - - /// Returns the index of the specified object. - /// - /// * value - _required_ - Value of the object. - /// - /// Returns Index value of the object. - @override - int indexOf(Object value) { - if (value is _TreeTableEntryBaseSource) { - return indexOfBase(value); - } else { - return -1; - } - } - - /// Removes the specified object. - /// - /// * value - _required_ - Value of the object to remove. - @override - void remove(Object value) { - if (value is _TreeTableEntryBaseSource) { - removeBase(value); - } - } - - /// Sets an `_TreeTableEntryBaseSource` at a specific position. - /// - /// * index - _required_ - Index value - /// - /// Returns the entry value for the specified position. - @override - void operator []=(int index, Object value) { - final _TreeTableEntry entry = _TreeTableEntry()..value = value; - if (value is _TreeTableEntryBaseSource) { - value.entry = entry; - } - inner[index] = entry; - } - - /// Gets an `_TreeTableEntryBaseSource` at a specific position. - /// - /// * index - _required_ - Index value - /// - /// Returns the entry value for the specified position. - @override - _TreeTableEntryBaseSource? operator [](num index) { - final Object? entry = inner[index.toInt()]; - if (entry is _TreeTableEntryBase) { - return entry.value! as _TreeTableEntryBaseSource; - } else { - return null; - } - } -} - -/// A strongly typed enumerator for the `TreeTableEntrySourceCollection`. -class _TreeTableEntrySourceCollectionEnumerator implements _EnumeratorBase { - /// Initializes a new instance of the - /// `TreeTableEntrySourceCollectionEnumerator` class. - /// - /// * collection - _required_ - Collection value - _TreeTableEntrySourceCollectionEnumerator( - _TreeTableEntrySourceCollection collection) { - inner = _TreeTableEnumerator(collection.inner); - } - - _TreeTableEnumerator? inner; - - /// Gets the current enumerator. - Object? get current => currentBase; - - /// Gets the current `_TreeTableEntryBaseSource` object. - _TreeTableEntryBaseSource? get currentBase { - final Object? currentBaseValue = inner?.currentBase?.value; - if (inner != null && - currentBaseValue != null && - currentBaseValue is _TreeTableEntryBaseSource) { - return currentBaseValue; - } else { - return null; - } - } - - /// Indicates whether to move to the next object in the collection. - /// - /// Returns the boolean value indicates whether to move to the next object - /// in the collection. - @override - bool moveNext() => inner?.moveNext() ?? false; - - /// Resets the enumerator. - @override - void reset() { - inner?.reset(); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart deleted file mode 100644 index ebf914333..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_counter.dart +++ /dev/null @@ -1,832 +0,0 @@ -part of datagrid; - -/// Interface definition for a node that has counters and summaries. -mixin _TreeTableCounterNodeBase on _TreeTableSummaryNodeBase { - /// Gets the cumulative position of this node. - /// - /// Returns Returns the cumulative position of this node. - _TreeTableCounterBase? get getCounterPosition; - - /// The total of this node's counter and child nodes. - /// - /// Returns the total of this node's counter and child nodes (cached). - _TreeTableCounterBase? getCounterTotal(); - - /// Marks all counters dirty in this node and child nodes. - /// - /// * notifyCounterSource - _required_ - If set to `true` notify - /// counter source. - void invalidateCounterTopDown(bool notifyCounterSource); -} - -/// Interface definition for an object that has counters. -abstract class _TreeTableCounterSourceBase { - /// Gets the counter object with counters. - /// - /// Returns the counter object with counters. - _TreeTableCounterBase getCounter(); - - /// Marks all counters dirty in this object and child nodes. - /// - /// * notifyCounterSource - _required_ - If set to true notify counter source. - void invalidateCounterTopDown(bool notifyCounterSource); - - /// Marks all counters dirty in this object and parent nodes. - void invalidateCounterBottomUp(); -} - -/// Interface definition for a counter object. -abstract class _TreeTableCounterBase { - _TreeTableCounterBase() { - _kind = -1; - } - - /// Gets the Counter Kind. - /// - /// Returns the kind. - int get kind => _kind; - late int _kind; - - /// Combines this counter object with another counter and returns a - /// new object. A cookie can specify a specific counter type. - /// - /// * other - _required_ - Counter total - /// * cookie - _required_ - Cookie value. - /// - /// Returns the new object - _TreeTableCounterBase combine(_TreeTableCounterBase? other, int cookie); - - /// Compares this counter with another counter. A cookie can specify - /// a specific counter type. - /// - /// * other - _required_ - The other. - /// * cookie - _required_ - The cookie. - /// - /// Returns the compared value. - double compare(_TreeTableCounterBase? other, int cookie); - - /// Indicates whether the counter object is empty. A cookie can specify - /// a specific counter type. - /// - /// * cookie - _required_ - The cookie. - /// Returns `true` if the specified cookie is empty; otherwise,`false`. - bool isEmpty(int cookie); - - /// Gets the integer value of the counter. A cookie specifies - /// a specific counter type. - /// - /// * cookie - _required_ - The cookie. - /// - /// Returns the integer value of the counter. - double getValue(int cookie); -} - -/// Default counter cookies for identifying counter types. -class _TreeTableCounterCookies { - /// All counters. - static const int countAll = 0xffff; - - /// Visible Counter. - static const int countVisible = 0x8000; -} - -/// A tree table branch with a counter. -class _TreeTableWithCounterBranch extends _TreeTableWithSummaryBranch - with _TreeTableCounterNodeBase { - ///Initializes a new instance of the `_TreeTableWithCounterBranch` class. - /// - /// * tree - _required_ - Tree instance - _TreeTableWithCounterBranch(_TreeTable tree) : super(tree); - - _TreeTableCounterBase? counter; - - /// Gets the parent branch. - @override - _TreeTableWithCounterBranch? get parent { - if (super.parent is _TreeTableWithCounterBranch) { - return super.parent! as _TreeTableWithCounterBranch; - } else { - return null; - } - } - - /// Gets the tree this branch belongs to. - _TreeTableWithCounter? get treeTableWithCounter { - if (tree is _TreeTableWithCounter) { - return tree! as _TreeTableWithCounter; - } else { - return null; - } - } - - /// Gets the cumulative counter position object of a child node with all - /// counter values. - /// - /// * node - _required_ - The node. - /// - /// Returns the cumulative counter position object of a child node with all - /// counter values. - _TreeTableCounterBase getCounterPositionOfChild(_TreeTableNodeBase node) { - final _TreeTableCounterBase pos = getCounterPosition; - - if (_MathHelper.referenceEquals(node, right)) { - return pos.combine( - getLeftNode()?.getCounterTotal(), _TreeTableCounterCookies.countAll); - } else if (_MathHelper.referenceEquals(node, left)) { - return pos; - } - - throw ArgumentError('must be a child node'); - } - - /// Gets the cumulative position of this node. - /// - /// Returns the cumulative position of this node. - @override - _TreeTableCounterBase get getCounterPosition { - if (parent == null) { - return treeTableWithCounter!.getStartCounterPosition(); - } - - return parent!.getCounterPositionOfChild(this); - } - - /// Gets the total of this node's counter and child nodes (cached). - /// - /// Returns Returns the total of this node's counter and child - /// nodes (cached). - @override - _TreeTableCounterBase? getCounterTotal() { - if (tree!.isInitializing) { - return null; - } else if (counter == null) { - final _TreeTableCounterBase? _left = getLeftNode()?.getCounterTotal(); - final _TreeTableCounterBase? _right = getRightNode()?.getCounterTotal(); - if (_left != null && _right != null) { - counter = _left.combine(_right, _TreeTableCounterCookies.countAll); - } - } - - return counter; - } - - /// The left branch node cast to ITreeTableCounterNode. - /// - /// Returns the left branch node cast to ITreeTableCounterNode. - @override - _TreeTableCounterNodeBase? getLeftNode() { - if (left is _TreeTableCounterNodeBase) { - return left! as _TreeTableCounterNodeBase; - } else { - return null; - } - } - - /// The left branch node cast to ITreeTableCounterNode. - /// - /// Returns the left branch node cast to ITreeTableCounterNode. - @override - _TreeTableCounterNodeBase? getLeftC() => getLeftNode(); - - /// The right branch node cast to ITreeTableCounterNode. - /// - /// Returns the right branch node cast to ITreeTableCounterNode. - @override - _TreeTableCounterNodeBase? getRightNode() { - if (right is _TreeTableCounterNodeBase) { - return right! as _TreeTableCounterNodeBase; - } else { - return null; - } - } - - /// The right branch node cast to ITreeTableCounterNode. - /// - /// Returns the right branch node cast to ITreeTableCounterNode. - @override - _TreeTableCounterNodeBase? getRightC() => getRightNode(); - - /// Invalidates the counter bottom up. - /// - /// * notifyCounterSource - _required_ - If set to true notify counter source. - @override - void invalidateCounterBottomUp(bool notifyCounterSource) { - if (tree!.isInitializing) { - return; - } - - counter = null; - if (parent != null) { - parent!.invalidateCounterBottomUp(notifyCounterSource); - } else if (notifyCounterSource) { - final Object _tree = tree!; - if (_tree is _TreeTableWithCounter) { - _TreeTableCounterSourceBase? tcs; - if (_tree.tag is _TreeTableCounterSourceBase) { - tcs = _tree.tag! as _TreeTableCounterSourceBase; - } - - if (tcs != null) { - tcs.invalidateCounterBottomUp(); - } - - tcs = _tree.parentCounterSource; - if (tcs != null) { - tcs.invalidateCounterBottomUp(); - } - } - } - } - - /// Marks all counters dirty in this node and child nodes. - /// - /// * notifyCounterSource - _required_ - If set to true notify counter source. - @override - void invalidateCounterTopDown(bool notifyCounterSource) { - if (tree!.isInitializing) { - return; - } - - counter = null; - getLeftNode()?.invalidateCounterTopDown(notifyCounterSource); - getRightNode()?.invalidateCounterTopDown(notifyCounterSource); - } -} - -/// A tree leaf with value, sort key and counter information. -class _TreeTableWithCounterEntry extends _TreeTableWithSummaryEntryBase - with _TreeTableCounterNodeBase { - _TreeTableCounterBase? _counter; - - /// Gets the tree this leaf belongs to. - _TreeTableWithCounter? get treeTableWithCounter { - if (super.tree is _TreeTableWithCounter) { - return super.tree! as _TreeTableWithCounter; - } else { - return null; - } - } - - /// Gets the parent branch. - @override - _TreeTableWithCounterBranch? get parent { - if (super.parent is _TreeTableWithCounterBranch) { - return super.parent! as _TreeTableWithCounterBranch; - } else { - return null; - } - } - - /// Sets the parent branch. - @override - set parent(Object? value) { - super.parent = value; - } - - /// Gets the cumulative position of this node. - /// - /// Returns Returns the cumulative position of this node. - @override - _TreeTableCounterBase? get getCounterPosition { - if (parent == null) { - if (treeTableWithCounter == null) { - return null; - } - - return treeTableWithCounter?.getStartCounterPosition(); - } - - return parent?.getCounterPositionOfChild(this); - } - - /// Indicates whether the counter was set dirty. - /// - /// Returns `True` if dirty; `False` otherwise. - bool isCounterDirty() => _counter == null; - - /// Creates a branch that can hold this entry when new leaves are inserted - /// into the tree. - /// - /// * tree - _required_ - Tree instance - /// - /// Returns the instance of newly created branch - @override - _TreeTableBranchBase createBranch(_TreeTable tree) => - _TreeTableWithCounterBranch(tree); - - /// Gets the value as `_TreeTableCounterSourceBase`. - /// - /// Returns the value as `_TreeTableCounterSourceBase`. - _TreeTableCounterSourceBase? getCounterSource() { - if (value is _TreeTableCounterSourceBase) { - return value! as _TreeTableCounterSourceBase; - } else { - return null; - } - } - - /// Gets the total of this node's counter and child nodes. - /// - /// Returns the total of this node's counter and child nodes. - @override - _TreeTableCounterBase getCounterTotal() { - if (_counter == null) { - final _TreeTableCounterSourceBase? source = getCounterSource(); - if (source != null) { - _counter = source.getCounter(); - } - } - - return _counter!; - } - - /// Reset cached counter. - void invalidateCounter() { - _counter = null; - } - - /// Invalidates the counter bottom up. - /// - /// * notifyCounterSource - _required_ - If set to true notify counter source. - @override - void invalidateCounterBottomUp(bool notifyCounterSource) { - _counter = null; - if (parent != null) { - parent!.invalidateCounterBottomUp(notifyCounterSource); - } else if (notifyCounterSource) { - final Object _tree = tree!; - if (_tree is _TreeTableWithCounter) { - _TreeTableCounterSourceBase? tcs; - - if (_tree.tag is _TreeTableCounterSourceBase) { - tcs = _tree.tag! as _TreeTableCounterSourceBase; - } - - if (tcs != null) { - tcs.invalidateCounterBottomUp(); - } - - tcs = _tree.parentCounterSource; - if (tcs != null) { - tcs.invalidateCounterBottomUp(); - } - } - } - } - - /// Marks all summaries dirty in this node and child nodes. - /// - /// * notifyCounterSource - _required_ - If set to true notify counter source. - @override - void invalidateCounterTopDown(bool notifyCounterSource) { - _counter = null; - if (notifyCounterSource) { - final _TreeTableCounterSourceBase? source = getCounterSource(); - if (notifyCounterSource && source != null) { - source.invalidateCounterTopDown(notifyCounterSource); - } - } - } -} - -/// A balanced tree with [_TreeTableWithCounterEntry] entries. -class _TreeTableWithCounter extends _TreeTableWithSummary { - /// Initializes a new instance of the [TreeTableWithCounter] class. - /// - /// * startPosition - _required_ - Sorting position - /// * sorted - _required_ - Boolean value - _TreeTableWithCounter(_TreeTableCounterBase startPosition, bool sorted) - : super(sorted) { - _startPos = startPosition; - } - - late _TreeTableCounterBase _startPos; - - /// Gets an object that implements the - /// [Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances] property. - _TreeTableCounterSourceBase? get parentCounterSource => _parentCounterSource; - _TreeTableCounterSourceBase? _parentCounterSource; - - /// Sets an object that implements the - /// [Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances] property. - set parentCounterSource(_TreeTableCounterSourceBase? value) { - if (value == _parentCounterSource) { - return; - } - - _parentCounterSource = value; - } - - /// Gets the total of all counters in this tree. - /// - /// Returns the total of all counters in this tree. - _TreeTableCounterBase? getCounterTotal() { - if (root == null) { - return _startPos; - } - - final Object? _root = root; - if (_root != null && _root is _TreeTableCounterNodeBase) { - return _root.getCounterTotal(); - } else { - return null; - } - } - - /// Overloaded. Gets an entry at the specified counter position. - /// A cookie defines the type of counter. - /// - /// * searchPosition - _required_ - The search position. - /// * cookie - _required_ - The cookie. - /// - /// Returns an entry at the specified counter position. - _TreeTableWithCounterEntry? getEntryAtCounterPosition( - _TreeTableCounterBase searchPosition, int cookie) => - getEntryAtCounterPositionWithForParameter( - getStartCounterPosition(), searchPosition, cookie, false); - - /// Gets an entry at the specified counter position. A cookie defines the - /// type of counter. - /// - /// * searchPosition - _required_ - The search position. - /// * cookie - _required_ - The cookie. - /// * preferLeftMost - _required_ - Indicates if the leftmost entry should - /// be returned if multiple tree elements have the same SearchPosition. - /// - /// Returns an entry at the specified counter position. - _TreeTableWithCounterEntry? getEntryAtCounterPositionwithThreeParameter( - _TreeTableCounterBase searchPosition, - int cookie, - bool preferLeftMost) => - getEntryAtCounterPositionWithForParameter( - getStartCounterPosition(), searchPosition, cookie, preferLeftMost); - - /// Gets the entry at counter position. - /// - /// * start - _required_ - The start. - /// * searchPosition - _required_ - The search position. - /// * cookie - _required_ - The cookie. - /// * preferLeftMost - _required_ - If set to true prefer left most. - /// - /// Returns an entry at the specified counter position. - _TreeTableWithCounterEntry? getEntryAtCounterPositionWithForParameter( - _TreeTableCounterBase start, - _TreeTableCounterBase searchPosition, - int cookie, - bool preferLeftMost) { - if (searchPosition.compare(getStartCounterPosition(), cookie) < 0) { - throw Exception('SearchPosition'); - } - - if (searchPosition.compare(getCounterTotal(), cookie) > 0) { - throw Exception('$searchPosition out of range $this.getCounterTotal()'); - } - - if (root == null) { - return null; - } else { - // find node - final _TreeTableNodeBase? currentNode = root; - final _TreeTableCounterBase currentNodePosition = start; - return getEntryAtCounterPositionWithSixParameter(currentNode!, start, - searchPosition, cookie, preferLeftMost, currentNodePosition); - } - } - - /// An object that implements the - /// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` property. - /// - /// * currentNode - _required_ - Current node value - /// * start - _required_ - Start counter - /// * searchPosition - _required_ - Position needs to be search - /// * cookie - _required_ - Cookie value - /// * preferLeftMost - _required_ - Indicates if the leftmost entry - /// should be returned if multiple tree elements have the same SearchPosition - /// * currentNodePosition - _required_ - Position of the current node - /// - /// Returns the current node. - _TreeTableWithCounterEntry getEntryAtCounterPositionWithSixParameter( - _TreeTableNodeBase currentNode, - _TreeTableCounterBase start, - _TreeTableCounterBase searchPosition, - int cookie, - bool preferLeftMost, - _TreeTableCounterBase? currentNodePosition) { - _TreeTableWithCounterBranch? savedBranch; - currentNodePosition = start; - while (!currentNode.isEntry()) { - final _TreeTableWithCounterBranch branch = - currentNode as _TreeTableWithCounterBranch; - final _TreeTableCounterNodeBase leftB = - branch.left! as _TreeTableCounterNodeBase; - final _TreeTableCounterBase rightNodePosition = - currentNodePosition!.combine(leftB.getCounterTotal(), cookie); - - if (searchPosition.compare(rightNodePosition, cookie) < 0) { - currentNode = branch.left!; - } else if (preferLeftMost && - searchPosition.compare(currentNodePosition, cookie) == 0) { - while (!currentNode.isEntry()) { - final _TreeTableWithCounterBranch branch = - currentNode as _TreeTableWithCounterBranch; - currentNode = branch.left!; - } - } else { - if (preferLeftMost && - searchPosition.compare(rightNodePosition, cookie) == 0) { - _TreeTableCounterBase? currentNode2Position; - final _TreeTableNodeBase currentNode2 = - getEntryAtCounterPositionWithSixParameter( - branch.left!, - currentNodePosition, - searchPosition, - cookie, - preferLeftMost, - currentNode2Position); - if (rightNodePosition.compare(currentNode2Position, cookie) == 0) { - currentNode = currentNode2; - currentNodePosition = currentNode2Position; - } else { - currentNodePosition = rightNodePosition; - currentNode = branch.right!; - } - } else { - savedBranch ??= branch; - currentNodePosition = rightNodePosition; - currentNode = branch.right!; - } - } - } - - return currentNode as _TreeTableWithCounterEntry; - } - - /// Gets the subsequent entry in the collection for which the specific - /// counter is not empty. A cookie defines the type of counter. - /// - /// * current - _required_ - The current. - /// * cookie - _required_ - The cookie. - /// - /// Returns the subsequent entry in the collection for which the - /// specific counter is not empty. - _TreeTableEntryBase? getNextNotEmptyCounterEntry( - _TreeTableEntryBase current, int cookie) { - _TreeTableBranchBase? parent = current.parent; - _TreeTableNodeBase? next; - - if (parent == null) { - next = null; - return null; - } else { - next = current; - // walk up until we find a branch that has visible entries - do { - if (_MathHelper.referenceEquals(next, parent!.left)) { - next = parent.right; - } else { - _TreeTableBranchBase? parentParent = parent.parent; - if (parentParent == null) { - return null; - } else { - while (_MathHelper.referenceEquals(parentParent!.right, parent) - // for something that most likely went wrong when - // adding the node or when doing a rotation ... - || - _MathHelper.referenceEquals(parentParent.right, next)) { - parent = parentParent; - parentParent = parentParent.parent; - if (parentParent == null) { - return null; - } - } - - if (next == parentParent.right) { - throw Exception(); - } else { - next = parentParent.right; - } - } - } - } while (next != null && - (next is _TreeTableCounterNodeBase && - next.getCounterTotal()!.isEmpty(cookie))); - - // walk down to most left leaf that has visible entries - while (!next!.isEntry()) { - final _TreeTableBranchBase branch = next as _TreeTableBranchBase; - final _TreeTableCounterNodeBase _left = - branch.left! as _TreeTableCounterNodeBase; - next = !_left.getCounterTotal()!.isEmpty(cookie) - ? branch.left - : branch.right; - } - } - - return next as _TreeTableEntryBase; - } - - /// Returns the previous entry in the collection for which the specific - /// counter is not empty. - /// - /// * current - _required_ - The current. - /// * cookie - _required_ - The cookie. - /// - /// Returns the previous entry in the collection for which the specific - /// counter is not empty. - _TreeTableEntryBase? getPreviousNotEmptyCounterEntry( - _TreeTableEntryBase current, int cookie) { - _TreeTableBranchBase? parent = current.parent; - _TreeTableNodeBase? next; - - if (parent == null) { - next = null; - return null; - } else { - next = current; - // walk up until we find a branch that has visible entries - do { - if (_MathHelper.referenceEquals(next, parent!.right)) { - next = parent.left; - } else { - _TreeTableBranchBase? parentParent = parent.parent; - if (parentParent == null) { - return null; - } else { - while (_MathHelper.referenceEquals(parentParent!.left, parent) - // for something that most likely went wrong when - // adding the node or when doing a rotation ... - || - _MathHelper.referenceEquals(parentParent.left, next)) { - parent = parentParent; - parentParent = parentParent.parent; - if (parentParent == null) { - return null; - } - } - if (next == parentParent.left) { - throw Exception(); - } else { - next = parentParent.left; - } - } - } - } while (next != null && - (next is _TreeTableCounterNodeBase && - next.getCounterTotal()!.isEmpty(cookie))); - - // walk down to most left leaf that has visible entries - while (!next!.isEntry()) { - final _TreeTableBranchBase branch = next as _TreeTableBranchBase; - final _TreeTableCounterNodeBase _right = - branch.right! as _TreeTableCounterNodeBase; - next = !_right.getCounterTotal()!.isEmpty(cookie) - ? branch.right - : branch.left; - } - } - - return next as _TreeTableEntryBase; - } - - /// Gets the next entry in the collection for which CountVisible counter - /// is not empty. - /// - /// * current - _required_ - The current. - /// - /// Returns the next entry in the collection for which CountVisible counter - /// is not empty. - _TreeTableWithCounterEntry? getNextVisibleEntry( - _TreeTableWithCounterEntry current) { - final _TreeTableEntryBase? nextCounterEntry = getNextNotEmptyCounterEntry( - current, _TreeTableCounterCookies.countVisible); - if (nextCounterEntry != null) { - return nextCounterEntry as _TreeTableWithCounterEntry; - } - - return null; - } - - /// Gets the previous entry in the collection for which CountVisible counter - /// is not empty. - /// - /// * current - _required_ - The current. - /// - /// Returns the previous entry in the collection for which CountVisible - /// counter is not empty. - _TreeTableWithCounterEntry? getPreviousVisibleEntry( - _TreeTableWithCounterEntry current) { - final _TreeTableEntryBase? previousCounterEntry = - getPreviousNotEmptyCounterEntry( - current, _TreeTableCounterCookies.countVisible); - if (previousCounterEntry != null) { - return previousCounterEntry as _TreeTableWithCounterEntry; - } - - return null; - } - - /// Gets the starting counter for this tree. - /// - /// Returns Returns the starting counter for this tree. - _TreeTableCounterBase getStartCounterPosition() => _startPos; - - /// Marks all counters dirty. - /// - /// * notifyCounterSource - _required_ - Boolean value - void invalidateCounterTopDown(bool notifyCounterSource) { - if (root != null) { - final Object _root = root!; - if (_root is _TreeTableCounterNodeBase) { - _root.invalidateCounterTopDown(notifyCounterSource); - } - } - } - - /// Appends an object. - /// - /// * value - _required_ - The value. - /// - /// Returns the zero-based collection index at which the value has been added. - @override - int add(Object value) => super.add(value); - - /// Indicates whether an entry belongs to the tree. - /// - /// * value - _required_ - The entry. - /// - /// Returns `true` if tree contains the specified entry; otherwise, `false`. - @override - bool contains(Object? value) { - if (value == null) { - return false; - } - - return super.contains(value); - } - - /// Copies the elements of this tree to an array. - /// - /// * array - _required_ - The array. - /// * index - _required_ - The index. - @override - void copyTo(List array, int index) { - super.copyTo(array, index); - } - - /// Ends optimization of insertion of elements when tree is initialized - /// for the first time. - @override - void endInit() { - super.endInit(); - } - - /// Gets the position of an object in the tree. - /// - /// * value - _required_ - The value. - /// - /// Returns the position of an object in the tree. - @override - int indexOf(Object value) => super.indexOf(value); - - /// Inserts a `_TreeTableWithCounterEntry` object at the specified index. - /// - /// * index - _required_ - The index. - /// * value - _required_ - The value. - @override - void insert(int index, Object value) { - super.insert(index, value); - } - - /// Removes an object from the tree. - /// - /// * value - _required_ - The value. - /// - /// Returns the collection after removing the specified item from the - /// tree collection - @override - bool remove(Object? value) => super.remove(value); - - /// Gets a _TreeTableWithCounterEntry. - /// - /// * index - _required_ - Index value - /// - /// Returns a new instance for _TreeTableWithCounterEntry - @override - _TreeTableWithCounterEntry? operator [](int index) { - if (super[index] is _TreeTableWithCounterEntry) { - return super[index]! as _TreeTableWithCounterEntry; - } else { - return null; - } - } - - /// Sets a _TreeTableWithCounterEntry. - /// - /// * index - _required_ - Index value - /// - /// Returns a new instance for _TreeTableWithCounterEntry - @override - void operator []=(int index, Object value) { - super[index] = value; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart deleted file mode 100644 index 3fa9ff218..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/collections/tree_table_with_summary.dart +++ /dev/null @@ -1,482 +0,0 @@ -part of datagrid; - -/// Interface definition for a summary object. -abstract class _TreeTableSummaryBase { - /// Combines this summary information with another object's summary - /// and returns a new object. - /// - /// * other - _required_ - The other. - /// - /// Returns a combined object. - _TreeTableSummaryBase combine(_TreeTableSummaryBase other); -} - -/// Interface definition for a node that has one or more summaries. -mixin _TreeTableSummaryNodeBase on _TreeTableNodeBase { - /// Gets a value indicating whether node has summaries or not. - bool get hasSummaries => false; - - /// Gets an array of summary objects. - /// - /// * emptySummaries - _required_ - The empty summaries. - /// - /// Returns an array of summary objects. - List<_TreeTableSummaryBase>? getSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries); - - /// Marks all summaries dirty in this node and child nodes. - /// - /// * notifyEntrySummary - _required_ - if set to true notify entry summary. - void invalidateSummariesTopDown(bool notifyEntrySummary); -} - -/// Provides a `GetEmptySummaries` method. -abstract class _TreeTableEmptySummaryArraySourceBase { - /// Gets an array of summary objects. - /// - /// Returns an array of summary objects. - List<_TreeTableSummaryBase> getEmptySummaries(); -} - -/// Interface definition for an object that has summaries. -abstract class _TreeTableSummaryArraySourceBase { - /// Returns an array of summary objects. - /// - /// * emptySummaries - _required_ - An array of empty summary objects. - /// * changed - _required_ - Returns true if summaries were recalculated; - /// False if already cached. - /// - /// Returns An array of summary objects. - List<_TreeTableSummaryBase> getSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries, bool changed); - - /// Marks all summaries dirty in this object and parent nodes. - void invalidateSummariesBottomUp(); - - /// Marks all summaries dirty in this object only. - void invalidateSummary(); - - /// Marks all summaries dirty in this object and child nodes. - void invalidateSummariesTopDown(); -} - -/// A tree table branch with a counter. -class _TreeTableWithSummaryBranch extends _TreeTableBranch - with _TreeTableSummaryNodeBase { - _TreeTableWithSummaryBranch(_TreeTable tree) : super(tree); - - List<_TreeTableSummaryBase>? _summaries; - - ///Initializes a new instance of the `_TreeTableWithSummaryBranch` class. - /// - /// * tree - _required_ - Tree instance - /// - /// Gets the tree this branch belongs to. - _TreeTableWithSummary get treeTableWithSummary => - super.tree! as _TreeTableWithSummary; - - /// Gets a value indicating whether this node has summaries or not. - @override - bool get hasSummaries => _summaries != null; - - /// Gets the parent branch. - @override - _TreeTableWithSummaryBranch? get parent => super.parent != null - ? super.parent! as _TreeTableWithSummaryBranch - : null; - - /// Sets the parent branch. - @override - set parent(Object? value) { - if (value != null) { - super.parent = value as _TreeTableBranchBase; - } - } - - /// The left branch node cast to _TreeTableSummaryNodeBase. - /// - /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase? getLeftC() => getLeftNode(); - - /// The left branch node cast to _TreeTableSummaryNodeBase. - /// - /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase? getLeftNode() => - left! as _TreeTableSummaryNodeBase; - - /// Gets the right branch node cast to _TreeTableSummaryNodeBase. - /// - /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase? getRightC() => getRightNode(); - - /// Returns the left branch node cast to _TreeTableSummaryNodeBase. - _TreeTableSummaryNodeBase? getRightNode() => - right! as _TreeTableSummaryNodeBase; - - /// Gets an array of summary objects. - /// - /// * emptySummaries - _required_ - The empty summaries. - /// - /// Returns an array of summary objects. - @override - List<_TreeTableSummaryBase>? getSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries) { - if (tree!.isInitializing) { - return null; - } else if (_summaries == null) { - final List<_TreeTableSummaryBase>? left = - getLeftNode()?.getSummaries(emptySummaries); - final List<_TreeTableSummaryBase>? right = - getRightNode()?.getSummaries(emptySummaries); - if (left != null && right != null) { - int reuseLeft = 0; - int reuseRight = 0; - _summaries = <_TreeTableSummaryBase>[]; - for (int i = 0; i < _summaries!.length; i++) { - _summaries![i] = left[i].combine(right[i]); - // preserve memory optimization - if (reuseLeft == i || reuseRight == i) { - if (_MathHelper.referenceEquals(_summaries![i], left[i])) { - reuseLeft++; - } else if (_MathHelper.referenceEquals(_summaries![i], right[i])) { - reuseRight++; - } - } - } - - // preserve memory optimization - if (reuseLeft == _summaries!.length) { - _summaries = left; - } else if (reuseRight == _summaries!.length) { - _summaries = right; - } - } - } - - return _summaries; - } - - /// Walks up parent branches and reset summaries. - /// - /// * notifyParentRecordSource - _required_ - Boolean value - @override - void invalidateSummariesBottomUp(bool notifyParentRecordSource) { - if (tree!.isInitializing) { - return; - } - - _summaries = null; - if (parent != null) { - parent!.invalidateSummariesBottomUp(notifyParentRecordSource); - } else if (notifyParentRecordSource) { - if (tree != null && tree!.tag is _TreeTableSummaryArraySourceBase) { - final _TreeTableSummaryArraySourceBase _treeTag = - tree!.tag! as _TreeTableSummaryArraySourceBase; - _treeTag.invalidateSummariesBottomUp(); - } - } - } - - /// Marks all summaries dirty in this node and child nodes. - /// - /// * notifyCounterSource - _required_ - If set to true notify counter source. - @override - void invalidateSummariesTopDown(bool notifyCounterSource) { - if (tree!.isInitializing) { - return; - } - - _summaries = null; - getLeftNode()?.invalidateSummariesTopDown(notifyCounterSource); - getRightNode()?.invalidateSummariesTopDown(notifyCounterSource); - } -} - -/// A tree leaf with value and summary information. -class _TreeTableWithSummaryEntryBase extends _TreeTableEntry - with _TreeTableSummaryNodeBase { - static List<_TreeTableSummaryBase> emptySummaryArray = - <_TreeTableSummaryBase>[]; - List<_TreeTableSummaryBase>? _summaries; - - /// Gets the tree this leaf belongs to. - _TreeTableWithSummary get treeTableWithSummary => - tree! as _TreeTableWithSummary; - - /// Gets a value indicating whether the node has summaries or not. - @override - bool get hasSummaries => _summaries != null; - - /// Gets the parent branch. - @override - _TreeTableWithSummaryBranch? get parent { - if (super.parent is _TreeTableWithSummaryBranch) { - return super.parent! as _TreeTableWithSummaryBranch; - } else { - return null; - } - } - - /// Sets the parent branch. - @override - set parent(Object? value) { - if (value != null) { - super.parent = value as _TreeTableBranchBase; - } - } - - /// Gets the value as `_TreeTableSummaryArraySourceBase`. - /// - /// Returns the value as `_TreeTableSummaryArraySourceBase`. - _TreeTableSummaryArraySourceBase? getSummaryArraySource() { - if (value is _TreeTableSummaryArraySourceBase) { - return value! as _TreeTableSummaryArraySourceBase; - } else { - return null; - } - } - - /// Called from `GetSummaries` when called the first time after summaries - /// were invalidated. - /// - /// * emptySummaries - _required_ - The empty summaries. - /// - /// Returns an array of summary objects. - List<_TreeTableSummaryBase>? onGetSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries) { - List<_TreeTableSummaryBase>? summaries; - final _TreeTableSummaryArraySourceBase? summaryArraySource = - getSummaryArraySource(); - if (summaryArraySource != null) { - const bool summaryChanged = false; - summaries = - summaryArraySource.getSummaries(emptySummaries, summaryChanged); - } - - return summaries; - } - - /// Creates a branch that can hold this entry when new leaves are inserted - /// into the tree. - /// - /// * tree - _required_ - Tree instance - /// - /// Returns an instance for newly created TreeTable - @override - _TreeTableBranchBase? createBranch(_TreeTable tree) { - final Object _tree = tree; - if (_tree is _TreeTableWithSummaryBranch) { - return _tree; - } else { - return null; - } - } - - /// Gets an array of summary objects. - /// - /// * emptySummaries - _required_ - The empty summaries. - /// - /// Returns an array of summary objects. - @override - List<_TreeTableSummaryBase> getSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries) => - _summaries ??= onGetSummaries(emptySummaries) ?? emptySummaryArray; - - /// Walks up parent branches and reset summaries. - /// - /// * notifyParentRecordSource - _required_ - Boolean value - @override - void invalidateSummariesBottomUp(bool notifyParentRecordSource) { - _summaries = null; - if (value is _TreeTableSummaryArraySourceBase && tree != null) { - final _TreeTableSummaryArraySourceBase _tree = - tree!.tag! as _TreeTableSummaryArraySourceBase; - _tree.invalidateSummary(); - } - - if (parent != null) { - parent!.invalidateSummariesBottomUp(notifyParentRecordSource); - } else if (notifyParentRecordSource) { - if (tree != null && tree!.tag is _TreeTableSummaryArraySourceBase) { - final _TreeTableSummaryArraySourceBase _tree = - tree!.tag! as _TreeTableSummaryArraySourceBase; - _tree.invalidateSummariesBottomUp(); - } - } - } - - /// Marks all summaries dirty in this node and child nodes. - /// - /// * notifySummaryArraySource - _required_ - if set to true notify - /// summary array source. - @override - void invalidateSummariesTopDown(bool notifySummaryArraySource) { - _summaries = null; - if (notifySummaryArraySource) { - final _TreeTableSummaryArraySourceBase? summaryArraySource = - getSummaryArraySource(); - if (summaryArraySource != null) { - summaryArraySource.invalidateSummariesTopDown(); - } - - _summaries = null; - } - } -} - -/// A balanced tree with _TreeTableWithSummaryEntryBase entries. -class _TreeTableWithSummary extends _TreeTable { - /// Initializes a new instance of the `TreeTableWithSummary` class. - /// - /// * sorted - _required_ - Boolean value - _TreeTableWithSummary(bool sorted) : super(sorted); - - /// Gets a value indicating whether the tree has summaries or not. - bool get hasSummaries { - if (root == null) { - return false; - } - - final Object _root = root!; - if (_root is _TreeTableSummaryNodeBase) { - return _root.hasSummaries; - } else { - return false; - } - } - - /// Gets an array of summary objects. - /// - /// * emptySummaries - _required_ - summary value - /// - /// Returns an array of summary objects. - List<_TreeTableSummaryBase>? getSummaries( - _TreeTableEmptySummaryArraySourceBase emptySummaries) { - if (root == null) { - return emptySummaries.getEmptySummaries(); - } - - final Object _root = root!; - if (_root is _TreeTableSummaryNodeBase) { - return _root.getSummaries(emptySummaries); - } else { - return null; - } - } - - /// Marks all summaries dirty. - /// - /// * notifySummariesSource - _required_ - If set to true notify - /// summaries source. - void invalidateSummariesTopDown(bool notifySummariesSource) { - if (root != null) { - final Object _root = root!; - if (_root is _TreeTableSummaryNodeBase) { - _root.invalidateSummariesTopDown(notifySummariesSource); - } - } - } - - /// Appends an object. - /// - /// * value - _required_ - The item to be added to the end of the collection. - /// The value must not be a NULL reference (Nothing in Visual Basic). - /// - /// Returns the zero-based collection index at which the value has been added. - @override - int add(Object value) => super.add(value); - - /// Indicates whether an object belongs to the tree. - /// - /// * value - _required_ - Value needs to be check - /// - /// Returns a boolean value indicates whether an object belongs to the tree. - @override - bool contains(Object? value) { - if (value == null) { - return false; - } - - return super.contains(value); - } - - /// Copies the elements of this tree to an array. - /// - /// * array - _required_ - Collection of array - /// * index - _required_ - Index value - @override - void copyTo(List array, int index) { - super.copyTo(array, index); - } - - /// Gets a strongly typed enumerator. - /// - /// Returns a strongly typed enumerator. - @override - _TreeTableWithSummaryEnumerator getEnumerator() => - _TreeTableWithSummaryEnumerator(this); - - /// Inserts a `_TreeTableWithSummaryEntryBase` object at the specified index. - /// - /// * index - _required_ - Tree index - /// * value - _required_ - Value needs to be insert - @override - void insert(int index, Object value) { - super.insert(index, value); - } - - /// Gets the index of an object in the tree. - /// - /// * value - _required_ - value needs to be find the index - /// - /// Returns Returns the index of an object in the tree. - @override - int indexOf(Object value) => super.indexOf(value); - - /// Removes an object from the tree. - /// - /// * value - _required_ - Value needs to be remove - /// - /// Returns the removed value. - @override - bool remove(Object? value) => super.remove(value); - - /// Gets a _TreeTableWithSummaryEntryBase. - /// - /// * index - _required_ - Index value - /// - /// Returns the new instance for _TreeTableWithSummaryEntryBase - @override - _TreeTableWithSummaryEntryBase? operator [](int index) { - if (super[index] is _TreeTableWithSummaryEntryBase) { - return super[index]! as _TreeTableWithSummaryEntryBase; - } else { - return null; - } - } - - /// Sets a _TreeTableWithSummaryEntryBase. - @override - void operator []=(int index, Object value) { - super[index] = value; - } -} - -/// A strongly typed enumerator for the `TreeTableWithSummary` collection. - -class _TreeTableWithSummaryEnumerator extends _TreeTableEnumerator { - /// Initializes a new instance of the `TreeTableWithSummaryEnumerator` class. - /// - /// * tree - _required_ - Tree instance - _TreeTableWithSummaryEnumerator(_TreeTable tree) : super(tree); - - /// Gets the current `TreeTableWithSummary` object. - @override - _TreeTableWithSummaryEntryBase? get current { - if (super.current is _TreeTableWithSummaryEntryBase) { - return super.current! as _TreeTableWithSummaryEntryBase; - } else { - return null; - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_range_counter_collection.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/distance_counter.dart similarity index 54% rename from packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_range_counter_collection.dart rename to packages/syncfusion_flutter_datagrid/lib/src/grid_common/distance_counter.dart index 822781f9a..8e1ecb0ef 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_range_counter_collection.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/distance_counter.dart @@ -1,4 +1,495 @@ -part of datagrid; +import 'line_size_host.dart'; +import 'tree_table.dart'; +import 'utility_helper.dart'; + +/// A collection of entities for which distances need to counted. +/// +/// The collection provides methods for mapping from a distance position to +/// an entity and vice versa. +/// For example, in a scrollable grid control you have rows with different +/// heights. +/// Use this collection to determine the total height for all rows in the grid, +/// quickly determine the row index for a given point and also quickly determine +/// the point at which a row is displayed. This also allows a mapping between +/// the scrollbars value and the rows or columns associated with that value. +abstract class DistanceCounterCollectionBase { + /// Create the DistanceCounterCollection Base + DistanceCounterCollectionBase( + {int count = 0, double defaultDistance = 0.0, double totalDistance = 0.0}) + : _count = count, + _defaultDistance = defaultDistance, + _totalDistance = totalDistance; + + /// Gets the raw number of entities (lines, rows or columns). + /// + /// Returns the raw number of entities (lines, rows or columns). + int get count => _count; + int _count = 0; + + /// Sets the raw number of entities (lines, rows or columns). + set count(int value) { + if (value == _count) { + return; + } + + _count = value; + } + + /// Gets the default distance (row height or column width) an entity spans. + /// + /// Returns the default distance (row height or column width) an entity spans. + double get defaultDistance => _defaultDistance; + double _defaultDistance = 0; + + /// Sets the default distance (row height or column width) an entity spans. + set defaultDistance(double value) { + if (value == _defaultDistance) { + return; + } + + _defaultDistance = value; + } + + /// Gets the total distance all entities span + /// (e.g. total height of all rows in grid). + /// + /// Returns the total distance all entities span + /// (e.g. total height of all rows in grid). + double get totalDistance => _totalDistance; + double _totalDistance = 0; + set totalDistance(double value) { + if (value == _totalDistance) { + return; + } + + _totalDistance = value; + } + + /// Clears this instance. + void clear(); + + /// Connects a nested distance collection with a parent. + /// + /// * treeTableCounterSource - _required_ - The `_TreeTableCounterSourceBase` + /// representing the nested tree table visible counter source. + void connectWithParent(TreeTableCounterSourceBase treeTableCounterSource); + + /// Gets the aligned scroll value which is the starting point of the entity + /// found at the given distance position. + /// + /// * point - _required_ - The point. + /// + /// Returns the starting point of the entity found at the + /// given distance position. + double getAlignedScrollValue(double point); + + /// Gets the nested entities at a given index. If the index does not hold + /// a nested distances collection the method returns null. + /// + /// * index - _required_ - The index. + /// Returns - _required_ - the nested entities at a given index or null. + DistanceCounterCollectionBase? getNestedDistances(int index); + + /// Gets the distance position of the next entity after a given point. + /// + /// * point - _required_ - The point after which the next entity is + /// to be found. + /// Returns - _required_ - the distance position of the next entity + /// after a given point. + double getNextScrollValue(double point); + + /// Gets the distance position of the entity preceding a given point. + /// If the point is in between entities, the starting point of + /// the matching entity is returned. + /// + /// * point - _required_ - The point of the entity preceding a given point. + /// + /// Returns the distance position of the entity preceding a given point. + double getPreviousScrollValue(double point); + + /// Gets the cumulated count of previous distances for the + /// entity at the specific index. + /// (e.g. return pixel position for a row index). + /// + /// * index - _required_ - The entity index. + /// + /// Returns the cumulated count of previous distances for the + /// entity at the specific index. + double getCumulatedDistanceAt(int index); + + /// Gets the next visible index. Skip subsequent entities for which + /// the distance is 0.0 and return the next entity. + /// + /// * index - _required_ - The index. + /// + /// Returns the next visible index from the given index. + int getNextVisibleIndex(int index); + + /// Gets the previous visible index. Skip previous entities for which + /// the distance is 0.0 and return the previous entity. + /// + /// * index - _required_ - The index. + /// Returns the previous visible index from the given index. + int getPreviousVisibleIndex(int index); + + /// Inserts entities in the collection from the given index. + /// + /// * insertAt - _required_ - The index of the first entity to be inserted. + /// * count - _required_ - The number of entities to be inserted. + void insert(int insertAt, int count); + + /// Gets the index of an entity in this collection for which + /// the cumulated count of previous distances is greater than or equal to + /// the specified cumulatedDistance. (e.g. return row index for + /// pixel position). + /// + /// * cumulatedDistance - _required_ - The cumulated count + /// of previous distances. + /// + /// Returns the index of an entity in this collection for which + /// the cumulated count of previous distances is greater than or equal to + /// the specified cumulatedDistance. + int indexOfCumulatedDistance(double cumulatedDistance); + + /// Removes entities in the collection from the given index. + /// + /// * removeAt - _required_ - Index of the first entity to be removed. + /// * count - _required_ - The number of entities to be removed. + void remove(int removeAt, int count); + + /// Resets the range by restoring the default distance for all + /// entries in the specified range. + /// + /// * from The index for the first entity. + /// * to The raw index for the last entity. + void resetRange(int from, int to); + + /// Assigns a collection with nested entities to an item. + /// + /// * index - _required_ - The index. + /// * nestedCollection - _required_ - The nested collection. + void setNestedDistances( + int index, DistanceCounterCollectionBase? nestedCollection); + + /// Hides a specified range of entities (lines, rows or columns). + /// + /// * from - _required_ - The index for the first entity. + /// * to - _required_ - The raw index for the last entity. + /// * distance - _required_ - The distance. + void setRange(int from, int to, double distance); + + /// Gets the distance for an entity from the given index. + /// + /// * index - _required_ - The index for the entity. + /// + /// Returns the distance for an entity from the given index. + double operator [](int index) => this[index]; + + /// Sets the distance for an entity from the given index. + void operator []=(int index, double value) => this[index] = value; +} + +/// A collection of entities that is shared with a parent collection for which +/// distances need to counted. +/// +/// The collection only is a subset for a specific range in +/// the parent distance collection. When you change the size of an element in +/// this collection the change will +/// also be reflected in the parent collection and vice versa. +// Need to check whether the distance is DistanceCounterSubset or not in +// PixelScrollAxis's SetFooterLineCount method. +// ignore: unused_element +class DistanceCounterSubset extends DistanceCounterCollectionBase + with DistancesHostBase { + /// Initializes a new instance of the DistanceCounterSubset class. + /// + /// * trackedParentCollection - _required_ - The parent collection for which + /// a subset is "tracked". + DistanceCounterSubset(DistanceCounterCollectionBase trackedParentCollection) + : super(count: 0) { + _trackDCC = trackedParentCollection; + } + + late DistanceCounterCollectionBase _trackDCC; + + /// start index + int start = 0; + + /// Gets an object that implements the `Distances` property. + @override + DistanceCounterCollectionBase get distances => _distances; + + /// Gets an distance the `Distances` property. + DistanceCounterCollectionBase get _distances => this; + + /// Gets the ending index of this collection in the parent collection. + /// + /// Returns the ending index of this collection in the parent collection. + int get end => start + count - 1; + + /// Sets the raw number of entities + @override + set count(int value) { + if (count != value) { + super.count = value; + } + } + + /// Gets the default distance (row height or column width) an entity spans. + /// + /// Returns the default distance (row height or column width) an entity spans. + @override + double get defaultDistance => _trackDCC.defaultDistance; + + /// Sets the dafault distnace an entity spans. + @override + set defaultDistance(double value) { + _trackDCC.defaultDistance = value; + } + + /// Gets the total distance all entities span + /// (e.g. total height of all rows in grid). + /// + /// Returns the total distance all entities span + /// (e.g. total height of all rows in grid). + @override + double get totalDistance { + if (count == 0) { + return 0; + } + + return _trackDCC.getCumulatedDistanceAt(end) - + _trackDCC.getCumulatedDistanceAt(start) + + _trackDCC[end]; + } + + /// Restores the distances in the parent collection for this subset to + /// their default distance. + @override + void clear() { + _trackDCC.resetRange(start, end); + } + + /// This method is not supported for DistanceCounterSubset. + /// + /// * treeTableCounterSource - _required_ - The nested tree + /// table visible counter source. + @override + void connectWithParent(TreeTableCounterSourceBase treeTableCounterSource) { + connectWithParentBase(treeTableCounterSource); + } + + /// This method is not supported for DistanceCounterSubset. + /// + /// * treeTableCounterSource - _required_ - The nested tree + /// table visible counter source. + void connectWithParentBase( + TreeTableCounterSourceBase treeTableCounterSource) { + throw Exception('Do not use DistanceCounterSubset as nested collection!'); + } + + /// Gets the aligned scroll value which is the starting point of the entity + /// found at the given distance position. + /// + /// * point - _required_ - The point. + /// + /// Returns the starting point of the entity found at + /// the given distance position. + @override + double getAlignedScrollValue(double point) { + final double offset = _trackDCC.getPreviousScrollValue(start.toDouble()); + final double d = _trackDCC.getAlignedScrollValue(point + offset); + if (d == double.nan || d < offset || d - offset > totalDistance) { + return double.nan; + } + + return d - offset; + } + + /// Gets the cumulated count of previous distances for the entity at + /// the specific index. (e.g. return pixel position for a row index). + /// + /// * index - _required_ - The entity index. + /// + /// Returns the cumulated count of previous distances for the + /// entity at the specific index. + @override + double getCumulatedDistanceAt(int index) => + _trackDCC.getCumulatedDistanceAt(index + start) - + _trackDCC.getCumulatedDistanceAt(start); + + /// Gets the nested entities at a given index. + /// + /// If the index does not hold a nested distances collection + /// the method returns null. + /// + /// * index - _required_ - The index. + /// + /// Returns the nested entities at a given index or null. + @override + DistanceCounterCollectionBase? getNestedDistances(int index) => + _trackDCC.getNestedDistances(index + start); + + /// Gets the next visible index. + /// + /// Skip subsequent entities for which the distance is 0.0 + /// and return the next entity. + /// + /// * index - _required_ - The index. + /// + /// Returns the next visible index from the given index. + @override + int getNextVisibleIndex(int index) { + final int n = _trackDCC.getNextVisibleIndex(index + start); + if (n > end) { + return -1; + } + + return n - start; + } + + /// Gets the distance position of the next entity after a given point. + /// + /// * point - _required_ - The point after which the next entity + /// is to be found. + /// + /// Returns the distance position of the next entity after a given point. + @override + double getNextScrollValue(double point) { + final double offset = _trackDCC.getCumulatedDistanceAt(start); + final double d = _trackDCC.getNextScrollValue(point + offset); + if (d == double.nan || d < offset || d - offset > totalDistance) { + return double.nan; + } + + return d - offset; + } + + /// Gets the previous visible index. + /// + /// Skip previous entities for which the distance is 0.0 and return + /// the previous entity. + /// + /// * index - _required_ - The index. + /// + /// Returns the previous visible index from the given index. + @override + int getPreviousVisibleIndex(int index) { + final int n = _trackDCC.getPreviousVisibleIndex(index + start); + if (n < start) { + return -1; + } + + return n - start; + } + + /// Gets the distance position of the entity preceding a given point. + /// + /// If the point is in between entities, the starting point of + /// the matching entity is returned. + /// + /// * point - _required_ - The point of the entity preceding a given point. + /// + /// Returns the distance position of the entity preceding a given point. + @override + double getPreviousScrollValue(double point) { + final double offset = _trackDCC.getCumulatedDistanceAt(start); + final double d = _trackDCC.getPreviousScrollValue(point + offset); + if (d == double.nan || d < offset || d - offset > totalDistance) { + return double.nan; + } + + return d - offset; + } + + /// Inserts entities in the collection from the given index. + /// + /// * insertAt - _required_ - The index of the first entity to be inserted. + /// * count - _required_ - The number of entities to be inserted. + @override + void insert(int insertAt, int count) { + _trackDCC.insert(insertAt + start, count); + } + + /// Gets the index of an entity in this collection for which + /// the cumulated count of previous distances is greater than or equal to + /// the specified cumulatedDistance. (e.g. return row index for + /// pixel position). + /// + /// * cumulatedDistance - _required_ - The cumulated count of previous + /// distances. + /// + /// Returns the index of an entity in this collection for which + /// the cumulated count of previous distances is greater than or equal to + /// the specified cumulatedDistance. + @override + int indexOfCumulatedDistance(double cumulatedDistance) { + if (count == 0 && cumulatedDistance == 0) { + return 0; + } + + final int n = _trackDCC.indexOfCumulatedDistance( + cumulatedDistance + _trackDCC.getCumulatedDistanceAt(start)); + if (n > end || n < start) { + return -1; + } + + return n - start; + } + + /// Removes entities in the collection from the given index. + /// + /// * removeAt - _required_ - Index of the first entity to be removed. + /// * count - _required_ - The number of entities to be removed. + @override + void remove(int removeAt, int count) { + _trackDCC.remove(removeAt + start, count); + } + + /// Resets the range by restoring the default distance for all + /// entries in the specified range. + /// + /// * from - _required_ - The index for the first entity. + /// * to - _required_ - The raw index for the last entity. + @override + void resetRange(int from, int to) { + _trackDCC.resetRange(from + start, to + start); + } + + /// Hides a specified range of entities (lines, rows or columns). + /// + /// * from - _required_ - The index for the first entity. + /// * to - _required_ - The raw index for the last entity. + /// * distance - _required_ - The distance. + @override + void setRange(int from, int to, double distance) { + _trackDCC.setRange(from + start, to + start, distance); + } + + /// Assigns a collection with nested entities to an item. + /// + /// * index - _required_ - The index. + /// * nestedCollection - _required_ - The nested collection. + @override + void setNestedDistances( + int index, DistanceCounterCollectionBase? nestedCollection) { + _trackDCC.setNestedDistances(index + start, nestedCollection); + } + + /// Gets the distance for an entity from the given index. + /// + /// * index - _required_ - The index for the entity. + /// + /// Returns the distance for an entity from the given index. + @override + double operator [](int index) => _trackDCC[index + start]; + + /// Sets the distance for an entity from the given index. + @override + void operator []=(int index, double value) { + _trackDCC[index + start] = value; + } +} /// A collection of entities for which distances need to be counted. The /// collection provides methods for mapping from a distance position to @@ -21,47 +512,49 @@ part of datagrid; /// DefaultSize and Range 2 from 100-100 with size 10. This approach makes /// this collection work very efficient with grid scenarios where often /// many rows have the same height. -class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { +class DistanceRangeCounterCollection extends DistanceCounterCollectionBase { /// Initializes a new instance of the DistanceRangeCounterCollection class. - _DistanceRangeCounterCollection(); + DistanceRangeCounterCollection(); /// Initializes a new instance of the DistanceRangeCounterCollection class. /// /// * paddingDistance - _required_ - The padding distance. - _DistanceRangeCounterCollection.fromPaddingDistance(this.paddingDistance) { - count = 0; - _defaultDistance = 1.0; - final _DistanceLineCounter startPos = _DistanceLineCounter(0, 0); - _rbTree = _DistanceLineCounterTree(startPos, false); + DistanceRangeCounterCollection.fromPaddingDistance(this.paddingDistance) + : super(defaultDistance: 1.0, count: 0) { + final DistanceLineCounter startPos = DistanceLineCounter(0, 0); + _rbTree = DistanceLineCounterTree(startPos, false); } - late _DistanceLineCounterTree _rbTree; + late DistanceLineCounterTree _rbTree; /// Gets the padding distance of the counter collection. double paddingDistance = 0; + /// Get the internal distance line count. int get internalCount { if (_rbTree.getCount() == 0) { return 0; } - final _DistanceLineCounter counter = _rbTree.getCounterTotal()!; + final DistanceLineCounter counter = _rbTree.getCounterTotal()!; return counter.lineCount; } + /// Set the internal distance line count. set internalCount(int value) { if (value >= internalCount) { ensureTreeCount(value); } } + /// Get the internal total distance. double get internalTotalDistance { final int treeCount = internalCount; if (treeCount == 0) { return paddingDistance; } - final _DistanceLineCounter counter = _rbTree.getCounterTotal()!; + final DistanceLineCounter counter = _rbTree.getCounterTotal()!; return counter.distance + paddingDistance; } @@ -69,12 +562,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// /// Returns the default distance (row height or column width) an entity spans. @override - double get defaultDistance => _defaultDistance; - - @override - set defaultDistance(double value) { - _defaultDistance = value; - } + double get defaultDistance => super.defaultDistance; /// Gets the total distance all entities span (e.g. total height of all rows /// in grid). @@ -87,6 +575,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return internalTotalDistance + (delta * defaultDistance); } + /// Check the range of line in between from and to. void checkRange(String paramName, int from, int to, int actualValue) { if (actualValue < from || actualValue > to) { throw Exception( @@ -94,18 +583,20 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } } - _DistanceLineCounterEntry createTreeTableEntry(double distance, int count) { - final _DistanceLineCounterEntry entry = _DistanceLineCounterEntry() - ..value = _DistanceLineCounterSource(distance, count) + /// + DistanceLineCounterEntry createTreeTableEntry(double distance, int count) { + final DistanceLineCounterEntry entry = DistanceLineCounterEntry() + ..value = DistanceLineCounterSource(distance, count) ..tree = _rbTree; return entry; } + /// void ensureTreeCount(int count) { final int treeCount = internalCount; final int insert = count - treeCount; if (insert > 0) { - final _DistanceLineCounterEntry entry = + final DistanceLineCounterEntry entry = createTreeTableEntry(defaultDistance, insert); _rbTree.add(entry); } @@ -128,10 +619,10 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } if (index >= count) { final int delta = index - count; - final _DistanceLineCounter counter = _rbTree.getCounterTotal()!; + final DistanceLineCounter counter = _rbTree.getCounterTotal()!; return counter.distance + (delta * defaultDistance); } else { - final _LineIndexEntryAt e = initDistanceLine(index, false); + final LineIndexEntryAt e = initDistanceLine(index, false); e.rbEntryPosition = e.rbEntry!.getCounterPosition!; return e.rbEntryPosition!.distance + ((index - e.rbEntryPosition!.lineCount) * @@ -156,13 +647,13 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return internalCount + delta; } - final _DistanceLineCounter searchPosition = - _DistanceLineCounter(cumulatedDistance, 0); - final _DistanceLineCounterEntry? rbEntry = + final DistanceLineCounter searchPosition = + DistanceLineCounter(cumulatedDistance, 0); + final DistanceLineCounterEntry? rbEntry = _rbTree.getEntryAtCounterPositionWithThreeArgs( - searchPosition, _DistanceLineCounterKind.distance, false); - final _DistanceLineCounterSource? rbValue = rbEntry!.value; - final _DistanceLineCounter? rbEntryPosition = rbEntry.getCounterPosition; + searchPosition, DistanceLineCounterKind.distance, false); + final DistanceLineCounterSource? rbValue = rbEntry!.value; + final DistanceLineCounter? rbEntryPosition = rbEntry.getCounterPosition; if (rbValue != null && rbEntryPosition != null && rbValue.singleLineDistance > 0) { @@ -175,12 +666,13 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return (rbEntryPosition?.lineCount ?? 0) + delta; } - _LineIndexEntryAt initDistanceLine( + /// + LineIndexEntryAt initDistanceLine( int lineIndex, bool determineEntryPosition) { - final _LineIndexEntryAt e = _LineIndexEntryAt() - ..searchPosition = _DistanceLineCounter(0, lineIndex); + final LineIndexEntryAt e = LineIndexEntryAt() + ..searchPosition = DistanceLineCounter(0, lineIndex); e.rbEntry = _rbTree.getEntryAtCounterPositionWithThreeArgs( - e.searchPosition!, _DistanceLineCounterKind.lines, false); + e.searchPosition!, DistanceLineCounterKind.lines, false); e.rbValue = e.rbEntry!.value!; e.rbEntryPosition = null; if (determineEntryPosition) { @@ -200,19 +692,19 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { void insertBase(int insertAt, int count, double distance) { this.count += count; - if (insertAt >= internalCount && distance == _defaultDistance) { + if (insertAt >= internalCount && distance == defaultDistance) { return; } ensureTreeCount(insertAt); - final _LineIndexEntryAt e = initDistanceLine(insertAt, false); + final LineIndexEntryAt e = initDistanceLine(insertAt, false); if (e.rbValue!.singleLineDistance == distance) { e.rbValue!.lineCount += count; e.rbEntry!.invalidateCounterBottomUp(true); } else { - final _DistanceLineCounterEntry? rbEntry0 = split(insertAt); - final _DistanceLineCounterEntry entry = + final DistanceLineCounterEntry? rbEntry0 = split(insertAt); + final DistanceLineCounterEntry entry = createTreeTableEntry(distance, count); if (rbEntry0 == null) { _rbTree.add(entry); @@ -232,21 +724,22 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final double start = getCumulatedDistanceAt(index); final double end = getCumulatedDistanceAt(index + 1); final double distance = end - start; - final _DistanceCounterCollectionBase? nested = getNestedDistances(index); + final DistanceCounterCollectionBase? nested = getNestedDistances(index); if (nested != null && distance != nested.totalDistance) { - final _LineIndexEntryAt e = initDistanceLine(index, false); + final LineIndexEntryAt e = initDistanceLine(index, false); e.rbEntry!.invalidateCounterBottomUp(true); } } - void merge(_DistanceLineCounterEntry entry, bool checkPrevious) { - final _DistanceLineCounterSource value = entry.value!; - _DistanceLineCounterEntry? previousEntry; + /// + void merge(DistanceLineCounterEntry entry, bool checkPrevious) { + final DistanceLineCounterSource value = entry.value!; + DistanceLineCounterEntry? previousEntry; if (checkPrevious) { previousEntry = _rbTree.getPreviousEntry(entry); } - final _DistanceLineCounterEntry? nextEntry = _rbTree.getNextEntry(entry); + final DistanceLineCounterEntry? nextEntry = _rbTree.getNextEntry(entry); bool dirty = false; if (previousEntry != null && @@ -268,20 +761,21 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } } + /// int removeHelper(int removeAt, int count) { if (removeAt >= internalCount) { return _rbTree.getCount(); } - _DistanceLineCounterEntry? entry = split(removeAt); + DistanceLineCounterEntry? entry = split(removeAt); split(removeAt + count); late int n; if (entry != null) { n = _rbTree.indexOf(entry); } - final List<_DistanceLineCounterEntry> toDelete = - <_DistanceLineCounterEntry>[]; + final List toDelete = + []; int total = 0; while (total < count && entry != null) { @@ -297,19 +791,20 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return n; } - _DistanceLineCounterEntry? split(int index) { + /// + DistanceLineCounterEntry? split(int index) { if (index >= internalCount) { return null; } - final _LineIndexEntryAt e = initDistanceLine(index, true); + final LineIndexEntryAt e = initDistanceLine(index, true); if (e.rbEntryPosition!.lineCount != index) { final int count1 = index - e.rbEntryPosition!.lineCount; final int count2 = e.rbValue!.lineCount - count1; e.rbValue!.lineCount = count1; - final _DistanceLineCounterEntry rbEntry2 = + final DistanceLineCounterEntry rbEntry2 = createTreeTableEntry(e.rbValue!.singleLineDistance, count2); _rbTree.insert(_rbTree.indexOf(e.rbEntry!) + 1, rbEntry2); e.rbEntry!.invalidateCounterBottomUp(true); @@ -327,10 +822,10 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// Connects a nested distance collection with a parent. /// - /// * treeTableCounterSource - _required_ - The `_TreeTableCounterSourceBase` + /// * treeTableCounterSource - _required_ - The `TreeTableCounterSourceBase` /// representing the nested tree table visible counter source. @override - void connectWithParent(_TreeTableCounterSourceBase treeTableCounterSource) { + void connectWithParent(TreeTableCounterSourceBase treeTableCounterSource) { _rbTree.tag = treeTableCounterSource; } @@ -348,7 +843,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final double delta = point - nestedStart; if (delta > 0) { - final _DistanceCounterCollectionBase? nestedDcc = + final DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { final double r = nestedDcc.getAlignedScrollValue(delta); @@ -378,15 +873,15 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// * index - _required_ - The index. /// Returns the nested entities at a given index or null. @override - _DistanceCounterCollectionBase? getNestedDistances(int index) { + DistanceCounterCollectionBase? getNestedDistances(int index) { if (index >= internalCount) { return null; } checkRange('index', 0, count - 1, index); - final _LineIndexEntryAt e = initDistanceLine(index, false); - final _NestedDistanceCounterCollectionSource vcs = - e.rbValue! as _NestedDistanceCounterCollectionSource; + final LineIndexEntryAt e = initDistanceLine(index, false); + final NestedDistanceCounterCollectionSource vcs = + e.rbValue! as NestedDistanceCounterCollectionSource; return vcs.nestedDistances; } @@ -405,7 +900,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { final double nestedStart = getCumulatedDistanceAt(index); final double delta = point - nestedStart; - final _DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); + final DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { final double r = nestedDcc.getNextScrollValue(delta); if (!(r == double.nan) && r >= 0 && r < nestedDcc.totalDistance) { @@ -437,7 +932,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return index + 1; } - final _LineIndexEntryAt e = initDistanceLine(index + 1, true); + final LineIndexEntryAt e = initDistanceLine(index + 1, true); if (e.rbValue!.singleLineDistance > 0) { if (index - e.rbEntryPosition!.lineCount < e.rbValue!.lineCount) { return index + 1; @@ -472,7 +967,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { double delta = point - nestedStart; if (delta > 0) { - final _DistanceCounterCollectionBase? nestedDcc = + final DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { final double r = nestedDcc.getPreviousScrollValue(delta); @@ -489,7 +984,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { if (index >= 0 && index < count) { nestedStart = getCumulatedDistanceAt(index); - final _DistanceCounterCollectionBase? nestedDcc = + final DistanceCounterCollectionBase? nestedDcc = getNestedDistances(index); if (nestedDcc != null) { delta = nestedDcc.totalDistance; @@ -520,7 +1015,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return index - 1; } - final _LineIndexEntryAt e = initDistanceLine(index - 1, false); + final LineIndexEntryAt e = initDistanceLine(index - 1, false); if (e.rbValue!.singleLineDistance > 0) { return index - 1; } @@ -549,9 +1044,9 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// the specified cumulatedDistance. @override int indexOfCumulatedDistance(double cumulatedDistance) { - final _DistanceLineCounter _distanceLineCounter = - _rbTree.getStartCounterPosition() as _DistanceLineCounter; - if (cumulatedDistance < _distanceLineCounter.distance) { + final DistanceLineCounter distanceLineCounter = + _rbTree.getStartCounterPosition() as DistanceLineCounter; + if (cumulatedDistance < distanceLineCounter.distance) { return -1; } @@ -600,7 +1095,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } final int count = to - from + 1; - setRange(from, count, _defaultDistance); + setRange(from, count, defaultDistance); } /// Hides a specified range of entities (lines, rows or columns). @@ -618,14 +1113,14 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return; } - if (from >= internalCount && (distance == _defaultDistance)) { + if (from >= internalCount && (distance == defaultDistance)) { return; } final int count = to - from + 1; ensureTreeCount(from); final int n = removeHelper(from, count); - final _DistanceLineCounterEntry rb = createTreeTableEntry(distance, count); + final DistanceLineCounterEntry rb = createTreeTableEntry(distance, count); _rbTree.insert(n, rb); merge(rb, true); } @@ -636,7 +1131,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { /// * nestedCollection - _required_ - The nested collection. @override void setNestedDistances( - int index, _DistanceCounterCollectionBase? nestedCollection) { + int index, DistanceCounterCollectionBase? nestedCollection) { checkRange('index', 0, count - 1, index); if (getNestedDistances(index) != nestedCollection) { @@ -644,16 +1139,16 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { ensureTreeCount(index + 1); } - final _DistanceLineCounterEntry entry = split(index)!; + final DistanceLineCounterEntry entry = split(index)!; split(index + 1); if (nestedCollection != null) { - final _NestedDistanceCounterCollectionSource vcs = - _NestedDistanceCounterCollectionSource( + final NestedDistanceCounterCollectionSource vcs = + NestedDistanceCounterCollectionSource( this, nestedCollection, entry); entry.value = vcs; } else { - entry.value = _DistanceLineCounterSource(0, 1); + entry.value = DistanceLineCounterSource(0, 1); } entry.invalidateCounterBottomUp(true); @@ -673,7 +1168,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { return defaultDistance; } - final _LineIndexEntryAt e = initDistanceLine(index, false); + final LineIndexEntryAt e = initDistanceLine(index, false); return e.rbValue!.singleLineDistance; } @@ -689,7 +1184,7 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { ensureTreeCount(count); } - final _DistanceLineCounterEntry entry = split(index)!; + final DistanceLineCounterEntry entry = split(index)!; split(index + 1); entry.value!.singleLineDistance = value; entry.invalidateCounterBottomUp(true); @@ -698,11 +1193,18 @@ class _DistanceRangeCounterCollection extends _DistanceCounterCollectionBase { } /// Initializes the LineIndexEntryAt class -class _LineIndexEntryAt { - _DistanceLineCounter? searchPosition; - _DistanceLineCounterEntry? rbEntry; - _DistanceLineCounterSource? rbValue; - _DistanceLineCounter? rbEntryPosition; +class LineIndexEntryAt { + /// + DistanceLineCounter? searchPosition; + + /// + DistanceLineCounterEntry? rbEntry; + + /// + DistanceLineCounterSource? rbValue; + + /// + DistanceLineCounter? rbEntryPosition; } /// An object that maintains a collection of nested distances and wires @@ -711,17 +1213,16 @@ class _LineIndexEntryAt { /// The object is used by the DistanceCounterCollection.SetNestedDistances /// method to associated the nested distances with an index in the /// parent collection. -class _NestedDistanceCounterCollectionSource - extends _DistanceLineCounterSource { +class NestedDistanceCounterCollectionSource extends DistanceLineCounterSource { /// Initializes a new instance of the NestedDistanceCounterCollectionSource /// class. /// /// * parentDistances - _required_ - The parent distances. /// * nestedDistances - _required_ - The nested distances. /// * entry - _required_ - The entry. - _NestedDistanceCounterCollectionSource( - _DistanceCounterCollectionBase parentDistances, - _DistanceCounterCollectionBase nestedDistances, + NestedDistanceCounterCollectionSource( + DistanceCounterCollectionBase parentDistances, + DistanceCounterCollectionBase nestedDistances, this.entry) : super(0, 1) { _parentDistances = parentDistances; @@ -730,19 +1231,21 @@ class _NestedDistanceCounterCollectionSource nestedDistances.connectWithParent(this); } - _DistanceCounterCollectionBase? _nestedDistances; - _DistanceCounterCollectionBase? _parentDistances; - _DistanceLineCounterEntry? entry; + DistanceCounterCollectionBase? _nestedDistances; + DistanceCounterCollectionBase? _parentDistances; + + /// + DistanceLineCounterEntry? entry; /// Gets the nested distances. /// /// Returns the nested distances. - _DistanceCounterCollectionBase? get nestedDistances => _nestedDistances; + DistanceCounterCollectionBase? get nestedDistances => _nestedDistances; /// Gets the parent distances. /// /// Returns the parent distances. - _DistanceCounterCollectionBase? get parentDistances => _parentDistances; + DistanceCounterCollectionBase? get parentDistances => _parentDistances; /// Gets the distance of a single line. /// @@ -759,7 +1262,7 @@ class _NestedDistanceCounterCollectionSource /// Returns the `_TreeTableVisibleCounter` object with counters. @override - _TreeTableCounterBase getCounter() => _DistanceLineCounter( + TreeTableCounterBase getCounter() => DistanceLineCounter( _nestedDistances == null ? 0 : _nestedDistances!.totalDistance, 1); /// Marks all counters dirty in this object and parent nodes. @@ -773,21 +1276,23 @@ class _NestedDistanceCounterCollectionSource /// Returns a string describing the state of the object. @override String toString() => - '_NestedDistanceCounterCollectionSource LineCount = $lineCount, SingleLineDistance = $singleLineDistance '; + 'NestedDistanceCounterCollectionSource LineCount = $lineCount, SingleLineDistance = $singleLineDistance '; } /// An object that counts objects that are marked "Visible". It implements -/// the _TreeTableCounterSourceBase interface and +/// the TreeTableCounterSourceBase interface and /// creates a [DistanceLineCounter]. -class _DistanceLineCounterSource extends _TreeTableCounterSourceBase { +class DistanceLineCounterSource extends TreeTableCounterSourceBase { /// Initializes a new instance of the [DistanceLineCounterSource] class. /// /// * visibleCount - _required_ - The visible count. /// * lineCount - _required_ - The line count. - _DistanceLineCounterSource(this.singleLineDistance, this.lineCount); + DistanceLineCounterSource(this.singleLineDistance, this.lineCount); + /// int lineCount = 0; + /// double singleLineDistance = 0.0; /// Marks all counters dirty in this object and child nodes. @@ -803,20 +1308,20 @@ class _DistanceLineCounterSource extends _TreeTableCounterSourceBase { /// Returns the `_TreeTableVisibleCounter` object with counters. @override - _TreeTableCounterBase getCounter() => - _DistanceLineCounter(singleLineDistance * lineCount, lineCount); + TreeTableCounterBase getCounter() => + DistanceLineCounter(singleLineDistance * lineCount, lineCount); /// Returns a string describing the state of the object. @override String toString() => - '_DistanceLineCounterSource LineCount = $lineCount, SingleLineDistance = $singleLineDistance'; + 'DistanceLineCounterSource LineCount = $lineCount, SingleLineDistance = $singleLineDistance'; } /// A collection of integers used to specify various counter kinds. -class _DistanceLineCounterKind { +class DistanceLineCounterKind { /// Prevents a default instance of the [DistanceLineCounterKind] class /// from being created. - _DistanceLineCounterKind(); + DistanceLineCounterKind(); /// Visible Counter. static const int distance = 0x8000; @@ -826,19 +1331,19 @@ class _DistanceLineCounterKind { } /// A counter that counts objects that are marked "Visible". -class _DistanceLineCounter extends _TreeTableCounterBase { +class DistanceLineCounter extends TreeTableCounterBase { /// Initializes a new instance of the [DistanceLineCounter] class. /// /// * distance - _required_ - The distance. /// * lineCount - _required_ - The line count. - _DistanceLineCounter(double distance, int lineCount) { + DistanceLineCounter(double distance, int lineCount) { _distance = distance; _lineCount = lineCount; } /// Initializes an empty DistanceLineCounter that represents zero /// visible elements. - _DistanceLineCounter.empty() { + DistanceLineCounter.empty() { _distance = 0; _lineCount = 0; } @@ -878,8 +1383,8 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns the new object @override - _TreeTableCounterBase combine(_TreeTableCounterBase? other, int cookie) { - if (other is _DistanceLineCounter) { + TreeTableCounterBase combine(TreeTableCounterBase? other, int cookie) { + if (other is DistanceLineCounter) { return combineBase(other, cookie); } else { return combineBase(null, cookie); @@ -894,17 +1399,17 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns a new object which is the combination of the counter values /// of this counter object with the values of another counter object. - _DistanceLineCounter combineBase(_DistanceLineCounter? other, int cookie) { - if (other == null || other.isEmpty(_MathHelper.maxvalue)) { + DistanceLineCounter combineBase(DistanceLineCounter? other, int cookie) { + if (other == null || other.isEmpty(maxvalue)) { return this; } - if (isEmpty(_MathHelper.maxvalue)) { + if (isEmpty(maxvalue)) { return other; } final double addedValue = distance + other.distance; - return _DistanceLineCounter(addedValue, lineCount + other.lineCount); + return DistanceLineCounter(addedValue, lineCount + other.lineCount); } /// Compares this counter with another counter. A cookie can specify @@ -915,8 +1420,8 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns the compared value @override - double compare(_TreeTableCounterBase? other, int cookie) { - if (other is _DistanceLineCounter) { + double compare(TreeTableCounterBase? other, int cookie) { + if (other is DistanceLineCounter) { return compareBase(other, cookie); } else { return compareBase(null, cookie); @@ -931,18 +1436,18 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// Returns a value indicating the comparison of the current object /// and the given object. - double compareBase(_DistanceLineCounter? other, int cookie) { + double compareBase(DistanceLineCounter? other, int cookie) { if (other == null) { return 0; } int cmp = 0; - if ((cookie & _DistanceLineCounterKind.distance) != 0) { + if ((cookie & DistanceLineCounterKind.distance) != 0) { cmp = distance.compareTo(other.distance); } - if (cmp == 0 && (cookie & _DistanceLineCounterKind.lines) != 0) { + if (cmp == 0 && (cookie & DistanceLineCounterKind.lines) != 0) { cmp = lineCount.compareTo(other.lineCount); } @@ -957,7 +1462,7 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// Returns the value of the counter. @override double getValue(int cookie) { - if (cookie == _DistanceLineCounterKind.lines) { + if (cookie == DistanceLineCounterKind.lines) { return _lineCount.toDouble(); } return _distance; @@ -970,24 +1475,23 @@ class _DistanceLineCounter extends _TreeTableCounterBase { /// /// True if the specified cookie is empty, otherwise false. @override - bool isEmpty(int cookie) => - compare(_DistanceLineCounter.empty(), cookie) == 0; + bool isEmpty(int cookie) => compare(DistanceLineCounter.empty(), cookie) == 0; /// A `string` that represents the current `object`. /// /// Returns a `string` that represents the current `object`. @override String toString() => - '_DistanceLineCounter LineCount = $lineCount Distance = $distance'; + 'DistanceLineCounter LineCount = $lineCount Distance = $distance'; } /// Initializes a new instance of the DistanceLineCounterTree class. -class _DistanceLineCounterTree extends _TreeTableWithCounter { +class DistanceLineCounterTree extends TreeTableWithCounter { /// Initializes a new instance of the DistanceLineCounterTree class. /// /// * startPos - _required_ - The start position. /// * sorted - _required_ - A boolean value indicating whether sorted. - _DistanceLineCounterTree(_DistanceLineCounter startPos, bool sorted) + DistanceLineCounterTree(DistanceLineCounter startPos, bool sorted) : super(startPos, sorted); /// Appends the given object. @@ -1010,9 +1514,9 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns the total number of counters. @override - _DistanceLineCounter? getCounterTotal() { - if (super.getCounterTotal() is _DistanceLineCounter) { - return super.getCounterTotal()! as _DistanceLineCounter; + DistanceLineCounter? getCounterTotal() { + if (super.getCounterTotal() is DistanceLineCounter) { + return super.getCounterTotal()! as DistanceLineCounter; } else { return null; } @@ -1026,13 +1530,13 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns an entry at the specified counter position. @override - _DistanceLineCounterEntry? getEntryAtCounterPosition( + DistanceLineCounterEntry? getEntryAtCounterPosition( Object searchPosition, int cookie) { - if (searchPosition is _TreeTableCounterBase && + if (searchPosition is TreeTableCounterBase && super.getEntryAtCounterPosition(searchPosition, cookie) - is _DistanceLineCounterEntry) { + is DistanceLineCounterEntry) { return super.getEntryAtCounterPosition(searchPosition, cookie)! - as _DistanceLineCounterEntry; + as DistanceLineCounterEntry; } else { return null; } @@ -1048,14 +1552,14 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// same SearchPosition. /// /// Returns an entry at the specified counter position. - _DistanceLineCounterEntry? getEntryAtCounterPositionWithThreeArgs( + DistanceLineCounterEntry? getEntryAtCounterPositionWithThreeArgs( Object searchPosition, int cookie, bool preferLeftMost) { - if (searchPosition is _TreeTableCounterBase && + if (searchPosition is TreeTableCounterBase && super.getEntryAtCounterPositionwithThreeParameter( searchPosition, cookie, preferLeftMost) - is _DistanceLineCounterEntry) { + is DistanceLineCounterEntry) { return super.getEntryAtCounterPositionwithThreeParameter( - searchPosition, cookie, preferLeftMost)! as _DistanceLineCounterEntry; + searchPosition, cookie, preferLeftMost)! as DistanceLineCounterEntry; } else { return null; } @@ -1067,10 +1571,10 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns the next entry. @override - _DistanceLineCounterEntry? getNextEntry(Object? current) { - if (current is _TreeTableEntryBase && - super.getNextEntry(current) is _DistanceLineCounterEntry) { - return super.getNextEntry(current)! as _DistanceLineCounterEntry; + DistanceLineCounterEntry? getNextEntry(Object? current) { + if (current is TreeTableEntryBase && + super.getNextEntry(current) is DistanceLineCounterEntry) { + return super.getNextEntry(current)! as DistanceLineCounterEntry; } else { return null; } @@ -1085,13 +1589,13 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the subsequent entry in the collection for which /// the specific counter is not empty. @override - _DistanceLineCounterEntry? getNextNotEmptyCounterEntry( + DistanceLineCounterEntry? getNextNotEmptyCounterEntry( Object current, int cookie) { - if (current is _TreeTableEntryBase && + if (current is TreeTableEntryBase && super.getNextNotEmptyCounterEntry(current, cookie) - is _DistanceLineCounterEntry) { + is DistanceLineCounterEntry) { return super.getNextNotEmptyCounterEntry(current, cookie)! - as _DistanceLineCounterEntry; + as DistanceLineCounterEntry; } else { return null; } @@ -1105,10 +1609,10 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the next entry in the collection for which /// CountVisible counter is not empty. @override - _DistanceLineCounterEntry? getNextVisibleEntry(Object current) { - if (current is _TreeTableWithCounterEntry && - super.getNextVisibleEntry(current) is _DistanceLineCounterEntry) { - return super.getNextVisibleEntry(current)! as _DistanceLineCounterEntry; + DistanceLineCounterEntry? getNextVisibleEntry(Object current) { + if (current is TreeTableWithCounterEntry && + super.getNextVisibleEntry(current) is DistanceLineCounterEntry) { + return super.getNextVisibleEntry(current)! as DistanceLineCounterEntry; } else { return null; } @@ -1120,10 +1624,10 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns the previous entry. @override - _DistanceLineCounterEntry? getPreviousEntry(Object current) { - if (current is _TreeTableEntryBase && - super.getPreviousEntry(current) is _DistanceLineCounterEntry) { - return super.getPreviousEntry(current)! as _DistanceLineCounterEntry; + DistanceLineCounterEntry? getPreviousEntry(Object current) { + if (current is TreeTableEntryBase && + super.getPreviousEntry(current) is DistanceLineCounterEntry) { + return super.getPreviousEntry(current)! as DistanceLineCounterEntry; } else { return null; } @@ -1138,13 +1642,13 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the previous entry in the collection for which the /// specific counter is not empty. @override - _DistanceLineCounterEntry? getPreviousNotEmptyCounterEntry( + DistanceLineCounterEntry? getPreviousNotEmptyCounterEntry( Object current, int cookie) { - if (current is _TreeTableEntryBase && + if (current is TreeTableEntryBase && super.getPreviousNotEmptyCounterEntry(current, cookie) - is _DistanceLineCounterEntry) { + is DistanceLineCounterEntry) { return super.getPreviousNotEmptyCounterEntry(current, cookie)! - as _DistanceLineCounterEntry; + as DistanceLineCounterEntry; } else { return null; } @@ -1158,11 +1662,11 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// Returns the previous entry in the collection for which CountVisible /// counter is not empty. @override - _DistanceLineCounterEntry? getPreviousVisibleEntry(Object current) { - if (current is _TreeTableWithCounterEntry && - super.getPreviousVisibleEntry(current) is _DistanceLineCounterEntry) { + DistanceLineCounterEntry? getPreviousVisibleEntry(Object current) { + if (current is TreeTableWithCounterEntry && + super.getPreviousVisibleEntry(current) is DistanceLineCounterEntry) { return super.getPreviousVisibleEntry(current)! - as _DistanceLineCounterEntry; + as DistanceLineCounterEntry; } else { return null; } @@ -1197,9 +1701,9 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { /// /// Returns the distance line counter entry for the given index. @override - _DistanceLineCounterEntry? operator [](int index) { - if (super[index] is _DistanceLineCounterEntry) { - return super[index]! as _DistanceLineCounterEntry; + DistanceLineCounterEntry? operator [](int index) { + if (super[index] is DistanceLineCounterEntry) { + return super[index]! as DistanceLineCounterEntry; } else { return null; } @@ -1213,14 +1717,14 @@ class _DistanceLineCounterTree extends _TreeTableWithCounter { } /// Initializes a new instance of the [DistanceLineCounterTree] class. -class _DistanceLineCounterEntry extends _TreeTableWithCounterEntry { +class DistanceLineCounterEntry extends TreeTableWithCounterEntry { /// The cumulative position of this node. /// /// Returns the cumulative position of this node. @override - _DistanceLineCounter? get getCounterPosition { - if (super.getCounterPosition is _DistanceLineCounter) { - return super.getCounterPosition! as _DistanceLineCounter; + DistanceLineCounter? get getCounterPosition { + if (super.getCounterPosition is DistanceLineCounter) { + return super.getCounterPosition! as DistanceLineCounter; } else { return null; } @@ -1230,9 +1734,9 @@ class _DistanceLineCounterEntry extends _TreeTableWithCounterEntry { /// /// Returns the distance line counter source. @override - _DistanceLineCounterSource? get value { - if (super.value is _DistanceLineCounterSource) { - return super.value! as _DistanceLineCounterSource; + DistanceLineCounterSource? get value { + if (super.value is DistanceLineCounterSource) { + return super.value! as DistanceLineCounterSource; } else { return null; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart new file mode 100644 index 000000000..69e871861 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/enums.dart @@ -0,0 +1,41 @@ +/// Used by Tree Table to balance the tree with algorithm based +/// on Red - Black tree. +enum TreeTableNodeColor { + /// TreeTableNodeColoe.red, will represented the Red color. + red, + + /// TreeTableNodeColoe.black, will represented the Black color. + black +} + +/// Type of scroll axis regions +/// +/// A scroll axis has three regions: Header, Body and Footer. +enum ScrollAxisRegion { + /// - ScrollAxisRegion.header specifies the header region + /// (at top or left side). + header, + + /// - ScrollAxisRegion.body Specifies the body region + /// (center between header and footer). + body, + + /// - ScrollAxisRegion.footer Specifies the footer region + /// (at bottom or right side). + footer +} + +/// Corner side enumeration. +enum CornerSide { + /// Includes both Left and right side or top and bottom side. + both, + + /// Left side alone. + left, + + /// Right side alone. + right, + + /// Bottom side alone. + bottom +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/range_changed_event.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/event_args.dart similarity index 70% rename from packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/range_changed_event.dart rename to packages/syncfusion_flutter_datagrid/lib/src/grid_common/event_args.dart index 51dac4ceb..38b89750f 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/range_changed_event.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/event_args.dart @@ -1,14 +1,10 @@ -part of datagrid; - /// Provides data for the [ILineSizeHost.LineSizeChanged] event. -class _RangeChangedArgs { - _RangeChangedArgs(); - +class RangeChangedArgs { /// Initializes a new instance of the RangeChangedArgs class. /// /// * from - _required_ - The start index. /// * to - _required_ - The end index. - _RangeChangedArgs.fromArgs(int from, int to) { + RangeChangedArgs.fromArgs(int from, int to) { _from = from; _to = to; } @@ -19,7 +15,7 @@ class _RangeChangedArgs { /// * to - _required_ - The end index. /// * oldSize - _required_ - The old size. /// * newSize - _required_ - The new size. - _RangeChangedArgs.fromRangeChangedArgs( + RangeChangedArgs.fromRangeChangedArgs( int from, int to, double oldSize, double newSize) { _from = from; _to = to; @@ -54,14 +50,12 @@ class _RangeChangedArgs { } /// Provides data for the [ILineSizeHost.LinesRemoved] event. -class _LinesRemovedArgs { - _LinesRemovedArgs(); - +class LinesRemovedArgs { /// Initializes a new instance of the [LinesRemovedArgs] class. /// /// * removeAt - _required_ - The index to remove. /// * count - _required_ - The count of the lines. - _LinesRemovedArgs.fromArgs(int removeAt, int count) { + LinesRemovedArgs.fromArgs(int removeAt, int count) { _removeAt = removeAt; _count = count; } @@ -81,14 +75,12 @@ class _LinesRemovedArgs { } /// Provides data for the [ILineSizeHost.LinesInserted] event. -class _LinesInsertedArgs { - _LinesInsertedArgs(); - +class LinesInsertedArgs { /// Initializes a new instance of the [_LinesInsertedArgs] class. /// /// * insertAt - _required_ - The index to insert. /// * count - _required_ - The count of the items to be inserted. - _LinesInsertedArgs.fromArgs(int insertAt, int count) { + LinesInsertedArgs.fromArgs(int insertAt, int count) { _insertAt = insertAt; _count = count; } @@ -108,15 +100,15 @@ class _LinesInsertedArgs { } /// Provides data for the [ILineSizeHost.DefaultLineSizeChanged] event. -class _DefaultLineSizeChangedArgs { +class DefaultLineSizeChangedArgs { /// Initializes a new instance of the DefaultLineSizeChangedArgs class. - _DefaultLineSizeChangedArgs(); + DefaultLineSizeChangedArgs(); /// Initializes a new instance of the DefaultLineSizeChangedArgs class. /// /// * oldValue - _required_ - The old line size. /// * name - _required_ - newValue The new line size. - _DefaultLineSizeChangedArgs.fromArgs(double oldValue, double newValue) { + DefaultLineSizeChangedArgs.fromArgs(double oldValue, double newValue) { _oldValue = oldValue; _newValue = newValue; } @@ -136,15 +128,13 @@ class _DefaultLineSizeChangedArgs { } /// Provides data for the [ILineSizeHost.LineHiddenChanged] event. -class _HiddenRangeChangedArgs { - _HiddenRangeChangedArgs(); - +class HiddenRangeChangedArgs { /// Initializes a new instance of the [HiddenRangeChangedArgs] class. /// /// * from - _required_ - The start index of the hidden range. /// * to - _required_ - The end index of the hidden range. /// * hide - _required_ - hide value - _HiddenRangeChangedArgs.fromArgs(int from, int to, bool hide) { + HiddenRangeChangedArgs.fromArgs(int from, int to, bool hide) { _from = from; _to = to; _hide = hide; @@ -173,7 +163,7 @@ class _HiddenRangeChangedArgs { } /// Defines the various constants for the scroll changed action. -enum _ScrollChangedAction { +enum ScrollChangedAction { /// - ScrollChangedAction.linesInserted, Specifies that the scroll is /// changed as the lines are inserted. linesInserted, @@ -204,20 +194,76 @@ enum _ScrollChangedAction { } /// Provides data for the `ScrollAxisBase.Changed` event. -class _ScrollChangedArgs { - _ScrollChangedArgs(); +class ScrollChangedArgs { + /// + ScrollChangedArgs(); /// Initializes a new instance of the ScrollChangedArgs class. /// /// * action - _required_ - The `ScrollChangedAction`. - _ScrollChangedArgs.fromArgs(_ScrollChangedAction action) { + ScrollChangedArgs.fromArgs(ScrollChangedAction action) { _scrollChangedAction = action; } - _ScrollChangedAction? _scrollChangedAction; + ScrollChangedAction? _scrollChangedAction; /// Gets the scroll changed action. /// /// Returns the `ScrollChangedAction`. - _ScrollChangedAction? get action => _scrollChangedAction; + ScrollChangedAction? get action => _scrollChangedAction; +} + +/// Provides data for the `ScrollInfo.ValueChanging` event. +class ValueChangingArgs { + /// Initializes a new instance of the ValueChangingArgs class. + /// + /// * newValue - _required_ - The new value. + /// * oldValue - _required_ - The old value. + ValueChangingArgs(double newValue, double oldValue) { + _newValue = newValue; + _oldValue = oldValue; + } + + double _newValue = 0.0; + double _oldValue = 0.0; + + /// Gets a value indicating whether to cancel the value change in scroll bar. + /// + /// Returns a boolean value indicating whether to cancel + /// the value change in scroll bar. + bool get cancel => _cancel; + bool _cancel = false; + + /// Sets a value indicating whether to cancel the value change in scroll bar. + set cancel(bool value) { + if (value == _cancel) { + return; + } + + _cancel = value; + } + + /// Gets the new value. + /// + /// Returns the new value. + double get newValue => _newValue; + + /// Gets the old value. + /// + /// Returns the old value. + double get oldValue => _oldValue; +} + +/// +class PropertyChangedArgs { + /// Occurs when name of the property was changed. + PropertyChangedArgs(String propertyName) { + _propertyName = propertyName; + } + + /// Gets the name of the property that changed. + /// + /// Returns the property name. + String get propertyName => _propertyName; + String _propertyName = ''; } diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_collection.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/line_size_host.dart similarity index 55% rename from packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_collection.dart rename to packages/syncfusion_flutter_datagrid/lib/src/grid_common/line_size_host.dart index 77e804e36..22008342d 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_collection.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/line_size_host.dart @@ -1,4 +1,370 @@ -part of datagrid; +import 'dart:math'; + +import 'distance_counter.dart'; +import 'event_args.dart'; +import 'scroll_axis.dart'; +import 'utility_helper.dart'; + +/// Returns the DefaultLineSizeChangedArgs used by the +/// [onDefaultLineSizeChanged] event. +typedef _DefaultLineSizeChangedCallback = void Function( + DefaultLineSizeChangedArgs _defaultLineSizeChangedArgs); + +/// Returns the LineCountChangedArgs used by the +/// [onLineCountChanged][onHeaderLineCountChanged][onFooderLineCountChanged] +/// event. +typedef _LineCountChangedCallback = void Function(); + +/// Returns the HiddenRangeChangedArgs used by the [onLineHiddenChanged] event. +typedef _LineHiddenChangedCallback = void Function( + HiddenRangeChangedArgs _hiddenRangeChangedArgs); + +/// Returns the LinesInsertedArgs used by the [onLinesInserted] event. +typedef _LinesInsertedCallback = void Function( + LinesInsertedArgs _linesInsertedArgs); + +/// Returns the LinesRemovedArgs used by the [onLinesRemoved] event. +typedef _LinesRemovedCallback = void Function( + LinesRemovedArgs _linesRemovedArgs); + +/// Returns the RangeChangedArgs used by the [onLineSizeChanged] event. +typedef _LineSizeChangedCallback = void Function( + RangeChangedArgs rangeChangedArgs); + +/// A collection that manages lines with varying height and hidden state. +/// +/// It has properties for header and footer lines, total line count, default +/// size of a line and also lets you add nested collections. +/// +/// _Note:_ This is common for header, footer, total line count and default +/// size of line. +abstract class LineSizeHostBase { + /// Occurs when the default line size changed. + _DefaultLineSizeChangedCallback? onDefaultLineSizeChanged; + + /// Occurs when the footer line count was changed. + _LineCountChangedCallback? onFooterLineCountChanged; + + /// Occurs when the header line count was changed. + _LineCountChangedCallback? onHeaderLineCountChanged; + + /// Occurs when a lines size was changed. + _LineSizeChangedCallback? onLineSizeChanged; + + /// Occurs when a lines hidden state changed. + _LineHiddenChangedCallback? onLineHiddenChanged; + + /// Occurs when the line count was changed. + _LineCountChangedCallback? onLineCountChanged; + + /// Occurs when lines were inserted. + _LinesInsertedCallback? onLinesInserted; + + /// Occurs when lines were removed. + _LinesRemovedCallback? onLinesRemoved; + + /// Returns the default line size.. + double getDefaultLineSize(); + + /// Gets the footer line count. + /// + /// Returns the footer line count. + int getFooterLineCount(); + + /// Gets the header line count. + /// + /// Returns the header line count. + int getHeaderLineCount(); + + /// Gets the boolean value indicating the hidden state for the line + /// with given index. + /// + /// * index - _required_ - The index of the line for which the hidden state + /// is to be obtained. + /// * repeatValueCount - _required_ - The number of subsequent lines with + /// same state. + /// + /// Returns the boolean value indicating the hidden state for a line. + List getHidden(int index, int repeatValueCount); + + /// Returns the line count. + int getLineCount(); + + /// Gets the size of the line at the given index. + /// + /// * index - _required_ - The index of the line for which the size is to + /// be obtained. + /// * repeatValueCount - _required_ - The number of subsequent values + /// with same size. + /// + /// Retuns the size of the line at the given index. + List getSize(int index, int repeatValueCount); + + /// Initializes the scroll axis. + /// + /// * scrollAxis - _required_ - The scroll axis. + void initializeScrollAxis(ScrollAxisBase scrollAxis); +} + +/// An object that implements the `Distances` property. +mixin DistancesHostBase { + /// Gets the distances of the lines. + /// + /// Returns the distances of the lines. + DistanceCounterCollectionBase? get distances; +} + +/// An object that implements the `GetDistances` method. +abstract class NestedDistancesHostBase { + /// Gets the nested distances, if a line contains a nested lines collection, + /// otherwise null. + /// + /// * line - _required_ - The line at which the distances is to be obtained. + /// + /// Returns the nested distances, if a line contains a nested lines + /// collection, otherwise null. + DistanceCounterCollectionBase? getDistances(int line); +} + +/// A collection that manages lines with varying height and hidden state. +/// +/// It has properties for header and footer lines, total line count, default +/// size of a line and also lets you add nested collections. Methods +/// are provided for changing the values and getting the total extent. +/// +/// _Note:_ This is common for header, footer, total line count and default +/// size of line. +abstract class EditableLineSizeHostBase extends LineSizeHostBase { + /// + EditableLineSizeHostBase( + {double defaultLineSize = 0.0, + int footerLineCount = 0, + int headerLineCount = 0, + int lineCount = 0, + bool supportsInsertRemove = false, + bool supportsNestedLines = false, + double totalExtent = 0.0}) + : _defaultLineSize = defaultLineSize, + _footerLineCount = footerLineCount, + _headerLineCount = headerLineCount, + _lineCount = lineCount, + _supportsInsertRemove = supportsInsertRemove, + _supportsNestedLines = supportsNestedLines, + _totalExtent = totalExtent; + + /// Gets the default size of lines. + /// + /// Returns the default size of lines. + double get defaultLineSize => _defaultLineSize; + double _defaultLineSize = 0.0; + + /// Sets the default size of lines. + set defaultLineSize(double value) { + if (value == _defaultLineSize) { + return; + } + + _defaultLineSize = value; + } + + /// Gets the footer line count. + /// + /// Returns the footer line count. + int get footerLineCount => _footerLineCount; + int _footerLineCount = 0; + + /// Sets the footer line count. + set footerLineCount(int value) { + if (value == _footerLineCount) { + return; + } + + _footerLineCount = value; + } + + /// Gets the header line count. + /// + /// Returns the header line count. + int get headerLineCount => _headerLineCount; + int _headerLineCount = 0; + + /// Sets the header line count. + set headerLineCount(int value) { + if (value == _headerLineCount) { + return; + } + + _headerLineCount = value; + } + + /// Gets the line count. + /// + /// Returns the line count. + int get lineCount => _lineCount; + int _lineCount = 0; + + /// Sets the line count. + set lineCount(int value) { + if (value == _lineCount) { + return; + } + + _lineCount = value; + } + + /// Gets a value indicating whether the host supports inserting and + /// removing lines. + /// + /// Returns the boolean value indicating whether the host supports inserting + /// and removing lines. + bool get supportsInsertRemove => _supportsInsertRemove; + bool _supportsInsertRemove = false; + set supportsInsertRemove(bool value) { + if (value == _supportsInsertRemove) { + return; + } + _supportsInsertRemove = value; + } + + /// Gets a value indicating whether the host supports nesting or not. + /// + /// Returns a boolean value indicating whether the host supports nesting. + bool get supportsNestedLines => _supportsNestedLines; + bool _supportsNestedLines = false; + set supportsNestedLines(bool value) { + if (value == _supportsNestedLines) { + return; + } + + _supportsNestedLines = value; + } + + /// Gets the total extent which is the total of all line sizes. + /// + /// Returns the total extent which is the total of all line sizes + /// or `double.NaN`. + /// + /// Note: This property only works if the `DistanceCounterCollection` has + /// been setup for pixel scrolling, + /// otherwise it returns `double.NaN`. + double get totalExtent => _totalExtent; + double _totalExtent = 0; + set totalExtent(double value) { + if (value == _totalExtent) { + return; + } + + _totalExtent = value; + } + + /// Creates the object which holds temporary state when moving lines. + /// + /// Returns the object which holds temporary state when moving lines. + EditableLineSizeHostBase createMoveLines(); + + /// Gets the nested lines at the given index. + /// + /// * index - _required_ - The index at which the nested lines is to + /// be obtained. + /// + /// Returns the `IEditableLineSizeHost` representing the nested lines. + EditableLineSizeHostBase? getNestedLines(int index); + + /// Insert the given number of lines at the given index. + /// + /// * insertAtLine - _required_ - The index of the first line to insert. + /// * count - _required_ - The count of the lines to be inserted. + /// * moveLines - _required_ - A container with saved state from a preceding + /// `RemoveLines` call when lines should be moved. When it is null, + /// empty lines with default size are inserted. + void insertLines( + int insertAtLine, int count, EditableLineSizeHostBase? moveLines); + + /// Removes a number of lines at the given index. + /// + /// * removeAtLine - _required_ - The index of the first line to be removed. + /// * count - _required_ - The count of the lines to be removed. + /// * moveLines - _required_ - A container to save state for a subsequent + /// `InsertLines` call when lines should be moved. + void removeLines( + int removeAtLine, int count, EditableLineSizeHostBase? moveLines); + + /// Sets the hidden state for the given range of lines. + /// + /// * from - _required_ - The start index of the line for which + /// the hidden state to be set. + /// * to - _required_ - The end index of the line for which the + /// hidden state to be set. + /// * hide - _required_ - A boolean value indicating whether to + /// hide the lines. If set to true + /// hide the lines. + void setHidden(int from, int to, bool hide); + + /// Sets the nested lines at the given index. + /// + /// * index - _required_ - The index at which the nested lines is to be added. + /// * nestedLines - _required_ - The nested lines to be added. If parameter + /// is null the line will + /// be converted to a normal (not nested) line with default line size. + void setNestedLines(int index, EditableLineSizeHostBase nestedLines); + + /// Sets the line size for the range of lines. + /// + /// * from - _required_ - The start index of the line for which the + /// line size is to be set. + /// * to - _required_ - The end index of the line for which the + /// line size is to be set. + /// * size - _required_ - The line size to be set to the given range of lines. + void setRange(int from, int to, double size); + + /// Gets the line size at the specified index. + /// + /// * index - _required_ - index value + /// Returns the line size at the specified index. + double operator [](int index) => this[index]; + + /// Sets the line size at the specified index. + void operator []=(int index, double value) => this[index] = value; +} + +/// An object that implements the `PaddingDistance` property +/// and `DeferRefresh` method. +abstract class PaddedEditableLineSizeHostBase extends EditableLineSizeHostBase { + /// + PaddedEditableLineSizeHostBase( + {double paddingDistance = 0.0, + double defaultLineSize = 0.0, + int footerLineCount = 0, + int headerLineCount = 0, + int lineCount = 0, + bool supportsInsertRemove = false, + bool supportsNestedLines = false, + double totalExtent = 0.0}) + : _paddingDistance = paddingDistance, + super( + defaultLineSize: defaultLineSize, + footerLineCount: footerLineCount, + headerLineCount: headerLineCount, + lineCount: lineCount, + supportsInsertRemove: supportsInsertRemove, + supportsNestedLines: supportsNestedLines, + totalExtent: totalExtent); + + /// Gets the padding distance for the line. + /// + /// Returns the padding distance for the line. + double get paddingDistance => _paddingDistance; + double _paddingDistance = 0.0; + + /// Sets the padding distance for the line. + set paddingDistance(double value) { + if (value == _paddingDistance) { + return; + } + + _paddingDistance = value; + } +} /// A collection that manages lines with varying height and hidden state. /// @@ -7,26 +373,26 @@ part of datagrid; /// /// _Note:_ This is common for header, footer, total line count and /// default size of line. -class _LineSizeCollection extends _PaddedEditableLineSizeHostBase - with _DistancesHostBase, _NestedDistancesHostBase { +class LineSizeCollection extends PaddedEditableLineSizeHostBase + with DistancesHostBase, NestedDistancesHostBase { ///Initializes a new instance of the [LineSizeCollection] class. - _LineSizeCollection() { - _headerLineCount = 0; - _footerLineCount = 0; - _lineCount = 0; - _defaultLineSize = 1.0; - _paddingDistance = 0.0; - } - - final _SortedRangeValueList _lineSizes = - _SortedRangeValueList.from(-1); - _DistanceCounterCollectionBase? _distances; + LineSizeCollection() + : super( + headerLineCount: 0, + footerLineCount: 0, + lineCount: 0, + defaultLineSize: 1.0, + paddingDistance: 0.0); + + final SortedRangeValueList _lineSizes = + SortedRangeValueList.from(-1); + DistanceCounterCollectionBase? _distances; int _isSuspendUpdates = 0; - _SortedRangeValueList _lineHidden = - _SortedRangeValueList.from(false); - Map _lineNested = {}; + SortedRangeValueList _lineHidden = + SortedRangeValueList.from(false); + Map _lineNested = {}; - set distances(_DistanceCounterCollectionBase? newValue) { + set distances(DistanceCounterCollectionBase? newValue) { if (_distances == newValue) { return; } @@ -40,10 +406,10 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Returns the distances collection which is used internally for mapping /// from a point position to a line index and vice versa. @override - _DistanceCounterCollectionBase? get distances { + DistanceCounterCollectionBase? get distances { if (_distances == null) { _distances = - _DistanceRangeCounterCollection.fromPaddingDistance(paddingDistance); + DistanceRangeCounterCollection.fromPaddingDistance(paddingDistance); initializeDistances(); } @@ -53,9 +419,9 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Sets the default size of lines. @override set defaultLineSize(double value) { - if (_defaultLineSize != value) { - final double savedValue = _defaultLineSize; - _defaultLineSize = value; + if (defaultLineSize != value) { + final double savedValue = defaultLineSize; + super.defaultLineSize = value; if (isSuspendUpdates) { return; @@ -66,8 +432,8 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (onDefaultLineSizeChanged != null) { - final _DefaultLineSizeChangedArgs defaultLineSizeChangedArgs = - _DefaultLineSizeChangedArgs.fromArgs(savedValue, _defaultLineSize); + final DefaultLineSizeChangedArgs defaultLineSizeChangedArgs = + DefaultLineSizeChangedArgs.fromArgs(savedValue, defaultLineSize); onDefaultLineSizeChanged!(defaultLineSizeChangedArgs); } } @@ -76,8 +442,8 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Sets the footer line count. @override set footerLineCount(int value) { - if (_footerLineCount != value) { - _footerLineCount = value; + if (footerLineCount != value) { + super.footerLineCount = value; if (onFooterLineCountChanged != null) { onFooterLineCountChanged!(); } @@ -87,28 +453,29 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Sets the header line count. @override set headerLineCount(int value) { - if (_headerLineCount != value) { - _headerLineCount = value; + if (headerLineCount != value) { + super.headerLineCount = value; if (onHeaderLineCountChanged != null) { onHeaderLineCountChanged!(); } } } + /// bool get isSuspendUpdates => _isSuspendUpdates > 0; /// Sets the line count. @override set lineCount(int value) { - if (_lineCount != value) { - _lineCount = value; + if (lineCount != value) { + super.lineCount = value; if (isSuspendUpdates) { return; } if (_distances != null) { - _distances!.count = _lineCount; + _distances!.count = lineCount; } } } @@ -116,15 +483,15 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// sets the padding distance for the line. @override set paddingDistance(double value) { - if (_paddingDistance != value) { - _paddingDistance = value; + if (paddingDistance != value) { + super.paddingDistance = value; if (isSuspendUpdates) { return; } _distances = - _DistanceRangeCounterCollection.fromPaddingDistance(paddingDistance); + DistanceRangeCounterCollection.fromPaddingDistance(paddingDistance); initializeDistances(); } } @@ -156,7 +523,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase double get totalExtent { // This only works if the DistanceCollection has been // setup for pixel scrolling. - if (distances != null && distances!.defaultDistance == _defaultLineSize) { + if (distances != null && distances!.defaultDistance == defaultLineSize) { return _distances!.totalDistance; } return double.nan; @@ -166,7 +533,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// /// Returns the object which holds temporary state when moving lines. @override - _EditableLineSizeHostBase createMoveLines() => _LineSizeCollection(); + EditableLineSizeHostBase createMoveLines() => LineSizeCollection(); /// Gets the default line size. /// @@ -182,9 +549,9 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// Returns the nested distances, if a line contains a nested lines /// collection, otherwise null. @override - _DistanceCounterCollectionBase? getDistances(int line) { + DistanceCounterCollectionBase? getDistances(int line) { final Object? nestedLines = getNestedLines(line); - if (nestedLines is _DistancesHostBase) { + if (nestedLines is DistancesHostBase) { return nestedLines.distances; } else { return null; @@ -216,7 +583,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// /// Returns the [IEditableLineSizeHost] representing the nested lines. @override - _EditableLineSizeHostBase? getNestedLines(int index) { + EditableLineSizeHostBase? getNestedLines(int index) { if (_lineNested.containsKey(index)) { return _lineNested[index]; } @@ -235,7 +602,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase @override List getSize(int index, int repeatValueCount) { repeatValueCount = 1; - final _EditableLineSizeHostBase? nested = getNestedLines(index); + final EditableLineSizeHostBase? nested = getNestedLines(index); if (nested != null) { return [nested.totalExtent, repeatValueCount]; } @@ -259,6 +626,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase return [rangeValue[0], rangeValue[1]]; } + /// List getRange(int index, int repeatValueCount) { repeatValueCount = 1; @@ -280,16 +648,17 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase if (size >= 0) { return [size, repeatValueCount]; } - return [_defaultLineSize, repeatValueCount]; + return [defaultLineSize, repeatValueCount]; } + /// void initializeDistances() { if (distances != null) { distances! ..clear() ..count = getLineCount() ..defaultDistance = defaultLineSize; - _lineNested.forEach((int key, _LineSizeCollection value) { + _lineNested.forEach((int key, LineSizeCollection value) { int repeatSizeCount = -1; final List hiddenValue = getHidden(key, repeatSizeCount); final bool hide = hiddenValue[0] as bool; @@ -301,7 +670,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } }); - for (final _RangeValuePair entry in _lineSizes.rangeValues) { + for (final RangeValuePair entry in _lineSizes.rangeValues) { final double entryValue = entry.value as double; if (entryValue != -2) { _distances!.setRange(entry.start.toInt(), entry.end.toInt(), @@ -309,7 +678,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } } - for (final _RangeValuePair entry in _lineHidden.rangeValues) { + for (final RangeValuePair entry in _lineHidden.rangeValues) { final bool entryValue = entry.value as bool; if (entryValue is bool && entryValue) { setRange(entry.start.toInt(), entry.end.toInt(), 0.0); @@ -336,16 +705,16 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// size are inserted. @override void insertLines( - int insertAtLine, int count, _EditableLineSizeHostBase? moveLines) { - final _LineSizeCollection? _moveLines = - moveLines != null ? moveLines as _LineSizeCollection : null; + int insertAtLine, int count, EditableLineSizeHostBase? moveLines) { + final LineSizeCollection? _moveLines = + moveLines != null ? moveLines as LineSizeCollection : null; _lineSizes.insertWithThreeArgs( insertAtLine, count, _moveLines == null ? null : _moveLines._lineSizes); _lineHidden.insertWithThreeArgs(insertAtLine, count, _moveLines == null ? null : _moveLines._lineHidden); - _lineNested = {}; + _lineNested = {}; - _lineNested.forEach((int key, _LineSizeCollection value) { + _lineNested.forEach((int key, LineSizeCollection value) { if (key >= insertAtLine) { _lineNested.putIfAbsent(key + count, () => value); } else { @@ -355,25 +724,25 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase if (_moveLines != null) { for (int i = 0; i < _moveLines._lineNested.length; i++) { - _moveLines._lineNested.forEach((int key, _LineSizeCollection value) { + _moveLines._lineNested.forEach((int key, LineSizeCollection value) { _lineNested.putIfAbsent(key + insertAtLine, () => value); }); } } - _lineCount += count; + super.lineCount += count; if (isSuspendUpdates) { return; } if (_distances != null) { - _DistancesUtil.onInserted(_distances!, this, insertAtLine, count); + DistancesUtil.onInserted(_distances!, this, insertAtLine, count); } if (onLinesInserted != null) { - final _LinesInsertedArgs linesInsertedArgs = - _LinesInsertedArgs.fromArgs(insertAtLine, count); + final LinesInsertedArgs linesInsertedArgs = + LinesInsertedArgs.fromArgs(insertAtLine, count); onLinesInserted!(linesInsertedArgs); } } @@ -382,8 +751,8 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// /// * scrollAxis - _required_ - The scroll axis. @override - void initializeScrollAxis(_ScrollAxisBase scrollAxis) { - final _PixelScrollAxis pixelScrollAxis = scrollAxis as _PixelScrollAxis; + void initializeScrollAxis(ScrollAxisBase scrollAxis) { + final PixelScrollAxis pixelScrollAxis = scrollAxis as PixelScrollAxis; if (_lineNested.isNotEmpty) { throw Exception( 'When you have nested line collections you need to use PixelScrolling!'); @@ -392,7 +761,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase ..defaultLineSize = defaultLineSize ..lineCount = lineCount; - for (final _RangeValuePair entry in _lineSizes) { + for (final RangeValuePair entry in _lineSizes) { if (entry.value != -2) { final double entryValue = entry.value as double; scrollAxis.setLineSize(entry.start, entry.end, @@ -400,19 +769,18 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } } - for (final MapEntry entry - in _lineNested.entries) { + for (final MapEntry entry in _lineNested.entries) { pixelScrollAxis.setNestedLines(entry.key, entry.value.distances); } - for (final _RangeValuePair entry in _lineHidden) { + for (final RangeValuePair entry in _lineHidden) { scrollAxis.setLineHiddenState(entry.start, entry.end, entry.value); } } /// Resets the hidden state of the line. void resetHiddenState() { - _lineHidden = _SortedRangeValueList(); + _lineHidden = SortedRangeValueList(); } /// Reset the lines with default line size. @@ -429,19 +797,19 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// call when lines should be moved. @override void removeLines( - int removeAtLine, int count, _EditableLineSizeHostBase? moveLines) { - final _LineSizeCollection? _moveLines = - moveLines != null ? moveLines as _LineSizeCollection : null; + int removeAtLine, int count, EditableLineSizeHostBase? moveLines) { + final LineSizeCollection? _moveLines = + moveLines != null ? moveLines as LineSizeCollection : null; _lineSizes.removeWithThreeArgs( removeAtLine, count, _moveLines == null ? null : _moveLines._lineSizes); _lineHidden.removeWithThreeArgs(removeAtLine, count, _moveLines == null ? null : _moveLines._lineHidden); - final Map lineNested = _lineNested; - _lineNested = {}; + final Map lineNested = _lineNested; + _lineNested = {}; for (int i = 0; i < lineNested.length; i++) { - lineNested.forEach((int key, _LineSizeCollection value) { + lineNested.forEach((int key, LineSizeCollection value) { if (key >= removeAtLine) { if (key >= removeAtLine + count) { _lineNested.putIfAbsent(key - count, () => value); @@ -454,7 +822,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase }); } - _lineCount -= count; + super.lineCount -= count; if (isSuspendUpdates) { return; @@ -465,8 +833,8 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (onLinesRemoved != null) { - final _LinesRemovedArgs linesRemovedArgs = - _LinesRemovedArgs.fromArgs(removeAtLine, count); + final LinesRemovedArgs linesRemovedArgs = + LinesRemovedArgs.fromArgs(removeAtLine, count); onLinesRemoved!(linesRemovedArgs); } } @@ -483,7 +851,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// default line size. void resetNestedLines() { for (int i = 0; i < _lineNested.length; i++) { - _lineNested.forEach((int key, _LineSizeCollection value) { + _lineNested.forEach((int key, LineSizeCollection value) { _lineSizes[key] = -1; }); } @@ -508,8 +876,8 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (onLineHiddenChanged != null) { - final _HiddenRangeChangedArgs hiddenRangeChangedArgs = - _HiddenRangeChangedArgs.fromArgs(0, _lineCount - 1, false); + final HiddenRangeChangedArgs hiddenRangeChangedArgs = + HiddenRangeChangedArgs.fromArgs(0, lineCount - 1, false); onLineHiddenChanged!(hiddenRangeChangedArgs); } } @@ -532,11 +900,11 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } // DistancesLineHiddenChanged checks both hidden state and sizes together... if (_distances != null) { - _DistancesUtil.distancesLineHiddenChanged(distances!, this, from, to); + DistancesUtil.distancesLineHiddenChanged(distances!, this, from, to); } - _HiddenRangeChangedArgs hiddenRangeChangedArgs; + HiddenRangeChangedArgs hiddenRangeChangedArgs; if (onLineHiddenChanged != null) { - hiddenRangeChangedArgs = _HiddenRangeChangedArgs.fromArgs(from, to, hide); + hiddenRangeChangedArgs = HiddenRangeChangedArgs.fromArgs(from, to, hide); onLineHiddenChanged!(hiddenRangeChangedArgs); } } @@ -572,7 +940,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase void setHiddenState(List values) { suspendUpdates(); - _lineHidden = _SortedRangeValueList(); + _lineHidden = SortedRangeValueList(); final int count = min(lineCount, values.length); for (int index = 0; index < count; index++) { @@ -592,7 +960,7 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase void setHiddenInterval(int start, int lineCount, List values) { suspendUpdates(); - _lineHidden = _SortedRangeValueList(); + _lineHidden = SortedRangeValueList(); for (int index = start; index < lineCount; index += values.length) { for (int n = 0; n < values.length; n++) { @@ -612,11 +980,11 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase /// is null the line will be converted to a normal (not nested) line with /// default line size. @override - void setNestedLines(int index, _EditableLineSizeHostBase? nestedLines) { + void setNestedLines(int index, EditableLineSizeHostBase? nestedLines) { if (nestedLines != null) { _lineSizes[index] = -2; // -1 indicates default value, -2 indicates nested. - _lineNested[index] = nestedLines as _LineSizeCollection; + _lineNested[index] = nestedLines as LineSizeCollection; } else { _lineSizes[index] = -1; // -1 indicates default value, -2 indicates nested. @@ -627,11 +995,11 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (_distances != null) { - _DistancesUtil.distancesLineSizeChanged(distances!, this, index, index); + DistancesUtil.distancesLineSizeChanged(distances!, this, index, index); } if (onLineSizeChanged != null) { - onLineSizeChanged!(_RangeChangedArgs.fromArgs(index, index)); + onLineSizeChanged!(RangeChangedArgs.fromArgs(index, index)); } } @@ -655,12 +1023,12 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } if (_distances != null) { - _DistancesUtil.distancesLineHiddenChanged(_distances!, this, from, to); + DistancesUtil.distancesLineHiddenChanged(_distances!, this, from, to); } - _RangeChangedArgs rangeChangedArgs; + RangeChangedArgs rangeChangedArgs; if (onLineSizeChanged != null) { rangeChangedArgs = - _RangeChangedArgs.fromRangeChangedArgs(from, to, saveValue, size); + RangeChangedArgs.fromRangeChangedArgs(from, to, saveValue, size); onLineSizeChanged!(rangeChangedArgs); } } @@ -688,9 +1056,9 @@ class _LineSizeCollection extends _PaddedEditableLineSizeHostBase } /// Initializes a new instance of the DistanceLineCounterTree class. -class _DistancesUtil { +class DistancesUtil { /// Prevents a default instance of the DistancesUtil class from being created. - _DistancesUtil(); + DistancesUtil(); /// This method fires when distances line hidden changed. /// @@ -699,8 +1067,8 @@ class _DistancesUtil { /// * from - _required_ - The start index of the line. /// * to - _required_ - The end index of the line. static void distancesLineHiddenChanged( - _DistanceCounterCollectionBase distances, - _LineSizeHostBase linesHost, + DistanceCounterCollectionBase distances, + LineSizeHostBase linesHost, int from, int to) { final Object ndh = linesHost; @@ -721,7 +1089,7 @@ class _DistancesUtil { n = rangeTo; } - if (ndh is _NestedDistancesHostBase) { + if (ndh is NestedDistancesHostBase) { if (ndh.getDistances(n) == null) { _setRange(); } else { @@ -739,8 +1107,8 @@ class _DistancesUtil { /// * linesHost - _required_ - The line host. /// * from - _required_ - The start index of the line. /// * to - _required_ - The end index of the line. - static void distancesLineSizeChanged(_DistanceCounterCollectionBase distances, - _LineSizeHostBase linesHost, int from, int to) { + static void distancesLineSizeChanged(DistanceCounterCollectionBase distances, + LineSizeHostBase linesHost, int from, int to) { final Object ndh = linesHost; for (int n = from; n <= to; n++) { @@ -754,7 +1122,7 @@ class _DistancesUtil { n = rangeTo; } - if (ndh is _NestedDistancesHostBase) { + if (ndh is NestedDistancesHostBase) { if (ndh.getDistances(n) == null) { _setRange(); } else { @@ -774,7 +1142,7 @@ class _DistancesUtil { /// /// Returns the minimum index value static int getRangeToHelper(int n, int to, int repeatSizeCount) { - if (repeatSizeCount == _MathHelper.maxvalue) { + if (repeatSizeCount == maxvalue) { return to; } @@ -787,8 +1155,8 @@ class _DistancesUtil { /// * linesHost - _required_ - The line host. /// * insertAt - _required_ - The index to insert. /// * count - _required_ - The count of the lines inserted. - static void onInserted(_DistanceCounterCollectionBase distances, - _LineSizeHostBase linesHost, int insertAt, int count) { + static void onInserted(DistanceCounterCollectionBase distances, + LineSizeHostBase linesHost, int insertAt, int count) { distances.insert(insertAt, count); final int to = insertAt + count - 1; int repeatSizeCount = -1; diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart deleted file mode 100644 index 154828e16..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_base.dart +++ /dev/null @@ -1,112 +0,0 @@ -part of datagrid; - -/// An indexable collection of objects with a length. -/// -/// Creates a list of the given length. -/// The created list is fixed-length if [length] is provided. -class _ListBase implements _CollectionBase, _EnumerableBase { - _ListBase() { - _isFixedSize = false; - _isReadOnly = false; - } - - /// Gets the fixed size value - bool get isFixedSize => _isFixedSize; - late bool _isFixedSize; - - bool get isReadOnly => _isReadOnly; - late bool _isReadOnly; - - /// Add an new element to the list collection - /// - /// * value - _required_ - New element - int add(Object value); - - /// Check whether the value is found or not. - /// - /// * value - _required_ - list element - bool contains(Object value); - - /// Clear the entire list - void clear(); - - /// insert a element into the list - /// - /// * index - _required_ - Index position. - /// * value - _required_ - An element - void insert(int index, Object value); - - /// Check the value based on index - /// - /// * value - _required_ - An element - int indexOf(Object value); - - /// Remove the element from the list - /// - /// * value - _required_ - An element. - void remove(Object value); - - /// Remove the element based on index. - /// - /// * index - The index position. - void removeAt(int index); - - /// Gets the index value - /// - /// Returns the index value - Object? operator [](int index) => this[index]; - - /// Sets the index value - void operator []=(int index, Object value) => this[index] = value; - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - -/// Collection -class _CollectionBase extends _EnumerableBase { - _CollectionBase() { - _isSynchronized = false; - _count = 0; - } - - /// Gets the count of the collection. - int get count => _count; - late int _count; - - /// Gets the synchronized. - bool get isSynchronized => _isSynchronized; - bool _isSynchronized = false; - - /// Gets the syncroot - Object? get syncRoot => _syncRoot; - Object? _syncRoot; - - /// Copy an element based on index. - /// - /// * list - _required_ - List of element - /// * index - _required_ - Index position - void copyTo(List list, int index) {} - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - -/// Enumerable -abstract class _EnumerableBase { - /// Gets the enumerator - /// - /// Returns the enumerator. - _EnumeratorBase getEnumerator(); -} - -/// Enumerator -abstract class _EnumeratorBase { - /// Move a next postion - /// - /// Returns true when it is move one element. otherwise false - bool moveNext(); - - /// Reset the value from an enumerator. - void reset() {} -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart deleted file mode 100644 index c28884ddd..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/list_generic_base.dart +++ /dev/null @@ -1,16 +0,0 @@ -part of datagrid; - -/// Generic Enumerable -abstract class _EnumerableGenericBase with IterableMixin { - /// Gets the enumerator - /// - /// Returns the enumerator. - _EnumeratorGenericBase getEnumeratorGeneric(); -} - -/// Generic Enumerator -class _EnumeratorGenericBase { - /// Gets the current item from an list - T? get currentGeneric => _current; - T? _current; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart deleted file mode 100644 index 32cc1302f..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/math_helper.dart +++ /dev/null @@ -1,43 +0,0 @@ -part of datagrid; - -/// Provide information of minValue,maxValue,ReferenceEquals -/// and binarySearch APIs. -class _MathHelper { - /// Provide the largest possible value of an Int32 - static const int maxvalue = 2 ^ 53; - - /// Provide the smallest possible value of an Int32 - static const int minvalue = -2 ^ 53; - - /// Returns the position of `value` in the `sortedList`, if it exists. - /// - /// * sortedList - _required_ - The sortedList - /// * value - _required_ - Represented to a value - static int binarySearch>( - List sortedList, T value) { - int min = 0; - int max = sortedList.length; - late int index; - while (min < max) { - final int mid = min + ((max - min) >> 1); - final T element = sortedList[mid]; - final int comp = element.compareTo(value); - if (comp == 0) { - return mid; - } - if (comp < 0) { - min = mid + 1; - index = mid; - } else { - max = mid; - } - } - return index; - } - - /// Determines whether the specified Object instances are the same instance. - /// - /// * objA - _required_ - Instance of a class - /// * objB - _required_ - Instance of a class - static bool referenceEquals(Object? objA, Object? objB) => objA == objB; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/row_column_index.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/row_column_index.dart similarity index 86% rename from packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/row_column_index.dart rename to packages/syncfusion_flutter_datagrid/lib/src/grid_common/row_column_index.dart index 70cdab16d..56cf14124 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/row_column_index.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/row_column_index.dart @@ -1,4 +1,4 @@ -part of datagrid; +import 'utility_helper.dart'; /// Holds the coordinates for a cell. class RowColumnIndex { @@ -13,13 +13,12 @@ class RowColumnIndex { /// Creates the empty object with row index and column index set to /// `int.MinValue`. - static RowColumnIndex get empty => - RowColumnIndex(_MathHelper.maxvalue, _MathHelper.minvalue); + static RowColumnIndex get empty => RowColumnIndex(maxvalue, minvalue); /// Whether this object is empty. /// /// Returns true, if this instance is empty, otherwise false. - bool get isEmpty => rowIndex == _MathHelper.minvalue; + bool get isEmpty => rowIndex == minvalue; /// Whether this object and a specified object are equal. bool equals(RowColumnIndex obj) { diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart new file mode 100644 index 000000000..c1dbed28c --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis.dart @@ -0,0 +1,2863 @@ +import 'dart:math'; + +import 'distance_counter.dart'; +import 'enums.dart'; +import 'event_args.dart'; +import 'line_size_host.dart'; +import 'scrollbar.dart'; +import 'utility_helper.dart'; +import 'visible_line_info.dart'; + +/// ScrollAxisBase is an abstract base class and implements scrolling +/// logic for both horizontal and vertical scrolling in a +/// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances`. +/// Logical units in the ScrollAxisBase are called "Lines". With the +/// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.distances` a +/// line represents rows in a grid +/// and with `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` +/// a line represents columns in a grid. +/// +/// ScrollAxisBase has support for frozen header and footer lines, maintaining a +/// scroll position and updating and listening to scrollbars. It also maintains +/// a collection of `VisibleLineInfo` items for all the lines that are +/// visible in the viewing area. ScrollAxisBase wires itself with a +/// `ScrollLinesHost` and reacts to changes in line count, +/// line sizes, hidden state and default line size. +typedef _ChangedCallback = void Function(ScrollChangedArgs scrollChangedArgs); + +/// +abstract class ScrollAxisBase { + /// Initializes a new instance of the ScrollAxisBase class. + /// + /// * scrollBar The scroll bar state. + /// * scrollLinesHost The scroll lines host. + ScrollAxisBase( + ScrollBarBase? scrollBar, + LineSizeHostBase? scrollLinesHost, { + double headerExtent = 0, + double footerExtent = 0.0, + double viewSize = 0.0, + double defaultLineSize = 0.0, + }) : _defaultLineSize = defaultLineSize { + if (scrollBar == null) { + throw Exception(); + } + viewSize = viewSize; + headerExtent = headerExtent; + footerExtent = footerExtent; + _isPixelScroll = false; + this.scrollBar = scrollBar; + _scrollLinesHost = scrollLinesHost; + wireScrollLinesHost(); + } + + double _renderSize = 0.0; + ScrollBarBase? _scrollBar; + late LineSizeHostBase? _scrollLinesHost; + bool _layoutDirty = false; + int _lineResizeIndex = -1; + double _lineResizeSize = 0; + final VisibleLinesCollection _visibleLines = VisibleLinesCollection(); + double _lastScrollValue = -1; + + /// + DoubleSpan clip = DoubleSpan.empty(); + bool _ignoreScrollBarPropertyChange = false; + bool _inGetVisibleLines = false; + bool _allBodyLinesShown = false; + int _lastBodyLineIndex = -1; + + /// + bool indebug = false; + + /// code to handle issue in DT 77714 + static double ePSILON = 2.2204460492503131e-016; + /* smallest such that 1.0+EPSILON != 1.0 */ + + /// + static bool strictlyLessThan(double d1, double d2) => + !(d1 > d2 || areClose(d1, d2)); + + /// + static bool areClose(double d1, double d2) { + if (d1 == d2) { + return true; + } + + final double eps = (d1.abs() + d2.abs() + 10.0) * ePSILON; + return (d1 - d2).abs() < eps; + } + + /// Occurs when a scroll axis changed. + _ChangedCallback? onChangedCallback; + + /// Get visibleLine + VisibleLinesCollection get visibleLines { + return _visibleLines; + } + + /// Gets the default size of lines. + /// + /// Returns the default size of lines. + double get defaultLineSize => _defaultLineSize; + double _defaultLineSize = 0.0; + + /// Sets the default size of lines. + set defaultLineSize(double value) { + if (value == _defaultLineSize) { + return; + } + + _defaultLineSize = value; + } + + /// Gets the footer extent. This is total height (or width) + /// of the footer lines. + /// + /// Returns the footer extent. + double footerExtent = 0.0; + + /// Gets the footer line count. + /// + /// Returns the footer line count. + int get footerLineCount { + if (scrollLinesHost == null) { + return 0; + } + + return scrollLinesHost?.getFooterLineCount() ?? 0; + } + + /// Gets the index of the first footer line. + /// + /// Returns the index of the first footer line. + int get firstFooterLineIndex => lineCount - footerLineCount; + + /// Gets the header extent. This is total height (or width) + /// of the header lines. + /// + /// Returns the header extent. + double headerExtent = 0.0; + + /// Gets the header line count. + /// + /// Returns the header line count. + int get headerLineCount { + if (scrollLinesHost == null) { + return 0; + } + + return scrollLinesHost?.getHeaderLineCount() ?? 0; + } + + /// Gets a value indicating whether footer lines are visible. + /// + /// @value + /// true if footer lines are visible, otherwise false. + bool get isFooterVisible { + final VisibleLinesCollection visibleLines = getVisibleLines(); + return visibleLines.firstFooterVisibleIndex < visibleLines.length; + } + + /// Gets a value indicating whether this axis supports pixel scrolling. + /// + /// @value + /// `true` if this instance supports pixel scrolling, otherwise `false`. + bool get isPixelScroll => _isPixelScroll; + late bool _isPixelScroll; + + /// Gets the last visible line. + /// + /// Returns the last visible line. + VisibleLineInfo? get lastBodyVisibleLine { + final VisibleLinesCollection visibleLines = getVisibleLines(); + if (visibleLines.isEmpty || + visibleLines.lastBodyVisibleIndex > visibleLines.length) { + return null; + } + + return visibleLines[visibleLines.lastBodyVisibleIndex]; + } + + /// Gets the index of the last visible line. + /// + /// Returns the index of the last visible line. + int get lastBodyVisibleLineIndex { + final VisibleLinesCollection visibleLines = getVisibleLines(); + if (visibleLines.isEmpty || + visibleLines.lastBodyVisibleIndex > visibleLines.length) { + return -1; + } + + return visibleLines[visibleLines.lastBodyVisibleIndex].lineIndex; + } + + /// Gets the line count. + /// + /// Returns the line count. + int get lineCount => _lineCount; + int _lineCount = 0; + + /// Sets the line count. + set lineCount(int value) { + if (value == _lineCount) { + return; + } + + _lineCount = value; + } + + /// Gets the name. + /// + /// Returns the name. + String? get name => _name; + String? _name; + + /// Sets the name. + set name(String? value) { + if (value == _name) { + return; + } + + _name = value; + } + + /// Sets the size (either height or width) of the parent control. + /// + /// Returns the size of the parent control. + double get renderSize => _renderSize; + + /// Sets the size (either height or width) of the parent control. + set renderSize(double value) { + if (_renderSize != value) { + _renderSize = value; + markDirty(); + updateScrollBar(true); + } + } + + /// Gets the scroll bar state. + /// + /// Returns the scroll bar state. + ScrollBarBase? get scrollBar => _scrollBar; + set scrollBar(ScrollBarBase? value) { + if (value == _scrollBar) { + return; + } + + _scrollBar = value; + } + + /// Gets the scroll lines host. + /// + /// Returns the scroll lines host. + LineSizeHostBase? get scrollLinesHost => _scrollLinesHost; + + /// Scroll = First Visible Body Line + /// + /// Gets the index of the first visible Line in the body region. + /// + /// Returns the index of the scroll line. + int get scrollLineIndex => _scrollLineIndex; + int _scrollLineIndex = 0; + + /// Sets the index of the first visible Line in the body region. + set scrollLineIndex(int value) { + if (value == _scrollLineIndex) { + return; + } + + _scrollLineIndex = value; + } + + /// Support for sharing axis with a parent axis + /// Gets the index of the first line in a parent axis. + /// + /// This is used for shared + /// or nested scroll axis (e.g. a nested grid with shared axis + /// in a covered cell). + /// + /// Returns the index of the first line. + int get startLineIndex => _startLineIndex; + int _startLineIndex = -1; + + /// Sets the index of the first line in a parent axis. + set startLineIndex(int value) { + if (value == _startLineIndex) { + return; + } + + _startLineIndex = value; + } + + /// Gets the size (either height or width) of the parent control excluding the + /// area occupied by Header and Footer. This size is used for scrolling down + /// or up one page. + /// + /// Returns the size of the parent control. + double get scrollPageSize => renderSize - headerExtent - footerExtent; + + /// Gets the view size of the (either height or width) of the parent control. + /// Normally the ViewSize is the same as `RenderSize`. + /// Only if the parent control has more space then needed to display + /// all lines, the ViewSize will be less. In such case the ViewSize is + /// the total height for all lines. + /// + /// Returns the view size of the (either height or width) + /// of the parent control. + double viewSize = 0.0; + + /// Adjusts the footer extent to avoid gap between last visible line + /// of body region and first line of footer in case the view is larger + /// than the height/width of all lines. + /// + /// * footerSize Size of the footer. + /// * arrangeSize Size of the arrange. + /// Returns the + double adjustFooterExtentToAvoidGap(double footerSize, double arrangeSize) { + // Adjust start of footer to avoid gap after last row. + if (viewSize < arrangeSize) { + footerSize += arrangeSize - viewSize; + } + + if (footerSize + headerExtent > arrangeSize) { + footerSize = max(0, arrangeSize - headerExtent); + } + + return footerSize; + } + + /// Aligns the scroll line. + void alignScrollLine(); + + /// Gets a boolean value indicating whether any of the lines with the + /// given absolute line index are visible. + /// + /// * lineIndex1 The line index 1. + /// * lineIndex2 The line index 2. + /// A boolean value indicating whether any of the lines with + /// the given absolute line index are visible. + bool anyVisibleLines(int lineIndex1, int lineIndex2) => + _visibleLines.anyVisibleLines(lineIndex1, lineIndex2); + + /// + void defaultLineSizeChangedCallback( + DefaultLineSizeChangedArgs defaultLineSizeChangedArgs) { + if (scrollLinesHost != null) { + defaultLineSize = scrollLinesHost!.getDefaultLineSize(); + markDirty(); + updateScrollBar(true); + raiseChanged(ScrollChangedAction.defaultLineSizeChanged); + } + } + + /// + void footerLineChangedCallback() { + if (scrollLinesHost != null) { + setFooterLineCount(scrollLinesHost!.getFooterLineCount()); + markDirty(); + raiseChanged(ScrollChangedAction.footerLineCountChanged); + } + } + + /// Freezes the visible lines. + void freezeVisibleLines() { + _inGetVisibleLines = true; + } + + /// Gets the size from `ScrollLinesHost` or if the line is being resized + /// then get temporary value previously set with `SetLineResize`. + /// If size is negative then `DefaultLineSize` is returned. + /// + /// * index The index of the line. + /// * repeatSizeCount The number of subsequent values with same size. + /// + /// The size from `ScrollLinesHost` or if the line is being resized + /// then get temporary value previously set with `SetLineResize`. + /// If size is negative then `DefaultLineSize` is returned. + List getScrollLinesHostSize(int index, int repeatSizeCount) { + final List lineSize = + scrollLinesHost!.getSize(index, repeatSizeCount); + double size = lineSize[0] as double; + repeatSizeCount = lineSize[1] as int; + + if (size < 0) { + size = defaultLineSize; + } + + return [size, repeatSizeCount]; + } + + /// Gets the size from `ScrollLinesHost` or if the line is being resized + /// then get temporary value previously set with `SetLineResize` + /// + /// * index The index. + /// * repeatSizeCount The number of subsequent values with same size. + /// Returns the size from `ScrollLinesHost` or if the line is being resized + /// then get temporary value previously set with `SetLineResize` + List getLineSizeWithTwoArgs(int index, int? repeatSizeCount) { + repeatSizeCount = 1; + if (index == _lineResizeIndex) { + return [_lineResizeSize, repeatSizeCount]; + } + + if (scrollLinesHost == null) { + repeatSizeCount = maxvalue; + return [defaultLineSize, repeatSizeCount]; + } + + return [ + getScrollLinesHostSize(index, repeatSizeCount)[0], + getScrollLinesHostSize(index, repeatSizeCount)[1] + ]; + } + + /// Gets the size of the line. + /// + /// * index The index of the line. + /// Returns the size of the line. + double getLineSize(int index) { + int? repeatSizeCount; + return getLineSizeWithTwoArgs(index, repeatSizeCount)[0] as double; + } + + /// Gets the index of the next scroll line. + /// + /// * lineIndex The current index of the line. + /// Returns the index of the next scroll line. + int getNextScrollLineIndex(int lineIndex); + + /// Gets the origin and corner points of body region. + /// + /// * origin The origin. + /// * corner The corner. + void getOriginAndCornerOfBodyRegion(double origin, double corner) { + int scrollLineIndex = 0; + double scrollOffset = 0.0; + final List lineInfo = + getScrollLineIndex(scrollLineIndex, scrollOffset); + scrollLineIndex = lineInfo[0] as int; + scrollOffset = lineInfo[1] as double; + final double arrangeSize = renderSize; + final double adjustedFooterExtent = + adjustFooterExtentToAvoidGap(footerExtent, arrangeSize); + origin = headerExtent - scrollOffset; + corner = arrangeSize - adjustedFooterExtent; + } + + /// Gets the index of the previous scroll line. + /// + /// * lineIndex The current index of the line. + /// Returns the index of the previous scroll line. + int getPreviousScrollLineIndex(int lineIndex); + + /// Gets the index of the scroll line in RTL Mode + /// + /// * scrollLineIndex Index of the scroll line. + /// * scrollLineOffset The scroll line offset. + /// * isRightToLeft The boolean value used to calculate visible columns + /// in right to left mode. + List getScrollLineIndex(int scrollLineIndex, double scrollLineOffset, + [bool isRightToLeft = false]); + + /// Gets the maximum range + /// + /// * n start index + /// * to end index + /// * repeatSizeCount repeat count value + /// Returns the minimum index value + int getRangeToHelper(int n, int to, int repeatSizeCount) { + if (repeatSizeCount == maxvalue) { + return to; + } + + return min(to, n + repeatSizeCount - 1); + } + + /// Gets the visible lines collection. + /// + /// * isRightToLeft The boolean value used to calculate visible columns + /// in right to left mode. + /// Returns the visible lines collection. + VisibleLinesCollection getVisibleLines([bool isRightToLeft = false]) { + if (!isRightToLeft) { + if (_inGetVisibleLines) { + return _visibleLines; + } + + _inGetVisibleLines = true; + try { + if (_layoutDirty) { + setHeaderLineCount(headerLineCount); + setFooterLineCount(footerLineCount); + + _lastScrollValue = -1; + _layoutDirty = false; + updateScrollBar(true); + } + if (_visibleLines.isEmpty || _lastScrollValue != scrollBar!.value) { + _visibleLines.clear(); + + int visibleIndex = 0; + int scrollLineIndex = 0; + double scrollOffset = 0.0; + final int headerLineCount = this.headerLineCount; + final List scrollLineValues = + getScrollLineIndex(scrollLineIndex, scrollOffset); + scrollLineIndex = scrollLineValues[0] as int; + scrollOffset = scrollLineValues[1] as double; + final int firstFooterLine = lineCount - footerLineCount; + final double footerStartPoint = renderSize - footerExtent; + int index; + + // Header + double point = 0; + int lastHeaderLineIndex = -1; + for (index = 0; + index != -1 && + strictlyLessThan(point, headerExtent) && + index < firstFooterLine && + index < headerLineCount; + index = getNextScrollLineIndex(index)) { + final double size = getLineSize(index); + final VisibleLineInfo line = VisibleLineInfo( + visibleIndex++, index, size, point, 0, true, false); + _visibleLines.add(line); + point += size; + lastHeaderLineIndex = index; + } + + _visibleLines.firstBodyVisibleIndex = _visibleLines.length; + + VisibleLineInfo? lastScrollableLine; + // Body + point = headerExtent; + final int firstBodyLineIndex = + max(scrollLineIndex, lastHeaderLineIndex + 1); + for (index = firstBodyLineIndex; + index != -1 && + strictlyLessThan(point, footerStartPoint) && + index < firstFooterLine; + index = getNextScrollLineIndex(index)) { + final double size = getLineSize(index); + _visibleLines.add(lastScrollableLine = VisibleLineInfo( + visibleIndex++, + index, + size, + point, + scrollOffset, + false, + false)); + point += size - scrollOffset; + scrollOffset = 0; // reset scrollOffset after first line. + // Subsequent lines will start at given point. + } + + if (lastScrollableLine == null) { + _allBodyLinesShown = true; + _lastBodyLineIndex = -1; + } else { + _allBodyLinesShown = index >= firstFooterLine; + _lastBodyLineIndex = lastScrollableLine.lineIndex; + } + + _visibleLines.firstFooterVisibleIndex = _visibleLines.length; + + // Footer + point = max(headerExtent, viewSize - footerExtent); + for (index = firstFooterLine; + index != -1 && + strictlyLessThan(point, renderSize) && + index < lineCount; + index = getNextScrollLineIndex(index)) { + if (lastScrollableLine != null) { + lastScrollableLine.clippedCornerExtent = + lastScrollableLine.corner - point; + lastScrollableLine = null; + } + + final double size = getLineSize(index); + _visibleLines.add(VisibleLineInfo( + visibleIndex++, index, size, point, 0, false, true)); + point += size; + } + + if (lastScrollableLine != null) { + lastScrollableLine.clippedCornerExtent = + lastScrollableLine.corner - point; + lastScrollableLine = null; + } + + _lastScrollValue = scrollBar!.value; + + if (_visibleLines.isNotEmpty) { + _visibleLines[_visibleLines.length - 1].isLastLine = true; + } + try { + // throws exception when a line is duplicate + _visibleLines.getVisibleLineAtLineIndex(0); + } on Exception { + if (!indebug) { + indebug = true; + _visibleLines.clear(); + try { + getVisibleLines(); + } finally { + indebug = false; + } + } + } + } + } finally { + _inGetVisibleLines = false; + } + return _visibleLines; + } else { + if (_inGetVisibleLines) { + return _visibleLines; + } + + _inGetVisibleLines = true; + try { + if (_layoutDirty) { + setHeaderLineCount(headerLineCount); + setFooterLineCount(footerLineCount); + _lastScrollValue = -1; + _layoutDirty = false; + updateScrollBar(true); + } + + if (_visibleLines.isEmpty || _lastScrollValue != scrollBar!.value) { + _visibleLines.clear(); + int visibleIndex = 0; + int scrollLineIndex = 0; + double scrollOffset = 0.0; + final int _headerLineCount = headerLineCount; + final List scrollLineValues = + getScrollLineIndex(scrollLineIndex, scrollOffset, true); + scrollLineIndex = scrollLineValues[0] as int; + scrollOffset = scrollLineValues[1] as double; + final int firstFooterLine = lineCount - footerLineCount; + int index; + double footerStartPoint = renderSize + clip.start; + + // Header + double point = 0; + int lastHeaderLineIndex = -1; + for (index = 0; + index != -1 && + strictlyLessThan(point, headerExtent) && + index < firstFooterLine && + index < _headerLineCount; + index = getNextScrollLineIndex(index)) { + final double size = getLineSize(index); + _visibleLines.add(VisibleLineInfo(visibleIndex++, index, size, + footerStartPoint - size - point, 0, true, false)); + footerStartPoint -= size; + lastHeaderLineIndex = index; + } + + _visibleLines.firstBodyVisibleIndex = _visibleLines.length; + VisibleLineInfo? lastScrollableLine; + + // Body + point = headerExtent; + int firstBodyLineIndex = + max(scrollLineIndex, lastHeaderLineIndex + 1); + + point = footerStartPoint - clip.start; + + if (clip.start > 0 && scrollOffset < clip.start) { + scrollOffset = + getLineSize(scrollLineIndex - 1) - (clip.start - scrollOffset); + scrollLineIndex -= 1; + point += clip.start; + } + + firstBodyLineIndex = max(scrollLineIndex, lastHeaderLineIndex + 1); + for (index = firstBodyLineIndex; + index != -1 && + strictlyLessThan( + point - 1, renderSize + clip.start - headerExtent) && + (point > footerExtent) && + (index < firstFooterLine && index >= headerLineCount); + index = getNextScrollLineIndex(index)) { + final double size = getLineSize(index); + _visibleLines.add(lastScrollableLine = VisibleLineInfo( + visibleIndex++, + index, + size, + point - size + scrollOffset, + scrollOffset, + false, + false)); + point -= size - scrollOffset; + scrollOffset = 0; + } + + if (lastScrollableLine == null) { + _allBodyLinesShown = true; + _lastBodyLineIndex = -1; + } else { + _allBodyLinesShown = index >= firstFooterLine; + _lastBodyLineIndex = lastScrollableLine.lineIndex; + } + + _visibleLines.firstFooterVisibleIndex = _visibleLines.length; + + if (footerLineCount > 0) { + if (renderSize < scrollBar!.maximum + footerExtent) { + point = min(renderSize + clip.start - headerExtent, + clip.start + footerExtent); + for (index = firstFooterLine; + index != -1 && + strictlyLessThan( + point - 1, + renderSize < scrollBar!.maximum + ? clip.start + footerExtent + : renderSize - headerExtent) && + index < lineCount; + index = getNextScrollLineIndex(index)) { + if (lastScrollableLine != null) { + lastScrollableLine.clippedCornerExtent = + lastScrollableLine.corner - point; + lastScrollableLine = null; + } + + final double size = getLineSize(index); + _visibleLines.add(VisibleLineInfo( + visibleIndex++, index, size, point - size, 0, false, true)); + point -= size; + } + } else { + for (index = firstFooterLine; + index != -1 && + strictlyLessThan( + point - 1, + renderSize < scrollBar!.maximum + ? clip.start + footerExtent + : renderSize - headerExtent) && + index < lineCount; + index = getNextScrollLineIndex(index)) { + if (lastScrollableLine != null) { + lastScrollableLine.clippedCornerExtent = + lastScrollableLine.corner - point; + lastScrollableLine = null; + } + + final double size = getLineSize(index); + _visibleLines.add(VisibleLineInfo( + visibleIndex++, index, size, point - size, 0, false, true)); + point -= size; + } + } + } + + if (lastScrollableLine != null) { + lastScrollableLine.clippedCornerExtent = + lastScrollableLine.corner - point; + lastScrollableLine = null; + } + + _lastScrollValue = scrollBar!.value; + if (_visibleLines.isNotEmpty) { + _visibleLines[_visibleLines.length - 1].isLastLine = true; + } + + try { + // throws exception when a line is duplicate + _visibleLines.getVisibleLineAtLineIndex(0); + } on Exception { + if (!indebug) { + indebug = true; + _visibleLines.clear(); + try { + getVisibleLines(true); + } finally { + indebug = false; + } + } + } + } + } finally { + _inGetVisibleLines = false; + } + return _visibleLines; + } + } + + /// Gets the visible line for a point in the display. + /// + /// * point The point in the display for which the visible line + /// is to be obtained. + /// * allowOutSideLines boolean value + /// * isRightToLeft The boolean value used to calculate visible columns + /// in right to left mode. + /// Returns the visible line for a point in the display. + VisibleLineInfo? getVisibleLineAtPoint(double point, + [bool allowOutSideLines = false, bool isRightToLeft = false]) { + if (!isRightToLeft) { + if (allowOutSideLines) { + point = max(point, 0); + } + + final VisibleLineInfo? lineInfo = + getVisibleLines().getVisibleLineAtPoint(point); + if (lineInfo != null && (allowOutSideLines || point <= lineInfo.corner)) { + return lineInfo; + } + } else { + if (allowOutSideLines) { + point = max(point, 0); + } + + final VisibleLinesCollection collection = getVisibleLines(true); + final VisibleLinesCollection reversedCollection = collection.reversed; + final VisibleLineInfo? lineInfo = + reversedCollection.getVisibleLineAtPoint(point); + if (lineInfo != null && (allowOutSideLines || point <= lineInfo.corner)) { + return lineInfo; + } + } + + return null; + } + + /// Gets the visible line that displays the line with the + /// given absolute line index. + /// + /// * lineIndex Index of the line. + /// The visible line that displays the line with the given + /// absolute line index. + + VisibleLineInfo? getVisibleLineAtLineIndex(int lineIndex, + {bool isRightToLeft = false}) => + getVisibleLines(isRightToLeft).getVisibleLineAtLineIndex(lineIndex); + + /// Gets the visible line that displays the line with the given absolute + /// line index. If the line is outside the view and you specify + /// `allowCreateEmptyLineIfNotVisible` then the method will create an empty + /// line and initializes its line index and line size. + /// + /// * lineIndex Index of the line. + /// * allowCreateEmptyLineIfNotVisible if set to true and if the + /// line is outside the view then the method will create an empty + /// line and initializes its LineIndex and LineSize. + /// Returns the visible line that displays the line with the given + /// absolute line index. + VisibleLineInfo? getVisibleLineAtLineIndexWithTwoArgs( + int lineIndex, bool allowCreateEmptyLineIfNotVisible) { + VisibleLineInfo? line = getVisibleLineAtLineIndex(lineIndex); + if (line == null && allowCreateEmptyLineIfNotVisible) { + final double size = getLineSize(lineIndex); + line = VisibleLineInfo( + maxvalue, lineIndex, size, renderSize + 1, size, false, false); + } + + return line; + } + + /// Returns the first and last VisibleLine.LineIndex for area identified + /// by section. + /// + /// * section The integer value indicating the section : 0 - Header, + /// 1 - Body, 2 - Footer + /// Returns the first and last VisibleLine.LineIndex for area identified + /// by section. + Int32Span getVisibleLinesRange(ScrollAxisRegion section) { + final VisibleLinesCollection visibleLines = getVisibleLines(); + int start = 0; + int end = 0; + final List visibleSection = getVisibleSection(section, start, end); + start = visibleSection[0]; + end = visibleSection[1]; + return Int32Span( + visibleLines[start].lineIndex, visibleLines[end].lineIndex); + } + + /// Return indexes for VisibleLinesCollection for area identified by section. + /// + /// * section The integer value indicating the section : 0 - Header, + /// 1 - Body, 2 - Footer + /// * start The start index. + /// * end The end index. + void getVisibleSectionWithThreeArgs( + ScrollAxisRegion section, int start, int end) { + getVisibleSection(section, start, end); + } + + /// Return indexes for VisibleLinesCollection for area identified by section. + /// + /// * section The integer value indicating the section : 0 - Header, + /// 1 - Body, 2 - Footer + /// * start The start index. + /// * end The end index. + List getVisibleSection(ScrollAxisRegion section, int start, int end) { + final VisibleLinesCollection visibleLines = getVisibleLines(); + switch (section) { + case ScrollAxisRegion.header: + start = 0; + end = visibleLines.firstBodyVisibleIndex - 1; + break; + case ScrollAxisRegion.body: + start = visibleLines.firstBodyVisibleIndex; + end = visibleLines.firstFooterVisibleIndex - 1; + break; + case ScrollAxisRegion.footer: + start = visibleLines.firstFooterVisibleIndex; + end = visibleLines.length - 1; + break; + default: + start = end = -1; + break; + } + return [start, end]; + } + + /// Returns the clipping area for the specified visible lines. + /// Only if `VisibleLineInfo.IsClippedOrigin` is true for first line or + /// if `VisibleLineInfo.IsClippedCorner` is true for last line then the + /// area will be clipped. Otherwise, the whole area from 0 to `RenderSize` + /// is returned. + /// + /// * firstLine The first visible line. + /// * lastLine The last visible line. + /// Returns the clipping area for the specified visible lines. + DoubleSpan getBorderRangeClipPoints( + VisibleLineInfo firstLine, VisibleLineInfo lastLine) { + if (!firstLine.isClippedOrigin && !lastLine.isClippedCorner) { + return DoubleSpan(0, renderSize); + } + + if (firstLine.isClippedOrigin && !lastLine.isClippedCorner) { + return firstLine.clippedOrigin < renderSize + ? DoubleSpan(firstLine.clippedOrigin, renderSize) + : DoubleSpan(renderSize, firstLine.clippedOrigin); + } + + if (!firstLine.isClippedOrigin && lastLine.isClippedCorner) { + return DoubleSpan(0, lastLine.clippedCorner); + } + + return DoubleSpan(firstLine.clippedOrigin, lastLine.clippedCorner); + } + + /// Gets the line near the given corner point. Use this method + /// for hit-testing row or column lines for resizing cells. + /// + /// * point The point. + /// * hitTestPrecision The hit test precision in points. + /// * isRightToLeft The boolean value used to calculate visible columns + /// in right to left mode. + /// Returns the visible line. + VisibleLineInfo? getLineNearCorner( + double point, double hitTestPrecision, CornerSide side, + {bool isRightToLeft = false}) => + getLineNearCornerWithFourArgs( + point, hitTestPrecision, CornerSide.both, isRightToLeft); + + /// Gets the line near the given corner point. Use this method + /// for hit-testing row or column lines for resizing cells. + /// + /// * point The point. + /// * hitTestPrecision The hit test precision in points. + /// * side The hit test corner. + /// * isRightToLeft The boolean value indicates the right to left mode. + /// Returns the visible line. + VisibleLineInfo? getLineNearCornerWithFourArgs( + double point, double hitTestPrecision, CornerSide side, + [bool isRightToLeft = false]) { + if (!isRightToLeft) { + final VisibleLinesCollection lines = getVisibleLines(); + VisibleLineInfo? visibleLine = lines.getVisibleLineAtPoint(point); + if (visibleLine != null) { + double d; + + // Close to + for (int n = max(0, visibleLine.visibleIndex); n < lines.length; n++) { + visibleLine = lines[n]; + + d = visibleLine.clippedOrigin - point; + + if ((d > hitTestPrecision) || + (d > 0 && + (side == CornerSide.right || side == CornerSide.bottom))) { + return null; + } else if (d.abs() <= hitTestPrecision && side != CornerSide.left) { + if (visibleLine.visibleIndex == 0) { + return null; + } else { + return lines[visibleLine.visibleIndex - 1]; + } + } else if (visibleLine.size - d.abs() <= hitTestPrecision && + side == CornerSide.left) { + return lines[visibleLine.visibleIndex]; + } + } + + // last line - check corner instead of origin. + d = visibleLine!.clippedCorner - point; + if (d.abs() <= hitTestPrecision) { + return lines[visibleLine.visibleIndex]; + } + } + } else { + final VisibleLinesCollection lines = getVisibleLines(true); + VisibleLineInfo? visibleLine = + getVisibleLineAtPoint(point, false, isRightToLeft); + + if (visibleLine != null) { + double d; + // Close to + for (int n = max(0, visibleLine.visibleIndex); n < lines.length; n++) { + visibleLine = lines[n]; + + d = point - visibleLine.clippedOrigin; + + if ((d > hitTestPrecision && + d < visibleLine.size - hitTestPrecision) || + (d > 0 && + d < visibleLine.size - hitTestPrecision && + (side == CornerSide.right || side == CornerSide.bottom)) || + (d < 0 && (side == CornerSide.left))) { + return null; + } + + if (d > visibleLine.size - hitTestPrecision && + visibleLine.visibleIndex > 0) { + return lines[visibleLine.visibleIndex - 1]; + } + + if (d.abs() <= hitTestPrecision) { + return lines[visibleLine.visibleIndex]; + } + + if ((d.abs() + hitTestPrecision) >= visibleLine.clippedSize) { + return visibleLine; + } + } + // last line - check corner instead of origin. + d = visibleLine!.clippedCorner - point; + if (d.abs() <= hitTestPrecision) { + return lines[visibleLine.visibleIndex]; + } + } + } + return null; + } + + /// Returns points for given absolute line indexes. + /// + /// * firstIndex The first index. + /// * lastIndex The last index. + /// * allowAdjust if set to true return the first visible line if firstIndex + /// is above viewable area or return last visible line if lastIndex + /// is after viewable area (works also for header and footer). + /// + /// * firstVisible if set to true indicates the line with index + /// firstIndex is visible in viewable area. + /// * lastVisible if set to true indicates the line with index + /// lastIndex is visible in viewable area.. + /// * firstLine The first line or null if allowAdjust is false and line + /// is not in viewable area. + /// * lastLine The last line or null if allowAdjust is false and line + /// is not in viewable area. + List getLinesAndVisibility( + int firstIndex, + int lastIndex, + bool allowAdjust, + bool firstVisible, + bool lastVisible, + VisibleLineInfo? firstLine, + VisibleLineInfo? lastLine) { + final VisibleLinesCollection visibleLines = getVisibleLines(); + + if (firstIndex < 0) { + firstIndex = 0; + } + + // Invalid Line + if (firstIndex < 0 || firstIndex >= lineCount) { + firstVisible = false; + firstLine = null; + } + + // Header + else if (firstIndex < headerLineCount) { + firstVisible = true; + firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); + } + + // Footer + else if (firstIndex >= firstFooterLineIndex) { + firstVisible = true; + firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); + } + + // After Header and Before Scroll Position + else if (firstIndex < scrollLineIndex) { + firstVisible = false; + firstLine = + allowAdjust ? getVisibleLineAtLineIndex(scrollLineIndex) : null; + } + + // After Scroll Position and Before Footer + else if (firstIndex > lastBodyVisibleLineIndex) { + firstVisible = false; + if (allowAdjust && isFooterVisible) { + firstLine = visibleLines[visibleLines.firstFooterVisibleIndex]; + } else { + firstLine = null; + } + } + // Regular line (Body) - Visible and not a Header or Footer. + else { + firstVisible = true; + firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); + } + + if (lastIndex >= lineCount) { + lastIndex = lineCount - 1; + } + + // Invalid Line + if (lastIndex < 0 || lastIndex >= lineCount) { + lastVisible = false; + lastLine = null; + } + + // Header + else if (lastIndex < headerLineCount) { + lastVisible = true; + lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); + } + + // Footer + else if (lastIndex >= firstFooterLineIndex) { + lastVisible = true; + lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); + } + + // After Header and Before Scroll Position + else if (lastIndex < scrollLineIndex) { + lastVisible = false; + if (!firstVisible && + firstIndex < scrollLineIndex) // maybe - in case you want right + // border to look through ...: && lastIndex+1 < ScrollLineIndex) + { + firstLine = null; + lastLine = null; + } else { + if (allowAdjust && headerLineCount > 0) { + lastLine = visibleLines[visibleLines.firstBodyVisibleIndex - 1]; + } else { + lastLine = null; + } + } + } + + // After Scroll Position and Before Footer + else if (lastIndex > lastBodyVisibleLineIndex) { + lastVisible = false; + if (!firstVisible && firstIndex > lastBodyVisibleLineIndex) { + firstLine = null; + lastLine = null; + } else { + lastLine = allowAdjust ? lastBodyVisibleLine : null; + } + } + + // Regular line (Body) - Visible and not a Header or Footer. + else { + lastVisible = true; + lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); + } + return [firstVisible, lastVisible, firstLine, lastLine]; + } + + /// Gets the visible lines clip points (clipped origin of first line + /// and clipped corner of last line). If both lines are above or below + /// viewable area an empty span is returned. If lines are both above and below + /// viewable are then the range for all viewable lines is returned. + /// + /// * firstIndex The first index. + /// * lastIndex The last index. + /// + /// The visible lines clip points (clipped origin of first line and clipped + /// corner of last line). + + DoubleSpan getVisibleLinesClipPoints(int firstIndex, int lastIndex) { + const bool firstVisible = false; + const bool lastVisible = false; + VisibleLineInfo? firstLine, lastLine; + + getLinesAndVisibility(firstIndex, lastIndex, true, firstVisible, + lastVisible, firstLine, lastLine); + if (firstLine == null || lastLine == null) { + return DoubleSpan.empty(); + } + + return DoubleSpan(firstLine.clippedOrigin, lastLine.clippedCorner); + } + + /// Gets the clip points for a region. + /// + /// * region The region for which the clip points is to be obtained. + /// * isRightToLeft The boolean value used to calculate visible columns + /// in right to left mode. + /// Returns the clip points for a region. + DoubleSpan getClipPoints(ScrollAxisRegion region, + {bool isRightToLeft = false}) { + final VisibleLinesCollection lines = getVisibleLines(); + int start = -1; + int end = -1; + final List visibleLineSection = getVisibleSection(region, start, end); + start = visibleLineSection[0]; + end = visibleLineSection[1]; + if (start == end && + region == ScrollAxisRegion.body && + lines[end].clippedOrigin > 0 && + lines[start].clippedCorner > 0) { + return DoubleSpan(lines[start].clippedOrigin, lines[end].clippedCorner); + } + + if (end < start) { + return DoubleSpan.empty(); + } + if (isRightToLeft) { + return DoubleSpan(lines[start].clippedCorner, lines[end].clippedOrigin); + } else { + return DoubleSpan(lines[start].clippedOrigin, lines[end].clippedCorner); + } + } + + /// Determines the line one page down from the given line. + /// + /// * lineIndex The index of the current line. + /// Returns the line index of the line one page down from the given line. + int getNextPage(int lineIndex) { + double extent = 0; + final double pageExtent = scrollPageSize - getLineSize(lineIndex); + final int count = lineCount; + while (extent < pageExtent && lineIndex < count && lineIndex != -1) { + final int index = getNextScrollLineIndex(lineIndex); + if (index < 0) { + break; + } + + lineIndex = index; + extent += getLineSize(index); + } + + return lineIndex; + } + + /// Determines the line one page up from the given line. + /// + /// * lineIndex The index of the current line. + /// Returns the line index of the line one page up from the given line. + int getPreviousPage(int lineIndex) { + double extent = 0; + final double pageExtent = scrollPageSize - getLineSize(lineIndex); + while (extent < pageExtent && lineIndex > 0) { + lineIndex = getPreviousScrollLineIndex(lineIndex); + extent += getLineSize(lineIndex); + } + + return lineIndex; + } + + /// + void headerLineChangedCallback() { + setFooterLineCount(scrollLinesHost!.getFooterLineCount()); + markDirty(); + raiseChanged(ScrollChangedAction.footerLineCountChanged); + } + + /// + void hiddenRangeChangedCallback( + HiddenRangeChangedArgs hiddenRangeChangedArgs) { + for (int n = hiddenRangeChangedArgs.from; + n <= hiddenRangeChangedArgs.to; + n++) { + int repeatSizeCount = 0; + final List hiddenValue = + scrollLinesHost!.getHidden(n, repeatSizeCount); + final bool hide = hiddenValue[0] as bool; + repeatSizeCount = hiddenValue[1] as int; + final int rangeTo = + getRangeToHelper(n, hiddenRangeChangedArgs.to, repeatSizeCount); + setLineHiddenState(n, rangeTo, hide); + n = rangeTo; + } + + markDirty(); + updateScrollBar(true); + raiseChanged(ScrollChangedAction.hiddenLineChanged); + } + + /// Gets a boolean value indicating whether the line with the + /// given absolute line index is visible. + /// + /// * lineIndex The index of the line. + /// A boolean value indicating whether the line with the given absolute + /// line index is visible. + + bool isLineVisible(int lineIndex) => + getVisibleLines().getVisibleLineAtLineIndex(lineIndex) != null || + ((lineIndex > _lastBodyLineIndex) && _allBodyLinesShown); + + /// + void linesRemovedCallback(LinesRemovedArgs linesRemovedArgs) { + onLinesRemoved(linesRemovedArgs.removeAt, linesRemovedArgs.count); + raiseChanged(ScrollChangedAction.linesRemoved); + } + + /// + void linesInsertedCallback(LinesInsertedArgs linesInsertedArgs) { + onLinesInserted(linesInsertedArgs.insertAt, linesInsertedArgs.count); + raiseChanged(ScrollChangedAction.linesInserted); + } + + /// + void lineCountChangedCallback() { + lineCount = scrollLinesHost!.getLineCount(); + markDirty(); + updateScrollBar(true); + raiseChanged(ScrollChangedAction.lineCountChanged); + } + + /// Makes the layout dirty. + /// + void markDirty() { + _layoutDirty = true; + } + + /// This method is called in response to a MouseWheel event. + /// + /// * delta The delta. + void mouseWheel(int delta); + + /// Called when lines were removed in ScrollLinesHost. + /// + /// * removeAt Index of the first removed line. + /// * count The count. + void onLinesRemoved(int removeAt, int count) {} + + /// Called when lines were inserted in ScrollLinesHost. + /// + /// * insertAt Index of the first inserted line. + /// * count The count. + void onLinesInserted(int insertAt, int count) {} + + /// Resets temporary value for line size after a resize operation. + void resetLineResize() { + const int repeatSizeCount = 0; + if (_lineResizeIndex >= 0 && _scrollLinesHost != null) { + setLineSize(_lineResizeIndex, _lineResizeIndex, + getScrollLinesHostSize(_lineResizeIndex, repeatSizeCount)[0]); + } + + _lineResizeIndex = -1; + _lineResizeSize = 0; + markDirty(); + raiseChanged(ScrollChangedAction.lineResized); + } + + /// + void rangeChangedCallback(RangeChangedArgs rangeChangedArgs) { + for (int n = rangeChangedArgs.from; n <= rangeChangedArgs.to; n++) { + int repeatSizeCount = 0; + final List lineSize = getScrollLinesHostSize(n, repeatSizeCount); + final double size = lineSize[0] as double; + repeatSizeCount = lineSize[1] as int; + final int rangeTo = + getRangeToHelper(n, rangeChangedArgs.to, repeatSizeCount); + setLineSize(n, rangeTo, size); + n = rangeTo; + } + // Also check whether I need to re-hide any of the rows. + hiddenRangeChangedCallback(HiddenRangeChangedArgs.fromArgs( + rangeChangedArgs.from, rangeChangedArgs.to, false)); + markDirty(); + raiseChanged(ScrollChangedAction.lineResized); + } + + /// Resets the visible lines collection. + void resetVisibleLines() { + _visibleLines.clear(); + } + + /// Returns an array with 3 ranges indicating the first and last point + /// for the given lines in each region. + /// + /// * first The index of the first line. + /// * last The index of the last line. + /// * allowEstimatesForOutOfViewLines if set to true allow estimates + /// for out of view lines. + /// Returns An array with 3 ranges indicating the first and last point + /// for the given lines in each region. + List rangeToRegionPoints( + int first, int last, bool allowEstimatesForOutOfViewLines); + + /// Gets the first and last point for the given lines in a region. + /// + /// * region The region. + /// * first The index of the first line. + /// * last The index of the last line. + /// * allowEstimatesForOutOfViewLines if set to true allow estimates + /// for out of view lines. + /// Returns the first and last point for the given lines in a region. + DoubleSpan rangeToPoints(ScrollAxisRegion region, int first, int last, + bool allowEstimatesForOutOfViewLines); + + /// Raises the `Changed` event. + /// + /// * action scroll action + void raiseChanged(ScrollChangedAction action) { + if (onChangedCallback != null) { + final ScrollChangedArgs scrollChangedArgs = + ScrollChangedArgs.fromArgs(action); + onChangedCallback!(scrollChangedArgs); + } + } + + /// + void scrollChangedCallback(ScrollChangedArgs scrollChangedArgs) {} + + /// Scrolls the line into viewable area. + /// * lineIndex Index of the line. + /// * lineSize Size of the line. + /// * isRightToLeft The boolean value indicates the right to left mode. + void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) {} + + /// Scrolls the line into viewable area. + /// + /// * lineIndex The index of the line. + /// * isRightToLeft The boolean value used to calculate visible columns + /// in right to left mode.s + void scrollInViewwithTwoArgs(int lineIndex, bool isRightToLeft) { + scrollInView(lineIndex, getLineSize(lineIndex), isRightToLeft); + } + + /// Sets the footer line count. + /// + /// * value The value. + void setFooterLineCount(int value); + + /// Sets the header line count. + /// + /// * value The value. + void setHeaderLineCount(int value); + + /// Sets the hidden state of the lines. + /// + /// * from The start index of the line. + /// * to The end index of the line. + /// * hide A boolean value indicating whether to hide the lines. + /// if set to true - [hide]. + void setLineHiddenState(int from, int to, bool hide); + + /// Sets the size of the lines for the given range of lines. + /// Will do nothing for a [LineScrollAxis]. + /// + /// * from The start index of the line. + /// * to The end index of the line. + /// * size The line size. + void setLineSize(int from, int to, double size); + + /// Set temporary value for a line size during a resize operation + /// without committing value to ScrollLinesHost. + /// + /// * index The index of the line. + /// * size The size of the line. + void setLineResize(int index, double size) { + _lineResizeIndex = index; + _lineResizeSize = size; + setLineSize(index, index, size); + markDirty(); + raiseChanged(ScrollChangedAction.lineResized); + } + + /// Sets the index of the scroll line. + /// + /// * scrollLineIndex The index of the scroll line. + /// * scrollLineOffset The scroll line offset. + void setScrollLineIndex(int scrollLineIndex, double scrollLineOffset); + + /// Scrolls to next page. + void scrollToNextPage(); + + /// Scrolls to next line. + void scrollToNextLine(); + + /// Scrolls to previous page. + void scrollToPreviousPage(); + + /// Scrolls to previous line. + void scrollToPreviousLine(); + + /// + void updateScrollbar(); + + /// Updates the scroll bar. + /// + /// * ignorePropertyChange A boolean value indicating whether to ignore the + /// property change. + void updateScrollBar(bool ignorePropertyChange) { + final bool b = _ignoreScrollBarPropertyChange; + _ignoreScrollBarPropertyChange |= ignorePropertyChange; + try { + updateScrollbar(); + } finally { + _ignoreScrollBarPropertyChange = b; + } + } + + /// Unfreezes the visible lines. + void unfreezeVisibleLines() { + _inGetVisibleLines = false; + } + + /// + void unwireScrollLinesHost() { + if (scrollLinesHost == null) { + return; + } + scrollLinesHost! + ..onDefaultLineSizeChanged = defaultLineSizeChangedCallback + ..onLineHiddenChanged = hiddenRangeChangedCallback + ..onLineSizeChanged = rangeChangedCallback + ..onLinesInserted = linesInsertedCallback + ..onLinesRemoved = linesRemovedCallback + ..onFooterLineCountChanged = headerLineChangedCallback + ..onFooterLineCountChanged = footerLineChangedCallback + ..onLineCountChanged = lineCountChangedCallback; + } + + /// Gets the view corner which is the point after the last visible line + /// of the body region. + /// + /// Returns the view corner which is the point after the last visible line + /// of the body region. + double get viewCorner { + int scrollLineIndex = 0; + double scrollOffset = 0.0; + final List lineSize = + getScrollLineIndex(scrollLineIndex, scrollOffset); + scrollLineIndex = lineSize[0] as int; + scrollOffset = lineSize[1] as double; + final double arrangeSize = renderSize; + final double adjustedFooterExtent = + adjustFooterExtentToAvoidGap(footerExtent, arrangeSize); + + return arrangeSize - adjustedFooterExtent; + } + + /// Gets the visible line index for a point in the display. + /// + /// * point The point. + /// * allowOutsideLines Set this true if point can be below corner + /// of last line. + /// Returns the visible line index for a point in the display. + int visiblePointToLineIndexWithTwoArgs(double point, bool allowOutsideLines) { + if (allowOutsideLines) { + point = max(point, 0); + } + + final VisibleLineInfo? line = + getVisibleLines().getVisibleLineAtPoint(point); + if (line != null && (allowOutsideLines || point <= line.corner)) { + return line.lineIndex; + } + + return -1; + } + + /// Gets the visible line index for a point in the display. + /// + /// * point The point in the display for which the line index + /// is to be obtained. + /// Returns the visible line index for a point in the display. + int visiblePointToLineIndex(double point) => + visiblePointToLineIndexWithTwoArgs(point, true); + + /// + void wireScrollLinesHost() { + if (scrollLinesHost == null) { + return; + } + + scrollLinesHost! + ..onDefaultLineSizeChanged = defaultLineSizeChangedCallback + ..onLineHiddenChanged = hiddenRangeChangedCallback + ..onLineSizeChanged = rangeChangedCallback + ..onLinesInserted = linesInsertedCallback + ..onLinesRemoved = linesRemovedCallback + ..onFooterLineCountChanged = headerLineChangedCallback + ..onFooterLineCountChanged = footerLineChangedCallback + ..onLineCountChanged = lineCountChangedCallback; + } +} + +/// PixelScrollAxis supports pixel scrolling and calculates the total height or +/// width of all lines. +/// +/// PixelScrollAxis implements scrolling logic for both horizontal and vertical +/// scrolling in a `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` +/// Logical units in the ScrollAxisBase are called "Lines". With the +/// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` a line +/// represents rows in a grid +/// and with `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` a +/// line represents columns in a grid. +class PixelScrollAxis extends ScrollAxisBase { + /// Initializes a new instance of the PixelScrollAxis class which + /// is nested as a single line in a parent scroll axis. + /// + /// * parentScrollAxis - _required_ - The parent scroll axis. + /// * scrollBar - _required_ - The scrollbar state. + /// * scrollLinesHost - _required_ - The scroll lines host. + /// * distancesHost - _required_ - The distances host. + PixelScrollAxis(ScrollAxisBase parentScrollAxis, ScrollBarBase scrollBar, + LineSizeHostBase scrollLinesHost, DistancesHostBase distancesHost) + : super(scrollBar, scrollLinesHost, + headerExtent: 0.0, footerExtent: 0.0) { + // GridCellGridRenderer passes in Distances. LineSizeCollection holds them. + // This allows faster construction of grids when they were scrolled + // out of view and unloaded. + _parentScrollAxis = parentScrollAxis; + _distancesHost = distancesHost; + if (distances == null) { + throw ArgumentError('Distances'); + } + } + + /// Initializes a new instance of the PixelScrollAxis class. + /// + /// * scrollBar - _required_ - The scrollbar state. + /// * scrollLinesHost - _required_ - The scroll lines host. + /// * distancesHost - _required_ - The distances host. + PixelScrollAxis.fromPixelScrollAxis(ScrollBarBase scrollBar, + LineSizeHostBase? scrollLinesHost, DistancesHostBase? distancesHost) + : super(scrollBar, scrollLinesHost) { + if (distancesHost != null) { + _distancesHost = distancesHost; + } else if (scrollLinesHost != null) { + _distances = DistanceRangeCounterCollection() + ..defaultDistance = scrollLinesHost.getDefaultLineSize(); + scrollLinesHost.initializeScrollAxis(this); + } + + if (distances == null) { + throw ArgumentError('Distances'); + } + } + + /// Distances holds the line sizes. Hidden lines + /// have a distance of 0.0. + DistanceCounterCollectionBase? _distances; + DistancesHostBase? _distancesHost; + ScrollAxisBase? _parentScrollAxis; + + /// + bool inLineResize = false; + + /// Gets the distances collection which is used internally for mapping + /// from a point position to a line index and vice versa. + /// + /// Returns the distances collection which is used internally for mapping + /// from a point position to a line index and vice versa. + DistanceCounterCollectionBase? get distances { + if (_distancesHost != null) { + return _distancesHost!.distances; + } + + return _distances; + } + + /// Gets the total extent of all line sizes. + /// + /// Returns the total extent of all line sizes. + double get totalExtent => distances?.totalDistance ?? 0.0; + + /// Gets the default size of lines. + /// + /// Returns the default size of lines. + @override + double get defaultLineSize => _distances?.defaultDistance ?? 0.0; + + /// Sets the default size of lines. + @override + set defaultLineSize(double value) { + if (defaultLineSize != value) { + if (_distances != null) { + _distances! + ..defaultDistance = value + ..clear(); + + if (scrollLinesHost != null) { + scrollLinesHost!.initializeScrollAxis(this); + } + } + + updateScrollbar(); + + if (_parentScrollAxis != null) { + _parentScrollAxis!.setLineSize( + startLineIndex, startLineIndex, distances?.totalDistance ?? 0); + } + } + } + + /// Gets the footer extent. This is total height (or width) of the + /// footer lines. + /// + /// Returns the footer extent. + @override + double get footerExtent { + getVisibleLines(); + return super.footerExtent; + } + + /// Gets the header extent. This is total height (or width) of the + /// header lines. + /// + /// Returns the header extent. + @override + double get headerExtent { + getVisibleLines(); + return super.headerExtent; + } + + /// Gets a value indicating whether this axis supports pixel scrolling. + /// + /// Returns true if this instance supports pixel scrolling, otherwise false. + @override + bool get isPixelScroll => true; + + /// Gets the line count. + /// + /// Returns the line count. + @override + int get lineCount => distances?.count ?? 0; + + /// Sets the line count. + @override + set lineCount(int value) { + if (lineCount != value) { + if (_distances != null) { + _distances!.count = value; + } + updateScrollbar(); + } + } + + /// Gets the index of the first visible Line in the Body region. + /// + /// Returns the index of the scroll line. + @override + int get scrollLineIndex => + distances?.indexOfCumulatedDistance(scrollBar?.value ?? 0.0) ?? -1; + + /// Sets the index of the first visible Line in the Body region. + @override + set scrollLineIndex(int value) { + setScrollLineIndex(value, 0.0); + } + + /// Gets the view size of the (either height or width) of the parent control. + /// + /// Normally the ViewSize is the same as `ScrollAxisBase.RenderSize`. + /// Only if the parent control has more space then needed to display + /// all lines, the ViewSize will be less. In such case the ViewSize is + /// the total height for all lines. + /// + /// Returns the size of the view. + @override + double get viewSize => min(renderSize, distances?.totalDistance ?? 0.0); + + /// Aligns the scroll line. + @override + void alignScrollLine() { + final double d = + distances?.getAlignedScrollValue(scrollBar?.value ?? 0.0) ?? 0.0; + if (!(d == double.nan)) { + scrollBar!.value = d; + } + } + + /// Gets the index of the next scroll line. + /// + /// * index - _required_ - The index. + /// + /// Returns the index of the next scroll line. + @override + int getNextScrollLineIndex(int index) => + distances?.getNextVisibleIndex(index) ?? -1; + + /// Gets the index of the previous scroll line. + /// + /// * index - _required_ - The index. + /// + /// Returns the index of the previous scroll line. + @override + int getPreviousScrollLineIndex(int index) { + double point = distances?.getCumulatedDistanceAt(index) ?? 0.0; + point--; + if (point > -1) { + return distances?.indexOfCumulatedDistance(point) ?? 0; + } + return -1; + } + + /// Gets the index of the scroll line. + /// + /// * scrollLineIndex - _required_ - Index of the scroll line. + /// * scrollLineDelta - _required_ - The scroll line offset. + /// * isRightToLeft - _optional_ - The boolean value used to calculate + /// visible columns in right to left mode. + @override + List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, + [bool isRightToLeft = false]) { + if (!isRightToLeft && scrollBar != null && distances != null) { + scrollLineIndex = + max(0, distances!.indexOfCumulatedDistance(scrollBar!.value)); + if (scrollLineIndex >= lineCount) { + scrollLineDelta = 0.0; + } else { + scrollLineDelta = (scrollBar!.value - + distances!.getCumulatedDistanceAt(scrollLineIndex)) + .toDouble(); + } + } else { + scrollLineIndex = max( + 0, + distances!.indexOfCumulatedDistance(scrollBar!.maximum - + scrollBar!.largeChange - + scrollBar!.value + + (headerLineCount == 0 ? clip.start : clip.start + headerExtent) + + (renderSize > scrollBar!.maximum + footerExtent + ? renderSize - + scrollBar!.maximum - + footerExtent - + headerExtent + : 0))); + + if (scrollLineIndex >= lineCount) { + scrollLineDelta = 0.0; + } else { + scrollLineDelta = (scrollBar!.maximum - + scrollBar!.largeChange - + scrollBar!.value + + (headerLineCount == 0 + ? clip.start + : (scrollBar!.maximum - + scrollBar!.largeChange - + scrollBar!.value != + -1 + ? clip.start + headerExtent + : (clip.start > 0 + ? clip.start + headerExtent + 1 + : 1))) + + (renderSize > scrollBar!.maximum + footerExtent + ? renderSize - + scrollBar!.maximum - + footerExtent - + headerExtent + : 0) - + distances!.getCumulatedDistanceAt(scrollLineIndex)) + .toDouble(); + } + } + return [scrollLineIndex, scrollLineDelta]; + } + + /// Gets the cumulated origin taking scroll position into account. + /// + /// + /// Returned value of this is between `ScrollBar.Minimum` and + /// `ScrollBar.Maximum`. + /// + /// * line - _required_ - The line. + /// + /// Returns the cumulated origin + double getCumulatedOrigin(VisibleLineInfo line) { + final VisibleLinesCollection lines = getVisibleLines(); + if (line.isHeader) { + return line.origin; + } else if (line.isFooter) { + return scrollBar!.maximum - + lines[lines.firstFooterVisibleIndex].origin + + line.origin; + } + + return line.origin - scrollBar!.minimum + scrollBar!.value; + } + + /// Gets the cumulated corner taking scroll position into account. + /// + /// Returned value of this is between `ScrollBar.Minimum` + /// and `ScrollBar.Maximum`. + /// + /// * line - _required_ - The line. + /// + /// Returns the cumulated corner + double getCumulatedCorner(VisibleLineInfo line) { + final VisibleLinesCollection lines = getVisibleLines(); + if (line.isHeader) { + return line.corner; + } else if (line.isFooter) { + return scrollBar!.maximum - + lines[lines.firstFooterVisibleIndex].origin + + line.corner; + } + + return line.corner - scrollBar!.minimum + scrollBar!.value; + } + + /// This method is called in response to a MouseWheel event. + /// + /// * delta - _required_ - The delta. + @override + void mouseWheel(int delta) { + scrollBar!.value -= delta; + } + + /// Called when lines were removed in ScrollLinesHost. + /// + /// * removeAt - _required_ - Index of the first removed line. + /// * count - _required_ - The count. + @override + void onLinesRemoved(int removeAt, int count) { + if (distances != null) { + distances!.remove(removeAt, count); + } + } + + /// Called when lines were inserted in ScrollLinesHost. + /// + /// * insertAt - _required_ - Index of the first inserted line. + /// * count - _required_ - The count. + @override + void onLinesInserted(int insertAt, int count) { + if (distances != null) { + DistancesUtil.onInserted(distances!, scrollLinesHost!, insertAt, count); + } + } + + /// Returns an array with 3 ranges indicating the first and last point + /// for the given lines in each region. + /// + /// * first - _required_ - The index of the first line. + /// * last - _required_ - The index of the last line. + /// * allowEstimatesForOutOfViewLines - _required_ - if set to true allow + /// estimates for out of view lines. + /// + /// Returns An array with 3 ranges indicating the first and last point for + /// the given lines in each region. + @override + List rangeToRegionPoints( + int first, int last, bool allowEstimatesForOutOfViewLines) { + double p1, p2; + p1 = distances!.getCumulatedDistanceAt(first); + p2 = last >= distances!.count - 1 + ? distances!.totalDistance + : distances!.getCumulatedDistanceAt(last + 1); + + final List result = []; + for (int n = 0; n < 3; n++) { + late ScrollAxisRegion region; + if (n == 0) { + region = ScrollAxisRegion.header; + } else if (n == 1) { + region = ScrollAxisRegion.body; + } else if (n == 2) { + region = ScrollAxisRegion.footer; + } + result.insert(n, rangeToPointsHelper(region, p1, p2)); + } + return result; + } + + /// Resets temporary value for line size after a resize operation. + @override + void resetLineResize() { + inLineResize = true; + super.resetLineResize(); + if (_parentScrollAxis != null) { + _parentScrollAxis!.resetLineResize(); + } + + inLineResize = false; + } + + /// Returns the first and last point for the given lines in a region. + /// + /// * region - _required_ - The region. + /// * first - _required_ - The index of the first line. + /// * last - _required_ - The index of the last line. + /// * allowEstimatesForOutOfViewLines - _required_ - if set to true allow + /// estimates for out of view lines. + /// + /// Returns the first and last point for the given lines in a region. + @override + DoubleSpan rangeToPoints(ScrollAxisRegion region, int first, int last, + bool allowEstimatesForOutOfViewLines) { + final VisibleLinesCollection lines = getVisibleLines(); + + // If line is visible use already calculated values, + // otherwise get value from Distances + final VisibleLineInfo? line1 = lines.getVisibleLineAtLineIndex(first); + final VisibleLineInfo? line2 = lines.getVisibleLineAtLineIndex(last); + + double p1, p2; + p1 = line1 == null + ? distances!.getCumulatedDistanceAt(first) + : getCumulatedOrigin(line1); + + p2 = line2 == null + ? distances!.getCumulatedDistanceAt(last + 1) + : getCumulatedCorner(line2); + + return rangeToPointsHelper(region, p1, p2); + } + + /// + DoubleSpan rangeToPointsHelper( + ScrollAxisRegion region, double p1, double p2) { + final VisibleLinesCollection lines = getVisibleLines(); + DoubleSpan doubleSpan = DoubleSpan.empty(); + switch (region) { + case ScrollAxisRegion.header: + if (headerLineCount > 0) { + doubleSpan = DoubleSpan(p1, p2); + } + break; + case ScrollAxisRegion.footer: + if (isFooterVisible) { + final VisibleLineInfo l = lines[lines.firstFooterVisibleIndex]; + final double p3 = distances!.totalDistance - footerExtent; + p1 += l.origin - p3; + p2 += l.origin - p3; + doubleSpan = DoubleSpan(p1, p2); + } + break; + case ScrollAxisRegion.body: + p1 += headerExtent - scrollBar!.value; + p2 += headerExtent - scrollBar!.value; + doubleSpan = DoubleSpan(p1, p2); + break; + default: + doubleSpan = DoubleSpan.empty(); + break; + } + + return doubleSpan; + } + + /// Sets the index of the scroll line. + /// + /// * scrollLineIndex - _required_ - Index of the scroll line. + /// * scrollLineDelta - _required_ - The scroll line delta. + @override + void setScrollLineIndex(int scrollLineIndex, double scrollLineDelta) { + scrollLineIndex = min(lineCount, max(0, scrollLineIndex)); + scrollBar!.value = + distances!.getCumulatedDistanceAt(scrollLineIndex) + scrollLineDelta; + resetVisibleLines(); + } + + /// Scrolls to next page. + @override + void scrollToNextPage() { + scrollBar!.value += max(scrollBar!.smallChange, + scrollBar!.largeChange - scrollBar!.smallChange); + scrollToNextLine(); + } + + /// Scrolls to next line. + @override + void scrollToNextLine() { + final double d = distances!.getNextScrollValue(scrollBar!.value); + if (!(d == double.nan)) { + scrollBar!.value = d <= scrollBar!.value + ? distances!.getNextScrollValue(scrollBar!.value + 1) + : d; + } else { + scrollBar!.value += scrollBar!.smallChange; + } + } + + /// Scrolls to previous line. + @override + void scrollToPreviousLine() { + final double d = distances!.getPreviousScrollValue(scrollBar!.value); + if (!(d == double.nan)) { + scrollBar!.value = d; + } + } + + /// Scrolls to previous page. + @override + void scrollToPreviousPage() { + scrollBar!.value -= max(scrollBar!.smallChange, + scrollBar!.largeChange - scrollBar!.smallChange); + alignScrollLine(); + } + + /// Scrolls the line into viewable area. + /// + /// * lineIndex - _required_ - Index of the line. + /// * lineSize - _required_ - Size of the line. + /// * isRightToLeft - _required_ - The boolean value used to calculate + /// visible columns in right to left mode. + @override + void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) { + final VisibleLinesCollection lines = getVisibleLines(); + final VisibleLineInfo? line = lines.getVisibleLineAtLineIndex(lineIndex); + double delta = 0; + + if (line != null) { + if (!line.isClipped || line.isFooter || line.isHeader) { + return; + } + + if (line.isClippedOrigin && !line.isClippedCorner) { + delta = -(lineSize - line.clippedSize); + } else if (!line.isClippedOrigin && line.isClippedCorner) { + //Following code prevent the horizontal auto scrolling when column + // size is bigger than viewPort size. + if (line.clippedOrigin < line.clippedSize) { + delta = 0; + } else { + delta = lineSize - line.clippedSize; + } + } else { + delta = lineSize - line.clippedSize; + } + } else { + double d = distances!.getCumulatedDistanceAt(lineIndex); + + if (d > scrollBar!.value) { + d = d + lineSize - scrollBar!.largeChange; + } + + delta = d - scrollBar!.value; + } + + if (delta != 0) { + scrollBar!.value += delta; + } + } + + /// Sets the footer line count. + /// + /// * value - _required_ - The value. + @override + void setFooterLineCount(int value) { + if (value == 0) { + super.footerExtent = 0; + } else { + if (distances!.count <= value) { + super.footerExtent = 0; + return; + } + + final int n = distances!.count - value; + + // The Total distance must be reduced by the padding size of the Distance + // total size. Then it should be calculated. This issue is occured in + // Nested Grid. SD 9312. this will give the exact size of the footer + // Extent when padding distance is reduced from total distance. + final bool isDistanceCounterSubset = distances is DistanceCounterSubset; + if (!isDistanceCounterSubset) { + // Nested Grid cells in GridControl is not + // DistanceRangeCounterCollection type. + final DistanceRangeCounterCollection _distances = + distances! as DistanceRangeCounterCollection; + super.footerExtent = distances!.totalDistance - + _distances.paddingDistance - + distances!.getCumulatedDistanceAt(n); + } else { + super.footerExtent = + distances!.totalDistance - distances!.getCumulatedDistanceAt(n); + } + } + } + + /// Sets the header line count. + /// + /// * value - _required_ - The value. + @override + void setHeaderLineCount(int value) { + super.headerExtent = + distances!.getCumulatedDistanceAt(min(value, distances!.count)); + } + + /// Sets the hidden state of the lines. + /// + /// * from - _required_ - The start index of the line. + /// * to - _required_ - The end index of the line. + /// * hide - _required_ - A boolean value indicating whether to hide the + /// lines. if set to true - [hide]. + @override + void setLineHiddenState(int from, int to, bool hide) { + if (hide) { + setLineSize(from, to, 0.0); + } else { + for (int n = from; n <= to; n++) { + int repeatSizeCount = -1; + final List lineSize = + getLineSizeWithTwoArgs(n, repeatSizeCount); + final double size = lineSize[0] as double; + repeatSizeCount = lineSize[1] as int; + final int rangeTo = getRangeToHelper(n, to, repeatSizeCount); + setLineSize(n, rangeTo, size); + n = rangeTo; + } + } + } + + /// Sets the size of the lines for the given range of lines. Will do nothing + /// for a `LineScrollAxis`. + /// + /// * from - _required_ - The start index of the line. + /// * to - _required_ - The end index of the line. + /// * size - _required_ - The line size. + @override + void setLineSize(int from, int to, double size) { + if (_distances != null) { + _distances!.setRange(from, to, size); + } + + // special case for SetLineResize when axis is nested. Parent Scroll + // Axis relies on Distances.TotalDistance and this only gets updated if + // we temporarily set the value in the collection. + else if (_distancesHost != null && inLineResize) { + _distancesHost!.distances?.setRange(from, to, size); + } + } + + /// Set temporary value for a line size during a resize operation + /// without committing value to ScrollLinesHost. + /// + /// * index - _required_ - The index of the line. + /// * size - _required_ - The size of the line. + @override + void setLineResize(int index, double size) { + inLineResize = true; + if (distances!.getNestedDistances(index) == null) { + super.setLineResize(index, size); + } else { + markDirty(); + raiseChanged(ScrollChangedAction.lineResized); + } + + if (_parentScrollAxis != null) { + _parentScrollAxis! + .setLineResize(startLineIndex, distances!.totalDistance); + } + + inLineResize = false; + } + + /// Associates a collection of nested lines with a line in this axis. + /// + /// * index - _required_ - The index. + /// * nestedLines - _required_ - The nested lines. + void setNestedLines(int index, DistanceCounterCollectionBase? nestedLines) { + if (distances != null) { + distances!.setNestedDistances(index, nestedLines); + } + } + + /// Initialize scrollbar properties from header and footer size and + /// total size of lines in body. + @override + void updateScrollbar() { + final ScrollBarBase? sb = scrollBar; + setHeaderLineCount(headerLineCount); + setFooterLineCount(footerLineCount); + + final double delta = headerExtent - sb!.minimum; + final double oldValue = sb.value; + + sb + ..minimum = headerExtent + ..maximum = distances!.totalDistance - footerExtent + ..smallChange = distances!.defaultDistance; + final double proposeLargeChange = scrollPageSize; + sb.largeChange = proposeLargeChange; + + // SH - Added check for delta != 0 to avoid value being reset when + // you resize grid such that only header columns are visible and then + // resize back to larger size. + // SH 6/22 - Commented out change to sb.Value and also modified + // ScrollInfo.Value to return Math.Max(minimum, + // Math.Min(maximum - largeChange, value)); instead. + if (proposeLargeChange >= 0 && delta != 0) { + sb.value = oldValue + delta; + } + } +} + +/// The LineScrollAxis implements scrolling only for whole lines. +/// +/// You can hide lines and LineScrollAxis provides a mapping mechanism between +/// the index of the line and the scroll index and vice versa. Hidden lines +/// are not be counted when the scroll index is determined for a line. +/// The LineScrollAxis does not support scrolling in between +/// lines (pixel scrolling). +/// This can be of advantage if you have a large number of lines with varying +/// line sizes. In such case the LineScrollAxis does not need to maintain +/// a collection that tracks line sizes whereas the `PixelScrollAxis` +/// does need to. +class LineScrollAxis extends ScrollAxisBase { + /// Initializes a new instance of the LineScrollAxis class. + /// + /// * scrollBar - _required_ - The state of the scrollbar. + /// * scrollLinesHost - _required_ - The scroll lines host. + LineScrollAxis(ScrollBarBase scrollBar, LineSizeHostBase scrollLinesHost) + : super(scrollBar, scrollLinesHost, + headerExtent: 0.0, + footerExtent: 0.0, + defaultLineSize: 0.0, + viewSize: 0.0) { + final Object _distancesHost = scrollLinesHost; + if (_distancesHost is DistancesHostBase) { + _distances = _distancesHost.distances; + } else { + _distances = DistanceRangeCounterCollection(); + } + + scrollLinesHost.initializeScrollAxis(this); + _distances!.defaultDistance = 1.0; + } + + /// distances holds the visible lines. Each visible line + /// has a distance of 1.0. Hidden lines have a distance of 0.0. + DistanceCounterCollectionBase? _distances; + + /// Gets the distances collection which is used internally for mapping + /// from a point position to + /// a line index and vice versa. + /// + /// Returns the distances collection which is used internally for mapping + /// from a point position + /// to a line index and vice versa. + DistanceCounterCollectionBase? get distances => _distances; + + /// Gets the total extent of all line sizes. + /// + /// Returns the total extent of all line sizes. + double get totalExtent => distances?.totalDistance ?? 0.0; + + /// Sets the default size of lines. + @override + set defaultLineSize(double value) { + if (defaultLineSize != value) { + super.defaultLineSize = value; + updateDistances(); + updateScrollbar(); + } + } + + /// Gets a value indicating whether this axis supports pixel scrolling. + /// + /// Returns `true` if this instance supports pixel scrolling; + /// otherwise, `false`. + @override + bool get isPixelScroll => false; + + /// Gets the line count. + /// + /// Returns the line count. + @override + int get lineCount => _distances?.count ?? 0; + + /// Sets the line count. + @override + set lineCount(int value) { + if (lineCount != value) { + _distances!.count = value; + updateScrollbar(); + } + } + + /// Gets the index of the first visible Line in the Body region. + /// + /// Returns the index of the first visible Line in the Body region. + @override + int get scrollLineIndex => scrollBarValueToLineIndex(scrollBar!.value); + + /// Sets the index of the first visible Line in the Body region. + set scrollLinesIndex(int value) { + setScrollLineIndex(value, 0.0); + } + + /// Gets the view size of the (either height or width) of the parent control. + /// + /// Normally the ViewSize is the same as `ScrollAxisBase.RenderSize`. + /// Only if the parent control + /// has more space then needed to display all lines, the ViewSize will be + /// less. In such case the ViewSize is the total height for all lines. + /// + /// Returns the size of the view. + @override + double get viewSize { + if (scrollBar != null && + scrollBar!.value + scrollBar!.largeChange > scrollBar!.maximum) { + return super.viewSize; + } else { + return renderSize; + } + } + + /// + int determineLargeChange() { + double sbValue = scrollBar!.maximum; + final double abortSize = scrollPageSize; + int count = 0; + super.viewSize = 0; + + while (sbValue >= scrollBar!.minimum) { + final int lineIndex = scrollBarValueToLineIndex(sbValue); + final double size = getLineSize(lineIndex); + if (super.viewSize + size > abortSize) { + break; + } + + count++; + sbValue--; + super.viewSize += size; + } + + super.viewSize += footerExtent + headerExtent; + + return count; + } + + /// + int intPreviousPageLineIndex(double p, int index) { + int result = index; + while (p > 0) { + result = index; + index = getPreviousScrollLineIndex(index); + if (index == -1) { + return -1; + } + + p -= getLineSize(index); + } + + return result; + } + + /// + double lineIndexToScrollBarValue(int lineIndex) => + _distances?.getCumulatedDistanceAt(lineIndex) ?? 0.0; + + /// + int scrollBarValueToLineIndex(double sbValue) => + _distances?.indexOfCumulatedDistance(sbValue) ?? 0; + + /// Updates the line size for visible lines to be "1" for LineScrollAxis + void updateDistances() { + int repeatSizeCount = -1; + + for (int index = 0; index < lineCount; index++) { + final List hiddenValue = + scrollLinesHost!.getHidden(index, repeatSizeCount); + final bool hide = hiddenValue[0] as bool; + repeatSizeCount = hiddenValue[1] as int; + final double value = hide == true ? 0.0 : 1.0; + final int rangeTo = + getRangeToHelper(index, lineCount - 1, repeatSizeCount); + _distances!.setRange(index, rangeTo, value); + index = rangeTo; + } + } + + /// Aligns the scroll line. + @override + void alignScrollLine() {} + + /// Gets the index of the next scroll line. + /// + /// * index - _required_ - The current index of the scroll line. + /// Returns the index of the next scroll line. + @override + int getNextScrollLineIndex(int index) { + if (_distances != null && _distances!.count > index) { + return _distances!.getNextVisibleIndex(index); + } else { + return 0; + } + } + + /// Gets the index of the previous scroll line. + /// + /// * index - _required_ - The current index of the scroll line. + /// Returns the index of the previous scroll line. + @override + int getPreviousScrollLineIndex(int index) { + if (_distances != null && _distances!.count > index) { + return _distances!.getPreviousVisibleIndex(index); + } else { + return 0; + } + } + + /// Gets the index of the scroll line. + /// + /// * scrollLineIndex - _required_ - Index of the scroll line. + /// * scrollLineDelta - _required_ - The scroll line delta. + /// * isRightToLeft - _required_ - The boolean value used to calculate visible + /// columns in right to left mode. + @override + List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, + [bool isRightToLeft = false]) { + scrollLineIndex = scrollBarValueToLineIndex(scrollBar!.value); + scrollLineDelta = 0.0; + return [scrollLineIndex, scrollLineDelta]; + } + + /// This method is called in response to a MouseWheel event. + /// + /// * delta - _required_ - The delta. + @override + void mouseWheel(int delta) { + if (delta > 0) { + scrollToPreviousLine(); + } else { + scrollToNextLine(); + } + } + + /// Called when lines were inserted in ScrollLinesHost. + /// + /// * insertAt - _required_ - Index of the first inserted line. + /// * count - _required_ - The count. + @override + void onLinesInserted(int insertAt, int count) { + final int to = insertAt + count - 1; + int repeatSizeCount = -1; + for (int index = insertAt; index <= to; index++) { + final List hiddenValue = + scrollLinesHost!.getHidden(index, repeatSizeCount); + final bool hide = hiddenValue[0] as bool; + repeatSizeCount = hiddenValue[1] as int; + final double value = hide == true ? 0.0 : 1.0; + final int rangeTo = getRangeToHelper(index, to, repeatSizeCount); + distances!.setRange(index, rangeTo, value); + index = rangeTo; + } + } + + /// The first and last point for the given lines in a region. + /// + /// * region - _required_ - The scroll axis region. + /// * first - _required_ - The index of the first line. + /// * last - _required_ - The index of the last line. + /// * allowEstimatesForOutOfViewLines - _required_ - if set to true + /// allow estimates for out of view lines. + /// + /// Returns the first and last point for the given lines in a region. + @override + DoubleSpan rangeToPoints(ScrollAxisRegion region, int first, int last, + bool allowEstimatesForOutOfViewLines) { + bool firstVisible = false; + bool lastVisible = false; + VisibleLineInfo? firstLine, lastLine; + + final List lineValues = getLinesAndVisibility( + first, last, true, firstVisible, lastVisible, firstLine, lastLine); + firstVisible = lineValues[0] as bool; + lastVisible = lineValues[1] as bool; + firstLine = lineValues[2] as VisibleLineInfo?; + lastLine = lineValues[3] as VisibleLineInfo?; + if (firstLine == null || lastLine == null) { + return DoubleSpan.empty(); + } + + if (allowEstimatesForOutOfViewLines) { + switch (region) { + case ScrollAxisRegion.header: + if (!firstLine.isHeader) { + return DoubleSpan.empty(); + } + + break; + case ScrollAxisRegion.footer: + if (!lastLine.isFooter) { + return DoubleSpan.empty(); + } + + break; + case ScrollAxisRegion.body: + if (firstLine.isFooter || lastLine.isHeader) { + return DoubleSpan.empty(); + } + + break; + } + + return DoubleSpan(firstLine.origin, lastLine.corner); + } else { + switch (region) { + case ScrollAxisRegion.header: + { + if (!firstLine.isHeader) { + return DoubleSpan.empty(); + } + + if (!lastVisible || !lastLine.isHeader) { + double corner = firstLine.corner; + for (int n = firstLine.lineIndex + 1; n <= last; n++) { + corner += getLineSize(n); + } + + return DoubleSpan(firstLine.origin, corner); + } + + return DoubleSpan(firstLine.origin, lastLine.corner); + } + + case ScrollAxisRegion.footer: + { + if (!lastLine.isFooter) { + return DoubleSpan.empty(); + } + + if (!firstVisible || !firstLine.isFooter) { + double origin = lastLine.origin; + for (int n = lastLine.lineIndex - 1; n >= first; n--) { + origin -= getLineSize(n); + } + + return DoubleSpan(origin, lastLine.corner); + } + + return DoubleSpan(firstLine.origin, lastLine.corner); + } + + case ScrollAxisRegion.body: + { + if (firstLine.isFooter || lastLine.isHeader) { + return DoubleSpan.empty(); + } + + double origin = firstLine.origin; + if (!firstVisible || firstLine.region != ScrollAxisRegion.body) { + origin = headerExtent; + for (int n = scrollLineIndex - 1; n >= first; n--) { + origin -= getLineSize(n); + } + } + + double corner = lastLine.corner; + if (!lastVisible || lastLine.region != ScrollAxisRegion.body) { + corner = lastBodyVisibleLine!.corner; + for (int n = lastBodyVisibleLine!.lineIndex + 1; n <= last; n++) { + corner += getLineSize(n); + } + } + + return DoubleSpan(origin, corner); + } + } + } + } + + /// An array with 3 ranges indicating the first and last point + /// for the given lines in each region. + /// + /// * first - _required_ - The index of the first line. + /// * last - _required_ - The index of the last line. + /// * allowEstimatesForOutOfViewLines - _required_ - if set to true + /// allow estimates for out of view lines. + /// + /// Returns An array with 3 ranges indicating the first and last point + /// for the given lines in each region. + @override + List rangeToRegionPoints( + int first, int last, bool allowEstimatesForOutOfViewLines) { + final List result = []; + for (int n = 0; n < 3; n++) { + late ScrollAxisRegion region; + if (n == 0) { + region = ScrollAxisRegion.header; + } else if (n == 1) { + region = ScrollAxisRegion.body; + } else if (n == 2) { + region = ScrollAxisRegion.footer; + } + + result.insert(n, + rangeToPoints(region, first, last, allowEstimatesForOutOfViewLines)); + } + + return result; + } + + /// Scrolls the line into viewable area. + /// + /// * lineIndex - _required_ - The index of the line. + /// * lineSize - _required_ - The size of the line. + /// * isRightToLeft - _required_ - The boolean value used to calculate + /// visible columns in right to left mode. + @override + void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) { + final VisibleLinesCollection lines = getVisibleLines(); + final VisibleLineInfo? line = lines.getVisibleLineAtLineIndex(lineIndex); + double delta = 0; + + if (line != null) { + if (!line.isClipped || line.isFooter || line.isHeader) { + return; + } + + if (line.isClippedOrigin && !line.isClippedCorner) { + delta = -1; + } else if (!line.isClippedOrigin && line.isClippedCorner) { + double y = line.size - line.clippedSize; + double visibleScrollIndex = scrollBar!.value; + while (y > 0 && visibleScrollIndex < scrollBar!.maximum) { + delta++; + visibleScrollIndex++; + y -= getLineSize(scrollBarValueToLineIndex(visibleScrollIndex)); + } + } + } else { + double visibleScrollIndex = lineIndexToScrollBarValue(lineIndex); + + if (visibleScrollIndex > scrollBar!.value) { + final int scrollIndexLinex = intPreviousPageLineIndex( + scrollPageSize - getLineSize(lineIndex), lineIndex); + visibleScrollIndex = lineIndexToScrollBarValue(scrollIndexLinex); + } + + delta = visibleScrollIndex - scrollBar!.value; + } + + if (delta != 0) { + scrollBar!.value += delta; + } + + super.scrollInView(lineIndex, lineSize, isRightToLeft); + } + + /// Scrolls to next line. + @override + void scrollToNextLine() { + scrollLineIndex = getNextScrollLineIndex(scrollLineIndex); + } + + /// Scrolls to next page. + @override + void scrollToNextPage() { + int index = visiblePointToLineIndexWithTwoArgs(scrollPageSize, true); + if (index == scrollLineIndex) { + index = getNextScrollLineIndex(index); + } + + scrollLineIndex = index; + } + + /// Scrolls to previous line. + @override + void scrollToPreviousLine() { + scrollLineIndex = getPreviousScrollLineIndex(scrollLineIndex); + } + + /// Scrolls to previous page. + @override + void scrollToPreviousPage() { + int index = intPreviousPageLineIndex(scrollPageSize, scrollLineIndex); + if (index == scrollLineIndex) { + index = getPreviousScrollLineIndex(index); + } + + scrollLineIndex = index; + } + + /// Sets the index of the scroll line. + /// + /// * scrollLineIndex - _required_ - The index of the scroll line. + /// * scrollLineDelta - _required_ - The scroll line delta. + @override + void setScrollLineIndex(int scrollLineIndex, double scrollLineDelta) { + scrollLineIndex = + min(max(_distances!.count - 1, 0), max(0, scrollLineIndex)); + scrollBar!.value = lineIndexToScrollBarValue(scrollLineIndex); + resetVisibleLines(); + } + + /// Sets the footer line count. + /// + /// * value - _required_ - The value. + @override + void setFooterLineCount(int value) { + double size = 0.0; + int lines = 0; + int index = lineCount - 1; + while (lines < value) { + size += getLineSize(index--); + lines++; + } + + super.footerExtent = size; + } + + /// Sets the header line count. + /// + /// * value - _required_ - The value. + @override + void setHeaderLineCount(int value) { + double size = 0.0; + int lines = 0; + while (lines < value) { + size += getLineSize(lines++); + } + super.headerExtent = size; + } + + /// Sets the hidden state of the lines. + /// + /// * from - _required_ - The start index of the line. + /// * to - _required_ - The end index of the line. + /// * hide - _required_ - A boolean value indicating whether to hide the + /// lines. if set to true - [hide]. + @override + void setLineHiddenState(int from, int to, bool hide) { + _distances!.setRange(from, to, hide ? 0.0 : 1.0); + } + + /// Sets the size of the lines for the given range of lines. Will do nothing + /// for a `LineScrollAxis`. + /// + /// * from - _required_ - The start index of the line. + /// * to - _required_ - The end index of the line. + /// * size - _required_ - The line size. + @override + void setLineSize(int from, int to, double size) {} + + /// Initialize scrollbar properties from line count in header, + /// footer and body. + @override + void updateScrollbar() { + setHeaderLineCount(headerLineCount); + setFooterLineCount(footerLineCount); + + final ScrollBarBase sb = scrollBar!; + final bool isMinimum = sb.minimum == sb.value; + sb.minimum = headerLineCount.toDouble(); + final double maximum = _distances!.totalDistance - footerLineCount; + sb + ..maximum = max(maximum, 0) + ..smallChange = 1 + ..largeChange = determineLargeChange().toDouble() + ..value = + isMinimum ? sb.minimum : max(sb.minimum, min(sb.maximum, sb.value)); + } +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart deleted file mode 100644 index 25ac4a893..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_collection_base.dart +++ /dev/null @@ -1,181 +0,0 @@ -part of datagrid; - -/// A collection of entities for which distances need to counted. -/// -/// The collection provides methods for mapping from a distance position to -/// an entity and vice versa. -/// For example, in a scrollable grid control you have rows with different -/// heights. -/// Use this collection to determine the total height for all rows in the grid, -/// quickly determine the row index for a given point and also quickly determine -/// the point at which a row is displayed. This also allows a mapping between -/// the scrollbars value and the rows or columns associated with that value. -abstract class _DistanceCounterCollectionBase { - /// Gets the raw number of entities (lines, rows or columns). - /// - /// Returns the raw number of entities (lines, rows or columns). - int get count => _count; - int _count = 0; - - /// Sets the raw number of entities (lines, rows or columns). - set count(int value) { - if (value == _count) { - return; - } - - _count = value; - } - - /// Gets the default distance (row height or column width) an entity spans. - /// - /// Returns the default distance (row height or column width) an entity spans. - double get defaultDistance => _defaultDistance; - double _defaultDistance = 0; - - /// Sets the default distance (row height or column width) an entity spans. - set defaultDistance(double value) { - if (value == _defaultDistance) { - return; - } - - _defaultDistance = value; - } - - /// Gets the total distance all entities span - /// (e.g. total height of all rows in grid). - /// - /// Returns the total distance all entities span - /// (e.g. total height of all rows in grid). - double get totalDistance => _totalDistance; - double _totalDistance = 0; - set totalDistance(double value) { - if (value == _totalDistance) { - return; - } - - _totalDistance = value; - } - - /// Clears this instance. - void clear(); - - /// Connects a nested distance collection with a parent. - /// - /// * treeTableCounterSource - _required_ - The `_TreeTableCounterSourceBase` - /// representing the nested tree table visible counter source. - void connectWithParent(_TreeTableCounterSourceBase treeTableCounterSource); - - /// Gets the aligned scroll value which is the starting point of the entity - /// found at the given distance position. - /// - /// * point - _required_ - The point. - /// - /// Returns the starting point of the entity found at the - /// given distance position. - double getAlignedScrollValue(double point); - - /// Gets the nested entities at a given index. If the index does not hold - /// a nested distances collection the method returns null. - /// - /// * index - _required_ - The index. - /// Returns - _required_ - the nested entities at a given index or null. - _DistanceCounterCollectionBase? getNestedDistances(int index); - - /// Gets the distance position of the next entity after a given point. - /// - /// * point - _required_ - The point after which the next entity is - /// to be found. - /// Returns - _required_ - the distance position of the next entity - /// after a given point. - double getNextScrollValue(double point); - - /// Gets the distance position of the entity preceding a given point. - /// If the point is in between entities, the starting point of - /// the matching entity is returned. - /// - /// * point - _required_ - The point of the entity preceding a given point. - /// - /// Returns the distance position of the entity preceding a given point. - double getPreviousScrollValue(double point); - - /// Gets the cumulated count of previous distances for the - /// entity at the specific index. - /// (e.g. return pixel position for a row index). - /// - /// * index - _required_ - The entity index. - /// - /// Returns the cumulated count of previous distances for the - /// entity at the specific index. - double getCumulatedDistanceAt(int index); - - /// Gets the next visible index. Skip subsequent entities for which - /// the distance is 0.0 and return the next entity. - /// - /// * index - _required_ - The index. - /// - /// Returns the next visible index from the given index. - int getNextVisibleIndex(int index); - - /// Gets the previous visible index. Skip previous entities for which - /// the distance is 0.0 and return the previous entity. - /// - /// * index - _required_ - The index. - /// Returns the previous visible index from the given index. - int getPreviousVisibleIndex(int index); - - /// Inserts entities in the collection from the given index. - /// - /// * insertAt - _required_ - The index of the first entity to be inserted. - /// * count - _required_ - The number of entities to be inserted. - void insert(int insertAt, int count); - - /// Gets the index of an entity in this collection for which - /// the cumulated count of previous distances is greater than or equal to - /// the specified cumulatedDistance. (e.g. return row index for - /// pixel position). - /// - /// * cumulatedDistance - _required_ - The cumulated count - /// of previous distances. - /// - /// Returns the index of an entity in this collection for which - /// the cumulated count of previous distances is greater than or equal to - /// the specified cumulatedDistance. - int indexOfCumulatedDistance(double cumulatedDistance); - - /// Removes entities in the collection from the given index. - /// - /// * removeAt - _required_ - Index of the first entity to be removed. - /// * count - _required_ - The number of entities to be removed. - void remove(int removeAt, int count); - - /// Resets the range by restoring the default distance for all - /// entries in the specified range. - /// - /// * from The index for the first entity. - /// * to The raw index for the last entity. - void resetRange(int from, int to); - - /// Assigns a collection with nested entities to an item. - /// - /// * index - _required_ - The index. - /// * nestedCollection - _required_ - The nested collection. - void setNestedDistances( - int index, _DistanceCounterCollectionBase? nestedCollection); - - /// Hides a specified range of entities (lines, rows or columns). - /// - /// * from - _required_ - The index for the first entity. - /// * to - _required_ - The raw index for the last entity. - /// * distance - _required_ - The distance. - void setRange(int from, int to, double distance); - - /// Gets the distance for an entity from the given index. - /// - /// * index - _required_ - The index for the entity. - /// - /// Returns the distance for an entity from the given index. - double operator [](int index) => this[index]; - - /// Sets the distance for an entity from the given index. - void operator []=(int index, double value) => this[index] = value; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart deleted file mode 100644 index 6785a40a2..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/distance_counter_subset.dart +++ /dev/null @@ -1,302 +0,0 @@ -part of datagrid; - -/// A collection of entities that is shared with a parent collection for which -/// distances need to counted. -/// -/// The collection only is a subset for a specific range in -/// the parent distance collection. When you change the size of an element in -/// this collection the change will -/// also be reflected in the parent collection and vice versa. -// Need to check whether the distance is DistanceCounterSubset or not in -// PixelScrollAxis's SetFooterLineCount method. -// ignore: unused_element -class _DistanceCounterSubset extends _DistanceCounterCollectionBase - with _DistancesHostBase { - /// Initializes a new instance of the DistanceCounterSubset class. - /// - /// * trackedParentCollection - _required_ - The parent collection for which - /// a subset is "tracked". - _DistanceCounterSubset( - _DistanceCounterCollectionBase trackedParentCollection) { - _trackDCC = trackedParentCollection; - _count = 0; - } - - late _DistanceCounterCollectionBase _trackDCC; - int start = 0; - - /// Gets an object that implements the `Distances` property. - @override - _DistanceCounterCollectionBase get distances => _distances; - - /// Gets an distance the `Distances` property. - _DistanceCounterCollectionBase get _distances => this; - - /// Gets the ending index of this collection in the parent collection. - /// - /// Returns the ending index of this collection in the parent collection. - int get end => start + _count - 1; - - /// Sets the raw number of entities - @override - set count(int value) { - if (_count != value) { - _count = value; - } - } - - /// Gets the default distance (row height or column width) an entity spans. - /// - /// Returns the default distance (row height or column width) an entity spans. - @override - double get defaultDistance => _trackDCC.defaultDistance; - - /// Sets the dafault distnace an entity spans. - @override - set defaultDistance(double value) { - _trackDCC.defaultDistance = value; - } - - /// Gets the total distance all entities span - /// (e.g. total height of all rows in grid). - /// - /// Returns the total distance all entities span - /// (e.g. total height of all rows in grid). - @override - double get totalDistance { - if (count == 0) { - return 0; - } - - return _trackDCC.getCumulatedDistanceAt(end) - - _trackDCC.getCumulatedDistanceAt(start) + - _trackDCC[end]; - } - - /// Restores the distances in the parent collection for this subset to - /// their default distance. - @override - void clear() { - _trackDCC.resetRange(start, end); - } - - /// This method is not supported for DistanceCounterSubset. - /// - /// * treeTableCounterSource - _required_ - The nested tree - /// table visible counter source. - @override - void connectWithParent(_TreeTableCounterSourceBase treeTableCounterSource) { - connectWithParentBase(treeTableCounterSource); - } - - /// This method is not supported for DistanceCounterSubset. - /// - /// * treeTableCounterSource - _required_ - The nested tree - /// table visible counter source. - void connectWithParentBase( - _TreeTableCounterSourceBase treeTableCounterSource) { - throw Exception('Do not use DistanceCounterSubset as nested collection!'); - } - - /// Gets the aligned scroll value which is the starting point of the entity - /// found at the given distance position. - /// - /// * point - _required_ - The point. - /// - /// Returns the starting point of the entity found at - /// the given distance position. - @override - double getAlignedScrollValue(double point) { - final double offset = _trackDCC.getPreviousScrollValue(start.toDouble()); - final double d = _trackDCC.getAlignedScrollValue(point + offset); - if (d == double.nan || d < offset || d - offset > totalDistance) { - return double.nan; - } - - return d - offset; - } - - /// Gets the cumulated count of previous distances for the entity at - /// the specific index. (e.g. return pixel position for a row index). - /// - /// * index - _required_ - The entity index. - /// - /// Returns the cumulated count of previous distances for the - /// entity at the specific index. - @override - double getCumulatedDistanceAt(int index) => - _trackDCC.getCumulatedDistanceAt(index + start) - - _trackDCC.getCumulatedDistanceAt(start); - - /// Gets the nested entities at a given index. - /// - /// If the index does not hold a nested distances collection - /// the method returns null. - /// - /// * index - _required_ - The index. - /// - /// Returns the nested entities at a given index or null. - @override - _DistanceCounterCollectionBase? getNestedDistances(int index) => - _trackDCC.getNestedDistances(index + start); - - /// Gets the next visible index. - /// - /// Skip subsequent entities for which the distance is 0.0 - /// and return the next entity. - /// - /// * index - _required_ - The index. - /// - /// Returns the next visible index from the given index. - @override - int getNextVisibleIndex(int index) { - final int n = _trackDCC.getNextVisibleIndex(index + start); - if (n > end) { - return -1; - } - - return n - start; - } - - /// Gets the distance position of the next entity after a given point. - /// - /// * point - _required_ - The point after which the next entity - /// is to be found. - /// - /// Returns the distance position of the next entity after a given point. - @override - double getNextScrollValue(double point) { - final double offset = _trackDCC.getCumulatedDistanceAt(start); - final double d = _trackDCC.getNextScrollValue(point + offset); - if (d == double.nan || d < offset || d - offset > totalDistance) { - return double.nan; - } - - return d - offset; - } - - /// Gets the previous visible index. - /// - /// Skip previous entities for which the distance is 0.0 and return - /// the previous entity. - /// - /// * index - _required_ - The index. - /// - /// Returns the previous visible index from the given index. - @override - int getPreviousVisibleIndex(int index) { - final int n = _trackDCC.getPreviousVisibleIndex(index + start); - if (n < start) { - return -1; - } - - return n - start; - } - - /// Gets the distance position of the entity preceding a given point. - /// - /// If the point is in between entities, the starting point of - /// the matching entity is returned. - /// - /// * point - _required_ - The point of the entity preceding a given point. - /// - /// Returns the distance position of the entity preceding a given point. - @override - double getPreviousScrollValue(double point) { - final double offset = _trackDCC.getCumulatedDistanceAt(start); - final double d = _trackDCC.getPreviousScrollValue(point + offset); - if (d == double.nan || d < offset || d - offset > totalDistance) { - return double.nan; - } - - return d - offset; - } - - /// Inserts entities in the collection from the given index. - /// - /// * insertAt - _required_ - The index of the first entity to be inserted. - /// * count - _required_ - The number of entities to be inserted. - @override - void insert(int insertAt, int count) { - _trackDCC.insert(insertAt + start, count); - } - - /// Gets the index of an entity in this collection for which - /// the cumulated count of previous distances is greater than or equal to - /// the specified cumulatedDistance. (e.g. return row index for - /// pixel position). - /// - /// * cumulatedDistance - _required_ - The cumulated count of previous - /// distances. - /// - /// Returns the index of an entity in this collection for which - /// the cumulated count of previous distances is greater than or equal to - /// the specified cumulatedDistance. - @override - int indexOfCumulatedDistance(double cumulatedDistance) { - if (count == 0 && cumulatedDistance == 0) { - return 0; - } - - final int n = _trackDCC.indexOfCumulatedDistance( - cumulatedDistance + _trackDCC.getCumulatedDistanceAt(start)); - if (n > end || n < start) { - return -1; - } - - return n - start; - } - - /// Removes entities in the collection from the given index. - /// - /// * removeAt - _required_ - Index of the first entity to be removed. - /// * count - _required_ - The number of entities to be removed. - @override - void remove(int removeAt, int count) { - _trackDCC.remove(removeAt + start, count); - } - - /// Resets the range by restoring the default distance for all - /// entries in the specified range. - /// - /// * from - _required_ - The index for the first entity. - /// * to - _required_ - The raw index for the last entity. - @override - void resetRange(int from, int to) { - _trackDCC.resetRange(from + start, to + start); - } - - /// Hides a specified range of entities (lines, rows or columns). - /// - /// * from - _required_ - The index for the first entity. - /// * to - _required_ - The raw index for the last entity. - /// * distance - _required_ - The distance. - @override - void setRange(int from, int to, double distance) { - _trackDCC.setRange(from + start, to + start, distance); - } - - /// Assigns a collection with nested entities to an item. - /// - /// * index - _required_ - The index. - /// * nestedCollection - _required_ - The nested collection. - @override - void setNestedDistances( - int index, _DistanceCounterCollectionBase? nestedCollection) { - _trackDCC.setNestedDistances(index + start, nestedCollection); - } - - /// Gets the distance for an entity from the given index. - /// - /// * index - _required_ - The index for the entity. - /// - /// Returns the distance for an entity from the given index. - @override - double operator [](int index) => _trackDCC[index + start]; - - /// Sets the distance for an entity from the given index. - @override - void operator []=(int index, double value) { - _trackDCC[index + start] = value; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart deleted file mode 100644 index b9410c320..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/editable_line_size_host_base.dart +++ /dev/null @@ -1,205 +0,0 @@ -part of datagrid; - -/// A collection that manages lines with varying height and hidden state. -/// -/// It has properties for header and footer lines, total line count, default -/// size of a line and also lets you add nested collections. Methods -/// are provided for changing the values and getting the total extent. -/// -/// _Note:_ This is common for header, footer, total line count and default -/// size of line. -abstract class _EditableLineSizeHostBase extends _LineSizeHostBase { - /// Gets the default size of lines. - /// - /// Returns the default size of lines. - double get defaultLineSize => _defaultLineSize; - double _defaultLineSize = 0.0; - - /// Sets the default size of lines. - set defaultLineSize(double value) { - if (value == _defaultLineSize) { - return; - } - - _defaultLineSize = value; - } - - /// Gets the footer line count. - /// - /// Returns the footer line count. - int get footerLineCount => _footerLineCount; - int _footerLineCount = 0; - - /// Sets the footer line count. - set footerLineCount(int value) { - if (value == _footerLineCount) { - return; - } - - _footerLineCount = value; - } - - /// Gets the header line count. - /// - /// Returns the header line count. - int get headerLineCount => _headerLineCount; - int _headerLineCount = 0; - - /// Sets the header line count. - set headerLineCount(int value) { - if (value == _headerLineCount) { - return; - } - - _headerLineCount = value; - } - - /// Gets the line count. - /// - /// Returns the line count. - int get lineCount => _lineCount; - int _lineCount = 0; - - /// Sets the line count. - set lineCount(int value) { - if (value == _lineCount) { - return; - } - - _lineCount = value; - } - - /// Gets a value indicating whether the host supports inserting and - /// removing lines. - /// - /// Returns the boolean value indicating whether the host supports inserting - /// and removing lines. - bool get supportsInsertRemove => _supportsInsertRemove; - bool _supportsInsertRemove = false; - set supportsInsertRemove(bool value) { - if (value == _supportsInsertRemove) { - return; - } - _supportsInsertRemove = value; - } - - /// Gets a value indicating whether the host supports nesting or not. - /// - /// Returns a boolean value indicating whether the host supports nesting. - bool get supportsNestedLines => _supportsNestedLines; - bool _supportsNestedLines = false; - set supportsNestedLines(bool value) { - if (value == _supportsNestedLines) { - return; - } - - _supportsNestedLines = value; - } - - /// Gets the total extent which is the total of all line sizes. - /// - /// Returns the total extent which is the total of all line sizes - /// or `double.NaN`. - /// - /// Note: This property only works if the `DistanceCounterCollection` has - /// been setup for pixel scrolling, - /// otherwise it returns `double.NaN`. - double get totalExtent => _totalExtent; - double _totalExtent = 0; - set totalExtent(double value) { - if (value == _totalExtent) { - return; - } - - _totalExtent = value; - } - - /// Creates the object which holds temporary state when moving lines. - /// - /// Returns the object which holds temporary state when moving lines. - _EditableLineSizeHostBase createMoveLines(); - - /// Gets the nested lines at the given index. - /// - /// * index - _required_ - The index at which the nested lines is to - /// be obtained. - /// - /// Returns the `IEditableLineSizeHost` representing the nested lines. - _EditableLineSizeHostBase? getNestedLines(int index); - - /// Insert the given number of lines at the given index. - /// - /// * insertAtLine - _required_ - The index of the first line to insert. - /// * count - _required_ - The count of the lines to be inserted. - /// * moveLines - _required_ - A container with saved state from a preceding - /// `RemoveLines` call when lines should be moved. When it is null, - /// empty lines with default size are inserted. - void insertLines( - int insertAtLine, int count, _EditableLineSizeHostBase? moveLines); - - /// Removes a number of lines at the given index. - /// - /// * removeAtLine - _required_ - The index of the first line to be removed. - /// * count - _required_ - The count of the lines to be removed. - /// * moveLines - _required_ - A container to save state for a subsequent - /// `InsertLines` call when lines should be moved. - void removeLines( - int removeAtLine, int count, _EditableLineSizeHostBase? moveLines); - - /// Sets the hidden state for the given range of lines. - /// - /// * from - _required_ - The start index of the line for which - /// the hidden state to be set. - /// * to - _required_ - The end index of the line for which the - /// hidden state to be set. - /// * hide - _required_ - A boolean value indicating whether to - /// hide the lines. If set to true - /// hide the lines. - void setHidden(int from, int to, bool hide); - - /// Sets the nested lines at the given index. - /// - /// * index - _required_ - The index at which the nested lines is to be added. - /// * nestedLines - _required_ - The nested lines to be added. If parameter - /// is null the line will - /// be converted to a normal (not nested) line with default line size. - void setNestedLines(int index, _EditableLineSizeHostBase nestedLines); - - /// Sets the line size for the range of lines. - /// - /// * from - _required_ - The start index of the line for which the - /// line size is to be set. - /// * to - _required_ - The end index of the line for which the - /// line size is to be set. - /// * size - _required_ - The line size to be set to the given range of lines. - void setRange(int from, int to, double size); - - /// Gets the line size at the specified index. - /// - /// * index - _required_ - index value - /// Returns the line size at the specified index. - double operator [](int index) => this[index]; - - /// Sets the line size at the specified index. - void operator []=(int index, double value) => this[index] = value; -} - -/// An object that implements the `PaddingDistance` property -/// and `DeferRefresh` method. -abstract class _PaddedEditableLineSizeHostBase - extends _EditableLineSizeHostBase { - /// Gets the padding distance for the line. - /// - /// Returns the padding distance for the line. - double get paddingDistance => _paddingDistance; - double _paddingDistance = 0.0; - - /// Sets the padding distance for the line. - set paddingDistance(double value) { - if (value == _paddingDistance) { - return; - } - - _paddingDistance = value; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart deleted file mode 100644 index 4bb8a755a..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_scroll_axis.dart +++ /dev/null @@ -1,558 +0,0 @@ -part of datagrid; - -/// The LineScrollAxis implements scrolling only for whole lines. -/// -/// You can hide lines and LineScrollAxis provides a mapping mechanism between -/// the index of the line and the scroll index and vice versa. Hidden lines -/// are not be counted when the scroll index is determined for a line. -/// The LineScrollAxis does not support scrolling in between -/// lines (pixel scrolling). -/// This can be of advantage if you have a large number of lines with varying -/// line sizes. In such case the LineScrollAxis does not need to maintain -/// a collection that tracks line sizes whereas the `PixelScrollAxis` -/// does need to. -class _LineScrollAxis extends _ScrollAxisBase { - /// Initializes a new instance of the LineScrollAxis class. - /// - /// * scrollBar - _required_ - The state of the scrollbar. - /// * scrollLinesHost - _required_ - The scroll lines host. - _LineScrollAxis(_ScrollBarBase scrollBar, _LineSizeHostBase scrollLinesHost) - : super(scrollBar, scrollLinesHost) { - _viewSize = 0.0; - _defaultLineSize = 0.0; - _headerExtent = 0.0; - _footerExtent = 0.0; - final Object _distancesHost = scrollLinesHost; - if (_distancesHost is _DistancesHostBase) { - _distances = _distancesHost.distances; - } else { - _distances = _DistanceRangeCounterCollection(); - } - - scrollLinesHost.initializeScrollAxis(this); - _distances!.defaultDistance = 1.0; - } - - /// distances holds the visible lines. Each visible line - /// has a distance of 1.0. Hidden lines have a distance of 0.0. - _DistanceCounterCollectionBase? _distances; - - /// Gets the distances collection which is used internally for mapping - /// from a point position to - /// a line index and vice versa. - /// - /// Returns the distances collection which is used internally for mapping - /// from a point position - /// to a line index and vice versa. - _DistanceCounterCollectionBase? get distances => _distances; - - /// Gets the total extent of all line sizes. - /// - /// Returns the total extent of all line sizes. - double get totalExtent => distances?.totalDistance ?? 0.0; - - /// Sets the default size of lines. - @override - set defaultLineSize(double value) { - if (defaultLineSize != value) { - _defaultLineSize = value; - updateDistances(); - updateScrollbar(); - } - } - - /// Gets a value indicating whether this axis supports pixel scrolling. - /// - /// Returns `true` if this instance supports pixel scrolling; - /// otherwise, `false`. - @override - bool get isPixelScroll => false; - - /// Gets the line count. - /// - /// Returns the line count. - @override - int get lineCount => _distances?.count ?? 0; - - /// Sets the line count. - @override - set lineCount(int value) { - if (lineCount != value) { - _distances!.count = value; - updateScrollbar(); - } - } - - /// Gets the index of the first visible Line in the Body region. - /// - /// Returns the index of the first visible Line in the Body region. - @override - int get scrollLineIndex => scrollBarValueToLineIndex(scrollBar!.value); - - /// Sets the index of the first visible Line in the Body region. - set scrollLinesIndex(int value) { - setScrollLineIndex(value, 0.0); - } - - /// Gets the view size of the (either height or width) of the parent control. - /// - /// Normally the ViewSize is the same as `ScrollAxisBase.RenderSize`. - /// Only if the parent control - /// has more space then needed to display all lines, the ViewSize will be - /// less. In such case the ViewSize is the total height for all lines. - /// - /// Returns the size of the view. - @override - double get viewSize { - if (scrollBar != null && - scrollBar!.value + scrollBar!.largeChange > scrollBar!.maximum) { - return _viewSize; - } else { - return renderSize; - } - } - - int determineLargeChange() { - double sbValue = scrollBar!.maximum; - final double abortSize = scrollPageSize; - int count = 0; - _viewSize = 0; - - while (sbValue >= scrollBar!.minimum) { - final int lineIndex = scrollBarValueToLineIndex(sbValue); - final double size = getLineSize(lineIndex); - if (_viewSize + size > abortSize) { - break; - } - - count++; - sbValue--; - _viewSize += size; - } - - _viewSize += footerExtent + headerExtent; - - return count; - } - - int intPreviousPageLineIndex(double p, int index) { - int result = index; - while (p > 0) { - result = index; - index = getPreviousScrollLineIndex(index); - if (index == -1) { - return -1; - } - - p -= getLineSize(index); - } - - return result; - } - - double lineIndexToScrollBarValue(int lineIndex) => - _distances?.getCumulatedDistanceAt(lineIndex) ?? 0.0; - - int scrollBarValueToLineIndex(double sbValue) => - _distances?.indexOfCumulatedDistance(sbValue) ?? 0; - - /// Updates the line size for visible lines to be "1" for LineScrollAxis - void updateDistances() { - int repeatSizeCount = -1; - - for (int index = 0; index < lineCount; index++) { - final List hiddenValue = - scrollLinesHost!.getHidden(index, repeatSizeCount); - final bool hide = hiddenValue[0] as bool; - repeatSizeCount = hiddenValue[1] as int; - final double value = hide == true ? 0.0 : 1.0; - final int rangeTo = - getRangeToHelper(index, lineCount - 1, repeatSizeCount); - _distances!.setRange(index, rangeTo, value); - index = rangeTo; - } - } - - /// Aligns the scroll line. - @override - void alignScrollLine() {} - - /// Gets the index of the next scroll line. - /// - /// * index - _required_ - The current index of the scroll line. - /// Returns the index of the next scroll line. - @override - int getNextScrollLineIndex(int index) { - if (_distances != null && _distances!.count > index) { - return _distances!.getNextVisibleIndex(index); - } else { - return 0; - } - } - - /// Gets the index of the previous scroll line. - /// - /// * index - _required_ - The current index of the scroll line. - /// Returns the index of the previous scroll line. - @override - int getPreviousScrollLineIndex(int index) { - if (_distances != null && _distances!.count > index) { - return _distances!.getPreviousVisibleIndex(index); - } else { - return 0; - } - } - - /// Gets the index of the scroll line. - /// - /// * scrollLineIndex - _required_ - Index of the scroll line. - /// * scrollLineDelta - _required_ - The scroll line delta. - /// * isRightToLeft - _required_ - The boolean value used to calculate visible - /// columns in right to left mode. - @override - List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, - [bool isRightToLeft = false]) { - scrollLineIndex = scrollBarValueToLineIndex(scrollBar!.value); - scrollLineDelta = 0.0; - return [scrollLineIndex, scrollLineDelta]; - } - - /// This method is called in response to a MouseWheel event. - /// - /// * delta - _required_ - The delta. - @override - void mouseWheel(int delta) { - if (delta > 0) { - scrollToPreviousLine(); - } else { - scrollToNextLine(); - } - } - - /// Called when lines were inserted in ScrollLinesHost. - /// - /// * insertAt - _required_ - Index of the first inserted line. - /// * count - _required_ - The count. - @override - void onLinesInserted(int insertAt, int count) { - final int to = insertAt + count - 1; - int repeatSizeCount = -1; - for (int index = insertAt; index <= to; index++) { - final List hiddenValue = - scrollLinesHost!.getHidden(index, repeatSizeCount); - final bool hide = hiddenValue[0] as bool; - repeatSizeCount = hiddenValue[1] as int; - final double value = hide == true ? 0.0 : 1.0; - final int rangeTo = getRangeToHelper(index, to, repeatSizeCount); - distances!.setRange(index, rangeTo, value); - index = rangeTo; - } - } - - /// The first and last point for the given lines in a region. - /// - /// * region - _required_ - The scroll axis region. - /// * first - _required_ - The index of the first line. - /// * last - _required_ - The index of the last line. - /// * allowEstimatesForOutOfViewLines - _required_ - if set to true - /// allow estimates for out of view lines. - /// - /// Returns the first and last point for the given lines in a region. - @override - _DoubleSpan rangeToPoints(_ScrollAxisRegion region, int first, int last, - bool allowEstimatesForOutOfViewLines) { - bool firstVisible = false; - bool lastVisible = false; - _VisibleLineInfo? firstLine, lastLine; - - final List lineValues = getLinesAndVisibility( - first, last, true, firstVisible, lastVisible, firstLine, lastLine); - firstVisible = lineValues[0] as bool; - lastVisible = lineValues[1] as bool; - firstLine = lineValues[2] as _VisibleLineInfo?; - lastLine = lineValues[3] as _VisibleLineInfo?; - if (firstLine == null || lastLine == null) { - return _DoubleSpan.empty(); - } - - if (allowEstimatesForOutOfViewLines) { - switch (region) { - case _ScrollAxisRegion.header: - if (!firstLine.isHeader) { - return _DoubleSpan.empty(); - } - - break; - case _ScrollAxisRegion.footer: - if (!lastLine.isFooter) { - return _DoubleSpan.empty(); - } - - break; - case _ScrollAxisRegion.body: - if (firstLine.isFooter || lastLine.isHeader) { - return _DoubleSpan.empty(); - } - - break; - } - - return _DoubleSpan(firstLine.origin, lastLine.corner); - } else { - switch (region) { - case _ScrollAxisRegion.header: - { - if (!firstLine.isHeader) { - return _DoubleSpan.empty(); - } - - if (!lastVisible || !lastLine.isHeader) { - double corner = firstLine.corner; - for (int n = firstLine.lineIndex + 1; n <= last; n++) { - corner += getLineSize(n); - } - - return _DoubleSpan(firstLine.origin, corner); - } - - return _DoubleSpan(firstLine.origin, lastLine.corner); - } - - case _ScrollAxisRegion.footer: - { - if (!lastLine.isFooter) { - return _DoubleSpan.empty(); - } - - if (!firstVisible || !firstLine.isFooter) { - double origin = lastLine.origin; - for (int n = lastLine.lineIndex - 1; n >= first; n--) { - origin -= getLineSize(n); - } - - return _DoubleSpan(origin, lastLine.corner); - } - - return _DoubleSpan(firstLine.origin, lastLine.corner); - } - - case _ScrollAxisRegion.body: - { - if (firstLine.isFooter || lastLine.isHeader) { - return _DoubleSpan.empty(); - } - - double origin = firstLine.origin; - if (!firstVisible || firstLine.region != _ScrollAxisRegion.body) { - origin = headerExtent; - for (int n = scrollLineIndex - 1; n >= first; n--) { - origin -= getLineSize(n); - } - } - - double corner = lastLine.corner; - if (!lastVisible || lastLine.region != _ScrollAxisRegion.body) { - corner = lastBodyVisibleLine!.corner; - for (int n = lastBodyVisibleLine!.lineIndex + 1; n <= last; n++) { - corner += getLineSize(n); - } - } - - return _DoubleSpan(origin, corner); - } - } - } - } - - /// An array with 3 ranges indicating the first and last point - /// for the given lines in each region. - /// - /// * first - _required_ - The index of the first line. - /// * last - _required_ - The index of the last line. - /// * allowEstimatesForOutOfViewLines - _required_ - if set to true - /// allow estimates for out of view lines. - /// - /// Returns An array with 3 ranges indicating the first and last point - /// for the given lines in each region. - @override - List<_DoubleSpan> rangeToRegionPoints( - int first, int last, bool allowEstimatesForOutOfViewLines) { - final List<_DoubleSpan> result = <_DoubleSpan>[]; - for (int n = 0; n < 3; n++) { - late _ScrollAxisRegion region; - if (n == 0) { - region = _ScrollAxisRegion.header; - } else if (n == 1) { - region = _ScrollAxisRegion.body; - } else if (n == 2) { - region = _ScrollAxisRegion.footer; - } - - result.insert(n, - rangeToPoints(region, first, last, allowEstimatesForOutOfViewLines)); - } - - return result; - } - - /// Scrolls the line into viewable area. - /// - /// * lineIndex - _required_ - The index of the line. - /// * lineSize - _required_ - The size of the line. - /// * isRightToLeft - _required_ - The boolean value used to calculate - /// visible columns in right to left mode. - @override - void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) { - final _VisibleLinesCollection lines = getVisibleLines(); - final _VisibleLineInfo? line = lines.getVisibleLineAtLineIndex(lineIndex); - double delta = 0; - - if (line != null) { - if (!line.isClipped || line.isFooter || line.isHeader) { - return; - } - - if (line.isClippedOrigin && !line.isClippedCorner) { - delta = -1; - } else if (!line.isClippedOrigin && line.isClippedCorner) { - double y = line.size - line.clippedSize; - double visibleScrollIndex = scrollBar!.value; - while (y > 0 && visibleScrollIndex < scrollBar!.maximum) { - delta++; - visibleScrollIndex++; - y -= getLineSize(scrollBarValueToLineIndex(visibleScrollIndex)); - } - } - } else { - double visibleScrollIndex = lineIndexToScrollBarValue(lineIndex); - - if (visibleScrollIndex > scrollBar!.value) { - final int scrollIndexLinex = intPreviousPageLineIndex( - scrollPageSize - getLineSize(lineIndex), lineIndex); - visibleScrollIndex = lineIndexToScrollBarValue(scrollIndexLinex); - } - - delta = visibleScrollIndex - scrollBar!.value; - } - - if (delta != 0) { - scrollBar!.value += delta; - } - - super.scrollInView(lineIndex, lineSize, isRightToLeft); - } - - /// Scrolls to next line. - @override - void scrollToNextLine() { - scrollLineIndex = getNextScrollLineIndex(scrollLineIndex); - } - - /// Scrolls to next page. - @override - void scrollToNextPage() { - int index = visiblePointToLineIndexWithTwoArgs(scrollPageSize, true); - if (index == scrollLineIndex) { - index = getNextScrollLineIndex(index); - } - - scrollLineIndex = index; - } - - /// Scrolls to previous line. - @override - void scrollToPreviousLine() { - scrollLineIndex = getPreviousScrollLineIndex(scrollLineIndex); - } - - /// Scrolls to previous page. - @override - void scrollToPreviousPage() { - int index = intPreviousPageLineIndex(scrollPageSize, scrollLineIndex); - if (index == scrollLineIndex) { - index = getPreviousScrollLineIndex(index); - } - - scrollLineIndex = index; - } - - /// Sets the index of the scroll line. - /// - /// * scrollLineIndex - _required_ - The index of the scroll line. - /// * scrollLineDelta - _required_ - The scroll line delta. - @override - void setScrollLineIndex(int scrollLineIndex, double scrollLineDelta) { - scrollLineIndex = - min(max(_distances!.count - 1, 0), max(0, scrollLineIndex)); - scrollBar!.value = lineIndexToScrollBarValue(scrollLineIndex); - resetVisibleLines(); - } - - /// Sets the footer line count. - /// - /// * value - _required_ - The value. - @override - void setFooterLineCount(int value) { - double size = 0.0; - int lines = 0; - int index = lineCount - 1; - while (lines < value) { - size += getLineSize(index--); - lines++; - } - - _footerExtent = size; - } - - /// Sets the header line count. - /// - /// * value - _required_ - The value. - @override - void setHeaderLineCount(int value) { - double size = 0.0; - int lines = 0; - while (lines < value) { - size += getLineSize(lines++); - } - _headerExtent = size; - } - - /// Sets the hidden state of the lines. - /// - /// * from - _required_ - The start index of the line. - /// * to - _required_ - The end index of the line. - /// * hide - _required_ - A boolean value indicating whether to hide the - /// lines. if set to true - [hide]. - @override - void setLineHiddenState(int from, int to, bool hide) { - _distances!.setRange(from, to, hide ? 0.0 : 1.0); - } - - /// Sets the size of the lines for the given range of lines. Will do nothing - /// for a `LineScrollAxis`. - /// - /// * from - _required_ - The start index of the line. - /// * to - _required_ - The end index of the line. - /// * size - _required_ - The line size. - @override - void setLineSize(int from, int to, double size) {} - - /// Initialize scrollbar properties from line count in header, - /// footer and body. - @override - void updateScrollbar() { - setHeaderLineCount(headerLineCount); - setFooterLineCount(footerLineCount); - - final _ScrollBarBase sb = scrollBar!; - final bool isMinimum = sb.minimum == sb.value; - sb.minimum = headerLineCount.toDouble(); - final double maximum = _distances!.totalDistance - footerLineCount; - sb - ..maximum = max(maximum, 0) - ..smallChange = 1 - ..largeChange = determineLargeChange().toDouble() - ..value = - isMinimum ? sb.minimum : max(sb.minimum, min(sb.maximum, sb.value)); - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart deleted file mode 100644 index 54c72f7e8..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/line_size_host_base.dart +++ /dev/null @@ -1,122 +0,0 @@ -part of datagrid; - -/// Returns the DefaultLineSizeChangedArgs used by the -/// [onDefaultLineSizeChanged] event. -typedef _DefaultLineSizeChangedCallback = void Function( - _DefaultLineSizeChangedArgs _defaultLineSizeChangedArgs); - -/// Returns the LineCountChangedArgs used by the -/// [onLineCountChanged][onHeaderLineCountChanged][onFooderLineCountChanged] -/// event. -typedef _LineCountChangedCallback = void Function(); - -/// Returns the HiddenRangeChangedArgs used by the [onLineHiddenChanged] event. -typedef _LineHiddenChangedCallback = void Function( - _HiddenRangeChangedArgs _hiddenRangeChangedArgs); - -/// Returns the LinesInsertedArgs used by the [onLinesInserted] event. -typedef _LinesInsertedCallback = void Function( - _LinesInsertedArgs _linesInsertedArgs); - -/// Returns the LinesRemovedArgs used by the [onLinesRemoved] event. -typedef _LinesRemovedCallback = void Function( - _LinesRemovedArgs _linesRemovedArgs); - -/// Returns the RangeChangedArgs used by the [onLineSizeChanged] event. -typedef _LineSizeChangedCallback = void Function( - _RangeChangedArgs _rangeChangedArgs); - -/// A collection that manages lines with varying height and hidden state. -/// -/// It has properties for header and footer lines, total line count, default -/// size of a line and also lets you add nested collections. -/// -/// _Note:_ This is common for header, footer, total line count and default -/// size of line. -abstract class _LineSizeHostBase { - /// Occurs when the default line size changed. - _DefaultLineSizeChangedCallback? onDefaultLineSizeChanged; - - /// Occurs when the footer line count was changed. - _LineCountChangedCallback? onFooterLineCountChanged; - - /// Occurs when the header line count was changed. - _LineCountChangedCallback? onHeaderLineCountChanged; - - /// Occurs when a lines size was changed. - _LineSizeChangedCallback? onLineSizeChanged; - - /// Occurs when a lines hidden state changed. - _LineHiddenChangedCallback? onLineHiddenChanged; - - /// Occurs when the line count was changed. - _LineCountChangedCallback? onLineCountChanged; - - /// Occurs when lines were inserted. - _LinesInsertedCallback? onLinesInserted; - - /// Occurs when lines were removed. - _LinesRemovedCallback? onLinesRemoved; - - /// Returns the default line size.. - double getDefaultLineSize(); - - /// Gets the footer line count. - /// - /// Returns the footer line count. - int getFooterLineCount(); - - /// Gets the header line count. - /// - /// Returns the header line count. - int getHeaderLineCount(); - - /// Gets the boolean value indicating the hidden state for the line - /// with given index. - /// - /// * index - _required_ - The index of the line for which the hidden state - /// is to be obtained. - /// * repeatValueCount - _required_ - The number of subsequent lines with - /// same state. - /// - /// Returns the boolean value indicating the hidden state for a line. - List getHidden(int index, int repeatValueCount); - - /// Returns the line count. - int getLineCount(); - - /// Gets the size of the line at the given index. - /// - /// * index - _required_ - The index of the line for which the size is to - /// be obtained. - /// * repeatValueCount - _required_ - The number of subsequent values - /// with same size. - /// - /// Retuns the size of the line at the given index. - List getSize(int index, int repeatValueCount); - - /// Initializes the scroll axis. - /// - /// * scrollAxis - _required_ - The scroll axis. - void initializeScrollAxis(_ScrollAxisBase scrollAxis); -} - -/// An object that implements the `Distances` property. -mixin _DistancesHostBase { - /// Gets the distances of the lines. - /// - /// Returns the distances of the lines. - _DistanceCounterCollectionBase? get distances; -} - -/// An object that implements the `GetDistances` method. -abstract class _NestedDistancesHostBase { - /// Gets the nested distances, if a line contains a nested lines collection, - /// otherwise null. - /// - /// * line - _required_ - The line at which the distances is to be obtained. - /// - /// Returns the nested distances, if a line contains a nested lines - /// collection, otherwise null. - _DistanceCounterCollectionBase? getDistances(int line); -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart deleted file mode 100644 index 7be87abae..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/pixel_scroll_axis.dart +++ /dev/null @@ -1,700 +0,0 @@ -part of datagrid; - -/// PixelScrollAxis supports pixel scrolling and calculates the total height or -/// width of all lines. -/// -/// PixelScrollAxis implements scrolling logic for both horizontal and vertical -/// scrolling in a `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` -/// Logical units in the ScrollAxisBase are called "Lines". With the -/// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` a line -/// represents rows in a grid -/// and with `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` a -/// line represents columns in a grid. -class _PixelScrollAxis extends _ScrollAxisBase { - /// Initializes a new instance of the PixelScrollAxis class which - /// is nested as a single line in a parent scroll axis. - /// - /// * parentScrollAxis - _required_ - The parent scroll axis. - /// * scrollBar - _required_ - The scrollbar state. - /// * scrollLinesHost - _required_ - The scroll lines host. - /// * distancesHost - _required_ - The distances host. - _PixelScrollAxis(_ScrollAxisBase parentScrollAxis, _ScrollBarBase scrollBar, - _LineSizeHostBase scrollLinesHost, _DistancesHostBase distancesHost) - : super(scrollBar, scrollLinesHost) { - // GridCellGridRenderer passes in Distances. LineSizeCollection holds them. - // This allows faster construction of grids when they were scrolled - // out of view and unloaded. - _parentScrollAxis = parentScrollAxis; - _distancesHost = distancesHost; - _footerExtent = 0.0; - _headerExtent = 0.0; - if (distances == null) { - throw ArgumentError('Distances'); - } - } - - /// Initializes a new instance of the PixelScrollAxis class. - /// - /// * scrollBar - _required_ - The scrollbar state. - /// * scrollLinesHost - _required_ - The scroll lines host. - /// * distancesHost - _required_ - The distances host. - _PixelScrollAxis.fromPixelScrollAxis(_ScrollBarBase scrollBar, - _LineSizeHostBase? scrollLinesHost, _DistancesHostBase? distancesHost) - : super(scrollBar, scrollLinesHost) { - if (distancesHost != null) { - _distancesHost = distancesHost; - } else if (scrollLinesHost != null) { - _distances = _DistanceRangeCounterCollection() - ..defaultDistance = scrollLinesHost.getDefaultLineSize(); - scrollLinesHost.initializeScrollAxis(this); - } - - if (distances == null) { - throw ArgumentError('Distances'); - } - } - - /// Distances holds the line sizes. Hidden lines - /// have a distance of 0.0. - _DistanceCounterCollectionBase? _distances; - _DistancesHostBase? _distancesHost; - _ScrollAxisBase? _parentScrollAxis; - bool inLineResize = false; - - /// Gets the distances collection which is used internally for mapping - /// from a point position to a line index and vice versa. - /// - /// Returns the distances collection which is used internally for mapping - /// from a point position to a line index and vice versa. - _DistanceCounterCollectionBase? get distances { - if (_distancesHost != null) { - return _distancesHost!.distances; - } - - return _distances; - } - - /// Gets the total extent of all line sizes. - /// - /// Returns the total extent of all line sizes. - double get totalExtent => distances?.totalDistance ?? 0.0; - - /// Gets the default size of lines. - /// - /// Returns the default size of lines. - @override - double get defaultLineSize => _distances?.defaultDistance ?? 0.0; - - /// Sets the default size of lines. - @override - set defaultLineSize(double value) { - if (_defaultLineSize != value) { - if (_distances != null) { - _distances! - ..defaultDistance = value - ..clear(); - - if (scrollLinesHost != null) { - scrollLinesHost!.initializeScrollAxis(this); - } - } - - updateScrollbar(); - - if (_parentScrollAxis != null) { - _parentScrollAxis!.setLineSize( - startLineIndex, startLineIndex, distances?.totalDistance ?? 0); - } - } - } - - /// Gets the footer extent. This is total height (or width) of the - /// footer lines. - /// - /// Returns the footer extent. - @override - double get footerExtent { - getVisibleLines(); - return _footerExtent; - } - - /// Gets the header extent. This is total height (or width) of the - /// header lines. - /// - /// Returns the header extent. - @override - double get headerExtent { - getVisibleLines(); - return _headerExtent; - } - - /// Gets a value indicating whether this axis supports pixel scrolling. - /// - /// Returns true if this instance supports pixel scrolling, otherwise false. - @override - bool get isPixelScroll => true; - - /// Gets the line count. - /// - /// Returns the line count. - @override - int get lineCount => distances?.count ?? 0; - - /// Sets the line count. - @override - set lineCount(int value) { - if (lineCount != value) { - if (_distances != null) { - _distances!.count = value; - } - updateScrollbar(); - } - } - - /// Gets the index of the first visible Line in the Body region. - /// - /// Returns the index of the scroll line. - @override - int get scrollLineIndex => - distances?.indexOfCumulatedDistance(scrollBar?.value ?? 0.0) ?? -1; - - /// Sets the index of the first visible Line in the Body region. - @override - set scrollLineIndex(int value) { - setScrollLineIndex(value, 0.0); - } - - /// Gets the view size of the (either height or width) of the parent control. - /// - /// Normally the ViewSize is the same as `ScrollAxisBase.RenderSize`. - /// Only if the parent control has more space then needed to display - /// all lines, the ViewSize will be less. In such case the ViewSize is - /// the total height for all lines. - /// - /// Returns the size of the view. - @override - double get viewSize => min(renderSize, distances?.totalDistance ?? 0.0); - - /// Aligns the scroll line. - @override - void alignScrollLine() { - final double d = - distances?.getAlignedScrollValue(scrollBar?.value ?? 0.0) ?? 0.0; - if (!(d == double.nan)) { - scrollBar!.value = d; - } - } - - /// Gets the index of the next scroll line. - /// - /// * index - _required_ - The index. - /// - /// Returns the index of the next scroll line. - @override - int getNextScrollLineIndex(int index) => - distances?.getNextVisibleIndex(index) ?? -1; - - /// Gets the index of the previous scroll line. - /// - /// * index - _required_ - The index. - /// - /// Returns the index of the previous scroll line. - @override - int getPreviousScrollLineIndex(int index) { - double point = distances?.getCumulatedDistanceAt(index) ?? 0.0; - point--; - if (point > -1) { - return distances?.indexOfCumulatedDistance(point) ?? 0; - } - return -1; - } - - /// Gets the index of the scroll line. - /// - /// * scrollLineIndex - _required_ - Index of the scroll line. - /// * scrollLineDelta - _required_ - The scroll line offset. - /// * isRightToLeft - _optional_ - The boolean value used to calculate - /// visible columns in right to left mode. - @override - List getScrollLineIndex(int scrollLineIndex, double scrollLineDelta, - [bool isRightToLeft = false]) { - if (!isRightToLeft && scrollBar != null && distances != null) { - scrollLineIndex = - max(0, distances!.indexOfCumulatedDistance(scrollBar!.value)); - if (scrollLineIndex >= lineCount) { - scrollLineDelta = 0.0; - } else { - scrollLineDelta = (scrollBar!.value - - distances!.getCumulatedDistanceAt(scrollLineIndex)) - .toDouble(); - } - } else { - scrollLineIndex = max( - 0, - distances!.indexOfCumulatedDistance(scrollBar!.maximum - - scrollBar!.largeChange - - scrollBar!.value + - (headerLineCount == 0 ? clip.start : clip.start + headerExtent) + - (renderSize > scrollBar!.maximum + footerExtent - ? renderSize - - scrollBar!.maximum - - footerExtent - - headerExtent - : 0))); - - if (scrollLineIndex >= lineCount) { - scrollLineDelta = 0.0; - } else { - scrollLineDelta = (scrollBar!.maximum - - scrollBar!.largeChange - - scrollBar!.value + - (headerLineCount == 0 - ? clip.start - : (scrollBar!.maximum - - scrollBar!.largeChange - - scrollBar!.value != - -1 - ? clip.start + headerExtent - : (clip.start > 0 - ? clip.start + headerExtent + 1 - : 1))) + - (renderSize > scrollBar!.maximum + footerExtent - ? renderSize - - scrollBar!.maximum - - footerExtent - - headerExtent - : 0) - - distances!.getCumulatedDistanceAt(scrollLineIndex)) - .toDouble(); - } - } - return [scrollLineIndex, scrollLineDelta]; - } - - /// Gets the cumulated origin taking scroll position into account. - /// - /// - /// Returned value of this is between `ScrollBar.Minimum` and - /// `ScrollBar.Maximum`. - /// - /// * line - _required_ - The line. - /// - /// Returns the cumulated origin - double getCumulatedOrigin(_VisibleLineInfo line) { - final _VisibleLinesCollection lines = getVisibleLines(); - if (line.isHeader) { - return line.origin; - } else if (line.isFooter) { - return scrollBar!.maximum - - lines[lines.firstFooterVisibleIndex].origin + - line.origin; - } - - return line.origin - scrollBar!.minimum + scrollBar!.value; - } - - /// Gets the cumulated corner taking scroll position into account. - /// - /// Returned value of this is between `ScrollBar.Minimum` - /// and `ScrollBar.Maximum`. - /// - /// * line - _required_ - The line. - /// - /// Returns the cumulated corner - double getCumulatedCorner(_VisibleLineInfo line) { - final _VisibleLinesCollection lines = getVisibleLines(); - if (line.isHeader) { - return line.corner; - } else if (line.isFooter) { - return scrollBar!.maximum - - lines[lines.firstFooterVisibleIndex].origin + - line.corner; - } - - return line.corner - scrollBar!.minimum + scrollBar!.value; - } - - /// This method is called in response to a MouseWheel event. - /// - /// * delta - _required_ - The delta. - @override - void mouseWheel(int delta) { - scrollBar!.value -= delta; - } - - /// Called when lines were removed in ScrollLinesHost. - /// - /// * removeAt - _required_ - Index of the first removed line. - /// * count - _required_ - The count. - @override - void onLinesRemoved(int removeAt, int count) { - if (distances != null) { - distances!.remove(removeAt, count); - } - } - - /// Called when lines were inserted in ScrollLinesHost. - /// - /// * insertAt - _required_ - Index of the first inserted line. - /// * count - _required_ - The count. - @override - void onLinesInserted(int insertAt, int count) { - if (distances != null) { - _DistancesUtil.onInserted(distances!, scrollLinesHost!, insertAt, count); - } - } - - /// Returns an array with 3 ranges indicating the first and last point - /// for the given lines in each region. - /// - /// * first - _required_ - The index of the first line. - /// * last - _required_ - The index of the last line. - /// * allowEstimatesForOutOfViewLines - _required_ - if set to true allow - /// estimates for out of view lines. - /// - /// Returns An array with 3 ranges indicating the first and last point for - /// the given lines in each region. - @override - List<_DoubleSpan> rangeToRegionPoints( - int first, int last, bool allowEstimatesForOutOfViewLines) { - double p1, p2; - p1 = distances!.getCumulatedDistanceAt(first); - p2 = last >= distances!.count - 1 - ? distances!.totalDistance - : distances!.getCumulatedDistanceAt(last + 1); - - final List<_DoubleSpan> result = <_DoubleSpan>[]; - for (int n = 0; n < 3; n++) { - late _ScrollAxisRegion region; - if (n == 0) { - region = _ScrollAxisRegion.header; - } else if (n == 1) { - region = _ScrollAxisRegion.body; - } else if (n == 2) { - region = _ScrollAxisRegion.footer; - } - result.insert(n, rangeToPointsHelper(region, p1, p2)); - } - return result; - } - - /// Resets temporary value for line size after a resize operation. - @override - void resetLineResize() { - inLineResize = true; - super.resetLineResize(); - if (_parentScrollAxis != null) { - _parentScrollAxis!.resetLineResize(); - } - - inLineResize = false; - } - - /// Returns the first and last point for the given lines in a region. - /// - /// * region - _required_ - The region. - /// * first - _required_ - The index of the first line. - /// * last - _required_ - The index of the last line. - /// * allowEstimatesForOutOfViewLines - _required_ - if set to true allow - /// estimates for out of view lines. - /// - /// Returns the first and last point for the given lines in a region. - @override - _DoubleSpan rangeToPoints(_ScrollAxisRegion region, int first, int last, - bool allowEstimatesForOutOfViewLines) { - final _VisibleLinesCollection lines = getVisibleLines(); - - // If line is visible use already calculated values, - // otherwise get value from Distances - final _VisibleLineInfo? line1 = lines.getVisibleLineAtLineIndex(first); - final _VisibleLineInfo? line2 = lines.getVisibleLineAtLineIndex(last); - - double p1, p2; - p1 = line1 == null - ? distances!.getCumulatedDistanceAt(first) - : getCumulatedOrigin(line1); - - p2 = line2 == null - ? distances!.getCumulatedDistanceAt(last + 1) - : getCumulatedCorner(line2); - - return rangeToPointsHelper(region, p1, p2); - } - - _DoubleSpan rangeToPointsHelper( - _ScrollAxisRegion region, double p1, double p2) { - final _VisibleLinesCollection lines = getVisibleLines(); - _DoubleSpan doubleSpan = _DoubleSpan.empty(); - switch (region) { - case _ScrollAxisRegion.header: - if (headerLineCount > 0) { - doubleSpan = _DoubleSpan(p1, p2); - } - break; - case _ScrollAxisRegion.footer: - if (isFooterVisible) { - final _VisibleLineInfo l = lines[lines.firstFooterVisibleIndex]; - final double p3 = distances!.totalDistance - footerExtent; - p1 += l.origin - p3; - p2 += l.origin - p3; - doubleSpan = _DoubleSpan(p1, p2); - } - break; - case _ScrollAxisRegion.body: - p1 += headerExtent - scrollBar!.value; - p2 += headerExtent - scrollBar!.value; - doubleSpan = _DoubleSpan(p1, p2); - break; - default: - doubleSpan = _DoubleSpan.empty(); - break; - } - - return doubleSpan; - } - - /// Sets the index of the scroll line. - /// - /// * scrollLineIndex - _required_ - Index of the scroll line. - /// * scrollLineDelta - _required_ - The scroll line delta. - @override - void setScrollLineIndex(int scrollLineIndex, double scrollLineDelta) { - scrollLineIndex = min(lineCount, max(0, scrollLineIndex)); - scrollBar!.value = - distances!.getCumulatedDistanceAt(scrollLineIndex) + scrollLineDelta; - resetVisibleLines(); - } - - /// Scrolls to next page. - @override - void scrollToNextPage() { - scrollBar!.value += max(scrollBar!.smallChange, - scrollBar!.largeChange - scrollBar!.smallChange); - scrollToNextLine(); - } - - /// Scrolls to next line. - @override - void scrollToNextLine() { - final double d = distances!.getNextScrollValue(scrollBar!.value); - if (!(d == double.nan)) { - scrollBar!.value = d <= scrollBar!.value - ? distances!.getNextScrollValue(scrollBar!.value + 1) - : d; - } else { - scrollBar!.value += scrollBar!.smallChange; - } - } - - /// Scrolls to previous line. - @override - void scrollToPreviousLine() { - final double d = distances!.getPreviousScrollValue(scrollBar!.value); - if (!(d == double.nan)) { - scrollBar!.value = d; - } - } - - /// Scrolls to previous page. - @override - void scrollToPreviousPage() { - scrollBar!.value -= max(scrollBar!.smallChange, - scrollBar!.largeChange - scrollBar!.smallChange); - alignScrollLine(); - } - - /// Scrolls the line into viewable area. - /// - /// * lineIndex - _required_ - Index of the line. - /// * lineSize - _required_ - Size of the line. - /// * isRightToLeft - _required_ - The boolean value used to calculate - /// visible columns in right to left mode. - @override - void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) { - final _VisibleLinesCollection lines = getVisibleLines(); - final _VisibleLineInfo? line = lines.getVisibleLineAtLineIndex(lineIndex); - double delta = 0; - - if (line != null) { - if (!line.isClipped || line.isFooter || line.isHeader) { - return; - } - - if (line.isClippedOrigin && !line.isClippedCorner) { - delta = -(lineSize - line.clippedSize); - } else if (!line.isClippedOrigin && line.isClippedCorner) { - //Following code prevent the horizontal auto scrolling when column - // size is bigger than viewPort size. - if (line.clippedOrigin < line.clippedSize) { - delta = 0; - } else { - delta = lineSize - line.clippedSize; - } - } else { - delta = lineSize - line.clippedSize; - } - } else { - double d = distances!.getCumulatedDistanceAt(lineIndex); - - if (d > scrollBar!.value) { - d = d + lineSize - scrollBar!.largeChange; - } - - delta = d - scrollBar!.value; - } - - if (delta != 0) { - scrollBar!.value += delta; - } - } - - /// Sets the footer line count. - /// - /// * value - _required_ - The value. - @override - void setFooterLineCount(int value) { - if (value == 0) { - _footerExtent = 0; - } else { - if (distances!.count <= value) { - _footerExtent = 0; - return; - } - - final int n = distances!.count - value; - - // The Total distance must be reduced by the padding size of the Distance - // total size. Then it should be calculated. This issue is occured in - // Nested Grid. SD 9312. this will give the exact size of the footer - // Extent when padding distance is reduced from total distance. - final bool isDistanceCounterSubset = distances is _DistanceCounterSubset; - if (!isDistanceCounterSubset) { - // Nested Grid cells in GridControl is not - // DistanceRangeCounterCollection type. - final _DistanceRangeCounterCollection _distances = - distances! as _DistanceRangeCounterCollection; - _footerExtent = distances!.totalDistance - - _distances.paddingDistance - - distances!.getCumulatedDistanceAt(n); - } else { - _footerExtent = - distances!.totalDistance - distances!.getCumulatedDistanceAt(n); - } - } - } - - /// Sets the header line count. - /// - /// * value - _required_ - The value. - @override - void setHeaderLineCount(int value) { - _headerExtent = - distances!.getCumulatedDistanceAt(min(value, distances!.count)); - } - - /// Sets the hidden state of the lines. - /// - /// * from - _required_ - The start index of the line. - /// * to - _required_ - The end index of the line. - /// * hide - _required_ - A boolean value indicating whether to hide the - /// lines. if set to true - [hide]. - @override - void setLineHiddenState(int from, int to, bool hide) { - if (hide) { - setLineSize(from, to, 0.0); - } else { - for (int n = from; n <= to; n++) { - int repeatSizeCount = -1; - final List lineSize = - getLineSizeWithTwoArgs(n, repeatSizeCount); - final double size = lineSize[0] as double; - repeatSizeCount = lineSize[1] as int; - final int rangeTo = getRangeToHelper(n, to, repeatSizeCount); - setLineSize(n, rangeTo, size); - n = rangeTo; - } - } - } - - /// Sets the size of the lines for the given range of lines. Will do nothing - /// for a `LineScrollAxis`. - /// - /// * from - _required_ - The start index of the line. - /// * to - _required_ - The end index of the line. - /// * size - _required_ - The line size. - @override - void setLineSize(int from, int to, double size) { - if (_distances != null) { - _distances!.setRange(from, to, size); - } - - // special case for SetLineResize when axis is nested. Parent Scroll - // Axis relies on Distances.TotalDistance and this only gets updated if - // we temporarily set the value in the collection. - else if (_distancesHost != null && inLineResize) { - _distancesHost!.distances?.setRange(from, to, size); - } - } - - /// Set temporary value for a line size during a resize operation - /// without committing value to ScrollLinesHost. - /// - /// * index - _required_ - The index of the line. - /// * size - _required_ - The size of the line. - @override - void setLineResize(int index, double size) { - inLineResize = true; - if (distances!.getNestedDistances(index) == null) { - super.setLineResize(index, size); - } else { - markDirty(); - raiseChanged(_ScrollChangedAction.lineResized); - } - - if (_parentScrollAxis != null) { - _parentScrollAxis! - .setLineResize(startLineIndex, distances!.totalDistance); - } - - inLineResize = false; - } - - /// Associates a collection of nested lines with a line in this axis. - /// - /// * index - _required_ - The index. - /// * nestedLines - _required_ - The nested lines. - void setNestedLines(int index, _DistanceCounterCollectionBase? nestedLines) { - if (distances != null) { - distances!.setNestedDistances(index, nestedLines); - } - } - - /// Initialize scrollbar properties from header and footer size and - /// total size of lines in body. - @override - void updateScrollbar() { - final _ScrollBarBase? sb = scrollBar; - setHeaderLineCount(headerLineCount); - setFooterLineCount(footerLineCount); - - final double delta = headerExtent - sb!.minimum; - final double oldValue = sb.value; - - sb - ..minimum = headerExtent - ..maximum = distances!.totalDistance - footerExtent - ..smallChange = distances!.defaultDistance; - final double proposeLargeChange = scrollPageSize; - sb.largeChange = proposeLargeChange; - - // SH - Added check for delta != 0 to avoid value being reset when - // you resize grid such that only header columns are visible and then - // resize back to larger size. - // SH 6/22 - Commented out change to sb.Value and also modified - // ScrollInfo.Value to return Math.Max(minimum, - // Math.Min(maximum - largeChange, value)); instead. - if (proposeLargeChange >= 0 && delta != 0) { - sb.value = oldValue + delta; - } - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart deleted file mode 100644 index 452c4aa18..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_base.dart +++ /dev/null @@ -1,1580 +0,0 @@ -part of datagrid; - -/// ScrollAxisBase is an abstract base class and implements scrolling -/// logic for both horizontal and vertical scrolling in a -/// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances`. -/// Logical units in the ScrollAxisBase are called "Lines". With the -/// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.distances` a -/// line represents rows in a grid -/// and with `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` -/// a line represents columns in a grid. -/// -/// ScrollAxisBase has support for frozen header and footer lines, maintaining a -/// scroll position and updating and listening to scrollbars. It also maintains -/// a collection of `VisibleLineInfo` items for all the lines that are -/// visible in the viewing area. ScrollAxisBase wires itself with a -/// `ScrollLinesHost` and reacts to changes in line count, -/// line sizes, hidden state and default line size. -typedef _ChangedCallback = void Function(_ScrollChangedArgs scrollChangedArgs); - -abstract class _ScrollAxisBase { - /// Initializes a new instance of the ScrollAxisBase class. - /// - /// * scrollBar The scroll bar state. - /// * scrollLinesHost The scroll lines host. - _ScrollAxisBase( - _ScrollBarBase? scrollBar, _LineSizeHostBase? scrollLinesHost) { - if (scrollBar == null) { - throw Exception(); - } - _viewSize = 0.0; - _headerExtent = 0.0; - _footerExtent = 0.0; - _isPixelScroll = false; - this.scrollBar = scrollBar; - _scrollLinesHost = scrollLinesHost; - wireScrollLinesHost(); - } - - double _renderSize = 0.0; - _ScrollBarBase? _scrollBar; - late _LineSizeHostBase? _scrollLinesHost; - bool _layoutDirty = false; - int _lineResizeIndex = -1; - double _lineResizeSize = 0; - final _VisibleLinesCollection _visibleLines = _VisibleLinesCollection(); - double _lastScrollValue = -1; - _DoubleSpan clip = _DoubleSpan.empty(); - bool _ignoreScrollBarPropertyChange = false; - bool _inGetVisibleLines = false; - bool _allBodyLinesShown = false; - int _lastBodyLineIndex = -1; - bool indebug = false; - - //code to handle issue in DT 77714 - static double ePSILON = 2.2204460492503131e-016; - /* smallest such that 1.0+EPSILON != 1.0 */ - - static bool strictlyLessThan(double d1, double d2) => - !(d1 > d2 || areClose(d1, d2)); - - static bool areClose(double d1, double d2) { - if (d1 == d2) { - return true; - } - - final double eps = (d1.abs() + d2.abs() + 10.0) * ePSILON; - return (d1 - d2).abs() < eps; - } - - /// Occurs when a scroll axis changed. - _ChangedCallback? onChangedCallback; - - /// Gets the default size of lines. - /// - /// Returns the default size of lines. - double get defaultLineSize => _defaultLineSize; - double _defaultLineSize = 0.0; - - /// Sets the default size of lines. - set defaultLineSize(double value) { - if (value == _defaultLineSize) { - return; - } - - _defaultLineSize = value; - } - - /// Gets the footer extent. This is total height (or width) - /// of the footer lines. - /// - /// Returns the footer extent. - double get footerExtent => _footerExtent; - double _footerExtent = 0.0; - - /// Gets the footer line count. - /// - /// Returns the footer line count. - int get footerLineCount { - if (scrollLinesHost == null) { - return 0; - } - - return scrollLinesHost?.getFooterLineCount() ?? 0; - } - - /// Gets the index of the first footer line. - /// - /// Returns the index of the first footer line. - int get firstFooterLineIndex => lineCount - footerLineCount; - - /// Gets the header extent. This is total height (or width) - /// of the header lines. - /// - /// Returns the header extent. - double get headerExtent => _headerExtent; - double _headerExtent = 0.0; - - /// Gets the header line count. - /// - /// Returns the header line count. - int get headerLineCount { - if (scrollLinesHost == null) { - return 0; - } - - return scrollLinesHost?.getHeaderLineCount() ?? 0; - } - - /// Gets a value indicating whether footer lines are visible. - /// - /// @value - /// true if footer lines are visible, otherwise false. - bool get isFooterVisible { - final _VisibleLinesCollection visibleLines = getVisibleLines(); - return visibleLines.firstFooterVisibleIndex < visibleLines.length; - } - - /// Gets a value indicating whether this axis supports pixel scrolling. - /// - /// @value - /// `true` if this instance supports pixel scrolling, otherwise `false`. - bool get isPixelScroll => _isPixelScroll; - late bool _isPixelScroll; - - /// Gets the last visible line. - /// - /// Returns the last visible line. - _VisibleLineInfo? get lastBodyVisibleLine { - final _VisibleLinesCollection visibleLines = getVisibleLines(); - if (visibleLines.isEmpty || - visibleLines.lastBodyVisibleIndex > visibleLines.length) { - return null; - } - - return visibleLines[visibleLines.lastBodyVisibleIndex]; - } - - /// Gets the index of the last visible line. - /// - /// Returns the index of the last visible line. - int get lastBodyVisibleLineIndex { - final _VisibleLinesCollection visibleLines = getVisibleLines(); - if (visibleLines.isEmpty || - visibleLines.lastBodyVisibleIndex > visibleLines.length) { - return -1; - } - - return visibleLines[visibleLines.lastBodyVisibleIndex].lineIndex; - } - - /// Gets the line count. - /// - /// Returns the line count. - int get lineCount => _lineCount; - int _lineCount = 0; - - /// Sets the line count. - set lineCount(int value) { - if (value == _lineCount) { - return; - } - - _lineCount = value; - } - - /// Gets the name. - /// - /// Returns the name. - String? get name => _name; - String? _name; - - /// Sets the name. - set name(String? value) { - if (value == _name) { - return; - } - - _name = value; - } - - /// Sets the size (either height or width) of the parent control. - /// - /// Returns the size of the parent control. - double get renderSize => _renderSize; - - /// Sets the size (either height or width) of the parent control. - set renderSize(double value) { - if (_renderSize != value) { - _renderSize = value; - markDirty(); - updateScrollBar(true); - } - } - - /// Gets the scroll bar state. - /// - /// Returns the scroll bar state. - _ScrollBarBase? get scrollBar => _scrollBar; - set scrollBar(_ScrollBarBase? value) { - if (value == _scrollBar) { - return; - } - - _scrollBar = value; - } - - /// Gets the scroll lines host. - /// - /// Returns the scroll lines host. - _LineSizeHostBase? get scrollLinesHost => _scrollLinesHost; - - /// Scroll = First Visible Body Line - /// - /// Gets the index of the first visible Line in the body region. - /// - /// Returns the index of the scroll line. - int get scrollLineIndex => _scrollLineIndex; - int _scrollLineIndex = 0; - - /// Sets the index of the first visible Line in the body region. - set scrollLineIndex(int value) { - if (value == _scrollLineIndex) { - return; - } - - _scrollLineIndex = value; - } - - /// Support for sharing axis with a parent axis - /// Gets the index of the first line in a parent axis. - /// - /// This is used for shared - /// or nested scroll axis (e.g. a nested grid with shared axis - /// in a covered cell). - /// - /// Returns the index of the first line. - int get startLineIndex => _startLineIndex; - int _startLineIndex = -1; - - /// Sets the index of the first line in a parent axis. - set startLineIndex(int value) { - if (value == _startLineIndex) { - return; - } - - _startLineIndex = value; - } - - /// Gets the size (either height or width) of the parent control excluding the - /// area occupied by Header and Footer. This size is used for scrolling down - /// or up one page. - /// - /// Returns the size of the parent control. - double get scrollPageSize => renderSize - headerExtent - footerExtent; - - /// Gets the view size of the (either height or width) of the parent control. - /// Normally the ViewSize is the same as `RenderSize`. - /// Only if the parent control has more space then needed to display - /// all lines, the ViewSize will be less. In such case the ViewSize is - /// the total height for all lines. - /// - /// Returns the view size of the (either height or width) - /// of the parent control. - double get viewSize => _viewSize; - double _viewSize = 0.0; - - /// Adjusts the footer extent to avoid gap between last visible line - /// of body region and first line of footer in case the view is larger - /// than the height/width of all lines. - /// - /// * footerSize Size of the footer. - /// * arrangeSize Size of the arrange. - /// Returns the - double adjustFooterExtentToAvoidGap(double footerSize, double arrangeSize) { - // Adjust start of footer to avoid gap after last row. - if (viewSize < arrangeSize) { - footerSize += arrangeSize - viewSize; - } - - if (footerSize + headerExtent > arrangeSize) { - footerSize = max(0, arrangeSize - headerExtent); - } - - return footerSize; - } - - /// Aligns the scroll line. - void alignScrollLine(); - - /// Gets a boolean value indicating whether any of the lines with the - /// given absolute line index are visible. - /// - /// * lineIndex1 The line index 1. - /// * lineIndex2 The line index 2. - /// A boolean value indicating whether any of the lines with - /// the given absolute line index are visible. - bool anyVisibleLines(int lineIndex1, int lineIndex2) => - _visibleLines.anyVisibleLines(lineIndex1, lineIndex2); - - void defaultLineSizeChangedCallback( - _DefaultLineSizeChangedArgs defaultLineSizeChangedArgs) { - if (scrollLinesHost != null) { - defaultLineSize = scrollLinesHost!.getDefaultLineSize(); - markDirty(); - updateScrollBar(true); - raiseChanged(_ScrollChangedAction.defaultLineSizeChanged); - } - } - - void footerLineChangedCallback() { - if (scrollLinesHost != null) { - setFooterLineCount(scrollLinesHost!.getFooterLineCount()); - markDirty(); - raiseChanged(_ScrollChangedAction.footerLineCountChanged); - } - } - - /// Freezes the visible lines. - void freezeVisibleLines() { - _inGetVisibleLines = true; - } - - /// Gets the size from `ScrollLinesHost` or if the line is being resized - /// then get temporary value previously set with `SetLineResize`. - /// If size is negative then `DefaultLineSize` is returned. - /// - /// * index The index of the line. - /// * repeatSizeCount The number of subsequent values with same size. - /// - /// The size from `ScrollLinesHost` or if the line is being resized - /// then get temporary value previously set with `SetLineResize`. - /// If size is negative then `DefaultLineSize` is returned. - List getScrollLinesHostSize(int index, int repeatSizeCount) { - final List lineSize = - scrollLinesHost!.getSize(index, repeatSizeCount); - double size = lineSize[0] as double; - repeatSizeCount = lineSize[1] as int; - - if (size < 0) { - size = defaultLineSize; - } - - return [size, repeatSizeCount]; - } - - /// Gets the size from `ScrollLinesHost` or if the line is being resized - /// then get temporary value previously set with `SetLineResize` - /// - /// * index The index. - /// * repeatSizeCount The number of subsequent values with same size. - /// Returns the size from `ScrollLinesHost` or if the line is being resized - /// then get temporary value previously set with `SetLineResize` - List getLineSizeWithTwoArgs(int index, int? repeatSizeCount) { - repeatSizeCount = 1; - if (index == _lineResizeIndex) { - return [_lineResizeSize, repeatSizeCount]; - } - - if (scrollLinesHost == null) { - repeatSizeCount = _MathHelper.maxvalue; - return [defaultLineSize, repeatSizeCount]; - } - - return [ - getScrollLinesHostSize(index, repeatSizeCount)[0], - getScrollLinesHostSize(index, repeatSizeCount)[1] - ]; - } - - /// Gets the size of the line. - /// - /// * index The index of the line. - /// Returns the size of the line. - double getLineSize(int index) { - int? repeatSizeCount; - return getLineSizeWithTwoArgs(index, repeatSizeCount)[0] as double; - } - - /// Gets the index of the next scroll line. - /// - /// * lineIndex The current index of the line. - /// Returns the index of the next scroll line. - int getNextScrollLineIndex(int lineIndex); - - /// Gets the origin and corner points of body region. - /// - /// * origin The origin. - /// * corner The corner. - void getOriginAndCornerOfBodyRegion(double origin, double corner) { - int scrollLineIndex = 0; - double scrollOffset = 0.0; - final List lineInfo = - getScrollLineIndex(scrollLineIndex, scrollOffset); - scrollLineIndex = lineInfo[0] as int; - scrollOffset = lineInfo[1] as double; - final double arrangeSize = renderSize; - final double adjustedFooterExtent = - adjustFooterExtentToAvoidGap(footerExtent, arrangeSize); - origin = headerExtent - scrollOffset; - corner = arrangeSize - adjustedFooterExtent; - } - - /// Gets the index of the previous scroll line. - /// - /// * lineIndex The current index of the line. - /// Returns the index of the previous scroll line. - int getPreviousScrollLineIndex(int lineIndex); - - /// Gets the index of the scroll line in RTL Mode - /// - /// * scrollLineIndex Index of the scroll line. - /// * scrollLineOffset The scroll line offset. - /// * isRightToLeft The boolean value used to calculate visible columns - /// in right to left mode. - List getScrollLineIndex(int scrollLineIndex, double scrollLineOffset, - [bool isRightToLeft = false]); - - /// Gets the maximum range - /// - /// * n start index - /// * to end index - /// * repeatSizeCount repeat count value - /// Returns the minimum index value - int getRangeToHelper(int n, int to, int repeatSizeCount) { - if (repeatSizeCount == _MathHelper.maxvalue) { - return to; - } - - return min(to, n + repeatSizeCount - 1); - } - - /// Gets the visible lines collection. - /// - /// * isRightToLeft The boolean value used to calculate visible columns - /// in right to left mode. - /// Returns the visible lines collection. - _VisibleLinesCollection getVisibleLines([bool isRightToLeft = false]) { - if (!isRightToLeft) { - if (_inGetVisibleLines) { - return _visibleLines; - } - - _inGetVisibleLines = true; - try { - if (_layoutDirty) { - setHeaderLineCount(headerLineCount); - setFooterLineCount(footerLineCount); - - _lastScrollValue = -1; - _layoutDirty = false; - updateScrollBar(true); - } - if (_visibleLines.isEmpty || _lastScrollValue != scrollBar!.value) { - _visibleLines.clear(); - - int visibleIndex = 0; - int scrollLineIndex = 0; - double scrollOffset = 0.0; - final int headerLineCount = this.headerLineCount; - final List scrollLineValues = - getScrollLineIndex(scrollLineIndex, scrollOffset); - scrollLineIndex = scrollLineValues[0] as int; - scrollOffset = scrollLineValues[1] as double; - final int firstFooterLine = lineCount - footerLineCount; - final double footerStartPoint = renderSize - footerExtent; - int index; - - // Header - double point = 0; - int lastHeaderLineIndex = -1; - for (index = 0; - index != -1 && - strictlyLessThan(point, headerExtent) && - index < firstFooterLine && - index < headerLineCount; - index = getNextScrollLineIndex(index)) { - final double size = getLineSize(index); - final _VisibleLineInfo line = _VisibleLineInfo( - visibleIndex++, index, size, point, 0, true, false); - _visibleLines.add(line); - point += size; - lastHeaderLineIndex = index; - } - - _visibleLines.firstBodyVisibleIndex = _visibleLines.length; - - _VisibleLineInfo? lastScrollableLine; - // Body - point = headerExtent; - final int firstBodyLineIndex = - max(scrollLineIndex, lastHeaderLineIndex + 1); - for (index = firstBodyLineIndex; - index != -1 && - strictlyLessThan(point, footerStartPoint) && - index < firstFooterLine; - index = getNextScrollLineIndex(index)) { - final double size = getLineSize(index); - _visibleLines.add(lastScrollableLine = _VisibleLineInfo( - visibleIndex++, - index, - size, - point, - scrollOffset, - false, - false)); - point += size - scrollOffset; - scrollOffset = 0; // reset scrollOffset after first line. - // Subsequent lines will start at given point. - } - - if (lastScrollableLine == null) { - _allBodyLinesShown = true; - _lastBodyLineIndex = -1; - } else { - _allBodyLinesShown = index >= firstFooterLine; - _lastBodyLineIndex = lastScrollableLine.lineIndex; - } - - _visibleLines.firstFooterVisibleIndex = _visibleLines.length; - - // Footer - point = max(headerExtent, viewSize - footerExtent); - for (index = firstFooterLine; - index != -1 && - strictlyLessThan(point, renderSize) && - index < lineCount; - index = getNextScrollLineIndex(index)) { - if (lastScrollableLine != null) { - lastScrollableLine.clippedCornerExtent = - lastScrollableLine.corner - point; - lastScrollableLine = null; - } - - final double size = getLineSize(index); - _visibleLines.add(_VisibleLineInfo( - visibleIndex++, index, size, point, 0, false, true)); - point += size; - } - - if (lastScrollableLine != null) { - lastScrollableLine.clippedCornerExtent = - lastScrollableLine.corner - point; - lastScrollableLine = null; - } - - _lastScrollValue = scrollBar!.value; - - if (_visibleLines.isNotEmpty) { - _visibleLines[_visibleLines.length - 1].isLastLine = true; - } - try { - // throws exception when a line is duplicate - _visibleLines.getVisibleLineAtLineIndex(0); - } on Exception { - if (!indebug) { - indebug = true; - _visibleLines.clear(); - try { - getVisibleLines(); - } finally { - indebug = false; - } - } - } - } - } finally { - _inGetVisibleLines = false; - } - return _visibleLines; - } else { - if (_inGetVisibleLines) { - return _visibleLines; - } - - _inGetVisibleLines = true; - try { - if (_layoutDirty) { - setHeaderLineCount(headerLineCount); - setFooterLineCount(footerLineCount); - _lastScrollValue = -1; - _layoutDirty = false; - updateScrollBar(true); - } - - if (_visibleLines.isEmpty || _lastScrollValue != scrollBar!.value) { - _visibleLines.clear(); - int visibleIndex = 0; - int scrollLineIndex = 0; - double scrollOffset = 0.0; - final int _headerLineCount = headerLineCount; - final List scrollLineValues = - getScrollLineIndex(scrollLineIndex, scrollOffset, true); - scrollLineIndex = scrollLineValues[0] as int; - scrollOffset = scrollLineValues[1] as double; - final int firstFooterLine = lineCount - footerLineCount; - int index; - double footerStartPoint = renderSize + clip.start; - - // Header - double point = 0; - int lastHeaderLineIndex = -1; - for (index = 0; - index != -1 && - strictlyLessThan(point, headerExtent) && - index < firstFooterLine && - index < _headerLineCount; - index = getNextScrollLineIndex(index)) { - final double size = getLineSize(index); - _visibleLines.add(_VisibleLineInfo(visibleIndex++, index, size, - footerStartPoint - size - point, 0, true, false)); - footerStartPoint -= size; - lastHeaderLineIndex = index; - } - - _visibleLines.firstBodyVisibleIndex = _visibleLines.length; - _VisibleLineInfo? lastScrollableLine; - - // Body - point = headerExtent; - int firstBodyLineIndex = - max(scrollLineIndex, lastHeaderLineIndex + 1); - - point = footerStartPoint - clip.start; - - if (clip.start > 0 && scrollOffset < clip.start) { - scrollOffset = - getLineSize(scrollLineIndex - 1) - (clip.start - scrollOffset); - scrollLineIndex -= 1; - point += clip.start; - } - - firstBodyLineIndex = max(scrollLineIndex, lastHeaderLineIndex + 1); - for (index = firstBodyLineIndex; - index != -1 && - strictlyLessThan( - point - 1, renderSize + clip.start - headerExtent) && - (point > footerExtent) && - (index < firstFooterLine && index >= headerLineCount); - index = getNextScrollLineIndex(index)) { - final double size = getLineSize(index); - _visibleLines.add(lastScrollableLine = _VisibleLineInfo( - visibleIndex++, - index, - size, - point - size + scrollOffset, - scrollOffset, - false, - false)); - point -= size - scrollOffset; - scrollOffset = 0; - } - - if (lastScrollableLine == null) { - _allBodyLinesShown = true; - _lastBodyLineIndex = -1; - } else { - _allBodyLinesShown = index >= firstFooterLine; - _lastBodyLineIndex = lastScrollableLine.lineIndex; - } - - _visibleLines.firstFooterVisibleIndex = _visibleLines.length; - - if (footerLineCount > 0) { - if (renderSize < scrollBar!.maximum + footerExtent) { - point = min(renderSize + clip.start - headerExtent, - clip.start + footerExtent); - for (index = firstFooterLine; - index != -1 && - strictlyLessThan( - point - 1, - renderSize < scrollBar!.maximum - ? clip.start + footerExtent - : renderSize - headerExtent) && - index < lineCount; - index = getNextScrollLineIndex(index)) { - if (lastScrollableLine != null) { - lastScrollableLine.clippedCornerExtent = - lastScrollableLine.corner - point; - lastScrollableLine = null; - } - - final double size = getLineSize(index); - _visibleLines.add(_VisibleLineInfo( - visibleIndex++, index, size, point - size, 0, false, true)); - point -= size; - } - } else { - for (index = firstFooterLine; - index != -1 && - strictlyLessThan( - point - 1, - renderSize < scrollBar!.maximum - ? clip.start + footerExtent - : renderSize - headerExtent) && - index < lineCount; - index = getNextScrollLineIndex(index)) { - if (lastScrollableLine != null) { - lastScrollableLine.clippedCornerExtent = - lastScrollableLine.corner - point; - lastScrollableLine = null; - } - - final double size = getLineSize(index); - _visibleLines.add(_VisibleLineInfo( - visibleIndex++, index, size, point - size, 0, false, true)); - point -= size; - } - } - } - - if (lastScrollableLine != null) { - lastScrollableLine.clippedCornerExtent = - lastScrollableLine.corner - point; - lastScrollableLine = null; - } - - _lastScrollValue = scrollBar!.value; - if (_visibleLines.isNotEmpty) { - _visibleLines[_visibleLines.length - 1].isLastLine = true; - } - - try { - // throws exception when a line is duplicate - _visibleLines.getVisibleLineAtLineIndex(0); - } on Exception { - if (!indebug) { - indebug = true; - _visibleLines.clear(); - try { - getVisibleLines(true); - } finally { - indebug = false; - } - } - } - } - } finally { - _inGetVisibleLines = false; - } - return _visibleLines; - } - } - - /// Gets the visible line for a point in the display. - /// - /// * point The point in the display for which the visible line - /// is to be obtained. - /// * allowOutSideLines boolean value - /// * isRightToLeft The boolean value used to calculate visible columns - /// in right to left mode. - /// Returns the visible line for a point in the display. - _VisibleLineInfo? getVisibleLineAtPoint(double point, - [bool allowOutSideLines = false, bool isRightToLeft = false]) { - if (!isRightToLeft) { - if (allowOutSideLines) { - point = max(point, 0); - } - - final _VisibleLineInfo? lineInfo = - getVisibleLines().getVisibleLineAtPoint(point); - if (lineInfo != null && (allowOutSideLines || point <= lineInfo.corner)) { - return lineInfo; - } - } else { - if (allowOutSideLines) { - point = max(point, 0); - } - - final _VisibleLinesCollection collection = getVisibleLines(true); - final _VisibleLinesCollection reversedCollection = collection.reversed; - final _VisibleLineInfo? lineInfo = - reversedCollection.getVisibleLineAtPoint(point); - if (lineInfo != null && (allowOutSideLines || point <= lineInfo.corner)) { - return lineInfo; - } - } - - return null; - } - - /// Gets the visible line that displays the line with the - /// given absolute line index. - /// - /// * lineIndex Index of the line. - /// The visible line that displays the line with the given - /// absolute line index. - - _VisibleLineInfo? getVisibleLineAtLineIndex(int lineIndex) => - getVisibleLines().getVisibleLineAtLineIndex(lineIndex); - - /// Gets the visible line that displays the line with the given absolute - /// line index. If the line is outside the view and you specify - /// `allowCreateEmptyLineIfNotVisible` then the method will create an empty - /// line and initializes its line index and line size. - /// - /// * lineIndex Index of the line. - /// * allowCreateEmptyLineIfNotVisible if set to true and if the - /// line is outside the view then the method will create an empty - /// line and initializes its LineIndex and LineSize. - /// Returns the visible line that displays the line with the given - /// absolute line index. - _VisibleLineInfo? getVisibleLineAtLineIndexWithTwoArgs( - int lineIndex, bool allowCreateEmptyLineIfNotVisible) { - _VisibleLineInfo? line = getVisibleLineAtLineIndex(lineIndex); - if (line == null && allowCreateEmptyLineIfNotVisible) { - final double size = getLineSize(lineIndex); - line = _VisibleLineInfo(_MathHelper.maxvalue, lineIndex, size, - renderSize + 1, size, false, false); - } - - return line; - } - - /// Returns the first and last VisibleLine.LineIndex for area identified - /// by section. - /// - /// * section The integer value indicating the section : 0 - Header, - /// 1 - Body, 2 - Footer - /// Returns the first and last VisibleLine.LineIndex for area identified - /// by section. - _Int32Span getVisibleLinesRange(_ScrollAxisRegion section) { - final _VisibleLinesCollection visibleLines = getVisibleLines(); - int start = 0; - int end = 0; - final List visibleSection = getVisibleSection(section, start, end); - start = visibleSection[0]; - end = visibleSection[1]; - return _Int32Span( - visibleLines[start].lineIndex, visibleLines[end].lineIndex); - } - - /// Return indexes for VisibleLinesCollection for area identified by section. - /// - /// * section The integer value indicating the section : 0 - Header, - /// 1 - Body, 2 - Footer - /// * start The start index. - /// * end The end index. - void getVisibleSectionWithThreeArgs( - _ScrollAxisRegion section, int start, int end) { - getVisibleSection(section, start, end); - } - - /// Return indexes for VisibleLinesCollection for area identified by section. - /// - /// * section The integer value indicating the section : 0 - Header, - /// 1 - Body, 2 - Footer - /// * start The start index. - /// * end The end index. - List getVisibleSection(_ScrollAxisRegion section, int start, int end) { - final _VisibleLinesCollection visibleLines = getVisibleLines(); - switch (section) { - case _ScrollAxisRegion.header: - start = 0; - end = visibleLines.firstBodyVisibleIndex - 1; - break; - case _ScrollAxisRegion.body: - start = visibleLines.firstBodyVisibleIndex; - end = visibleLines.firstFooterVisibleIndex - 1; - break; - case _ScrollAxisRegion.footer: - start = visibleLines.firstFooterVisibleIndex; - end = visibleLines.length - 1; - break; - default: - start = end = -1; - break; - } - return [start, end]; - } - - /// Returns the clipping area for the specified visible lines. - /// Only if `VisibleLineInfo.IsClippedOrigin` is true for first line or - /// if `VisibleLineInfo.IsClippedCorner` is true for last line then the - /// area will be clipped. Otherwise, the whole area from 0 to `RenderSize` - /// is returned. - /// - /// * firstLine The first visible line. - /// * lastLine The last visible line. - /// Returns the clipping area for the specified visible lines. - _DoubleSpan getBorderRangeClipPoints( - _VisibleLineInfo firstLine, _VisibleLineInfo lastLine) { - if (!firstLine.isClippedOrigin && !lastLine.isClippedCorner) { - return _DoubleSpan(0, renderSize); - } - - if (firstLine.isClippedOrigin && !lastLine.isClippedCorner) { - return firstLine.clippedOrigin < renderSize - ? _DoubleSpan(firstLine.clippedOrigin, renderSize) - : _DoubleSpan(renderSize, firstLine.clippedOrigin); - } - - if (!firstLine.isClippedOrigin && lastLine.isClippedCorner) { - return _DoubleSpan(0, lastLine.clippedCorner); - } - - return _DoubleSpan(firstLine.clippedOrigin, lastLine.clippedCorner); - } - - /// Gets the line near the given corner point. Use this method - /// for hit-testing row or column lines for resizing cells. - /// - /// * point The point. - /// * hitTestPrecision The hit test precision in points. - /// * isRightToLeft The boolean value used to calculate visible columns - /// in right to left mode. - /// Returns the visible line. - _VisibleLineInfo? getLineNearCorner( - double point, double hitTestPrecision, _CornerSide side, - {bool isRightToLeft = false}) => - getLineNearCornerWithFourArgs( - point, hitTestPrecision, _CornerSide.both, isRightToLeft); - - /// Gets the line near the given corner point. Use this method - /// for hit-testing row or column lines for resizing cells. - /// - /// * point The point. - /// * hitTestPrecision The hit test precision in points. - /// * side The hit test corner. - /// * isRightToLeft The boolean value indicates the right to left mode. - /// Returns the visible line. - _VisibleLineInfo? getLineNearCornerWithFourArgs( - double point, double hitTestPrecision, _CornerSide side, - [bool isRightToLeft = false]) { - if (!isRightToLeft) { - final _VisibleLinesCollection lines = getVisibleLines(); - _VisibleLineInfo? visibleLine = lines.getVisibleLineAtPoint(point); - if (visibleLine != null) { - double d; - - // Close to - for (int n = max(0, visibleLine.visibleIndex); n < lines.length; n++) { - visibleLine = lines[n]; - - d = visibleLine.clippedOrigin - point; - - if ((d > hitTestPrecision) || - (d > 0 && - (side == _CornerSide.right || side == _CornerSide.bottom))) { - return null; - } else if (d.abs() <= hitTestPrecision && side != _CornerSide.left) { - if (visibleLine.visibleIndex == 0) { - return null; - } else { - return lines[visibleLine.visibleIndex - 1]; - } - } else if (visibleLine.size - d.abs() <= hitTestPrecision && - side == _CornerSide.left) { - return lines[visibleLine.visibleIndex]; - } - } - - // last line - check corner instead of origin. - d = visibleLine!.clippedCorner - point; - if (d.abs() <= hitTestPrecision) { - return lines[visibleLine.visibleIndex]; - } - } - } else { - final _VisibleLinesCollection lines = getVisibleLines(true); - _VisibleLineInfo? visibleLine = - getVisibleLineAtPoint(point, false, isRightToLeft); - - if (visibleLine != null) { - double d; - // Close to - for (int n = max(0, visibleLine.visibleIndex); n < lines.length; n++) { - visibleLine = lines[n]; - - d = point - visibleLine.clippedOrigin; - - if ((d > hitTestPrecision && - d < visibleLine.size - hitTestPrecision) || - (d > 0 && - d < visibleLine.size - hitTestPrecision && - (side == _CornerSide.right || side == _CornerSide.bottom)) || - (d < 0 && (side == _CornerSide.left))) { - return null; - } - - if (d > visibleLine.size - hitTestPrecision && - visibleLine.visibleIndex > 0) { - return lines[visibleLine.visibleIndex - 1]; - } - - if (d.abs() <= hitTestPrecision) { - return lines[visibleLine.visibleIndex]; - } - - if ((d.abs() + hitTestPrecision) >= visibleLine.clippedSize) { - return visibleLine; - } - } - // last line - check corner instead of origin. - d = visibleLine!.clippedCorner - point; - if (d.abs() <= hitTestPrecision) { - return lines[visibleLine.visibleIndex]; - } - } - } - return null; - } - - /// Returns points for given absolute line indexes. - /// - /// * firstIndex The first index. - /// * lastIndex The last index. - /// * allowAdjust if set to true return the first visible line if firstIndex - /// is above viewable area or return last visible line if lastIndex - /// is after viewable area (works also for header and footer). - /// - /// * firstVisible if set to true indicates the line with index - /// firstIndex is visible in viewable area. - /// * lastVisible if set to true indicates the line with index - /// lastIndex is visible in viewable area.. - /// * firstLine The first line or null if allowAdjust is false and line - /// is not in viewable area. - /// * lastLine The last line or null if allowAdjust is false and line - /// is not in viewable area. - List getLinesAndVisibility( - int firstIndex, - int lastIndex, - bool allowAdjust, - bool firstVisible, - bool lastVisible, - _VisibleLineInfo? firstLine, - _VisibleLineInfo? lastLine) { - final _VisibleLinesCollection visibleLines = getVisibleLines(); - - if (firstIndex < 0) { - firstIndex = 0; - } - - // Invalid Line - if (firstIndex < 0 || firstIndex >= lineCount) { - firstVisible = false; - firstLine = null; - } - - // Header - else if (firstIndex < headerLineCount) { - firstVisible = true; - firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); - } - - // Footer - else if (firstIndex >= firstFooterLineIndex) { - firstVisible = true; - firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); - } - - // After Header and Before Scroll Position - else if (firstIndex < scrollLineIndex) { - firstVisible = false; - firstLine = - allowAdjust ? getVisibleLineAtLineIndex(scrollLineIndex) : null; - } - - // After Scroll Position and Before Footer - else if (firstIndex > lastBodyVisibleLineIndex) { - firstVisible = false; - if (allowAdjust && isFooterVisible) { - firstLine = visibleLines[visibleLines.firstFooterVisibleIndex]; - } else { - firstLine = null; - } - } - // Regular line (Body) - Visible and not a Header or Footer. - else { - firstVisible = true; - firstLine = visibleLines.getVisibleLineNearLineIndex(firstIndex); - } - - if (lastIndex >= lineCount) { - lastIndex = lineCount - 1; - } - - // Invalid Line - if (lastIndex < 0 || lastIndex >= lineCount) { - lastVisible = false; - lastLine = null; - } - - // Header - else if (lastIndex < headerLineCount) { - lastVisible = true; - lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); - } - - // Footer - else if (lastIndex >= firstFooterLineIndex) { - lastVisible = true; - lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); - } - - // After Header and Before Scroll Position - else if (lastIndex < scrollLineIndex) { - lastVisible = false; - if (!firstVisible && - firstIndex < scrollLineIndex) // maybe - in case you want right - // border to look through ...: && lastIndex+1 < ScrollLineIndex) - { - firstLine = null; - lastLine = null; - } else { - if (allowAdjust && headerLineCount > 0) { - lastLine = visibleLines[visibleLines.firstBodyVisibleIndex - 1]; - } else { - lastLine = null; - } - } - } - - // After Scroll Position and Before Footer - else if (lastIndex > lastBodyVisibleLineIndex) { - lastVisible = false; - if (!firstVisible && firstIndex > lastBodyVisibleLineIndex) { - firstLine = null; - lastLine = null; - } else { - lastLine = allowAdjust ? lastBodyVisibleLine : null; - } - } - - // Regular line (Body) - Visible and not a Header or Footer. - else { - lastVisible = true; - lastLine = visibleLines.getVisibleLineNearLineIndex(lastIndex); - } - return [firstVisible, lastVisible, firstLine, lastLine]; - } - - /// Gets the visible lines clip points (clipped origin of first line - /// and clipped corner of last line). If both lines are above or below - /// viewable area an empty span is returned. If lines are both above and below - /// viewable are then the range for all viewable lines is returned. - /// - /// * firstIndex The first index. - /// * lastIndex The last index. - /// - /// The visible lines clip points (clipped origin of first line and clipped - /// corner of last line). - - _DoubleSpan getVisibleLinesClipPoints(int firstIndex, int lastIndex) { - const bool firstVisible = false; - const bool lastVisible = false; - _VisibleLineInfo? firstLine, lastLine; - - getLinesAndVisibility(firstIndex, lastIndex, true, firstVisible, - lastVisible, firstLine, lastLine); - if (firstLine == null || lastLine == null) { - return _DoubleSpan.empty(); - } - - return _DoubleSpan(firstLine.clippedOrigin, lastLine.clippedCorner); - } - - /// Gets the clip points for a region. - /// - /// * region The region for which the clip points is to be obtained. - /// * isRightToLeft The boolean value used to calculate visible columns - /// in right to left mode. - /// Returns the clip points for a region. - _DoubleSpan getClipPoints(_ScrollAxisRegion region, - {bool isRightToLeft = false}) { - final _VisibleLinesCollection lines = getVisibleLines(); - int start = -1; - int end = -1; - final List visibleLineSection = getVisibleSection(region, start, end); - start = visibleLineSection[0]; - end = visibleLineSection[1]; - if (start == end && - region == _ScrollAxisRegion.body && - lines[end].clippedOrigin > 0 && - lines[start].clippedCorner > 0) { - return _DoubleSpan(lines[start].clippedOrigin, lines[end].clippedCorner); - } - - if (end < start) { - return _DoubleSpan.empty(); - } - if (isRightToLeft) { - return _DoubleSpan(lines[start].clippedCorner, lines[end].clippedOrigin); - } else { - return _DoubleSpan(lines[start].clippedOrigin, lines[end].clippedCorner); - } - } - - /// Determines the line one page down from the given line. - /// - /// * lineIndex The index of the current line. - /// Returns the line index of the line one page down from the given line. - int getNextPage(int lineIndex) { - double extent = 0; - final double pageExtent = scrollPageSize - getLineSize(lineIndex); - final int count = lineCount; - while (extent < pageExtent && lineIndex < count && lineIndex != -1) { - final int index = getNextScrollLineIndex(lineIndex); - if (index < 0) { - break; - } - - lineIndex = index; - extent += getLineSize(index); - } - - return lineIndex; - } - - /// Determines the line one page up from the given line. - /// - /// * lineIndex The index of the current line. - /// Returns the line index of the line one page up from the given line. - int getPreviousPage(int lineIndex) { - double extent = 0; - final double pageExtent = scrollPageSize - getLineSize(lineIndex); - while (extent < pageExtent && lineIndex > 0) { - lineIndex = getPreviousScrollLineIndex(lineIndex); - extent += getLineSize(lineIndex); - } - - return lineIndex; - } - - void headerLineChangedCallback() { - setFooterLineCount(scrollLinesHost!.getFooterLineCount()); - markDirty(); - raiseChanged(_ScrollChangedAction.footerLineCountChanged); - } - - void hiddenRangeChangedCallback( - _HiddenRangeChangedArgs hiddenRangeChangedArgs) { - for (int n = hiddenRangeChangedArgs.from; - n <= hiddenRangeChangedArgs.to; - n++) { - int repeatSizeCount = 0; - final List hiddenValue = - scrollLinesHost!.getHidden(n, repeatSizeCount); - final bool hide = hiddenValue[0] as bool; - repeatSizeCount = hiddenValue[1] as int; - final int rangeTo = - getRangeToHelper(n, hiddenRangeChangedArgs.to, repeatSizeCount); - setLineHiddenState(n, rangeTo, hide); - n = rangeTo; - } - - markDirty(); - updateScrollBar(true); - raiseChanged(_ScrollChangedAction.hiddenLineChanged); - } - - /// Gets a boolean value indicating whether the line with the - /// given absolute line index is visible. - /// - /// * lineIndex The index of the line. - /// A boolean value indicating whether the line with the given absolute - /// line index is visible. - - bool isLineVisible(int lineIndex) => - getVisibleLines().getVisibleLineAtLineIndex(lineIndex) != null || - ((lineIndex > _lastBodyLineIndex) && _allBodyLinesShown); - - void linesRemovedCallback(_LinesRemovedArgs linesRemovedArgs) { - onLinesRemoved(linesRemovedArgs.removeAt, linesRemovedArgs.count); - raiseChanged(_ScrollChangedAction.linesRemoved); - } - - void linesInsertedCallback(_LinesInsertedArgs linesInsertedArgs) { - onLinesInserted(linesInsertedArgs.insertAt, linesInsertedArgs.count); - raiseChanged(_ScrollChangedAction.linesInserted); - } - - void lineCountChangedCallback() { - lineCount = scrollLinesHost!.getLineCount(); - markDirty(); - updateScrollBar(true); - raiseChanged(_ScrollChangedAction.lineCountChanged); - } - - /// Makes the layout dirty. - /// - void markDirty() { - _layoutDirty = true; - } - - /// This method is called in response to a MouseWheel event. - /// - /// * delta The delta. - void mouseWheel(int delta); - - /// Called when lines were removed in ScrollLinesHost. - /// - /// * removeAt Index of the first removed line. - /// * count The count. - void onLinesRemoved(int removeAt, int count) {} - - /// Called when lines were inserted in ScrollLinesHost. - /// - /// * insertAt Index of the first inserted line. - /// * count The count. - void onLinesInserted(int insertAt, int count) {} - - /// Resets temporary value for line size after a resize operation. - void resetLineResize() { - const int repeatSizeCount = 0; - if (_lineResizeIndex >= 0 && _scrollLinesHost != null) { - setLineSize(_lineResizeIndex, _lineResizeIndex, - getScrollLinesHostSize(_lineResizeIndex, repeatSizeCount)[0]); - } - - _lineResizeIndex = -1; - _lineResizeSize = 0; - markDirty(); - raiseChanged(_ScrollChangedAction.lineResized); - } - - void rangeChangedCallback(_RangeChangedArgs rangeChangedArgs) { - for (int n = rangeChangedArgs.from; n <= rangeChangedArgs.to; n++) { - int repeatSizeCount = 0; - final List lineSize = getScrollLinesHostSize(n, repeatSizeCount); - final double size = lineSize[0] as double; - repeatSizeCount = lineSize[1] as int; - final int rangeTo = - getRangeToHelper(n, rangeChangedArgs.to, repeatSizeCount); - setLineSize(n, rangeTo, size); - n = rangeTo; - } - // Also check whether I need to re-hide any of the rows. - hiddenRangeChangedCallback(_HiddenRangeChangedArgs.fromArgs( - rangeChangedArgs.from, rangeChangedArgs.to, false)); - markDirty(); - raiseChanged(_ScrollChangedAction.lineResized); - } - - /// Resets the visible lines collection. - void resetVisibleLines() { - _visibleLines.clear(); - } - - /// Returns an array with 3 ranges indicating the first and last point - /// for the given lines in each region. - /// - /// * first The index of the first line. - /// * last The index of the last line. - /// * allowEstimatesForOutOfViewLines if set to true allow estimates - /// for out of view lines. - /// Returns An array with 3 ranges indicating the first and last point - /// for the given lines in each region. - List<_DoubleSpan> rangeToRegionPoints( - int first, int last, bool allowEstimatesForOutOfViewLines); - - /// Gets the first and last point for the given lines in a region. - /// - /// * region The region. - /// * first The index of the first line. - /// * last The index of the last line. - /// * allowEstimatesForOutOfViewLines if set to true allow estimates - /// for out of view lines. - /// Returns the first and last point for the given lines in a region. - _DoubleSpan rangeToPoints(_ScrollAxisRegion region, int first, int last, - bool allowEstimatesForOutOfViewLines); - - /// Raises the `Changed` event. - /// - /// * action scroll action - void raiseChanged(_ScrollChangedAction action) { - if (onChangedCallback != null) { - final _ScrollChangedArgs scrollChangedArgs = - _ScrollChangedArgs.fromArgs(action); - onChangedCallback!(scrollChangedArgs); - } - } - - void scrollChangedCallback(_ScrollChangedArgs scrollChangedArgs) {} - - /// Scrolls the line into viewable area. - /// * lineIndex Index of the line. - /// * lineSize Size of the line. - /// * isRightToLeft The boolean value indicates the right to left mode. - void scrollInView(int lineIndex, double lineSize, bool isRightToLeft) {} - - /// Scrolls the line into viewable area. - /// - /// * lineIndex The index of the line. - /// * isRightToLeft The boolean value used to calculate visible columns - /// in right to left mode.s - void scrollInViewwithTwoArgs(int lineIndex, bool isRightToLeft) { - scrollInView(lineIndex, getLineSize(lineIndex), isRightToLeft); - } - - /// Sets the footer line count. - /// - /// * value The value. - void setFooterLineCount(int value); - - /// Sets the header line count. - /// - /// * value The value. - void setHeaderLineCount(int value); - - /// Sets the hidden state of the lines. - /// - /// * from The start index of the line. - /// * to The end index of the line. - /// * hide A boolean value indicating whether to hide the lines. - /// if set to true - [hide]. - void setLineHiddenState(int from, int to, bool hide); - - /// Sets the size of the lines for the given range of lines. - /// Will do nothing for a [LineScrollAxis]. - /// - /// * from The start index of the line. - /// * to The end index of the line. - /// * size The line size. - void setLineSize(int from, int to, double size); - - /// Set temporary value for a line size during a resize operation - /// without committing value to ScrollLinesHost. - /// - /// * index The index of the line. - /// * size The size of the line. - void setLineResize(int index, double size) { - _lineResizeIndex = index; - _lineResizeSize = size; - setLineSize(index, index, size); - markDirty(); - raiseChanged(_ScrollChangedAction.lineResized); - } - - /// Sets the index of the scroll line. - /// - /// * scrollLineIndex The index of the scroll line. - /// * scrollLineOffset The scroll line offset. - void setScrollLineIndex(int scrollLineIndex, double scrollLineOffset); - - /// Scrolls to next page. - void scrollToNextPage(); - - /// Scrolls to next line. - void scrollToNextLine(); - - /// Scrolls to previous page. - void scrollToPreviousPage(); - - /// Scrolls to previous line. - void scrollToPreviousLine(); - - void updateScrollbar(); - - /// Updates the scroll bar. - /// - /// * ignorePropertyChange A boolean value indicating whether to ignore the - /// property change. - void updateScrollBar(bool ignorePropertyChange) { - final bool b = _ignoreScrollBarPropertyChange; - _ignoreScrollBarPropertyChange |= ignorePropertyChange; - try { - updateScrollbar(); - } finally { - _ignoreScrollBarPropertyChange = b; - } - } - - /// Unfreezes the visible lines. - void unfreezeVisibleLines() { - _inGetVisibleLines = false; - } - - void unwireScrollLinesHost() { - if (scrollLinesHost == null) { - return; - } - scrollLinesHost! - ..onDefaultLineSizeChanged = defaultLineSizeChangedCallback - ..onLineHiddenChanged = hiddenRangeChangedCallback - ..onLineSizeChanged = rangeChangedCallback - ..onLinesInserted = linesInsertedCallback - ..onLinesRemoved = linesRemovedCallback - ..onFooterLineCountChanged = headerLineChangedCallback - ..onFooterLineCountChanged = footerLineChangedCallback - ..onLineCountChanged = lineCountChangedCallback; - } - - /// Gets the view corner which is the point after the last visible line - /// of the body region. - /// - /// Returns the view corner which is the point after the last visible line - /// of the body region. - double get viewCorner { - int scrollLineIndex = 0; - double scrollOffset = 0.0; - final List lineSize = - getScrollLineIndex(scrollLineIndex, scrollOffset); - scrollLineIndex = lineSize[0] as int; - scrollOffset = lineSize[1] as double; - final double arrangeSize = renderSize; - final double adjustedFooterExtent = - adjustFooterExtentToAvoidGap(footerExtent, arrangeSize); - - return arrangeSize - adjustedFooterExtent; - } - - /// Gets the visible line index for a point in the display. - /// - /// * point The point. - /// * allowOutsideLines Set this true if point can be below corner - /// of last line. - /// Returns the visible line index for a point in the display. - int visiblePointToLineIndexWithTwoArgs(double point, bool allowOutsideLines) { - if (allowOutsideLines) { - point = max(point, 0); - } - - final _VisibleLineInfo? line = - getVisibleLines().getVisibleLineAtPoint(point); - if (line != null && (allowOutsideLines || point <= line.corner)) { - return line.lineIndex; - } - - return -1; - } - - /// Gets the visible line index for a point in the display. - /// - /// * point The point in the display for which the line index - /// is to be obtained. - /// Returns the visible line index for a point in the display. - int visiblePointToLineIndex(double point) => - visiblePointToLineIndexWithTwoArgs(point, true); - - void wireScrollLinesHost() { - if (scrollLinesHost == null) { - return; - } - - scrollLinesHost! - ..onDefaultLineSizeChanged = defaultLineSizeChangedCallback - ..onLineHiddenChanged = hiddenRangeChangedCallback - ..onLineSizeChanged = rangeChangedCallback - ..onLinesInserted = linesInsertedCallback - ..onLinesRemoved = linesRemovedCallback - ..onFooterLineCountChanged = headerLineChangedCallback - ..onFooterLineCountChanged = footerLineChangedCallback - ..onLineCountChanged = lineCountChangedCallback; - } -} - -/// Corner side enumeration. -enum _CornerSide { - /// Includes both Left and right side or top and bottom side. - both, - - /// Left side alone. - left, - - /// Right side alone. - right, - - /// Bottom side alone. - bottom -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_region.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_region.dart deleted file mode 100644 index e7273a27a..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_axis_region.dart +++ /dev/null @@ -1,18 +0,0 @@ -part of datagrid; - -/// Type of scroll axis regions -/// -/// A scroll axis has three regions: Header, Body and Footer. -enum _ScrollAxisRegion { - /// - ScrollAxisRegion.header specifies the header region - /// (at top or left side). - header, - - /// - ScrollAxisRegion.body Specifies the body region - /// (center between header and footer). - body, - - /// - ScrollAxisRegion.footer Specifies the footer region - /// (at bottom or right side). - footer -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart deleted file mode 100644 index 2df65cadb..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scroll_info.dart +++ /dev/null @@ -1,222 +0,0 @@ -part of datagrid; - -/// Returns the valueChangeArgs by used the [onValueChanged] event. -typedef _ValueChangedCallback = void Function(); - -/// Returns the ValueChangingArgs by used the [onValueChanging] event. -typedef _ValueChangingCallback = void Function( - _ValueChangingArgs valueChangingArgs); - -/// Returns the PropertyChangedArgs by used the [onPropertyChanged] event. -typedef _PropertyChangedCallback = void Function( - _PropertyChangedArgs propertyChangedArgs); - -/// Provides all properties to configure a scrollbar. -class _ScrollInfo extends _ScrollBarBase { - ///Initializes a new instance of the [_ScrollInfo] class. - _ScrollInfo() { - _value = 0; - _minimum = 0; - _maximum = 100; - _largeChange = 10; - _smallChange = 1; - _proposedLargeChange = 10; - _enabled = true; - } - - late double _proposedLargeChange; - - /// Occurs when a property value changes. - _PropertyChangedCallback? onPropertyChangedEvent; - - /// Occurs when the current position of the scroll box on the scroll bar - /// has changed. - _ValueChangedCallback? onValueChanged; - - /// Occurs when the current position of the scroll box on the scroll bar - /// is being changed. - _ValueChangingCallback? onValueChanging; - - /// Gets a value to be added to or subtracted from the value of the property - /// when the scroll box is moved a large distance. - /// - /// Returns a value to be added to or subtracted from the value of the - /// property when the scroll box is moved a large distance. - @override - double get largeChange => _largeChange; - - /// Sets a value to be added to or subtracted from the value of the property - /// when the scroll box is moved a large distance. - @override - set largeChange(double value) { - _proposedLargeChange = value; - if (value < 0) { - value = maximum - minimum; - } - - if (_largeChange != value) { - _largeChange = value; - } - } - - /// Gets a numeric value that represents the current position of the - /// scroll box on the scroll bar control. - /// - /// Returns a numeric value that represents the current position of the - /// scroll box on the scroll bar control. - @override - double get value { - if (_proposedLargeChange < 0) { - return _minimum; - } - return max(_minimum, min(_maximum - _largeChange + 1, _value)); - } - - /// Sets a numeric value that represents the current position of the - /// scroll box on the scroll bar control. - @override - set value(double value) { - if (this.value != value) { - final _ValueChangingArgs e = _ValueChangingArgs(value, this.value); - if (onValueChanging != null) { - onValueChanging!(e); - } - - if (!e.cancel) { - double offset = e.newValue; - - if (offset < minimum || largeChange >= maximum - minimum) { - offset = minimum; - } else { - if (offset + largeChange > maximum) { - offset = max(minimum, maximum - largeChange); - } - } - - _value = offset; - } - } - } - - /// Clones this instance. - /// - /// Returns the cloned instance. - _ScrollInfo clone() { - final _ScrollInfo sb = _ScrollInfo(); - copyTo(sb); - return sb; - } - - /// Copies current settings to another object. - /// - /// * scrollBar - _required_ - The object to which the settings - /// is to be copied. - void copyTo(_ScrollInfo scrollBar) { - scrollBar - ..value = _value - ..minimum = _minimum - ..maximum = _maximum - ..largeChange = _largeChange - ..smallChange = _smallChange - ..enabled = _enabled; - } - - /// Determines whether the specified `ScrollInfo` is equal - /// to the current `ScrollInfo`. - /// - /// * obj - _required_ - The `ScrollInfo` to compare with - /// the current `ScrollInfo`. - /// - /// Returns `True` if the specified `ScrollInfo` is equal - /// to the current `ScrollInfo`, otherwise `false`. - bool equals(_ScrollInfo? obj) { - final _ScrollInfo? sb = obj; - if (sb == null) { - return true; - } - - return sb.value == _value && - sb.minimum == _minimum && - sb.maximum == _maximum && - sb.largeChange == _largeChange && - sb.smallChange == _smallChange && - sb.enabled == _enabled; - } - - /// Serves as a hash function for a particular type. - /// - /// returns a hash code for the current `ScrollInfo`. - int getHashCode() => value.hashCode; - - /// Called when a property is changed and raises the [PropertyChanged] event. - /// - /// * propertyName - _required_ - Name of the property. - void onPropertyChanged(String propertyName) { - if (onPropertyChangedEvent != null) { - final _PropertyChangedArgs propertyChangedArgs = - _PropertyChangedArgs(propertyName); - onPropertyChangedEvent!(propertyChangedArgs); - } - } - - /// A `string` that represents the current `object`. - /// - /// Returns a `string` that represents the current `object`. - @override - String toString() => - 'ScrollInfo ( Value = $value, Minimum = $minimum, Maximum = $maximum, LargeChange = $largeChange, Enabled = $enabled )'; -} - -/// Provides data for the `ScrollInfo.ValueChanging` event. -class _ValueChangingArgs { - /// Initializes a new instance of the ValueChangingArgs class. - /// - /// * newValue - _required_ - The new value. - /// * oldValue - _required_ - The old value. - _ValueChangingArgs(double newValue, double oldValue) { - _newValue = newValue; - _oldValue = oldValue; - } - - double _newValue = 0.0; - double _oldValue = 0.0; - - /// Gets a value indicating whether to cancel the value change in scroll bar. - /// - /// Returns a boolean value indicating whether to cancel - /// the value change in scroll bar. - bool get cancel => _cancel; - bool _cancel = false; - - /// Sets a value indicating whether to cancel the value change in scroll bar. - set cancel(bool value) { - if (value == _cancel) { - return; - } - - _cancel = value; - } - - /// Gets the new value. - /// - /// Returns the new value. - double get newValue => _newValue; - - /// Gets the old value. - /// - /// Returns the old value. - double get oldValue => _oldValue; -} - -class _PropertyChangedArgs { - /// Occurs when name of the property was changed. - _PropertyChangedArgs(String propertyName) { - _propertyName = propertyName; - } - - /// Gets the name of the property that changed. - /// - /// Returns the property name. - String get propertyName => _propertyName; - String _propertyName = ''; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart deleted file mode 100644 index bf14bfa4c..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/scrollbar_base.dart +++ /dev/null @@ -1,90 +0,0 @@ -part of datagrid; - -/// Defines an interface that provides all properties to configure a scrollbar. -class _ScrollBarBase extends ChangeNotifier { - /// Gets a value indicating whether the scroll bar is enabled or not. - /// - /// Returns a number that represents the current position of the - /// scroll box on the scroll bar control. - bool get enabled => _enabled; - bool _enabled = false; - set enabled(bool value) { - if (value == _enabled) { - return; - } - - _enabled = value; - } - - /// Gets the upper limit of values of the scrollable range. - /// - /// Returns the upper limit of values of the scrollable range. - double get maximum => _maximum; - double _maximum = 0.0; - set maximum(double value) { - if (value == _maximum) { - return; - } - - _maximum = value; - } - - /// Gets the lower limit of values of the scrollable range. - /// - /// Returns the lower limit of values of the scrollable range. - double get minimum => _minimum; - double _minimum = 0.0; - set minimum(double value) { - if (value == _minimum) { - return; - } - - _minimum = value; - } - - /// Gets a value to be added to or subtracted from the value of the property - /// when the scroll box is moved a large distance. - /// - /// Returns the value to be added to or subtracted from the value - /// of the property when the scroll box is moved a large distance. - double get largeChange => _largeChange; - double _largeChange = 0.0; - set largeChange(double value) { - if (value == _largeChange) { - return; - } - - _largeChange = value; - } - - /// Gets the value to be added to or subtracted from the value of the - /// property when the scroll box is moved a small distance. - /// - /// Returns the value to be added to or subtracted from the value - /// of the property when the scroll box is moved a small distance. - double get smallChange => _smallChange; - double _smallChange = 0.0; - set smallChange(double value) { - if (value == _smallChange) { - return; - } - - _smallChange = value; - } - - /// Gets a numeric value that represents the current position of the - /// scroll box on the scroll bar control. - /// - /// Returns a numeric value that represents the current position of - /// the scroll box on the scroll - /// bar control. - double get value => _value; - double _value = 0.0; - set value(double value) { - if (value == _value) { - return; - } - - _value = value; - } -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart new file mode 100644 index 000000000..1f2fc46d0 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scrollbar.dart @@ -0,0 +1,269 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'event_args.dart'; + +/// Returns the valueChangeArgs by used the [onValueChanged] event. +typedef _ValueChangedCallback = void Function(); + +/// Returns the ValueChangingArgs by used the [onValueChanging] event. +typedef _ValueChangingCallback = void Function( + ValueChangingArgs valueChangingArgs); + +/// Returns the PropertyChangedArgs by used the [onPropertyChanged] event. +typedef _PropertyChangedCallback = void Function( + PropertyChangedArgs propertyChangedArgs); + +/// Defines an interface that provides all properties to configure a scrollbar. +class ScrollBarBase extends ChangeNotifier { + /// + ScrollBarBase( + {required bool enabled, + required double maximum, + required double minimum, + required double largeChange, + required double smallChange, + required double value}) + : _enabled = enabled, + _maximum = maximum, + _minimum = minimum, + _largeChange = largeChange, + _smallChange = smallChange, + _value = value; + + /// Gets a value indicating whether the scroll bar is enabled or not. + /// + /// Returns a number that represents the current position of the + /// scroll box on the scroll bar control. + bool get enabled => _enabled; + bool _enabled = false; + set enabled(bool value) { + if (value == _enabled) { + return; + } + + _enabled = value; + } + + /// Gets the upper limit of values of the scrollable range. + /// + /// Returns the upper limit of values of the scrollable range. + double get maximum => _maximum; + double _maximum = 0.0; + set maximum(double value) { + if (value == _maximum) { + return; + } + + _maximum = value; + } + + /// Gets the lower limit of values of the scrollable range. + /// + /// Returns the lower limit of values of the scrollable range. + double get minimum => _minimum; + double _minimum = 0.0; + set minimum(double value) { + if (value == _minimum) { + return; + } + + _minimum = value; + } + + /// Gets a value to be added to or subtracted from the value of the property + /// when the scroll box is moved a large distance. + /// + /// Returns the value to be added to or subtracted from the value + /// of the property when the scroll box is moved a large distance. + double get largeChange => _largeChange; + double _largeChange = 0.0; + set largeChange(double value) { + if (value == _largeChange) { + return; + } + + _largeChange = value; + } + + /// Gets the value to be added to or subtracted from the value of the + /// property when the scroll box is moved a small distance. + /// + /// Returns the value to be added to or subtracted from the value + /// of the property when the scroll box is moved a small distance. + double get smallChange => _smallChange; + double _smallChange = 0.0; + set smallChange(double value) { + if (value == _smallChange) { + return; + } + + _smallChange = value; + } + + /// Gets a numeric value that represents the current position of the + /// scroll box on the scroll bar control. + /// + /// Returns a numeric value that represents the current position of + /// the scroll box on the scroll + /// bar control. + double get value => _value; + double _value = 0.0; + set value(double value) { + if (value == _value) { + return; + } + + _value = value; + } +} + +/// Provides all properties to configure a scrollbar. +class ScrollInfo extends ScrollBarBase { + ///Initializes a new instance of the [ScrollInfo] class. + ScrollInfo() + : super( + value: 0, + minimum: 0, + maximum: 100, + smallChange: 1, + enabled: true, + largeChange: 10) { + _proposedLargeChange = 10; + } + + late double _proposedLargeChange; + + /// Occurs when a property value changes. + _PropertyChangedCallback? onPropertyChangedEvent; + + /// Occurs when the current position of the scroll box on the scroll bar + /// has changed. + _ValueChangedCallback? onValueChanged; + + /// Occurs when the current position of the scroll box on the scroll bar + /// is being changed. + _ValueChangingCallback? onValueChanging; + + /// Sets a value to be added to or subtracted from the value of the property + /// when the scroll box is moved a large distance. + @override + set largeChange(double value) { + _proposedLargeChange = value; + if (value < 0) { + value = maximum - minimum; + } + + if (largeChange != value) { + super.largeChange = value; + } + } + + /// Gets a numeric value that represents the current position of the + /// scroll box on the scroll bar control. + /// + /// Returns a numeric value that represents the current position of the + /// scroll box on the scroll bar control. + @override + double get value { + if (_proposedLargeChange < 0) { + return minimum; + } + return max(minimum, min(maximum - largeChange + 1, super.value)); + } + + /// Sets a numeric value that represents the current position of the + /// scroll box on the scroll bar control. + @override + set value(double value) { + if (this.value != value) { + final ValueChangingArgs e = ValueChangingArgs(value, this.value); + if (onValueChanging != null) { + onValueChanging!(e); + } + + if (!e.cancel) { + double offset = e.newValue; + + if (offset < minimum || largeChange >= maximum - minimum) { + offset = minimum; + } else { + if (offset + largeChange > maximum) { + offset = max(minimum, maximum - largeChange); + } + } + + super.value = offset; + } + } + } + + /// Clones this instance. + /// + /// Returns the cloned instance. + ScrollInfo clone() { + final ScrollInfo sb = ScrollInfo(); + copyTo(sb); + return sb; + } + + /// Copies current settings to another object. + /// + /// * scrollBar - _required_ - The object to which the settings + /// is to be copied. + void copyTo(ScrollInfo scrollBar) { + scrollBar + ..value = value + ..minimum = minimum + ..maximum = maximum + ..largeChange = largeChange + ..smallChange = smallChange + ..enabled = enabled; + } + + /// Determines whether the specified `ScrollInfo` is equal + /// to the current `ScrollInfo`. + /// + /// * obj - _required_ - The `ScrollInfo` to compare with + /// the current `ScrollInfo`. + /// + /// Returns `True` if the specified `ScrollInfo` is equal + /// to the current `ScrollInfo`, otherwise `false`. + bool equals(ScrollInfo? obj) { + final ScrollInfo? sb = obj; + if (sb == null) { + return true; + } + + return sb.value == value && + sb.minimum == minimum && + sb.maximum == maximum && + sb.largeChange == largeChange && + sb.smallChange == smallChange && + sb.enabled == enabled; + } + + /// Serves as a hash function for a particular type. + /// + /// returns a hash code for the current `ScrollInfo`. + int getHashCode() => value.hashCode; + + /// Called when a property is changed and raises the [PropertyChanged] event. + /// + /// * propertyName - _required_ - Name of the property. + void onPropertyChanged(String propertyName) { + if (onPropertyChangedEvent != null) { + final PropertyChangedArgs propertyChangedArgs = + PropertyChangedArgs(propertyName); + onPropertyChangedEvent!(propertyChangedArgs); + } + } + + /// A `string` that represents the current `object`. + /// + /// Returns a `string` that represents the current `object`. + @override + String toString() => + 'ScrollInfo ( Value = $value, Minimum = $minimum, Maximum = $maximum, LargeChange = $largeChange, Enabled = $enabled )'; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart new file mode 100644 index 000000000..a13d358a9 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/tree_table.dart @@ -0,0 +1,3617 @@ +import 'enums.dart'; +import 'utility_helper.dart'; + +/// A branch or leaf in the tree. +abstract class TreeTableNodeBase { + /// Gets the color to the branch. + TreeTableNodeColor? get color => _color; + TreeTableNodeColor? _color; + + /// Sets the color to the branch. + set color(TreeTableNodeColor? value) { + if (value == _color) { + return; + } + + _color = value; + } + + /// Gets the parent branch. + TreeTableBranchBase? get parent => _parent; + TreeTableBranchBase? _parent; + + /// Sets the parent color. + set parent(TreeTableBranchBase? value) { + if (value == _parent) { + return; + } + + _parent = value; + } + + /// Walk up parent branches and reset counters. + /// + /// * notifyParentRecordSource - _required_ - boolean value + void invalidateCounterBottomUp(bool notifyParentRecordSource); + + /// Walk up parent branches and reset summaries. + /// + /// * notifyParentRecordSource - _required_ - Boolean value + void invalidateSummariesBottomUp(bool notifyParentRecordSource); + + /// Indicates whether leaf is empty. + /// + /// Returns the boolean value indicates whether the leaf is empty + bool isEmpty(); + + /// Indicates whether this is a leaf. + /// + /// Returns the boolean value indicates whether this is a leaf + bool isEntry(); + + ///Gets the number of child nodes (+1 for the current node). + /// + /// Returns the number of child nodes (+1 for the current node). + int getCount(); + + /// Gets the minimum value (of the leftmost leaf) of the branch in + /// a sorted tree. + /// + /// Returns the minimum value (of the leftmost leaf) of the branch in + /// a sorted tree. + Object? getMinimum(); + + /// Gets the position in the tree. + /// + /// Returns the position in the tree. + int getPosition(); + + /// Gets the tree level of this node. + /// + /// Returns Returns the tree level of this node. + int getLevel(); +} + +/// A branch with left and right leaves or branches. +mixin TreeTableBranchBase on TreeTableNodeBase { + /// Gets the left node. + TreeTableNodeBase? get left => _left; + TreeTableNodeBase? _left; + + /// Sets the left node. + set left(TreeTableNodeBase? value) { + if (value == _left) { + return; + } + + _left = value; + } + + /// Gets the right node. + TreeTableNodeBase? get right => _right; + TreeTableNodeBase? _right; + + /// Sets the right node. + set right(TreeTableNodeBase? value) { + if (value == _right) { + return; + } + + _right = value; + } + + /// Sets this object's child node Count dirty and + /// marks parent nodes' child node Count dirty. + void invalidateCountBottomUp(); + + /// Sets this object's child node Count dirty and steps + /// through all child branches and marks their child node Count dirty. + void invalidateCountTopDown(); + + /// Sets this object's child node Minimum dirty and + /// marks parent nodes' child node Minimum dirty. + void invalidateMinimumBottomUp(); + + /// Sets this object's child node Minimum dirty and steps + /// through all child branches and marks their child node Minimum dirty. + void invalidateMinimumTopDown(); + + /// The left branch cast to TreeTableBranchBase. + /// + /// Returns the left branch cast to TreeTableBranchBase. + TreeTableBranchBase? getLeftBranch(); + + /// The right branch cast to TreeTableBranchBase. + /// + /// Returns the right branch cast to TreeTableBranchBase. + TreeTableBranchBase? getRightBranch(); + + /// Returns the position in the tree table of the specified child node. + /// + /// * node - _required_ - Tree node + /// + /// Returns the position in the tree. + int getEntryPositionOfChild(TreeTableNodeBase node); + + /// Sets the left node. + /// + /// Call this method instead of simply setting `Left` property if you want + /// to avoid the round-trip call to check whether the tree is in add-mode + /// or if tree-table is sorted. + /// + /// * value - _required_ - The new node. + /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. + /// * isSortedTree - _required_ - Indicates whether tree-table is sorted. + void setLeft(TreeTableNodeBase? value, bool inAddMode, bool isSortedTree); + + /// Sets the right node. + /// + /// Call this method instead of simply setting `Right` property if you want + /// to avoid the round-trip call to check whether the tree is in add-mode + /// or if tree-table is sorted. + /// + /// * value - _required_ - The new node. + /// * inAddMode - _required_ - Specifies if tree-table is in add-mode. + void setRight(TreeTableNodeBase? value, bool inAddMode); +} + +/// A leaf with value and optional sort key. +mixin TreeTableEntryBase on TreeTableNodeBase { + /// Gets the value attached to this leaf. + Object? get value => _value; + Object? _value; + + /// Sets the value attached to this leaf + set value(Object? value) { + if (value == _value) { + return; + } + + _value = value; + } + + /// Creates a branch that can hold this entry when new leaves are + /// inserted into the tree. + /// + /// * tree - _required_ - Tree table instance + /// + /// Returns the instance of newly created branch + TreeTableBranchBase? createBranch(TreeTable tree); + + /// Gets the sort key of this leaf. + /// + /// Returns the sort key of this leaf. + Object? getSortKey(); +} + +/// A branch or leaf in the tree. +abstract class TreeTableNode extends TreeTableNodeBase { + /// + static Object emptyMin = Object(); + + /// Gets the tree this node belongs to. + TreeTable? get tree => _tree; + TreeTable? _tree; + + /// Sets the tree this node belongs to. + set tree(TreeTable? value) { + if (value == _tree) { + return; + } + + _tree = value; + } + + /// Gets the parent branch. + @override + TreeTableBranchBase? get parent => _parent; + + /// Sets the parent branch. + @override + set parent(TreeTableBranchBase? value) { + _parent = value; + } + + /// Walks up parent branches and reset counters. + /// + /// * notifyParentRecordSource - _required_ - Boolean value + @override + void invalidateCounterBottomUp(bool notifyParentRecordSource) {} + + /// Walks up parent branches and reset summaries. + /// + /// * notifyParentRecordSource - _required_ - Boolean value + @override + void invalidateSummariesBottomUp(bool notifyParentRecordSource) {} + + /// Indicates whether leaf is empty. + /// + /// Returns the boolean value indicates whether the leaf is empty + @override + bool isEmpty() => getCount() == 0; + + /// Indicates whether this is a leaf. + /// + /// Returns the boolean value indicates whether this is a leaf + @override + bool isEntry(); + + /// Gets the minimum value (of the most-left leaf) of the branch + /// in a sorted tree. + /// + /// Returns the minimum value (of the most-left leaf) of the branch + /// in a sorted tree. + @override + Object? getMinimum() => emptyMin; + + /// Gets the number of child nodes (+1 for the current node). + /// + /// Returns the number of child nodes (+1 for the current node). + @override + int getCount(); + + /// Gets the tree level of this node. + /// + /// Returns the tree level of this node. + @override + int getLevel() { + int level = 0; + if (parent != null) { + level = parent!.getLevel() + 1; + } + + return level; + } + + /// Gets the Debug / text information about the node. + /// + /// Returns the Debug / text information about the node. + String getNodeInfoBase() { + String side = '_'; + if (parent != null) { + side = referenceEquals(parent!.left, this) ? 'L' : 'R'; + } + + return '${getLevel()} $side this., ${getPosition()} , ${getCount()}'; + } + + /// Gets the position in the tree. + /// + /// Returns the position in the tree. + @override + int getPosition() { + if (parent == null) { + return 0; + } + + return parent!.getEntryPositionOfChild(this); + } + + /// Gets the Debug / text information about the node. + /// + /// Returns the Debug / text information about the node. + @override + String toString() => '$runtimeType ${getNodeInfoBase()}'; +} + +/// A branch in a tree. +class TreeTableBranch extends TreeTableNode with TreeTableBranchBase { + /// Initializes a new instance of the `TreeTableBranch` class. + /// + /// * tree - _required_ - Tree table instance + TreeTableBranch(TreeTable tree) { + this.tree = tree; + } + + /// + int entryCount = -1; + Object? _minimum = TreeTableNode.emptyMin; + + /// Gets the right tree or branch. + @override + TreeTableNodeBase? get right => _right; + + /// Sets the right tree or branch. + @override + set right(TreeTableNodeBase? value) { + setRight(value, false); + } + + /// Gets the left leaf or branch. + @override + TreeTableNodeBase? get left => _left; + + /// Sets the left leaf or branch. + @override + set left(TreeTableNodeBase? value) { + setLeft(value, false, tree!.sorted); + } + + /// Indicates whether this is a leaf. + /// + /// Returns boolean value + @override + bool isEntry() => false; + + /// Sets this object's child node count dirty and + /// walks up parent nodes and marks their child node count dirty. + @override + void invalidateCountBottomUp() { + entryCount = -1; + if (parent != null && parent!.parent == parent) { + throw Exception(); + } + + if (parent != null) { + parent!.invalidateCountBottomUp(); + } + } + + /// Sets this object's child node count dirty and steps + /// through all child branches and marks their child node count dirty. + @override + void invalidateCountTopDown() { + entryCount = -1; + if (left != null && !left!.isEntry()) { + getLeftBranch()?.invalidateCountTopDown(); + } + + if (right != null && !right!.isEntry()) { + getRightBranch()?.invalidateCountTopDown(); + } + } + + /// Sets this object's child node minimum dirty and + /// marks parent nodes' child node minimum dirty. + @override + void invalidateMinimumBottomUp() { + _minimum = TreeTableNode.emptyMin; + if (parent != null) { + parent!.invalidateMinimumBottomUp(); + } + } + + /// Sets this object's child node minimum dirty and steps + /// through all child branches and marks their child node minimum dirty. + @override + void invalidateMinimumTopDown() { + if (left != null && !left!.isEntry()) { + getLeftBranch()?.invalidateMinimumTopDown(); + } + if (right != null && !right!.isEntry()) { + getRightBranch()?.invalidateMinimumTopDown(); + } + _minimum = TreeTableNode.emptyMin; + } + + /// Gets the number of child nodes (+1 for the current node). + /// + /// Returns the number of child nodes (+1 for the current node). + @override + int getCount() { + if (entryCount < 0 && _left != null && _right != null) { + entryCount = _left!.getCount() + _right!.getCount(); + } + + return entryCount; + } + + /// Gets the position in the tree table of the specific child node. + /// + /// * node - _required_ - Tree node + /// + /// Returns the position in the tree table of the specific child node. + @override + int getEntryPositionOfChild(TreeTableNodeBase node) { + int pos = getPosition(); + if (referenceEquals(node, right)) { + if (left != null) { + pos += left!.getCount(); + } + } else if (!referenceEquals(node, left)) { + //throw ArgumentError('must be a child node','node'); + throw ArgumentError('must be a child node'); + } + + return pos; + } + + /// The left node cast to TreeTableBranchBase. + /// + /// Returns the left node cast to TreeTableBranchBase. + @override + TreeTableBranchBase? getLeftBranch() { + if (_left is TreeTableBranchBase) { + return _left! as TreeTableBranchBase; + } else { + return null; + } + } + + /// Gets the minimum value (of the most-left leaf) of the branch + /// in a sorted tree. + /// + /// Returns the minimum value (of the most-left leaf) of the branch + /// in a sorted tree. + @override + Object? getMinimum() { + if (referenceEquals(TreeTableNode.emptyMin, _minimum)) { + _minimum = _left!.getMinimum(); + } + return _minimum; + } + + /// The right node cast to TreeTableBranchBase. + /// + /// Returns the right node cast to TreeTableBranchBase. + @override + TreeTableBranchBase? getRightBranch() { + if (_right is TreeTableBranchBase) { + return _right! as TreeTableBranchBase; + } else { + return null; + } + } + + /// Sets the left node. + /// + /// Call this method instead of simply setting `Left` property if you want + /// to avoid the round-trip call to check whether the tree is in add-mode + /// or if tree-table is sorted. + /// + /// * value - _required_ - The new node. + /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. + /// * isSorted - _required_ - Indicates whether tree-table is sorted. + @override + void setLeft(TreeTableNodeBase? value, bool inAddMode, bool isSorted) { + if (!referenceEquals(left, value)) { + if (inAddMode) { + if (_left != null && _left!.parent == this) { + _left!.parent = null; + } + + _left = value; + if (_left != null) { + _left!.parent = this; + } + } else { + final int lc = (_left != null) ? _left!.getCount() : 0; + final int vc = (value != null) ? value.getCount() : 0; + final int entryCountDelta = vc - lc; + if (_left != null && _left!.parent == this) { + _left!.parent = null; + } + + _left = value; + if (_left != null) { + _left!.parent = this; + } + + if (entryCountDelta != 0) { + invalidateCountBottomUp(); + } + + if (isSorted) { + invalidateMinimumBottomUp(); + } + + invalidateCounterBottomUp(false); + invalidateSummariesBottomUp(false); + } + } + } + + /// Sets the right node. + /// + /// Call this method instead of simply setting `Right` property if you want + /// to avoid the round-trip call to check whether the tree is in add-mode + /// or if tree-table is sorted. + /// + /// * value - _required_ - The new node. + /// * inAddMode - _required_ - Indicates whether tree-table is in add-mode. + @override + void setRight(TreeTableNodeBase? value, bool inAddMode) { + if (!referenceEquals(right, value)) { + if (inAddMode) { + if (_right != null && _right!.parent == this) { + _right!.parent = null; + } + + _right = value; + if (_right != null) { + _right!.parent = this; + } + } else { + final int lc = (_right != null) ? _right!.getCount() : 0; + final int vc = (value != null) ? value.getCount() : 0; + final int entryCountDelta = vc - lc; + if (_right != null && _right!.parent == this) { + _right!.parent = null; + } + + _right = value; + if (_right != null) { + _right!.parent = this; + } + + if (entryCountDelta != 0) { + invalidateCountBottomUp(); + } + + invalidateCounterBottomUp(false); + invalidateSummariesBottomUp(false); + } + } + } +} + +/// A leaf in the tree with value and optional sort key. +class TreeTableEntry extends TreeTableNode with TreeTableEntryBase { + /// Gets the Debug / text information about the node. + /// + /// Returns the Debug / text information about the node. + String getNodeInfo() => + '${getNodeInfoBase()} ${value != null ? value.toString() : 'null'}'; + + /// Indicates whether this is a leaf. + /// + /// Returns the boolean value + @override + bool isEntry() => true; + + /// Creates a branch that can hold this entry when new leaves are inserted + /// into the tree. + /// + /// * tree - _required_ - Tree table instance + /// + /// Returns the instance of newly created branch + + @override + TreeTableBranchBase? createBranch(TreeTable tree) => TreeTableBranch(tree); + + /// Gets the number of child nodes (+1 for the current node). + /// + /// Returns the number of child nodes (+1 for the current node). + @override + int getCount() => 1; + + /// Gets the minimum value (of the most-left leaf) of the branch + /// in a sorted tree. + /// + /// Returns the minimum value (of the most-left leaf) of the branch + /// in a sorted tree. + @override + Object? getMinimum() => getSortKey(); + + /// Gets the sort key of this leaf. + /// + /// Returns the sort key of this leaf. + @override + Object? getSortKey() => value; +} + +/// An empty node. +class TreeTableEmpty extends TreeTableNode { + /// + static TreeTableEmpty empty = TreeTableEmpty(); + + /// The Debug / text information about the node. + /// + /// Returns the Debug / text information about the node. + String getNodeInfo() => 'Empty'; + + /// The number of child nodes (+1 for the current node). + /// + /// Returns the number of child nodes (+1 for the current node). + @override + int getCount() => 0; + + /// Indicates whether this is a leaf. + /// + /// Returns boolean value + @override + bool isEntry() => true; +} + +/// Tree table interface definition. +class TreeTableBase extends ListBase { + /// + TreeTableBase() { + _isInitializing = false; + } + + /// Gets the comparer value used by sorted trees. + Comparable? get comparer => _comparer; + Comparable? _comparer; + + /// Sets the comparer value used by sorted trees. + set comparer(Comparable? value) { + if (value == _comparer) { + return; + } + + _comparer = value; + } + + /// + TreeTableNodeBase? get root => _root; + TreeTableNodeBase? _root; + + /// Gets a value indicating whether this is a sorted tree or not. + bool get sorted => _sorted; + bool _sorted = false; + + /// Gets the root node. + + /// Gets a value indicating whether the tree was initialize or not. + bool get isInitializing => _isInitializing; + late bool _isInitializing; + + /// Optimizes insertion of many elements when tree is initialized + /// for the first time. + void beginInit(); + + /// Ends optimization of insertion of elements when tree is initialized + /// for the first time. + void endInit(); + + /// Optimized access to a subsequent entry. + /// + /// * current - _required_ - Current item + /// + /// Returns next subsequent entry + TreeTableEntryBase? getNextEntry(TreeTableEntryBase current); + + /// Optimized access to a previous entry. + /// + /// * current - _required_ - Current item + /// + /// Returns previous entry + TreeTableEntryBase? getPreviousEntry(TreeTableEntryBase current); +} + +/// A tree table. +class TreeTable extends TreeTableBase { + /// Initializes a new instance of the `TreeTable` class. + /// + /// * sorted - _required_ - Boolean value + TreeTable(bool sorted) { + _sorted = sorted; + } + + bool _inAddMode = false; + TreeTableBranchBase? _lastAddBranch; + TreeTableEntryBase? _lastFoundEntry; + Object? _lastFoundEntryKey; + bool _lastFoundEntryHighestSmallerValue = false; + + /// + int lastIndex = -1; + + /// + Object? tag; + + /// Gets the last index leaf. + /// + /// Returns the last index leaf. + TreeTableEntryBase? get lastIndexLeaf => _lastIndexLeaf; + TreeTableEntryBase? _lastIndexLeaf; + + /// Sets the last index leaf. + /// + /// Returns the last index leaf. + set lastIndexLeaf(TreeTableEntryBase? value) { + if (_lastIndexLeaf != value) { + _lastIndexLeaf = value; + } + } + + /// Gets the comparer used by sorted trees. + @override + Comparable? get comparer => _comparer; + + /// Sets the comparer used by sorted trees. + @override + set comparer(Comparable? value) { + _comparer = value; + _sorted = _comparer != null; + } + + /// Gets the number of leaves. + @override + int get count => countBase; + + /// Gets the number of leaves. + int get countBase => getCount(); + + /// Gets a value indicating whether the tree was initialize or not. + @override + bool get isInitializing => _inAddMode; + + /// Gets a value indicating whether the tree is Read-only or not. + @override + bool get isReadOnly => false; + + /// Gets a value indicating whether the nodes can be added or removed. + @override + bool get isFixedSize => false; + + /// Gets a value indicating whether the tree is Synchronized or not. + @override + bool get isSynchronized => false; + + /// Gets a value indicating whether tree is sorted or not. + @override + bool get sorted => _sorted; + + /// Gets the root node. + @override + TreeTableNodeBase? get root => _root; + + /// Gets an object that can be used to synchronize access to + /// the `ICollection`. + /// + /// Returns - _required_ - an object that can be used to synchronize access + /// to the `ICollection`. + @override + Object? get syncRoot => null; + + /// Appends a node. + /// + /// * value - _required_ - Node value to append. + /// + /// Returns the zero-based collection index at which the value has been added. + int addBase(TreeTableNodeBase value) { + cacheLastFoundEntry(null, null, false); + lastIndex = -1; + + if (sorted && !_inAddMode) { + return addSorted(value); + } + + if (_root == null) { + // replace root + _root = value; + return 0; + } else { + // add node to most right branch + TreeTableBranchBase? branch; + TreeTableNodeBase? current = _lastAddBranch ?? _root; + + while (current != null && !current.isEntry()) { + branch = current as TreeTableBranchBase; + current = branch.right; + } + + final TreeTableEntryBase leaf = current! as TreeTableEntryBase; + + final TreeTableBranchBase? newBranch = leaf.createBranch(this); + if (newBranch != null) { + newBranch + ..setLeft(leaf, _inAddMode, sorted) + // will set leaf.Parent ... + ..setRight(value, _inAddMode); + } + + if (branch == null) { + _root = newBranch; + } else { + // swap out leafs parent with new node + _replaceNode(branch, current, newBranch, _inAddMode); + if (!(branch.parent == null || + branch.parent?.parent == null || + branch.right != branch.parent?.parent?.right)) { + throw Exception(); + } + + final Object? _left = branch.parent?.left; + if (!(branch.parent == null || + (branch.parent != null && + branch.parent!.left != null && + branch.parent!.left!.isEntry()) || + (_left is TreeTableBranch && _left.right != branch))) { + throw Exception(); + } + } + + insertFixup(newBranch!, _inAddMode); + + if (value.parent != null && value.parent?.parent != null) { + if (value.parent!.parent?.right == value) { + throw Exception(); + } + } + + _lastAddBranch = newBranch; + + if (_inAddMode) { + return -1; + } else { + return _root!.getCount() - 1; + } + } + } + + /// Adds a node in a sorted tree only if no node with the same value has + /// not been added yet. + /// + /// * key - _required_ - Key needs to be add in the collection + /// * value - _required_ - Node value to add. + /// + /// Returns the instance for the tree + TreeTableEntryBase? addIfNotExists( + Comparable? key, TreeTableEntryBase? value) { + if (!sorted) { + throw Exception('This tree is not sorted.'); + } + + cacheLastFoundEntry(null, null, false); + + if (_root == null) { + // replace root + _root = value; + return value; + } else { + // find node + TreeTableBranchBase? branch; + TreeTableNodeBase? current = _root!; + int cmp = 0; + final Comparable? comparer = this.comparer; + const bool inAddMode = false; + Comparable? comparableKey = key!; + while (current != null && !current.isEntry()) { + branch = current as TreeTableBranchBase; + if (comparer != null) { + final TreeTableNodeBase? tableNodeBase = branch.right; + if (tableNodeBase != null) { + final Object? value = tableNodeBase.getMinimum(); + cmp = key.compareTo(value); + } + } else if (comparableKey is Comparable) { + cmp = comparableKey.compareTo(branch.right?.getMinimum()); + } else { + throw Exception('No Comparer specified.'); + } + + if (cmp == 0) { + current = branch.right; + while (current != null && !current.isEntry()) { + final TreeTableBranchBase _current = current as TreeTableBranchBase; + current = _current.left; + } + + return current! as TreeTableEntryBase; + } else if (cmp < 0) { + current = branch.left; + } else { + current = branch.right; + } + } + + final TreeTableEntryBase leaf = current! as TreeTableEntryBase; + + if (comparer != null) { + cmp = key.compareTo(leaf.getSortKey()); + } else if (value!.getMinimum() is Comparable) { + cmp = comparableKey.compareTo(leaf.getSortKey()); + } + + comparableKey = null; + + if (cmp == 0) { + return leaf; + } + + final TreeTableBranchBase? newBranch = leaf.createBranch(this); + + if (newBranch != null && cmp < 0) { + newBranch + ..setLeft(value, false, sorted) // will set leaf.Parent ... + ..right = leaf; + } else if (newBranch != null && cmp > 0) { + newBranch + ..setLeft(leaf, false, sorted) // will set leaf.Parent ... + ..right = value; + } + + if (branch == null) { + _root = newBranch; + } else { + _replaceNode(branch, leaf, newBranch, false); + } + + insertFixup(newBranch, inAddMode); + return value; + } + } + + /// Adds a node into a sorted tree. + /// + /// * value - _required_ - Node value to add. + /// + /// Returns the zero-based collection index at which the value has been added. + int addSorted(TreeTableNodeBase value) { + if (!sorted) { + throw const FormatException('This tree is not sorted.'); + } + + if (_inAddMode) { + return add(value); + } + + cacheLastFoundEntry(null, null, false); + + if (_root == null) { + // replace root + _root = value; + return 0; + } else { + const bool inAddMode = false; + final Comparable? comparer = this.comparer; + + // find node + TreeTableBranchBase? branch; + TreeTableNodeBase? current = _root; + int count = 0; + int? cmp = 0; + + while (current != null && !current.isEntry()) { + branch = current as TreeTableBranchBase; + if (comparer != null) { + final dynamic minimum = value.getMinimum(); + final dynamic right = branch.right!.getMinimum(); + cmp = Comparable.compare(minimum, right); + } else if (value.getMinimum() is Comparable) { + final Object? _minimum = value.getMinimum(); + if (_minimum != null && _minimum is Comparable) { + cmp = _minimum.compareTo(branch.right!.getMinimum()); + } else { + cmp = null; + } + } else { + throw Exception('No Comparer Specified'); + } + + if (cmp != null && cmp <= 0) { + current = branch.left; + } else { + count += branch.left!.getCount(); + current = branch.right; + } + } + + final TreeTableEntryBase leaf = current! as TreeTableEntryBase; + if (leaf is TreeTableEntryBase) {} + + final TreeTableBranchBase? newBranch = leaf.createBranch(this); + + if (comparer != null) { + final Object? minimum = value.getMinimum(); + final Object? sortKey = leaf.getSortKey(); + if (minimum is Comparable && sortKey is Comparable) { + cmp = Comparable.compare(minimum, sortKey); + } + } else if (value.getMinimum() is Comparable) { + final Object? _minimum = value.getMinimum(); + if (_minimum != null && _minimum is Comparable) { + cmp = _minimum.compareTo(leaf.getSortKey()); + } else { + cmp = null; + } + } + + if (newBranch != null && cmp != null && cmp <= 0) { + newBranch + ..setLeft(value, false, sorted) // will set leaf.Parent ... + ..right = leaf; + } else { + if (newBranch != null) { + newBranch.setLeft(leaf, false, sorted); // will set leaf.Parent ... + count++; + newBranch.right = value; + } + } + + if (branch == null) { + _root = newBranch; + } else { + // swap out leafs parent with new node + _replaceNode(branch, leaf, newBranch, inAddMode); + } + + //Debug.Assert(value.Position == index); + insertFixup(newBranch, inAddMode); + return count; + } + } + + /// + TreeTableEntryBase? cacheLastFoundEntry( + TreeTableEntryBase? entry, Object? key, bool highestSmallerValue) { + lastIndex = -1; + _lastFoundEntry = entry; + _lastFoundEntryKey = key; + _lastFoundEntryHighestSmallerValue = highestSmallerValue; + return _lastFoundEntry; + } + + /// Indicates whether the node belongs to this tree. + /// + /// * value - _required_ - Node value to search for. + /// + /// Returns true if node belongs to this tree; false otherwise. + bool containsBase(TreeTableNodeBase? value) { + if (value == null || _root == null) { + return false; + } + + // search root + while (value!.parent != null) { + value = value.parent!; + } + + return referenceEquals(value, _root); + } + + /// Copies the elements from this collection into an array. + /// + /// * array - _required_ - The destination array. + /// * index - _required_ - The starting index in the destination array. + void copyToBase(List array, int index) { + final int count = getCount(); + for (int i = 0; i < count; i++) { + array[i + index] = this[i]!; + } + } + + /// + void deleteFixup(TreeTableBranchBase? x, bool isLeft) { + const bool inAddMode = false; + while (x != null && + !referenceEquals(x, _root) && + x._color == TreeTableNodeColor.black) { + if (isLeft) { + TreeTableNodeBase? w = x.parent?.right; + if (w != null && w.color == TreeTableNodeColor.red) { + w.color = TreeTableNodeColor.black; + x.parent?.color = TreeTableNodeColor.black; + leftRotate(x.parent!, inAddMode); + if (x.parent != null) { + w = x.parent!.right! as TreeTableBranchBase; + } + } + + if (w == null) { + return; + } + + if (w is TreeTableBranchBase && + w.color == TreeTableNodeColor.black && + (w.left!.isEntry() || + w.getLeftBranch()!.color == TreeTableNodeColor.black) && + (w.right!.isEntry() || + w.getRightBranch()!.color == TreeTableNodeColor.black)) { + w.color = TreeTableNodeColor.red; + if (x.color == TreeTableNodeColor.red) { + x.color = TreeTableNodeColor.black; + return; + } else { + isLeft = x.parent!.left == x; + x = x.parent; + } + } else if (w is TreeTableBranchBase && + w.color == TreeTableNodeColor.black && + !w.right!.isEntry() && + w.getRightBranch()!.color == TreeTableNodeColor.red) { + leftRotate(x.parent!, inAddMode); + w.color = x.parent!.color; + x.parent!.color = w.color; + return; + } else if (w is TreeTableBranchBase && + w.color == TreeTableNodeColor.black && + !w.left!.isEntry() && + w.getLeftBranch()!.color == TreeTableNodeColor.red && + (w.right!.isEntry() || + w.getRightBranch()!.color == TreeTableNodeColor.black)) { + rightRotate(w, inAddMode); + + w.parent!.color = TreeTableNodeColor.black; + w.color = TreeTableNodeColor.red; + + leftRotate(x.parent!, inAddMode); + w.color = x.parent!.color; + x.parent!.color = w.color; + return; + } else { + return; + } + } else { + TreeTableNodeBase? w = x.parent?.left; + if (w != null && w.color == TreeTableNodeColor.red) { + w.color = TreeTableNodeColor.black; + x.parent!.color = TreeTableNodeColor.red; + rightRotate(x.parent, inAddMode); + w = x.parent!.left; + } + + if (w == null) { + return; + } + + if (w is TreeTableBranchBase && + w.color == TreeTableNodeColor.black && + (w.left!.isEntry() || + w.getLeftBranch()!.color == TreeTableNodeColor.black) && + (w.right!.isEntry() || + w.getRightBranch()!.color == TreeTableNodeColor.black)) { + w.color = TreeTableNodeColor.red; + if (x.color == TreeTableNodeColor.red) { + x.color = TreeTableNodeColor.black; + return; + } else if (x.parent != null) { + isLeft = x.parent!.left == x; + x = x.parent; + } + } else { + if (w is TreeTableBranchBase && + w.color == TreeTableNodeColor.black && + !w.right!.isEntry() && + w.getRightBranch()!.color == TreeTableNodeColor.red) { + final TreeTableBranchBase xParent = x.parent!; + leftRotate(xParent, inAddMode); + final TreeTableNodeColor t = w.color!; + w.color = xParent.color; + xParent.color = t; + return; + } else if (w is TreeTableBranchBase && + w.color == TreeTableNodeColor.black && + !w.left!.isEntry() && + w.getLeftBranch()!.color == TreeTableNodeColor.red && + (w.right!.isEntry() || + w.getRightBranch()!.color == TreeTableNodeColor.black)) { + final TreeTableBranchBase wParent = w.parent!; + final TreeTableBranchBase xParent = x.parent!; + rightRotate(w, inAddMode); + + wParent.color = TreeTableNodeColor.black; + w.color = TreeTableNodeColor.red; + + leftRotate(x.parent, inAddMode); + w.color = xParent.color; + xParent.color = w.color; + return; + } + } + } + } + + x!.color = TreeTableNodeColor.black; + } + + /// Finds the node in a sorted tree is just one entry ahead of the + /// node with the specified key. + /// + /// It searches for the largest possible + /// key that is smaller than the specified key. + /// + /// * key - _required_ - The key to search. + /// + /// Returns the node; `NULL` if not found. + TreeTableEntryBase? findHighestSmallerOrEqualKey(Object key) => + _findKey(key, true); + + /// Finds a node in a sorted tree that matches the specified key. + /// + /// * key - _required_ - The key to search. + /// + /// Returns the node; `NULL` if not found. + TreeTableEntryBase? findKey(Object key) => _findKey(key, false); + + TreeTableEntryBase? _findKey(Object? key, bool highestSmallerValue) { + if (!sorted) { + throw Exception('This tree is not sorted.'); + } + + Object? comparableKey = key; + if (root == null) { + // replace root + return null; + } else { + final Comparable? comparer = this.comparer; + int cmp = 0; + + if (_lastFoundEntry != null && + _lastFoundEntryKey != null && + key != null && + _lastFoundEntryHighestSmallerValue == highestSmallerValue) { + if (comparer != null) { + final Object? lastFoundEntry = _lastFoundEntry!.getMinimum(); + if (key is Comparable && lastFoundEntry is Comparable) { + cmp = Comparable.compare(key, lastFoundEntry); + } + } else if (comparableKey != null && comparableKey is Comparable) { + cmp = comparableKey.compareTo(_lastFoundEntry!.getMinimum()); + } + + if (cmp == 0) { + return _lastFoundEntry; + } + } + + // find node + TreeTableBranchBase branch; + TreeTableNodeBase current = root!; + + TreeTableNodeBase? lastLeft; + + while (!current.isEntry()) { + branch = current as TreeTableBranchBase; + if (comparer != null) { + final Object? minimum = branch.right!.getMinimum(); + if (key is Comparable && minimum is Comparable) { + cmp = Comparable.compare(key, minimum); + } + } else if (comparableKey != null && comparableKey is Comparable) { + cmp = comparableKey.compareTo(branch.right!.getMinimum()); + } else { + throw Exception('No Comparer specified.'); + } + + if (cmp == 0) { + current = branch.right!; + while (!current.isEntry()) { + if (current is TreeTableBranchBase) { + current = current.left!; + } + } + + return cacheLastFoundEntry( + current as TreeTableEntryBase, key, highestSmallerValue); + } else if (cmp < 0) { + current = branch.left!; + lastLeft = branch.left; + } else { + current = branch.right!; + } + } + + final TreeTableEntryBase leaf = current as TreeTableEntryBase; + + if (comparer != null) { + final Object? sortKey = leaf.getSortKey(); + if (key is Comparable && sortKey is Comparable) { + cmp = Comparable.compare(key, sortKey); + } + } else if (comparableKey != null && comparableKey is Comparable) { + cmp = comparableKey.compareTo(leaf.getSortKey()); + } + + comparableKey = null; + if (cmp == 0) { + return cacheLastFoundEntry(leaf, key, highestSmallerValue); + } + + if (highestSmallerValue) { + if (cmp < 0) { + return cacheLastFoundEntry(leaf, key, highestSmallerValue); + } else if (lastLeft != null) { + current = lastLeft; + while (!current.isEntry()) { + final TreeTableBranchBase _current = current as TreeTableBranchBase; + current = _current.right!; + } + + return cacheLastFoundEntry( + current as TreeTableEntryBase, key, highestSmallerValue); + } + } + + _lastFoundEntry = null; + return null; + } + } + + /// Inserts a node at the specified index. + /// + /// * index - _required_ - Index value where the node is to be inserted. + /// * value - _required_ - Value of the node to insert. + void insertBase(int index, TreeTableNodeBase value) { + if (sorted) { + throw Exception('This tree is sorted - use AddSorted instead.'); + } + + final int treeCount = getCount(); + if (index < 0 || index > treeCount) { + throw ArgumentError( + 'index ${index.toString()} must be between 0 and ${treeCount.toString()}'); + } + + if (index == treeCount) { + add(value); + return; + } + + cacheLastFoundEntry(null, null, false); + if (_root == null) { + // replace root + _root = value; + } else { + TreeTableEntryBase? leaf; + if (lastIndex != -1) { + if (index == lastIndex) { + leaf = lastIndexLeaf; + } else if (index == lastIndex + 1) { + leaf = getNextEntry(lastIndexLeaf); + } + } + + leaf ??= _getEntryAt(index); + final TreeTableBranchBase? branch = leaf?.parent; + final TreeTableBranchBase newBranch = leaf!.createBranch(this)!; + newBranch + ..setLeft(value, false, sorted) // will set leaf.Parent ... + ..right = leaf; + + if (branch == null) { + _root = newBranch; + } else { + // swap out leafs parent with new node + _replaceNode(branch, leaf, newBranch, false); + } + + insertFixup(newBranch, _inAddMode); + + if (value.isEntry()) { + _lastIndexLeaf = value as TreeTableEntryBase; + lastIndex = index; + } else { + _lastIndexLeaf = null; + lastIndex = -1; + } + } + } + + /// Returns the position of a node. + /// + /// * value - _required_ - Node value to look for. + /// + /// Returns Index of the node if found. + int indexOfBase(TreeTableNodeBase value) { + if (!contains(value)) { + return -1; + } + + return value.getPosition(); + } + + /// Finds a node in a sorted tree. + /// + /// * key - _required_ - Key needs to be find an index + /// + /// Returns the index of the key + int indexOfKey(Object key) { + final TreeTableEntryBase? entry = findKey(key); + if (entry == null) { + return -1; + } + + return entry.getPosition(); + } + + /// + void insertFixup(TreeTableBranchBase? x, bool inAddMode) { + // Check Red-Black properties + while (x != null && + x.parent != null && + !referenceEquals(x, _root) && + x.parent!.color == TreeTableNodeColor.red && + x.parent!.parent != null) { + // We have a violation + if (x.parent == x.parent!.parent!.left) { + final TreeTableNodeBase? y = x.parent!.parent?.right; + if (y != null && y.color == TreeTableNodeColor.red) { + // uncle is red + x.parent!.color = TreeTableNodeColor.black; + y.color = TreeTableNodeColor.black; + x.parent!.parent?.color = TreeTableNodeColor.red; + x = x.parent!.parent; + } else { + // uncle is black + if (x == x.parent!.right) { + // Make x a left child + x = x.parent; + leftRotate(x, inAddMode); + } + + // Recolor and rotate + x!.parent!.color = TreeTableNodeColor.black; + x.parent!.parent!.color = TreeTableNodeColor.red; + rightRotate(x.parent!.parent, inAddMode); + } + } else { + // Mirror image of above code + final TreeTableNodeBase? y = x.parent!.parent?.left; + if (y != null && y.color == TreeTableNodeColor.red) { + // uncle is red + x.parent!.color = TreeTableNodeColor.black; + y.color = TreeTableNodeColor.black; + x.parent!.parent!.color = TreeTableNodeColor.red; + x = x.parent!.parent; + } else { + // uncle is black + if (x == x.parent!.left) { + x = x.parent; + rightRotate(x, inAddMode); + } + + x!.parent!.color = TreeTableNodeColor.black; + x.parent!.parent!.color = TreeTableNodeColor.red; + leftRotate(x.parent!.parent, inAddMode); + } + } + } + + root!.color = TreeTableNodeColor.black; + } + + /// Gets the number of leaves. + /// + /// Returns the number of leaves. + int getCount() => _root == null ? 0 : _root!.getCount(); + + /// Gets a [TreeTableEnumerator]. + /// + /// Returns a [TreeTableEnumerator]. + TreeTableEnumerator getEnumeratorBase() => TreeTableEnumerator(this); + + TreeTableEntryBase? _getEntryAt(int index) { + final int treeCount = getCount(); + if (index < 0 || index >= treeCount) { + throw ArgumentError( + 'index ${index.toString()} must be between 0 and ${(treeCount - 1).toString()}'); + } + + if (_root == null) { + // replace root + return null; + } else { + if (lastIndex != -1) { + if (index == lastIndex) { + return _lastIndexLeaf; + } else if (index == lastIndex + 1) { + lastIndex++; + return _lastIndexLeaf = getNextEntry(_lastIndexLeaf); + } + } + + // find node + TreeTableBranchBase branch; + TreeTableNodeBase? current = _root; + int count = 0; + while (current != null && !current.isEntry()) { + branch = current as TreeTableBranchBase; + final int leftCount = branch.left!.getCount(); + + if (index < count + leftCount) { + current = branch.left; + } else { + count += branch.left!.getCount(); + current = branch.right; + } + } + + if (current is TreeTableEntryBase) { + lastIndexLeaf = current; + } + lastIndex = index; + return _lastIndexLeaf; + } + } + + /// + TreeTableEntryBase? getMostLeftEntry(TreeTableBranchBase? parent) { + TreeTableNodeBase? next; + + if (parent == null) { + next = null; + return null; + } else { + next = parent.left; + while (!next!.isEntry()) { + final TreeTableBranchBase _next = next as TreeTableBranchBase; + next = _next.left; + } + } + + return next as TreeTableEntryBase; + } + + TreeTableNodeBase _getSisterNode( + TreeTableBranchBase leafsParent, TreeTableNodeBase node) { + final TreeTableNodeBase? sisterNode = + referenceEquals(leafsParent.left!, node) + ? leafsParent.right + : leafsParent.left; + + return sisterNode!; + } + + /// + void leftRotate(TreeTableBranchBase? x, bool inAddMode) { + if (x == null) { + return; + } + + final TreeTableBranchBase y = x.right! as TreeTableBranchBase; + + if (y.left is TreeTableNodeBase) { + y.setLeft(TreeTableEmpty.empty, inAddMode, sorted); + x.setRight(y.left, inAddMode); + if (x.parent != null) { + if (referenceEquals(x, x.parent!.left)) { + x.parent!.setLeft(y, inAddMode, sorted); + } else { + x.parent!.setRight(y, inAddMode); + } + } else { + _root = y; + } + y.setLeft(x, inAddMode, sorted); + } + } + + /// Removes the specified node. + /// + /// * value - _required_ - Node value to look for and remove. + /// + /// Returns the removed value + bool removeBase(TreeTableNodeBase value) => _remove(value, true); + + /// Used to remove the value from the tree table + /// + /// * value - _required_ - Tree value + /// * resetParent - _required_ - Boolean value + /// + /// Returns the boolean value + bool _remove(TreeTableNodeBase? value, bool resetParent) { + if (value == null) { + return false; + } + + if (!contains(value)) { + return false; + } + + cacheLastFoundEntry(null, null, false); + + _lastAddBranch = null; + lastIndex = -1; + _lastIndexLeaf = null; + + // root + if (referenceEquals(value, root)) { + _root = null; + if (resetParent) { + value.parent = null; + } + } else { + final TreeTableBranchBase? leafsParent = value.parent; + + // get the sister node + final TreeTableNodeBase sisterNode = _getSisterNode(leafsParent!, value); + + // swap out leaves parent with sister + if (referenceEquals(leafsParent, _root)) { + _root = sisterNode..parent = null; + } else { + final TreeTableBranchBase leafsParentParent = leafsParent.parent!; + final bool isLeft = leafsParentParent.left == leafsParent; + _replaceNode(leafsParentParent, leafsParent, sisterNode, false); + + if (leafsParent.color == TreeTableNodeColor.black) { + leafsParent.parent = leafsParentParent; + deleteFixup(leafsParent, isLeft); + } + } + + if (resetParent) { + value.parent = null; + } + } + + return true; + } + + /// Resets the cache. + void resetCache() { + _lastAddBranch = null; + lastIndex = -1; + _lastIndexLeaf = null; + } + + void _replaceNode(TreeTableBranchBase? branch, TreeTableNodeBase? oldNode, + TreeTableNodeBase? newNode, bool inAddMode) { + // also updates node count. + if (referenceEquals(branch?.left, oldNode)) { + branch?.setLeft(newNode, inAddMode, sorted); + } else { + branch?.setRight(newNode, inAddMode); + } + } + + /// + void rightRotate(TreeTableBranchBase? x, bool inAddMode) { + if (x == null) { + return; + } + + final TreeTableBranchBase y = x.left! as TreeTableBranchBase; + + final TreeTableNodeBase yRight = y.right!; + y.setRight( + TreeTableEmpty.empty, inAddMode); // make sure Parent is not reset later + x.setLeft(yRight, inAddMode, sorted); + if (x.parent != null) { + if (x == x.parent!.right) { + x.parent!.setRight(y, inAddMode); + } else { + x.parent!.setLeft(y, inAddMode, sorted); + } + } else { + _root = y; + } + y.setRight(x, inAddMode); + } + + /// Sets the node at the specified index. + /// + /// * index - _required_ - Index value where the node is to be inserted. + /// * value - _required_ - Value of the node that is to be inserted. + void setNodeAt(int index, TreeTableNodeBase value) { + final TreeTableEntryBase? leaf = _getEntryAt(index); + if (referenceEquals(leaf, _root)) { + _root = value; + } else { + if (leaf != null) { + final TreeTableBranchBase branch = leaf.parent!; + _replaceNode(branch, leaf, value, false); + } + } + + lastIndex = -1; + } + + /// Indicates whether the node belongs to this tree. + /// + /// * value - _required_ - Node value + /// + /// Returns the boolean value indicating whether the node belongs + /// to this tree. + @override + bool contains(Object value) { + if (value is TreeTableNodeBase) { + return containsBase(value); + } else { + return false; + } + } + + /// Clears all nodes in the tree. + @override + void clear() { + _root = null; + _lastAddBranch = null; + lastIndex = -1; + _lastIndexLeaf = null; + cacheLastFoundEntry(null, null, false); + } + + /// Adds the specified node to the tree. + /// + /// * value - _required_ - Adding value + /// + /// Returns the zero-based collection index at which the value has been added + @override + int add(Object value) { + if (value is TreeTableNodeBase) { + return addBase(value); + } else { + return -1; + } + } + + /// Optimizes insertion of many elements when tree is initialized + /// for the first time. + @override + void beginInit() { + _inAddMode = true; + } + + /// Copies the element from this collection into an array. + /// + /// * array - _required_ - The destination array. + /// * index - _required_ - The starting index in the destination array. + @override + void copyTo(List array, int index) { + if (array is List) { + copyToBase(array, index); + } + } + + /// Ends optimization of insertion of elements when tree is initialized + /// for the first time. + @override + void endInit() { + _inAddMode = false; + + // Fixes issues when GetCount() was called while debugging ... + final Object? branch = _root; + if (branch is TreeTableBranch && branch.entryCount != -1) { + branch.entryCount = -1; + } + } + + /// Inserts a node at the specified index. + /// + /// * index - _required_ - Position where to insert the value + /// * value - _required_ - Tree value need to be insert + @override + void insert(int index, Object value) { + if (value is TreeTableNodeBase) { + insertBase(index, value); + } + } + + /// Sets the index of the specified node. + /// + /// * value - _required_ - Tree value + /// + /// Returns the index of the specified node. + @override + int indexOf(Object value) { + if (value is TreeTableNodeBase) { + return indexOfBase(value); + } else { + return -1; + } + } + + /// Gets an enumerator. + /// + /// Returns an enumerator. + @override + EnumeratorBase getEnumerator() => getEnumerator(); + + /// Optimized access to a subsequent entry. + /// + /// * current - _required_ - Current item + /// + /// Returns next subsequent entry + @override + TreeTableEntryBase? getNextEntry(TreeTableEntryBase? current) { + TreeTableBranchBase? parent = current?.parent; + TreeTableNodeBase? next; + + if (parent == null) { + next = null; + return null; + } else { + if (referenceEquals(current, parent.left)) { + next = parent.right; + } else { + TreeTableBranchBase? parentParent = parent.parent; + if (parentParent == null) { + return null; + } else { + while (referenceEquals(parentParent!.right, parent)) { + parent = parentParent; + parentParent = parentParent.parent; + if (parentParent == null) { + return null; + } + } + + next = parentParent.right; + } + } + + while (!next!.isEntry()) { + if (next is TreeTableBranchBase) { + next = next.left; + } + } + } + + if (next is TreeTableEntryBase) { + return next; + } else { + return null; + } + } + + /// Optimized access to the previous entry. + /// + /// * current - _required_ - Current item + /// + /// Returns previous entry + @override + TreeTableEntryBase? getPreviousEntry(TreeTableEntryBase current) { + TreeTableBranchBase? parent = current.parent; + TreeTableNodeBase? prev; + + if (parent == null) { + prev = null; + return null; + } else { + if (referenceEquals(current, parent.right)) { + prev = parent.left; + } else { + TreeTableBranchBase? parentParent = parent.parent; + if (parentParent == null) { + return null; + } else { + while (referenceEquals(parentParent!.left, parent)) { + parent = parentParent; + parentParent = parentParent.parent; + if (parentParent == null) { + return null; + } + } + + prev = parentParent.left; + } + } + + while (!prev!.isEntry()) { + if (prev is TreeTableBranchBase) { + prev = prev.right; + } + } + } + + if (prev is TreeTableEntryBase) { + return prev; + } else { + return null; + } + } + + /// Removes the node with the specified value. + /// + /// * value - _required_ - Value needs to be remove + @override + bool remove(Object? value) { + if (value is TreeTableNodeBase) { + return removeBase(value); + } else { + return false; + } + } + + /// Removes a node at the specified position. + /// + /// * index - _required_ - Index value + @override + void removeAt(int index) { + remove(this[index]); + } + + /// Gets an item at the specified index. + /// + /// * index - _required_ - Index value + /// + /// Returns the item at the specified index. + @override + TreeTableNodeBase? operator [](int index) => _getEntryAt(index); + + /// Sets an item at the specified index. + @override + void operator []=(int index, Object value) { + if (value is TreeTableNodeBase) { + setNodeAt(index, value); + } + } + + /// + void lastIndexLeafDisposed() { + lastIndexLeaf = null; + lastIndex = -1; + } +} + +/// +class TreeTableEnumerator implements EnumeratorBase { + /// Initializes a new instance of the `TreeTableEnumerator` class. + /// + /// * tree - _required_ - Tree instance + TreeTableEnumerator(TreeTableBase tree) { + _tree = tree; + _cursor = null; + if (tree.count > 0 && (tree[0] is TreeTableNodeBase)) { + _next = tree[0]! as TreeTableNodeBase; + } + } + + TreeTableNodeBase? _cursor; + TreeTableNodeBase? _next; + TreeTableBase? _tree; + + /// Gets the current enumerator. + Object? get current => currentBase; + + /// Gets the current node. + TreeTableEntryBase? get currentBase { + if (_cursor is TreeTableEntryBase) { + return _cursor! as TreeTableEntryBase; + } else { + return null; + } + } + + /// Indicates whether to move to the next node. + /// + /// Returns a boolean value indicating whether to move to the next node. + @override + bool moveNext() { + if (_next == null) { + return false; + } + + _cursor = _next; + + TreeTableBranchBase? _parent = _cursor!.parent; + + if (_parent == null) { + _next = null; + return true; + } else { + if (referenceEquals(_cursor, _parent.left)) { + _next = _parent.right; + } else { + TreeTableBranchBase? parentParent = _parent.parent; + if (parentParent == null) { + _next = null; + return true; + } else { + while (referenceEquals(parentParent!.right, _parent)) { + _parent = parentParent; + parentParent = parentParent.parent; + if (parentParent == null) { + _next = null; + return true; + } + } + + _next = parentParent.right; + } + } + + while (!_next!.isEntry()) { + final TreeTableBranchBase next = _next! as TreeTableBranchBase; + _next = next.left; + } + } + + return _cursor != null; + } + + /// Resets the enumerator. + @override + void reset() { + _cursor = null; + if (_tree != null && + _tree!.count > 0 && + _tree?[0] != null && + (_tree![0] is TreeTableNodeBase)) { + _next = _tree![0]! as TreeTableNodeBase; + } else { + _next = null; + } + } +} + +/// An object that holds an [TreeTableEntryBase]. +class TreeTableEntryBaseSource { + /// Gets a reference to the [TreeTableEntryBase]. + TreeTableEntryBase? get entry => _entry; + TreeTableEntryBase? _entry; + + /// Sets a reference to the [TreeTableEntryBase]. + set entry(TreeTableEntryBase? value) { + if (value == _entry) { + return; + } + + _entry = value; + } +} + +/// A collection of `TreeTableEntryBaseSource` objects +/// that are internally using a `ITreeTable`. +class TreeTableEntrySourceCollection extends ListBase { + /// Initializes a new instance of the `TreeTableEntrySourceCollection` class. + TreeTableEntrySourceCollection() { + inner = TreeTable(false); + } + + /// + late TreeTableBase inner; + + /// Gets the number of objects in this collection. + @override + int get count => inner.count; + + /// Gets a value indicating whether the [BeginInit] was called or not. + bool get isInitializing => inner.isInitializing; + + /// Gets a value indicating whether the nodes can be added or removed. + @override + bool get isFixedSize => false; + + /// Gets a value indicating whether tree is Read-only or not. + @override + bool get isReadOnly => false; + + /// Gets a value indicating whether the tree is Synchronized or not. + @override + bool get isSynchronized => false; + + /// Appends an object. + /// + /// * value - _required_ - The value of the object to append. + /// + /// Returns an instance for the tree with newly added entry. + int addBase(TreeTableEntryBaseSource value) { + final TreeTableEntry entry = TreeTableEntry()..value = value; + value.entry = entry; + return inner.add(entry); + } + + /// Optimizes insertion of many elements when tree is initialized for the + /// first time. + void beginInit() { + inner.beginInit(); + } + + /// Indicates whether object belongs to this collection. + /// + /// * value - _required_ - The value of the object. + /// + /// Returns `True` if object belongs to the collection. `false` otherwise. + bool containsBase(TreeTableEntryBaseSource? value) { + if (value == null || value.entry == null) { + return false; + } + + return inner.contains(value.entry!); + } + + /// Copies the contents of the collection to an array. + /// + /// * array - _required_ - Destination array. + /// * index - _required_ - Starting index of the destination array. + void copyToBase(List? array, int index) { + final int count = inner.count; + for (int n = 0; n < count; n++) { + final Object _n = [n]; + if (_n is TreeTableEntryBaseSource && array != null) { + array[index + n] = _n; + } + } + } + + /// Ends optimization of insertion of elements when tree is initialized for + /// the first time. + void endInit() { + inner.endInit(); + } + + /// Inserts an object at the specified index. + /// + /// * index - _required_ - Index value where the object is to be inserted. + /// * value - _required_ - Value of the object to insert. + void insertBase(int index, TreeTableEntryBaseSource? value) { + if (value == null) { + return; + } + + final TreeTableEntry entry = TreeTableEntry()..value = value; + value.entry = entry; + inner.insert(index, entry); + } + + /// Returns the position of a object in the collection. + /// * value - _required_ - The value of the object. + /// Returns - _required_ - the position of the object. + int indexOfBase(TreeTableEntryBaseSource? value) => + (value != null && value.entry != null) ? inner.indexOf(value.entry!) : -1; + + /// Removes a node at the specified index. + /// + /// * index - _required_ - Index value of the node to remove. + void removeAtBase(int index) { + inner.removeAt(index); + } + + /// Removes the object. + /// + /// * value - _required_ - The value of the object to remove. + void removeBase(TreeTableEntryBaseSource? value) { + if (value == null || value.entry == null) { + return; + } + + inner.remove(value.entry!); + } + + /// Adds the specified object to the collection. + /// + /// * value - _required_ - Value of the object to add. + /// + /// Returns the zero-based collection index at which the value has been added + @override + int add(Object value) { + if (value is TreeTableEntryBaseSource) { + return addBase(value); + } else { + return -1; + } + } + + /// Clears all nodes in the tree. + @override + void clear() { + inner.clear(); + } + + /// Indicate whether the specified object belongs to this collection. + /// + /// * value - _required_ - Object value to look for. + /// Returns - _required_ - true if object belongs to the collection; + /// false otherwise. + @override + bool contains(Object value) { + if (value is TreeTableEntryBaseSource) { + return containsBase(value); + } else { + return false; + } + } + + /// Copies elements to destination array. + /// + /// * array - _required_ - Destination array. + /// * index - _required_ - Starting index of the destination array. + @override + void copyTo(List array, int index) { + if (array is List) { + copyToBase(array, index); + } + } + + /// Returns a strongly typed enumerator. + @override + TreeTableEntrySourceCollectionEnumerator getEnumerator() => + TreeTableEntrySourceCollectionEnumerator(this); + + /// Inserts the object at the specified index. + /// + /// * index - _required_ - Index value of the object to insert. + /// * value - _required_ - Value of the object to insert. + @override + void insert(int index, Object? value) { + if (value is TreeTableEntryBaseSource) { + insertBase(index, value); + } + } + + /// Returns the index of the specified object. + /// + /// * value - _required_ - Value of the object. + /// + /// Returns Index value of the object. + @override + int indexOf(Object value) { + if (value is TreeTableEntryBaseSource) { + return indexOfBase(value); + } else { + return -1; + } + } + + /// Removes the specified object. + /// + /// * value - _required_ - Value of the object to remove. + @override + void remove(Object value) { + if (value is TreeTableEntryBaseSource) { + removeBase(value); + } + } + + /// Sets an `TreeTableEntryBaseSource` at a specific position. + /// + /// * index - _required_ - Index value + /// + /// Returns the entry value for the specified position. + @override + void operator []=(int index, Object value) { + final TreeTableEntry entry = TreeTableEntry()..value = value; + if (value is TreeTableEntryBaseSource) { + value.entry = entry; + } + inner[index] = entry; + } + + /// Gets an `TreeTableEntryBaseSource` at a specific position. + /// + /// * index - _required_ - Index value + /// + /// Returns the entry value for the specified position. + @override + TreeTableEntryBaseSource? operator [](num index) { + final Object? entry = inner[index.toInt()]; + if (entry is TreeTableEntryBase) { + return entry.value! as TreeTableEntryBaseSource; + } else { + return null; + } + } +} + +/// A strongly typed enumerator for the `TreeTableEntrySourceCollection`. +class TreeTableEntrySourceCollectionEnumerator implements EnumeratorBase { + /// Initializes a new instance of the + /// `TreeTableEntrySourceCollectionEnumerator` class. + /// + /// * collection - _required_ - Collection value + TreeTableEntrySourceCollectionEnumerator( + TreeTableEntrySourceCollection collection) { + inner = TreeTableEnumerator(collection.inner); + } + + /// + TreeTableEnumerator? inner; + + /// Gets the current enumerator. + Object? get current => currentBase; + + /// Gets the current `TreeTableEntryBaseSource` object. + TreeTableEntryBaseSource? get currentBase { + final Object? currentBaseValue = inner?.currentBase?.value; + if (inner != null && + currentBaseValue != null && + currentBaseValue is TreeTableEntryBaseSource) { + return currentBaseValue; + } else { + return null; + } + } + + /// Indicates whether to move to the next object in the collection. + /// + /// Returns the boolean value indicates whether to move to the next object + /// in the collection. + @override + bool moveNext() => inner?.moveNext() ?? false; + + /// Resets the enumerator. + @override + void reset() { + inner?.reset(); + } +} + +/// Interface definition for a node that has counters and summaries. +mixin TreeTableCounterNodeBase on TreeTableSummaryNodeBase { + /// Gets the cumulative position of this node. + /// + /// Returns Returns the cumulative position of this node. + TreeTableCounterBase? get getCounterPosition; + + /// The total of this node's counter and child nodes. + /// + /// Returns the total of this node's counter and child nodes (cached). + TreeTableCounterBase? getCounterTotal(); + + /// Marks all counters dirty in this node and child nodes. + /// + /// * notifyCounterSource - _required_ - If set to `true` notify + /// counter source. + void invalidateCounterTopDown(bool notifyCounterSource); +} + +/// Interface definition for an object that has counters. +abstract class TreeTableCounterSourceBase { + /// Gets the counter object with counters. + /// + /// Returns the counter object with counters. + TreeTableCounterBase getCounter(); + + /// Marks all counters dirty in this object and child nodes. + /// + /// * notifyCounterSource - _required_ - If set to true notify counter source. + void invalidateCounterTopDown(bool notifyCounterSource); + + /// Marks all counters dirty in this object and parent nodes. + void invalidateCounterBottomUp(); +} + +/// Interface definition for a counter object. +abstract class TreeTableCounterBase { + /// + TreeTableCounterBase() { + _kind = -1; + } + + /// Gets the Counter Kind. + /// + /// Returns the kind. + int get kind => _kind; + late int _kind; + + /// Combines this counter object with another counter and returns a + /// new object. A cookie can specify a specific counter type. + /// + /// * other - _required_ - Counter total + /// * cookie - _required_ - Cookie value. + /// + /// Returns the new object + TreeTableCounterBase combine(TreeTableCounterBase? other, int cookie); + + /// Compares this counter with another counter. A cookie can specify + /// a specific counter type. + /// + /// * other - _required_ - The other. + /// * cookie - _required_ - The cookie. + /// + /// Returns the compared value. + double compare(TreeTableCounterBase? other, int cookie); + + /// Indicates whether the counter object is empty. A cookie can specify + /// a specific counter type. + /// + /// * cookie - _required_ - The cookie. + /// Returns `true` if the specified cookie is empty; otherwise,`false`. + bool isEmpty(int cookie); + + /// Gets the integer value of the counter. A cookie specifies + /// a specific counter type. + /// + /// * cookie - _required_ - The cookie. + /// + /// Returns the integer value of the counter. + double getValue(int cookie); +} + +/// Default counter cookies for identifying counter types. +class TreeTableCounterCookies { + /// All counters. + static const int countAll = 0xffff; + + /// Visible Counter. + static const int countVisible = 0x8000; +} + +/// A tree table branch with a counter. +class TreeTableWithCounterBranch extends TreeTableWithSummaryBranch + with TreeTableCounterNodeBase { + ///Initializes a new instance of the `TreeTableWithCounterBranch` class. + /// + /// * tree - _required_ - Tree instance + TreeTableWithCounterBranch(TreeTable tree) : super(tree); + + /// + TreeTableCounterBase? counter; + + /// Gets the parent branch. + @override + TreeTableWithCounterBranch? get parent { + if (super.parent is TreeTableWithCounterBranch) { + return super.parent! as TreeTableWithCounterBranch; + } else { + return null; + } + } + + /// Gets the tree this branch belongs to. + TreeTableWithCounter? get treeTableWithCounter { + if (tree is TreeTableWithCounter) { + return tree! as TreeTableWithCounter; + } else { + return null; + } + } + + /// Gets the cumulative counter position object of a child node with all + /// counter values. + /// + /// * node - _required_ - The node. + /// + /// Returns the cumulative counter position object of a child node with all + /// counter values. + TreeTableCounterBase getCounterPositionOfChild(TreeTableNodeBase node) { + final TreeTableCounterBase pos = getCounterPosition; + + if (referenceEquals(node, right)) { + return pos.combine( + getLeftNode()?.getCounterTotal(), TreeTableCounterCookies.countAll); + } else if (referenceEquals(node, left)) { + return pos; + } + + throw ArgumentError('must be a child node'); + } + + /// Gets the cumulative position of this node. + /// + /// Returns the cumulative position of this node. + @override + TreeTableCounterBase get getCounterPosition { + if (parent == null) { + return treeTableWithCounter!.getStartCounterPosition(); + } + + return parent!.getCounterPositionOfChild(this); + } + + /// Gets the total of this node's counter and child nodes (cached). + /// + /// Returns Returns the total of this node's counter and child + /// nodes (cached). + @override + TreeTableCounterBase? getCounterTotal() { + if (tree!.isInitializing) { + return null; + } else if (counter == null) { + final TreeTableCounterBase? _left = getLeftNode()?.getCounterTotal(); + final TreeTableCounterBase? _right = getRightNode()?.getCounterTotal(); + if (_left != null && _right != null) { + counter = _left.combine(_right, TreeTableCounterCookies.countAll); + } + } + + return counter; + } + + /// The left branch node cast to ITreeTableCounterNode. + /// + /// Returns the left branch node cast to ITreeTableCounterNode. + @override + TreeTableCounterNodeBase? getLeftNode() { + if (left is TreeTableCounterNodeBase) { + return left! as TreeTableCounterNodeBase; + } else { + return null; + } + } + + /// The left branch node cast to ITreeTableCounterNode. + /// + /// Returns the left branch node cast to ITreeTableCounterNode. + @override + TreeTableCounterNodeBase? getLeftC() => getLeftNode(); + + /// The right branch node cast to ITreeTableCounterNode. + /// + /// Returns the right branch node cast to ITreeTableCounterNode. + @override + TreeTableCounterNodeBase? getRightNode() { + if (right is TreeTableCounterNodeBase) { + return right! as TreeTableCounterNodeBase; + } else { + return null; + } + } + + /// The right branch node cast to ITreeTableCounterNode. + /// + /// Returns the right branch node cast to ITreeTableCounterNode. + @override + TreeTableCounterNodeBase? getRightC() => getRightNode(); + + /// Invalidates the counter bottom up. + /// + /// * notifyCounterSource - _required_ - If set to true notify counter source. + @override + void invalidateCounterBottomUp(bool notifyCounterSource) { + if (tree!.isInitializing) { + return; + } + + counter = null; + if (parent != null) { + parent!.invalidateCounterBottomUp(notifyCounterSource); + } else if (notifyCounterSource) { + final Object _tree = tree!; + if (_tree is TreeTableWithCounter) { + TreeTableCounterSourceBase? tcs; + if (_tree.tag is TreeTableCounterSourceBase) { + tcs = _tree.tag! as TreeTableCounterSourceBase; + } + + if (tcs != null) { + tcs.invalidateCounterBottomUp(); + } + + tcs = _tree.parentCounterSource; + if (tcs != null) { + tcs.invalidateCounterBottomUp(); + } + } + } + } + + /// Marks all counters dirty in this node and child nodes. + /// + /// * notifyCounterSource - _required_ - If set to true notify counter source. + @override + void invalidateCounterTopDown(bool notifyCounterSource) { + if (tree!.isInitializing) { + return; + } + + counter = null; + getLeftNode()?.invalidateCounterTopDown(notifyCounterSource); + getRightNode()?.invalidateCounterTopDown(notifyCounterSource); + } +} + +/// A tree leaf with value, sort key and counter information. +class TreeTableWithCounterEntry extends TreeTableWithSummaryEntryBase + with TreeTableCounterNodeBase { + TreeTableCounterBase? _counter; + + /// Gets the tree this leaf belongs to. + TreeTableWithCounter? get treeTableWithCounter { + if (super.tree is TreeTableWithCounter) { + return super.tree! as TreeTableWithCounter; + } else { + return null; + } + } + + /// Gets the parent branch. + @override + TreeTableWithCounterBranch? get parent { + if (super.parent is TreeTableWithCounterBranch) { + return super.parent! as TreeTableWithCounterBranch; + } else { + return null; + } + } + + /// Sets the parent branch. + @override + set parent(Object? value) { + super.parent = value; + } + + /// Gets the cumulative position of this node. + /// + /// Returns Returns the cumulative position of this node. + @override + TreeTableCounterBase? get getCounterPosition { + if (parent == null) { + if (treeTableWithCounter == null) { + return null; + } + + return treeTableWithCounter?.getStartCounterPosition(); + } + + return parent?.getCounterPositionOfChild(this); + } + + /// Indicates whether the counter was set dirty. + /// + /// Returns `True` if dirty; `False` otherwise. + bool isCounterDirty() => _counter == null; + + /// Creates a branch that can hold this entry when new leaves are inserted + /// into the tree. + /// + /// * tree - _required_ - Tree instance + /// + /// Returns the instance of newly created branch + @override + TreeTableBranchBase createBranch(TreeTable tree) => + TreeTableWithCounterBranch(tree); + + /// Gets the value as `TreeTableCounterSourceBase`. + /// + /// Returns the value as `TreeTableCounterSourceBase`. + TreeTableCounterSourceBase? getCounterSource() { + if (value is TreeTableCounterSourceBase) { + return value! as TreeTableCounterSourceBase; + } else { + return null; + } + } + + /// Gets the total of this node's counter and child nodes. + /// + /// Returns the total of this node's counter and child nodes. + @override + TreeTableCounterBase getCounterTotal() { + if (_counter == null) { + final TreeTableCounterSourceBase? source = getCounterSource(); + if (source != null) { + _counter = source.getCounter(); + } + } + + return _counter!; + } + + /// Reset cached counter. + void invalidateCounter() { + _counter = null; + } + + /// Invalidates the counter bottom up. + /// + /// * notifyCounterSource - _required_ - If set to true notify counter source. + @override + void invalidateCounterBottomUp(bool notifyCounterSource) { + _counter = null; + if (parent != null) { + parent!.invalidateCounterBottomUp(notifyCounterSource); + } else if (notifyCounterSource) { + final Object _tree = tree!; + if (_tree is TreeTableWithCounter) { + TreeTableCounterSourceBase? tcs; + + if (_tree.tag is TreeTableCounterSourceBase) { + tcs = _tree.tag! as TreeTableCounterSourceBase; + } + + if (tcs != null) { + tcs.invalidateCounterBottomUp(); + } + + tcs = _tree.parentCounterSource; + if (tcs != null) { + tcs.invalidateCounterBottomUp(); + } + } + } + } + + /// Marks all summaries dirty in this node and child nodes. + /// + /// * notifyCounterSource - _required_ - If set to true notify counter source. + @override + void invalidateCounterTopDown(bool notifyCounterSource) { + _counter = null; + if (notifyCounterSource) { + final TreeTableCounterSourceBase? source = getCounterSource(); + if (notifyCounterSource && source != null) { + source.invalidateCounterTopDown(notifyCounterSource); + } + } + } +} + +/// A balanced tree with [TreeTableWithCounterEntry] entries. +class TreeTableWithCounter extends TreeTableWithSummary { + /// Initializes a new instance of the [TreeTableWithCounter] class. + /// + /// * startPosition - _required_ - Sorting position + /// * sorted - _required_ - Boolean value + TreeTableWithCounter(TreeTableCounterBase startPosition, bool sorted) + : super(sorted) { + _startPos = startPosition; + } + + late TreeTableCounterBase _startPos; + + /// Gets an object that implements the + /// [Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances] property. + TreeTableCounterSourceBase? get parentCounterSource => _parentCounterSource; + TreeTableCounterSourceBase? _parentCounterSource; + + /// Sets an object that implements the + /// [Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances] property. + set parentCounterSource(TreeTableCounterSourceBase? value) { + if (value == _parentCounterSource) { + return; + } + + _parentCounterSource = value; + } + + /// Gets the total of all counters in this tree. + /// + /// Returns the total of all counters in this tree. + TreeTableCounterBase? getCounterTotal() { + if (root == null) { + return _startPos; + } + + final Object? _root = root; + if (_root != null && _root is TreeTableCounterNodeBase) { + return _root.getCounterTotal(); + } else { + return null; + } + } + + /// Overloaded. Gets an entry at the specified counter position. + /// A cookie defines the type of counter. + /// + /// * searchPosition - _required_ - The search position. + /// * cookie - _required_ - The cookie. + /// + /// Returns an entry at the specified counter position. + TreeTableWithCounterEntry? getEntryAtCounterPosition( + TreeTableCounterBase searchPosition, int cookie) => + getEntryAtCounterPositionWithForParameter( + getStartCounterPosition(), searchPosition, cookie, false); + + /// Gets an entry at the specified counter position. A cookie defines the + /// type of counter. + /// + /// * searchPosition - _required_ - The search position. + /// * cookie - _required_ - The cookie. + /// * preferLeftMost - _required_ - Indicates if the leftmost entry should + /// be returned if multiple tree elements have the same SearchPosition. + /// + /// Returns an entry at the specified counter position. + TreeTableWithCounterEntry? getEntryAtCounterPositionwithThreeParameter( + TreeTableCounterBase searchPosition, + int cookie, + bool preferLeftMost) => + getEntryAtCounterPositionWithForParameter( + getStartCounterPosition(), searchPosition, cookie, preferLeftMost); + + /// Gets the entry at counter position. + /// + /// * start - _required_ - The start. + /// * searchPosition - _required_ - The search position. + /// * cookie - _required_ - The cookie. + /// * preferLeftMost - _required_ - If set to true prefer left most. + /// + /// Returns an entry at the specified counter position. + TreeTableWithCounterEntry? getEntryAtCounterPositionWithForParameter( + TreeTableCounterBase start, + TreeTableCounterBase searchPosition, + int cookie, + bool preferLeftMost) { + if (searchPosition.compare(getStartCounterPosition(), cookie) < 0) { + throw Exception('SearchPosition'); + } + + if (searchPosition.compare(getCounterTotal(), cookie) > 0) { + throw Exception('$searchPosition out of range $this.getCounterTotal()'); + } + + if (root == null) { + return null; + } else { + // find node + final TreeTableNodeBase? currentNode = root; + final TreeTableCounterBase currentNodePosition = start; + return getEntryAtCounterPositionWithSixParameter(currentNode!, start, + searchPosition, cookie, preferLeftMost, currentNodePosition); + } + } + + /// An object that implements the + /// `Syncfusion.GridCommon.ScrollAxis.PixelScrollAxis.Distances` property. + /// + /// * currentNode - _required_ - Current node value + /// * start - _required_ - Start counter + /// * searchPosition - _required_ - Position needs to be search + /// * cookie - _required_ - Cookie value + /// * preferLeftMost - _required_ - Indicates if the leftmost entry + /// should be returned if multiple tree elements have the same SearchPosition + /// * currentNodePosition - _required_ - Position of the current node + /// + /// Returns the current node. + TreeTableWithCounterEntry getEntryAtCounterPositionWithSixParameter( + TreeTableNodeBase currentNode, + TreeTableCounterBase start, + TreeTableCounterBase searchPosition, + int cookie, + bool preferLeftMost, + TreeTableCounterBase? currentNodePosition) { + TreeTableWithCounterBranch? savedBranch; + currentNodePosition = start; + while (!currentNode.isEntry()) { + final TreeTableWithCounterBranch branch = + currentNode as TreeTableWithCounterBranch; + final TreeTableCounterNodeBase leftB = + branch.left! as TreeTableCounterNodeBase; + final TreeTableCounterBase rightNodePosition = + currentNodePosition!.combine(leftB.getCounterTotal(), cookie); + + if (searchPosition.compare(rightNodePosition, cookie) < 0) { + currentNode = branch.left!; + } else if (preferLeftMost && + searchPosition.compare(currentNodePosition, cookie) == 0) { + while (!currentNode.isEntry()) { + final TreeTableWithCounterBranch branch = + currentNode as TreeTableWithCounterBranch; + currentNode = branch.left!; + } + } else { + if (preferLeftMost && + searchPosition.compare(rightNodePosition, cookie) == 0) { + TreeTableCounterBase? currentNode2Position; + final TreeTableNodeBase currentNode2 = + getEntryAtCounterPositionWithSixParameter( + branch.left!, + currentNodePosition, + searchPosition, + cookie, + preferLeftMost, + currentNode2Position); + if (rightNodePosition.compare(currentNode2Position, cookie) == 0) { + currentNode = currentNode2; + currentNodePosition = currentNode2Position; + } else { + currentNodePosition = rightNodePosition; + currentNode = branch.right!; + } + } else { + savedBranch ??= branch; + currentNodePosition = rightNodePosition; + currentNode = branch.right!; + } + } + } + + return currentNode as TreeTableWithCounterEntry; + } + + /// Gets the subsequent entry in the collection for which the specific + /// counter is not empty. A cookie defines the type of counter. + /// + /// * current - _required_ - The current. + /// * cookie - _required_ - The cookie. + /// + /// Returns the subsequent entry in the collection for which the + /// specific counter is not empty. + TreeTableEntryBase? getNextNotEmptyCounterEntry( + TreeTableEntryBase current, int cookie) { + TreeTableBranchBase? parent = current.parent; + TreeTableNodeBase? next; + + if (parent == null) { + next = null; + return null; + } else { + next = current; + // walk up until we find a branch that has visible entries + do { + if (referenceEquals(next, parent!.left)) { + next = parent.right; + } else { + TreeTableBranchBase? parentParent = parent.parent; + if (parentParent == null) { + return null; + } else { + while (referenceEquals(parentParent!.right, parent) + // for something that most likely went wrong when + // adding the node or when doing a rotation ... + || + referenceEquals(parentParent.right, next)) { + parent = parentParent; + parentParent = parentParent.parent; + if (parentParent == null) { + return null; + } + } + + if (next == parentParent.right) { + throw Exception(); + } else { + next = parentParent.right; + } + } + } + } while (next != null && + (next is TreeTableCounterNodeBase && + next.getCounterTotal()!.isEmpty(cookie))); + + // walk down to most left leaf that has visible entries + while (!next!.isEntry()) { + final TreeTableBranchBase branch = next as TreeTableBranchBase; + final TreeTableCounterNodeBase _left = + branch.left! as TreeTableCounterNodeBase; + next = !_left.getCounterTotal()!.isEmpty(cookie) + ? branch.left + : branch.right; + } + } + + return next as TreeTableEntryBase; + } + + /// Returns the previous entry in the collection for which the specific + /// counter is not empty. + /// + /// * current - _required_ - The current. + /// * cookie - _required_ - The cookie. + /// + /// Returns the previous entry in the collection for which the specific + /// counter is not empty. + TreeTableEntryBase? getPreviousNotEmptyCounterEntry( + TreeTableEntryBase current, int cookie) { + TreeTableBranchBase? parent = current.parent; + TreeTableNodeBase? next; + + if (parent == null) { + next = null; + return null; + } else { + next = current; + // walk up until we find a branch that has visible entries + do { + if (referenceEquals(next, parent!.right)) { + next = parent.left; + } else { + TreeTableBranchBase? parentParent = parent.parent; + if (parentParent == null) { + return null; + } else { + while (referenceEquals(parentParent!.left, parent) + // for something that most likely went wrong when + // adding the node or when doing a rotation ... + || + referenceEquals(parentParent.left, next)) { + parent = parentParent; + parentParent = parentParent.parent; + if (parentParent == null) { + return null; + } + } + if (next == parentParent.left) { + throw Exception(); + } else { + next = parentParent.left; + } + } + } + } while (next != null && + (next is TreeTableCounterNodeBase && + next.getCounterTotal()!.isEmpty(cookie))); + + // walk down to most left leaf that has visible entries + while (!next!.isEntry()) { + final TreeTableBranchBase branch = next as TreeTableBranchBase; + final TreeTableCounterNodeBase _right = + branch.right! as TreeTableCounterNodeBase; + next = !_right.getCounterTotal()!.isEmpty(cookie) + ? branch.right + : branch.left; + } + } + + return next as TreeTableEntryBase; + } + + /// Gets the next entry in the collection for which CountVisible counter + /// is not empty. + /// + /// * current - _required_ - The current. + /// + /// Returns the next entry in the collection for which CountVisible counter + /// is not empty. + TreeTableWithCounterEntry? getNextVisibleEntry( + TreeTableWithCounterEntry current) { + final TreeTableEntryBase? nextCounterEntry = getNextNotEmptyCounterEntry( + current, TreeTableCounterCookies.countVisible); + if (nextCounterEntry != null) { + return nextCounterEntry as TreeTableWithCounterEntry; + } + + return null; + } + + /// Gets the previous entry in the collection for which CountVisible counter + /// is not empty. + /// + /// * current - _required_ - The current. + /// + /// Returns the previous entry in the collection for which CountVisible + /// counter is not empty. + TreeTableWithCounterEntry? getPreviousVisibleEntry( + TreeTableWithCounterEntry current) { + final TreeTableEntryBase? previousCounterEntry = + getPreviousNotEmptyCounterEntry( + current, TreeTableCounterCookies.countVisible); + if (previousCounterEntry != null) { + return previousCounterEntry as TreeTableWithCounterEntry; + } + + return null; + } + + /// Gets the starting counter for this tree. + /// + /// Returns Returns the starting counter for this tree. + TreeTableCounterBase getStartCounterPosition() => _startPos; + + /// Marks all counters dirty. + /// + /// * notifyCounterSource - _required_ - Boolean value + void invalidateCounterTopDown(bool notifyCounterSource) { + if (root != null) { + final Object _root = root!; + if (_root is TreeTableCounterNodeBase) { + _root.invalidateCounterTopDown(notifyCounterSource); + } + } + } + + /// Appends an object. + /// + /// * value - _required_ - The value. + /// + /// Returns the zero-based collection index at which the value has been added. + @override + int add(Object value) => super.add(value); + + /// Indicates whether an entry belongs to the tree. + /// + /// * value - _required_ - The entry. + /// + /// Returns `true` if tree contains the specified entry; otherwise, `false`. + @override + bool contains(Object? value) { + if (value == null) { + return false; + } + + return super.contains(value); + } + + /// Copies the elements of this tree to an array. + /// + /// * array - _required_ - The array. + /// * index - _required_ - The index. + @override + void copyTo(List array, int index) { + super.copyTo(array, index); + } + + /// Ends optimization of insertion of elements when tree is initialized + /// for the first time. + @override + void endInit() { + super.endInit(); + } + + /// Gets the position of an object in the tree. + /// + /// * value - _required_ - The value. + /// + /// Returns the position of an object in the tree. + @override + int indexOf(Object value) => super.indexOf(value); + + /// Inserts a `TreeTableWithCounterEntry` object at the specified index. + /// + /// * index - _required_ - The index. + /// * value - _required_ - The value. + @override + void insert(int index, Object value) { + super.insert(index, value); + } + + /// Removes an object from the tree. + /// + /// * value - _required_ - The value. + /// + /// Returns the collection after removing the specified item from the + /// tree collection + @override + bool remove(Object? value) => super.remove(value); + + /// Gets a TreeTableWithCounterEntry. + /// + /// * index - _required_ - Index value + /// + /// Returns a new instance for TreeTableWithCounterEntry + @override + TreeTableWithCounterEntry? operator [](int index) { + if (super[index] is TreeTableWithCounterEntry) { + return super[index]! as TreeTableWithCounterEntry; + } else { + return null; + } + } + + /// Sets a TreeTableWithCounterEntry. + /// + /// * index - _required_ - Index value + /// + /// Returns a new instance for TreeTableWithCounterEntry + @override + void operator []=(int index, Object value) { + super[index] = value; + } +} + +/// Interface definition for a summary object. +abstract class TreeTableSummaryBase { + /// Combines this summary information with another object's summary + /// and returns a new object. + /// + /// * other - _required_ - The other. + /// + /// Returns a combined object. + TreeTableSummaryBase combine(TreeTableSummaryBase other); +} + +/// Interface definition for a node that has one or more summaries. +mixin TreeTableSummaryNodeBase on TreeTableNodeBase { + /// Gets a value indicating whether node has summaries or not. + bool get hasSummaries => false; + + /// Gets an array of summary objects. + /// + /// * emptySummaries - _required_ - The empty summaries. + /// + /// Returns an array of summary objects. + List? getSummaries( + TreeTableEmptySummaryArraySourceBase emptySummaries); + + /// Marks all summaries dirty in this node and child nodes. + /// + /// * notifyEntrySummary - _required_ - if set to true notify entry summary. + void invalidateSummariesTopDown(bool notifyEntrySummary); +} + +/// Provides a `GetEmptySummaries` method. +abstract class TreeTableEmptySummaryArraySourceBase { + /// Gets an array of summary objects. + /// + /// Returns an array of summary objects. + List getEmptySummaries(); +} + +/// Interface definition for an object that has summaries. +abstract class TreeTableSummaryArraySourceBase { + /// Returns an array of summary objects. + /// + /// * emptySummaries - _required_ - An array of empty summary objects. + /// * changed - _required_ - Returns true if summaries were recalculated; + /// False if already cached. + /// + /// Returns An array of summary objects. + List getSummaries( + TreeTableEmptySummaryArraySourceBase emptySummaries, bool changed); + + /// Marks all summaries dirty in this object and parent nodes. + void invalidateSummariesBottomUp(); + + /// Marks all summaries dirty in this object only. + void invalidateSummary(); + + /// Marks all summaries dirty in this object and child nodes. + void invalidateSummariesTopDown(); +} + +/// A tree table branch with a counter. +class TreeTableWithSummaryBranch extends TreeTableBranch + with TreeTableSummaryNodeBase { + /// + TreeTableWithSummaryBranch(TreeTable tree) : super(tree); + + List? _summaries; + + ///Initializes a new instance of the `TreeTableWithSummaryBranch` class. + /// + /// * tree - _required_ - Tree instance + /// + /// Gets the tree this branch belongs to. + TreeTableWithSummary get treeTableWithSummary => + super.tree! as TreeTableWithSummary; + + /// Gets a value indicating whether this node has summaries or not. + @override + bool get hasSummaries => _summaries != null; + + /// Gets the parent branch. + @override + TreeTableWithSummaryBranch? get parent => + super.parent != null ? super.parent! as TreeTableWithSummaryBranch : null; + + /// Sets the parent branch. + @override + set parent(Object? value) { + if (value != null) { + super.parent = value as TreeTableBranchBase; + } + } + + /// The left branch node cast to TreeTableSummaryNodeBase. + /// + /// Returns the left branch node cast to TreeTableSummaryNodeBase. + TreeTableSummaryNodeBase? getLeftC() => getLeftNode(); + + /// The left branch node cast to TreeTableSummaryNodeBase. + /// + /// Returns the left branch node cast to TreeTableSummaryNodeBase. + TreeTableSummaryNodeBase? getLeftNode() => left! as TreeTableSummaryNodeBase; + + /// Gets the right branch node cast to TreeTableSummaryNodeBase. + /// + /// Returns the left branch node cast to TreeTableSummaryNodeBase. + TreeTableSummaryNodeBase? getRightC() => getRightNode(); + + /// Returns the left branch node cast to TreeTableSummaryNodeBase. + TreeTableSummaryNodeBase? getRightNode() => + right! as TreeTableSummaryNodeBase; + + /// Gets an array of summary objects. + /// + /// * emptySummaries - _required_ - The empty summaries. + /// + /// Returns an array of summary objects. + @override + List? getSummaries( + TreeTableEmptySummaryArraySourceBase emptySummaries) { + if (tree!.isInitializing) { + return null; + } else if (_summaries == null) { + final List? left = + getLeftNode()?.getSummaries(emptySummaries); + final List? right = + getRightNode()?.getSummaries(emptySummaries); + if (left != null && right != null) { + int reuseLeft = 0; + int reuseRight = 0; + _summaries = []; + for (int i = 0; i < _summaries!.length; i++) { + _summaries![i] = left[i].combine(right[i]); + // preserve memory optimization + if (reuseLeft == i || reuseRight == i) { + if (referenceEquals(_summaries![i], left[i])) { + reuseLeft++; + } else if (referenceEquals(_summaries![i], right[i])) { + reuseRight++; + } + } + } + + // preserve memory optimization + if (reuseLeft == _summaries!.length) { + _summaries = left; + } else if (reuseRight == _summaries!.length) { + _summaries = right; + } + } + } + + return _summaries; + } + + /// Walks up parent branches and reset summaries. + /// + /// * notifyParentRecordSource - _required_ - Boolean value + @override + void invalidateSummariesBottomUp(bool notifyParentRecordSource) { + if (tree!.isInitializing) { + return; + } + + _summaries = null; + if (parent != null) { + parent!.invalidateSummariesBottomUp(notifyParentRecordSource); + } else if (notifyParentRecordSource) { + if (tree != null && tree!.tag is TreeTableSummaryArraySourceBase) { + final TreeTableSummaryArraySourceBase _treeTag = + tree!.tag! as TreeTableSummaryArraySourceBase; + _treeTag.invalidateSummariesBottomUp(); + } + } + } + + /// Marks all summaries dirty in this node and child nodes. + /// + /// * notifyCounterSource - _required_ - If set to true notify counter source. + @override + void invalidateSummariesTopDown(bool notifyCounterSource) { + if (tree!.isInitializing) { + return; + } + + _summaries = null; + getLeftNode()?.invalidateSummariesTopDown(notifyCounterSource); + getRightNode()?.invalidateSummariesTopDown(notifyCounterSource); + } +} + +/// A tree leaf with value and summary information. +class TreeTableWithSummaryEntryBase extends TreeTableEntry + with TreeTableSummaryNodeBase { + /// + static List emptySummaryArray = + []; + List? _summaries; + + /// Gets the tree this leaf belongs to. + TreeTableWithSummary get treeTableWithSummary => + tree! as TreeTableWithSummary; + + /// Gets a value indicating whether the node has summaries or not. + @override + bool get hasSummaries => _summaries != null; + + /// Gets the parent branch. + @override + TreeTableWithSummaryBranch? get parent { + if (super.parent is TreeTableWithSummaryBranch) { + return super.parent! as TreeTableWithSummaryBranch; + } else { + return null; + } + } + + /// Sets the parent branch. + @override + set parent(Object? value) { + if (value != null) { + super.parent = value as TreeTableBranchBase; + } + } + + /// Gets the value as `TreeTableSummaryArraySourceBase`. + /// + /// Returns the value as `TreeTableSummaryArraySourceBase`. + TreeTableSummaryArraySourceBase? getSummaryArraySource() { + if (value is TreeTableSummaryArraySourceBase) { + return value! as TreeTableSummaryArraySourceBase; + } else { + return null; + } + } + + /// Called from `GetSummaries` when called the first time after summaries + /// were invalidated. + /// + /// * emptySummaries - _required_ - The empty summaries. + /// + /// Returns an array of summary objects. + List? onGetSummaries( + TreeTableEmptySummaryArraySourceBase emptySummaries) { + List? summaries; + final TreeTableSummaryArraySourceBase? summaryArraySource = + getSummaryArraySource(); + if (summaryArraySource != null) { + const bool summaryChanged = false; + summaries = + summaryArraySource.getSummaries(emptySummaries, summaryChanged); + } + + return summaries; + } + + /// Creates a branch that can hold this entry when new leaves are inserted + /// into the tree. + /// + /// * tree - _required_ - Tree instance + /// + /// Returns an instance for newly created TreeTable + @override + TreeTableBranchBase? createBranch(TreeTable tree) { + final Object _tree = tree; + if (_tree is TreeTableWithSummaryBranch) { + return _tree; + } else { + return null; + } + } + + /// Gets an array of summary objects. + /// + /// * emptySummaries - _required_ - The empty summaries. + /// + /// Returns an array of summary objects. + @override + List getSummaries( + TreeTableEmptySummaryArraySourceBase emptySummaries) => + _summaries ??= onGetSummaries(emptySummaries) ?? emptySummaryArray; + + /// Walks up parent branches and reset summaries. + /// + /// * notifyParentRecordSource - _required_ - Boolean value + @override + void invalidateSummariesBottomUp(bool notifyParentRecordSource) { + _summaries = null; + if (value is TreeTableSummaryArraySourceBase && tree != null) { + final TreeTableSummaryArraySourceBase _tree = + tree!.tag! as TreeTableSummaryArraySourceBase; + _tree.invalidateSummary(); + } + + if (parent != null) { + parent!.invalidateSummariesBottomUp(notifyParentRecordSource); + } else if (notifyParentRecordSource) { + if (tree != null && tree!.tag is TreeTableSummaryArraySourceBase) { + final TreeTableSummaryArraySourceBase _tree = + tree!.tag! as TreeTableSummaryArraySourceBase; + _tree.invalidateSummariesBottomUp(); + } + } + } + + /// Marks all summaries dirty in this node and child nodes. + /// + /// * notifySummaryArraySource - _required_ - if set to true notify + /// summary array source. + @override + void invalidateSummariesTopDown(bool notifySummaryArraySource) { + _summaries = null; + if (notifySummaryArraySource) { + final TreeTableSummaryArraySourceBase? summaryArraySource = + getSummaryArraySource(); + if (summaryArraySource != null) { + summaryArraySource.invalidateSummariesTopDown(); + } + + _summaries = null; + } + } +} + +/// A balanced tree with TreeTableWithSummaryEntryBase entries. +class TreeTableWithSummary extends TreeTable { + /// Initializes a new instance of the `TreeTableWithSummary` class. + /// + /// * sorted - _required_ - Boolean value + TreeTableWithSummary(bool sorted) : super(sorted); + + /// Gets a value indicating whether the tree has summaries or not. + bool get hasSummaries { + if (root == null) { + return false; + } + + final Object _root = root!; + if (_root is TreeTableSummaryNodeBase) { + return _root.hasSummaries; + } else { + return false; + } + } + + /// Gets an array of summary objects. + /// + /// * emptySummaries - _required_ - summary value + /// + /// Returns an array of summary objects. + List? getSummaries( + TreeTableEmptySummaryArraySourceBase emptySummaries) { + if (root == null) { + return emptySummaries.getEmptySummaries(); + } + + final Object _root = root!; + if (_root is TreeTableSummaryNodeBase) { + return _root.getSummaries(emptySummaries); + } else { + return null; + } + } + + /// Marks all summaries dirty. + /// + /// * notifySummariesSource - _required_ - If set to true notify + /// summaries source. + void invalidateSummariesTopDown(bool notifySummariesSource) { + if (root != null) { + final Object _root = root!; + if (_root is TreeTableSummaryNodeBase) { + _root.invalidateSummariesTopDown(notifySummariesSource); + } + } + } + + /// Appends an object. + /// + /// * value - _required_ - The item to be added to the end of the collection. + /// The value must not be a NULL reference (Nothing in Visual Basic). + /// + /// Returns the zero-based collection index at which the value has been added. + @override + int add(Object value) => super.add(value); + + /// Indicates whether an object belongs to the tree. + /// + /// * value - _required_ - Value needs to be check + /// + /// Returns a boolean value indicates whether an object belongs to the tree. + @override + bool contains(Object? value) { + if (value == null) { + return false; + } + + return super.contains(value); + } + + /// Copies the elements of this tree to an array. + /// + /// * array - _required_ - Collection of array + /// * index - _required_ - Index value + @override + void copyTo(List array, int index) { + super.copyTo(array, index); + } + + /// Gets a strongly typed enumerator. + /// + /// Returns a strongly typed enumerator. + @override + TreeTableWithSummaryEnumerator getEnumerator() => + TreeTableWithSummaryEnumerator(this); + + /// Inserts a `TreeTableWithSummaryEntryBase` object at the specified index. + /// + /// * index - _required_ - Tree index + /// * value - _required_ - Value needs to be insert + @override + void insert(int index, Object value) { + super.insert(index, value); + } + + /// Gets the index of an object in the tree. + /// + /// * value - _required_ - value needs to be find the index + /// + /// Returns Returns the index of an object in the tree. + @override + int indexOf(Object value) => super.indexOf(value); + + /// Removes an object from the tree. + /// + /// * value - _required_ - Value needs to be remove + /// + /// Returns the removed value. + @override + bool remove(Object? value) => super.remove(value); + + /// Gets a TreeTableWithSummaryEntryBase. + /// + /// * index - _required_ - Index value + /// + /// Returns the new instance for TreeTableWithSummaryEntryBase + @override + TreeTableWithSummaryEntryBase? operator [](int index) { + if (super[index] is TreeTableWithSummaryEntryBase) { + return super[index]! as TreeTableWithSummaryEntryBase; + } else { + return null; + } + } + + /// Sets a TreeTableWithSummaryEntryBase. + @override + void operator []=(int index, Object value) { + super[index] = value; + } +} + +/// A strongly typed enumerator for the `TreeTableWithSummary` collection. + +class TreeTableWithSummaryEnumerator extends TreeTableEnumerator { + /// Initializes a new instance of the `TreeTableWithSummaryEnumerator` class. + /// + /// * tree - _required_ - Tree instance + TreeTableWithSummaryEnumerator(TreeTable tree) : super(tree); + + /// Gets the current `TreeTableWithSummary` object. + @override + TreeTableWithSummaryEntryBase? get current { + if (super.current is TreeTableWithSummaryEntryBase) { + return super.current! as TreeTableWithSummaryEntryBase; + } else { + return null; + } + } +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart deleted file mode 100644 index 15ff0bbd1..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/double_span.dart +++ /dev/null @@ -1,66 +0,0 @@ -part of datagrid; - -/// Double precision's class -/// -/// Holds a start and end value with double precision. -class _DoubleSpan { - /// Initializes a new instance of the `DoubleSpan` struct. - /// - /// * start - _require_ - The start. - /// * end - _required_ - The end. - _DoubleSpan(this.start, this.end); - - /// Initializes a empty `DoubleSpan`. - _DoubleSpan.empty() { - start = 0; - end = -1; - } - - late double start; - late double end; - - /// Gets a value indicating whether this instance is empty. - /// - /// Returns `True` if this instance is empty. otherwise, `false`. - bool get isEmpty => end < start; - - /// Gets the length. - double get length => end - start; - - /// Sets the length. - set length(double value) { - end = start + value; - } - - /// Indicates whether this instance and a specified object are equal. - /// - /// * obj - _required_ - The object to compare with the current instance. - /// - /// Returns `True` if the given object and this instance are - /// the same type and represent the same value, - /// otherwise `false`. - bool equals(Object obj) { - if (super == obj) { - return true; - } else { - return false; - } - } - - /// A 32-bit signed integer that is the hash code for this instance. - /// - /// Returns the hash code for this instance. - int getHashCode() => super.hashCode; - - /// Gets a string with start and end values. - /// - /// Returns a string with start and end values. - @override - String toString() => 'DoubleSpan Start = $start, End = $end'; - - /// Gets an empty object. - Object operator [](int index) => this[index]; - - /// Sets an empty object. - void operator []=(int index, Object value) => this[index] = value; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/int_32_span.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/int_32_span.dart deleted file mode 100644 index d5b892f8d..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility/int_32_span.dart +++ /dev/null @@ -1,43 +0,0 @@ -part of datagrid; - -/// Integer precision's class -/// -/// Holds a start and end value with integer precision. -class _Int32Span { - /// Initializes a new instance of the [_Int32Span] struct. - /// - /// * start - _required_ - Hold the start. - /// * end - _required_ - Hold the end value. - _Int32Span(this.start, this.end); - - int start; - int end; - - /// Gets the count (equals end - start + 1) - int get count => end - start + 1; - - /// Sets the count - set count(int value) { - end = start + value - 1; - } - - /// Indicates whether this instance and a specified object are equal. - /// - /// * obj - _required_ - object to compare with the current instance. - /// - /// Returns `True` if the given object and this instance are the - /// same type and represent the same value, - /// otherwise `false`. - bool equals(Object obj) { - if (this == obj) { - return true; - } else { - return false; - } - } - - /// A 32-bit signed integer that is the hash code for this instance. - /// - /// Returns the hash code for this instance. - int getHashCode() => super.hashCode; -} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/sorted_range_value_list.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility_helper.dart similarity index 53% rename from packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/sorted_range_value_list.dart rename to packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility_helper.dart index d49e6d54e..554d75071 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/sorted_range_value_list.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/utility_helper.dart @@ -1,26 +1,31 @@ -part of datagrid; +import 'dart:collection'; /// Holds a range together with a value assigned to the range. /// /// * T - _required_ - The type of the parameter. -class _RangeValuePair extends Comparable { - /// Initializes a new instance of the _RangeValuePair class. +class RangeValuePair extends Comparable { + /// Initializes a new instance of the RangeValuePair class. /// /// * start - _required_ - The start of the range. - _RangeValuePair(this.start) { + RangeValuePair(this.start) { count = 1; value = 0; } - /// Initializes a new instance of the _RangeValuePair class. + /// Initializes a new instance of the RangeValuePair class. /// /// * start - _required_ - The start of the range. /// * count - _required_ - The count of the range. /// * value - _required_ - The value for the range. - _RangeValuePair.fromRangeValuePair(this.start, this.count, this.value); + RangeValuePair.fromRangeValuePair(this.start, this.count, this.value); + /// late int start; + + /// late int count; + + /// dynamic value; /// Gets the end of the range. @@ -41,8 +46,8 @@ class _RangeValuePair extends Comparable { /// * obj - _required_ - An object to compare with this instance. @override int compareTo(Object? obj) { - final _RangeValuePair x = this; - final _RangeValuePair y = obj! as _RangeValuePair; + final RangeValuePair x = this; + final RangeValuePair y = obj! as RangeValuePair; if (((x.start >= y.start) && (x.start < y.start + y.count)) || ((y.start >= x.start) && (y.start < x.start + x.count))) { @@ -56,13 +61,13 @@ class _RangeValuePair extends Comparable { /// /// Returns a `string` with state information about this `object`. @override - String toString() => '_RangeValuePair Start = $start ' + String toString() => 'RangeValuePair Start = $start ' 'Count = $count End = $end Value = $value'; } -/// Maintain a collection of _RangeValuePair +/// Maintain a collection of RangeValuePair /// -/// A sorted list with `_RangeValuePair{T}` ordered by the +/// A sorted list with `RangeValuePair{T}` ordered by the /// start index of the ranges. _SortedRangeValueList ensures that ranges /// of the elements inside the list do not overlap and it also ensures /// that there are no empty gaps meaning that the subsequent range will @@ -70,19 +75,19 @@ class _RangeValuePair extends Comparable { /// range plus one. /// /// * T - _required_ - The type of the parameter. -class _SortedRangeValueList - extends _EnumerableGenericBase<_RangeValuePair> { +class SortedRangeValueList extends EnumerableGenericBase> { /// Initializes a new instance of the _SortedRangeValueList class. - _SortedRangeValueList(); + SortedRangeValueList(); /// Initializes a new instance of the _SortedRangeValueList class. /// /// * defaultValue - _required_ - The default value used for filling gaps. - _SortedRangeValueList.from(T defaultValue) { + SortedRangeValueList.from(T defaultValue) { _defaultValue = defaultValue; } - List<_RangeValuePair> rangeValues = <_RangeValuePair>[]; + /// + List> rangeValues = >[]; T? _defaultValue; /// Gets the count which is the same as the end position of the last range. @@ -92,7 +97,7 @@ class _SortedRangeValueList if (rangeValues.isEmpty) { return 0; } - final _RangeValuePair rv = rangeValues[rangeValues.length - 1]; + final RangeValuePair rv = rangeValues[rangeValues.length - 1]; return rv.start + rv.count; } @@ -117,6 +122,7 @@ class _SortedRangeValueList _defaultValue = value; } + /// void adjustStart(num n, num delta) { int value = n.toInt(); while (n < rangeValues.length) { @@ -129,9 +135,10 @@ class _SortedRangeValueList rangeValues.clear(); } + /// void ensureCount(num index) { if (index - count > 0) { - rangeValues.add(_RangeValuePair.fromRangeValuePair( + rangeValues.add(RangeValuePair.fromRangeValuePair( count.toInt(), index.toInt() - count.toInt(), defaultValue)); } } @@ -146,18 +153,19 @@ class _SortedRangeValueList /// Returns a count indicating the delta List getRange(num index, num count) { if (index >= this.count) { - count = _MathHelper.maxvalue; + count = maxvalue; return [defaultValue, count]; } final int value = index.toInt(); - final _RangeValuePair rv = getRangeValue(value); + final RangeValuePair rv = getRangeValue(value); count = rv.end - index + 1; return [rv.value, count]; } - _RangeValuePair getRangeValue(int index) { - final int n = _MathHelper.binarySearch<_RangeValuePair>( - rangeValues, _RangeValuePair(index)); + /// + RangeValuePair getRangeValue(int index) { + final int n = + binarySearch>(rangeValues, RangeValuePair(index)); return rangeValues[n]; } @@ -184,7 +192,7 @@ class _SortedRangeValueList /// Remove call when moving ranges. /// Otherwise specify null. void insertWithFourArgs(num insertAt, num count, Object? value, - _SortedRangeValueList? moveRanges) { + SortedRangeValueList? moveRanges) { if (insertAt >= this.count) { if (value == defaultValue && (moveRanges == null || moveRanges.count == 0)) { @@ -192,22 +200,22 @@ class _SortedRangeValueList } ensureCount(insertAt); - rangeValues.add(_RangeValuePair.fromRangeValuePair( + rangeValues.add(RangeValuePair.fromRangeValuePair( insertAt.toInt(), count.toInt(), value)); if (rangeValues.length >= 2) { merge(rangeValues.length - 2); } } else { - int n = _MathHelper.binarySearch<_RangeValuePair>( - rangeValues, _RangeValuePair(insertAt.toInt())); - final _RangeValuePair rv = rangeValues[n]; + int n = binarySearch>( + rangeValues, RangeValuePair(insertAt.toInt())); + final RangeValuePair rv = rangeValues[n]; if (value == (rv.value)) { rv.count += count.toInt(); adjustStart(n + 1, count); } else { n = splitWithTwoArgs(insertAt, n); split(insertAt + 1); - final _RangeValuePair rv2 = _RangeValuePair.fromRangeValuePair( + final RangeValuePair rv2 = RangeValuePair.fromRangeValuePair( insertAt.toInt(), count.toInt(), value); rangeValues.insert(n, rv2); adjustStart(n + 1, count); @@ -219,7 +227,7 @@ class _SortedRangeValueList } if (moveRanges != null) { - for (final _RangeValuePair rv in moveRanges) { + for (final RangeValuePair rv in moveRanges) { setRange( rv.start.toInt() + insertAt.toInt(), rv.count.toInt(), rv.value); } @@ -238,7 +246,7 @@ class _SortedRangeValueList /// remove call when moving ranges. /// Otherwise specify null. void insertWithThreeArgs( - num insertAt, num count, _SortedRangeValueList? moveRanges) { + num insertAt, num count, SortedRangeValueList? moveRanges) { insertWithFourArgs(insertAt, count, defaultValue, moveRanges); } @@ -279,7 +287,7 @@ class _SortedRangeValueList /// when moving ranges /// and pass it to a subsequent insert call. Otherwise specify null. void removeWithThreeArgs( - num removeAt, num count, _SortedRangeValueList? moveRanges) { + num removeAt, num count, SortedRangeValueList? moveRanges) { if (removeAt >= this.count) { return; } @@ -291,8 +299,9 @@ class _SortedRangeValueList } } + /// num removeHelper( - int removeAt, int count, _SortedRangeValueList? moveRanges) { + int removeAt, int count, SortedRangeValueList? moveRanges) { if (removeAt >= this.count) { return rangeValues.length; } @@ -302,11 +311,11 @@ class _SortedRangeValueList int total = 0; int deleteCount = 0; while (total < count && n + deleteCount < rangeValues.length) { - final _RangeValuePair rv = rangeValues[n.toInt() + deleteCount]; + final RangeValuePair rv = rangeValues[n.toInt() + deleteCount]; total += rv.count.toInt(); deleteCount++; if (moveRanges != null && !(rv.value == defaultValue)) { - moveRanges.rangeValues.add(_RangeValuePair.fromRangeValuePair( + moveRanges.rangeValues.add(RangeValuePair.fromRangeValuePair( rv.start - removeAt, rv.count, rv.value)); } } @@ -335,8 +344,8 @@ class _SortedRangeValueList ensureCount(index); final num n = removeHelper(index, count, null); - final _RangeValuePair rv = - _RangeValuePair.fromRangeValuePair(index, count, value); + final RangeValuePair rv = + RangeValuePair.fromRangeValuePair(index, count, value); rangeValues.insert(n.toInt(), rv); merge(n.toInt()); if (n > 0) { @@ -344,17 +353,19 @@ class _SortedRangeValueList } } + /// num split(num index) { if (index >= count) { return rangeValues.length; } - final int n = _MathHelper.binarySearch<_RangeValuePair>( - rangeValues, _RangeValuePair(index.toInt())); + final int n = binarySearch>( + rangeValues, RangeValuePair(index.toInt())); return splitWithTwoArgs(index, n); } + /// int splitWithTwoArgs(num index, int n) { - final _RangeValuePair rv = rangeValues[n]; + final RangeValuePair rv = rangeValues[n]; if (rangeValues[n].start == index) { return n; } @@ -363,18 +374,19 @@ class _SortedRangeValueList final int count2 = rangeValues[n].count.toInt() - count1.toInt(); rv.count = count1; - final _RangeValuePair rv2 = - _RangeValuePair.fromRangeValuePair(index.toInt(), count2, rv.value); + final RangeValuePair rv2 = + RangeValuePair.fromRangeValuePair(index.toInt(), count2, rv.value); rangeValues.insert(n + 1, rv2); return n + 1; } + /// void merge(int n) { if (n >= rangeValues.length) { return; } - final _RangeValuePair rv1 = rangeValues[n]; + final RangeValuePair rv1 = rangeValues[n]; if (n == rangeValues.length - 1) { if (rv1.value == defaultValue) { rangeValues.removeAt(n); @@ -383,7 +395,7 @@ class _SortedRangeValueList return; } - final _RangeValuePair rv2 = rangeValues[n + 1]; + final RangeValuePair rv2 = rangeValues[n + 1]; if (rv1.value == rv2.value) { rv1.count += rv2.count; rangeValues.removeAt(n + 1); @@ -401,6 +413,7 @@ class _SortedRangeValueList /// Returns the value range for the specified index. T operator [](num index) => getRangeValue(index.toInt()).value as T; + /// void operator []=(num index, Object value) { bool b = false; if (index >= count) { @@ -420,7 +433,7 @@ class _SortedRangeValueList rangeValues[n.toInt() - 1].count++; } else { rangeValues.add( - _RangeValuePair.fromRangeValuePair(index.toInt(), 1, value)); + RangeValuePair.fromRangeValuePair(index.toInt(), 1, value)); } } else { rangeValues[n.toInt()].value = value; @@ -431,3 +444,286 @@ class _SortedRangeValueList @override dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); } + +/// An indexable collection of objects with a length. +/// +/// Creates a list of the given length. +/// The created list is fixed-length if [length] is provided. +class ListBase implements CollectionBase, EnumerableBase { + /// + ListBase() { + _isFixedSize = false; + _isReadOnly = false; + } + + /// Gets the fixed size value + bool get isFixedSize => _isFixedSize; + late bool _isFixedSize; + + /// + bool get isReadOnly => _isReadOnly; + late bool _isReadOnly; + + /// Add an new element to the list collection + /// + /// * value - _required_ - New element + int add(Object value); + + /// Check whether the value is found or not. + /// + /// * value - _required_ - list element + bool contains(Object value); + + /// Clear the entire list + void clear(); + + /// insert a element into the list + /// + /// * index - _required_ - Index position. + /// * value - _required_ - An element + void insert(int index, Object value); + + /// Check the value based on index + /// + /// * value - _required_ - An element + int indexOf(Object value); + + /// Remove the element from the list + /// + /// * value - _required_ - An element. + void remove(Object value); + + /// Remove the element based on index. + /// + /// * index - The index position. + void removeAt(int index); + + /// Gets the index value + /// + /// Returns the index value + Object? operator [](int index) => this[index]; + + /// Sets the index value + void operator []=(int index, Object value) => this[index] = value; + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +/// Collection +class CollectionBase extends EnumerableBase { + /// + CollectionBase() { + _isSynchronized = false; + _count = 0; + } + + /// Gets the count of the collection. + int get count => _count; + late int _count; + + /// Gets the synchronized. + bool get isSynchronized => _isSynchronized; + bool _isSynchronized = false; + + /// Gets the syncroot + Object? get syncRoot => _syncRoot; + Object? _syncRoot; + + /// Copy an element based on index. + /// + /// * list - _required_ - List of element + /// * index - _required_ - Index position + void copyTo(List list, int index) {} + + @override + dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); +} + +/// Enumerable +abstract class EnumerableBase { + /// Gets the enumerator + /// + /// Returns the enumerator. + EnumeratorBase getEnumerator(); +} + +/// Enumerator +abstract class EnumeratorBase { + /// Move a next postion + /// + /// Returns true when it is move one element. otherwise false + bool moveNext(); + + /// Reset the value from an enumerator. + void reset() {} +} + +/// Generic Enumerable +abstract class EnumerableGenericBase with IterableMixin { + /// Gets the enumerator + /// + /// Returns the enumerator. + EnumeratorGenericBase getEnumeratorGeneric(); +} + +/// Generic Enumerator +class EnumeratorGenericBase { + /// Gets the current item from an list + T? get currentGeneric => current; + + /// + T? current; +} + +/// Provide the largest possible value of an Int32 +const int maxvalue = 2 ^ 53; + +/// Provide the smallest possible value of an Int32 +const int minvalue = -2 ^ 53; + +/// Returns the position of `value` in the `sortedList`, if it exists. +/// +/// * sortedList - _required_ - The sortedList +/// * value - _required_ - Represented to a value +int binarySearch>(List sortedList, T value) { + int min = 0; + int max = sortedList.length; + // Initialize the default value of index to -1 to return `VisibleLineInfo` as + // null if there is no line at the given point. + int index = -1; + while (min < max) { + final int mid = min + ((max - min) >> 1); + final T element = sortedList[mid]; + final int comp = element.compareTo(value); + if (comp == 0) { + return mid; + } + if (comp < 0) { + min = mid + 1; + index = mid; + } else { + max = mid; + } + } + return index; +} + +/// Determines whether the specified Object instances are the same instance. +/// +/// * objA - _required_ - Instance of a class +/// * objB - _required_ - Instance of a class +bool referenceEquals(Object? objA, Object? objB) => objA == objB; + +/// Integer precision's class +/// +/// Holds a start and end value with integer precision. +class Int32Span { + /// Initializes a new instance of the [_Int32Span] struct. + /// + /// * start - _required_ - Hold the start. + /// * end - _required_ - Hold the end value. + Int32Span(this.start, this.end); + + /// + int start; + + /// + int end; + + /// Gets the count (equals end - start + 1) + int get count => end - start + 1; + + /// Sets the count + set count(int value) { + end = start + value - 1; + } + + /// Indicates whether this instance and a specified object are equal. + /// + /// * obj - _required_ - object to compare with the current instance. + /// + /// Returns `True` if the given object and this instance are the + /// same type and represent the same value, + /// otherwise `false`. + bool equals(Object obj) { + if (this == obj) { + return true; + } else { + return false; + } + } + + /// A 32-bit signed integer that is the hash code for this instance. + /// + /// Returns the hash code for this instance. + int getHashCode() => super.hashCode; +} + +/// Double precision's class +/// +/// Holds a start and end value with double precision. +class DoubleSpan { + /// Initializes a new instance of the `DoubleSpan` struct. + /// + /// * start - _require_ - The start. + /// * end - _required_ - The end. + DoubleSpan(this.start, this.end); + + /// Initializes a empty `DoubleSpan`. + DoubleSpan.empty() { + start = 0; + end = -1; + } + + /// + late double start; + + /// + late double end; + + /// Gets a value indicating whether this instance is empty. + /// + /// Returns `True` if this instance is empty. otherwise, `false`. + bool get isEmpty => end < start; + + /// Gets the length. + double get length => end - start; + + /// Sets the length. + set length(double value) { + end = start + value; + } + + /// Indicates whether this instance and a specified object are equal. + /// + /// * obj - _required_ - The object to compare with the current instance. + /// + /// Returns `True` if the given object and this instance are + /// the same type and represent the same value, + /// otherwise `false`. + bool equals(Object obj) { + if (super == obj) { + return true; + } else { + return false; + } + } + + /// A 32-bit signed integer that is the hash code for this instance. + /// + /// Returns the hash code for this instance. + int getHashCode() => super.hashCode; + + /// Gets a string with start and end values. + /// + /// Returns a string with start and end values. + @override + String toString() => 'DoubleSpan Start = $start, End = $end'; + + /// Gets an empty object. + Object operator [](int index) => this[index]; + + /// Sets an empty object. + void operator []=(int index, Object value) => this[index] = value; +} diff --git a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/visible_line_info.dart b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/visible_line_info.dart similarity index 81% rename from packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/visible_line_info.dart rename to packages/syncfusion_flutter_datagrid/lib/src/grid_common/visible_line_info.dart index 3d9fdd7f0..825a671d1 100644 --- a/packages/syncfusion_flutter_datagrid/lib/src/grid_common/scroll_axis_base/visible_line_info.dart +++ b/packages/syncfusion_flutter_datagrid/lib/src/grid_common/visible_line_info.dart @@ -1,9 +1,13 @@ -part of datagrid; +import 'dart:collection'; +import 'dart:math'; + +import 'enums.dart'; +import 'utility_helper.dart' hide ListBase; /// VisibleLines Information /// /// Contains information about a visible line (can also be a row or column). -class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { +class VisibleLineInfo extends Comparable { /// Initializes a new instance of the VisibleLineInfo class. /// /// * visibleIndex - _required_ - Visible index of the line. @@ -13,7 +17,7 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { /// * scrollOffset - _required_ - The scroll offset. /// * isHeader - _required_ - if set to true line is a header. /// * isFooter - _required_ - if set to true line is a footer. - _VisibleLineInfo(int visibleIndex, this.lineIndex, double size, + VisibleLineInfo(int visibleIndex, this.lineIndex, double size, double clippedOrigin, double scrollOffset, bool isHeader, bool isFooter) { _visibleIndex = visibleIndex; _size = size; @@ -23,19 +27,19 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { _isFooter = isFooter; } - /// Initializes a new instance of the [_VisibleLineInfo] class. + /// Initializes a new instance of the [VisibleLineInfo] class. /// Used for BinarySearch. /// /// * clippedOrigin - _required_ - The clipped origin. - _VisibleLineInfo.fromClippedOrigin(double clippedOrigin) { + VisibleLineInfo.fromClippedOrigin(double clippedOrigin) { _clippedOrigin = clippedOrigin; } - /// Initializes a new instance of the [_VisibleLineInfo] class. + /// Initializes a new instance of the [VisibleLineInfo] class. /// Used for BinarySearch. /// /// * lineIndex - _required_ - Index of the line. - _VisibleLineInfo.fromLineIndex(this.lineIndex); + VisibleLineInfo.fromLineIndex(this.lineIndex); double _clippedOrigin = 0.0; bool _isHeader = false; @@ -43,7 +47,11 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { int _visibleIndex = 0; double _size = 0.0; double _scrollOffset = 0.0; + + /// double clippedCornerExtent = 0.0; + + /// int lineIndex = 0; /// A boolean value indicating whether the visible line is the last line. @@ -112,7 +120,7 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { /// Gets a value indicating whether the line is visible. /// /// Returns a boolean value indicating whether the line is visible. - bool get isVisible => _visibleIndex != _MathHelper.maxvalue; + bool get isVisible => _visibleIndex != maxvalue; /// Gets the origin. /// @@ -137,23 +145,26 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { /// Gets the axis region this line belongs to. /// /// Returns the axis region this line belongs to. - _ScrollAxisRegion get region { + ScrollAxisRegion get region { if (_isHeader) { - return _ScrollAxisRegion.header; + return ScrollAxisRegion.header; } else if (_isFooter) { - return _ScrollAxisRegion.footer; + return ScrollAxisRegion.footer; } - return _ScrollAxisRegion.body; + return ScrollAxisRegion.body; } + /// bool isClippedBodyAny(bool hasOriginMargin, bool hasCornerMargin) => ((hasOriginMargin || visibleIndex > 0) && isClippedOrigin) || ((hasCornerMargin || !isLastLine) && isClippedCorner); + /// bool isClippedBodyCorner(bool hasCornerMargin) => (hasCornerMargin || !isLastLine) && isClippedCorner; + /// bool isClippedBodyOrigin(bool hasOriginMargin) => (hasOriginMargin || visibleIndex > 0) && isClippedOrigin; @@ -161,7 +172,7 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { /// /// * other - _required_ - An object to compare with this object. @override - int compareTo(_VisibleLineInfo other) => + int compareTo(VisibleLineInfo other) => ((clippedOrigin - other.clippedOrigin).sign).toInt(); /// A string describing the state of the object. @@ -187,12 +198,17 @@ class _VisibleLineInfo extends Comparable<_VisibleLineInfo> { /// Collection of VisibleLineInfo items. /// /// A strong-typed collection of `VisibleLineInfo` items. -class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { - List<_VisibleLineInfo?> visibleLines = - List<_VisibleLineInfo?>.empty(growable: true); - _VisibleLineInfoLineIndexComparer lineIndexComparer = - _VisibleLineInfoLineIndexComparer(); - Map? lineIndexes = {}; +class VisibleLinesCollection extends ListBase { + /// + List visibleLines = + List.empty(growable: true); + + /// + VisibleLineInfoLineIndexComparer lineIndexComparer = + VisibleLineInfoLineIndexComparer(); + + /// + Map? lineIndexes = {}; /// Gets the first visible index of the body. int firstBodyVisibleIndex = 0; @@ -208,7 +224,7 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// Gets the visible line indexes. /// /// Returns the visible line indexes. - Map? get visibleLineIndexes => lineIndexes; + Map? get visibleLineIndexes => lineIndexes; @override int get length => visibleLines.length; @@ -232,7 +248,7 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { } for (int n = 0; n < length; n++) { - final _VisibleLineInfo line = this[n]; + final VisibleLineInfo line = this[n]; if (line.lineIndex >= lineIndex1 && line.lineIndex <= lineIndex2) { return true; } @@ -245,14 +261,14 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// * lineIndex - _required_ - Index of the line. /// /// Returns the visible line at line index. - _VisibleLineInfo? getVisibleLineAtLineIndex(int lineIndex) { + VisibleLineInfo? getVisibleLineAtLineIndex(int lineIndex) { if (lineIndexes != null && lineIndexes!.isEmpty) { for (int i = 0; i < length; i++) { lineIndexes!.putIfAbsent(this[i].lineIndex, () => this[i]); } //this.shadowedLineIndexes = this.lineIndexes; } - _VisibleLineInfo? lineInfo; + VisibleLineInfo? lineInfo; if (lineIndexes != null && lineIndexes!.containsKey(lineIndex)) { return lineInfo = lineIndexes![lineIndex]; } @@ -266,11 +282,11 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// * lineIndex - _required_ - Index of the line. /// /// Returns the first visible line for a line index that is not hidden. - _VisibleLineInfo? getVisibleLineNearLineIndex(int lineIndex) { - final List<_VisibleLineInfo> _visibleLine = - visibleLines as List<_VisibleLineInfo>; - int index = _MathHelper.binarySearch<_VisibleLineInfo>( - _visibleLine, _VisibleLineInfo.fromLineIndex(lineIndex)); + VisibleLineInfo? getVisibleLineNearLineIndex(int lineIndex) { + final List _visibleLine = + visibleLines as List; + int index = binarySearch( + _visibleLine, VisibleLineInfo.fromLineIndex(lineIndex)); index = (index < 0) ? (~index) - 1 : index; if (index >= 0) { return this[index]; @@ -284,10 +300,10 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { /// to be obtained. /// /// Returns the visible line at point. - _VisibleLineInfo? getVisibleLineAtPoint(double point) { - final List<_VisibleLineInfo> _visibleLines = visibleLines.cast(); - int index = _MathHelper.binarySearch<_VisibleLineInfo>( - _visibleLines, _VisibleLineInfo.fromClippedOrigin(point)); + VisibleLineInfo? getVisibleLineAtPoint(double point) { + final List _visibleLines = visibleLines.cast(); + int index = binarySearch( + _visibleLines, VisibleLineInfo.fromClippedOrigin(point)); index = (index < 0) ? (~index) - 1 : index; if (index >= 0) { return this[index]; @@ -304,7 +320,7 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { bool insertLinesInternal(int lineIndex, int count) { bool visibleLinesAffected = false; for (int n = 0; n < length; n++) { - final _VisibleLineInfo line = this[n]; + final VisibleLineInfo line = this[n]; if (line.lineIndex >= lineIndex) { if (line.lineIndex == lineIndex) { visibleLinesAffected = true; @@ -325,7 +341,7 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { bool removeLinesInternal(int lineIndex, int count) { bool visibleLinesAffected = false; for (int n = 0; n < length; n++) { - final _VisibleLineInfo line = this[n]; + final VisibleLineInfo line = this[n]; if (line.lineIndex >= lineIndex) { if (line.lineIndex < lineIndex + count) { visibleLinesAffected = true; @@ -345,12 +361,12 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { lineIndexes = null; visibleLines.clear(); //this.shadowedLineIndexes = null; - lineIndexes = {}; + lineIndexes = {}; } @override - _VisibleLinesCollection get reversed { - final _VisibleLinesCollection reverseCollection = _VisibleLinesCollection(); + VisibleLinesCollection get reversed { + final VisibleLinesCollection reverseCollection = VisibleLinesCollection(); for (int i = length - 1; i >= 0; i--) { reverseCollection.add(this[i]); } @@ -358,16 +374,16 @@ class _VisibleLinesCollection extends ListBase<_VisibleLineInfo> { } @override - void operator []=(int index, _VisibleLineInfo value) { + void operator []=(int index, VisibleLineInfo value) { visibleLines[index] = value; } @override - _VisibleLineInfo operator [](int index) => visibleLines[index]!; + VisibleLineInfo operator [](int index) => visibleLines[index]!; } /// Initializes a new instance of the `VisibleLineInfoLineIndexComparer` class. -class _VisibleLineInfoLineIndexComparer extends Comparable<_VisibleLineInfo> { +class VisibleLineInfoLineIndexComparer extends Comparable { /// Compares the given visible lines. /// /// * x - _required_ - The visible line-1. @@ -375,7 +391,7 @@ class _VisibleLineInfoLineIndexComparer extends Comparable<_VisibleLineInfo> { /// /// Returns a integer value indicating the comparison of the /// given visible lines. - int compare(_VisibleLineInfo x, _VisibleLineInfo y) => + int compare(VisibleLineInfo x, VisibleLineInfo y) => (x.lineIndex - y.lineIndex).sign; @override diff --git a/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart b/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart deleted file mode 100644 index 5d337090f..000000000 --- a/packages/syncfusion_flutter_datagrid/lib/src/sfdatagrid.dart +++ /dev/null @@ -1,2179 +0,0 @@ -part of datagrid; - -/// Signature for the `_DataGridSettings` callback. -typedef _DataGridStateDetails = _DataGridSettings Function(); - -/// Signature for [SfDataGrid.onQueryRowHeight] callback -typedef QueryRowHeightCallback = double Function(RowHeightDetails details); - -/// Signature for [SfDataGrid.onSelectionChanging] callback. -typedef SelectionChangingCallback = bool Function( - List addedRows, List removedRows); - -/// Signature for [SfDataGrid.onSelectionChanged] callback. -typedef SelectionChangedCallback = void Function( - List addedRows, List removedRows); - -/// Signature for [SfDataGrid.onCellRenderersCreated] callback. -typedef CellRenderersCreatedCallback = void Function( - Map cellRenderers); - -/// Signature for [SfDataGrid.onCurrentCellActivating] callback. -typedef CurrentCellActivatingCallback = bool Function( - RowColumnIndex newRowColumnIndex, RowColumnIndex oldRowColumnIndex); - -/// Signature for [SfDataGrid.onCurrentCellActivated] callback. -typedef CurrentCellActivatedCallback = void Function( - RowColumnIndex newRowColumnIndex, RowColumnIndex oldRowColumnIndex); - -/// Signature for [SfDataGrid.onCellTap] and [SfDataGrid.onCellSecondaryTap] -/// callbacks. -typedef DataGridCellTapCallback = void Function(DataGridCellTapDetails details); - -/// Signature for [SfDataGrid.onCellDoubleTap] callback. -typedef DataGridCellDoubleTapCallback = void Function( - DataGridCellDoubleTapDetails details); - -/// Signature for [SfDataGrid.onCellLongPress] callback. -typedef DataGridCellLongPressCallback = void Function( - DataGridCellLongPressDetails details); - -/// The signature of [DataGridSource.handleLoadMoreRows] function. -typedef LoadMoreRows = Future Function(); - -/// Signature for the [SfDataGrid.loadMoreViewBuilder] function. -typedef LoadMoreViewBuilder = Widget? Function( - BuildContext context, LoadMoreRows loadMoreRows); - -/// Signature for the [SfDataGrid.onSwipeStart] callback. -typedef DataGridSwipeStartCallback = bool Function( - DataGridSwipeStartDetails swipeStartDetails); - -/// Signature for the [SfDataGrid.onSwipeUpdate] callback. -typedef DataGridSwipeUpdateCallback = bool Function( - DataGridSwipeUpdateDetails swipeUpdateDetails); - -/// Signature for the [SfDataGrid.onSwipeEnd] callback. -typedef DataGridSwipeEndCallback = void Function( - DataGridSwipeEndDetails swipeEndDetails); - -/// Holds the arguments for the [SfDataGrid.startSwipeActionsBuilder] callback. -typedef DataGridSwipeActionsBuilder = Widget? Function( - BuildContext context, DataGridRow dataGridRow); - -/// The signature of [DataGridSource.canSubmitCell] and -/// [DataGridSource.onCellSubmit] methods. -typedef CellSubmit = void Function(); - -/// Row configuration and cell data for a [SfDataGrid]. -/// -/// Return this list of [DataGridRow] objects to [DataGridSource.rows] property. -/// -/// The data for each row can be passed as the cells argument to the -/// constructor of each [DataGridRow] object. -class DataGridRow { - /// ToDo - const DataGridRow({required List cells}) : _cells = cells; - - /// The data for this row. - /// - /// There must be exactly as many cells as there are columns in the - /// [SfDataGrid]. - final List _cells; - - /// Returns the collection of [DataGridCell] which is created for - /// [DataGridRow]. - List getCells() { - return _cells; - } -} - -/// The data for a cell of a [SfDataGrid]. -/// -/// The list of [DataGridCell] objects should be passed as the cells argument -/// to the constructor of each [DataGridRow] object. -@optionalTypeArgs -class DataGridCell { - /// ToDo - const DataGridCell({required this.columnName, required this.value}); - - /// The name of a column - final String columnName; - - /// The value of a cell. - /// - /// Provide value of a cell to perform the sorting for whole data available - /// in datagrid. - final T? value; -} - -/// Row configuration and widget of cell for a [SfDataGrid]. -/// -/// The widget for each cell can be provided in the [DataGridRowAdapter.cells] -/// property. -class DataGridRowAdapter { - /// ToDo - const DataGridRowAdapter({required this.cells, this.key, this.color}); - - /// ToDo - final Key? key; - - /// The color for the row. - final Color? color; - - /// The widget of each cell for this row. - /// - /// There must be exactly as many cells as there are columns in the - /// [SfDataGrid]. - final List cells; -} - -/// A material design datagrid. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=-ULsEfjxFuY} -/// -/// DataGrid lets you display and manipulate data in a tabular view. It is built -/// from the ground up to achieve the best possible performance even when -/// loading large amounts of data. -/// -/// DataGrid supports different types of column types to populate the columns -/// for different types of data such as int, double, DateTime, String. -/// -/// [source] property enables you to populate the data for the [SfDataGrid]. -/// -/// This sample shows how to populate the data for the [SfDataGrid] and display -/// with four columns: id, name, designation and salary. -/// The columns are defined by four [GridColumn] objects. -/// -/// ``` dart -/// final List _employees = []; -/// final EmployeeDataSource _employeeDataSource = EmployeeDataSource(); -/// -/// @override -/// void initState(){ -/// super.initState(); -/// populateData(); -/// } -/// -/// @override -/// Widget build(BuildContext context) { -/// return SfDataGrid( -/// source: _employeeDataSource, -/// columnWidthMode: ColumnWidthMode.fill, -/// columns: [ -/// GridColumn(columnName: 'id', label: Text('ID')), -/// GridColumn(columnName: 'name', label: Text('Name')), -/// GridColumn(columnName: 'designation', label: Text('Designation')), -/// GridColumn(columnName: 'salary', label: Text('Salary')), -/// ); -/// } -/// -/// void populateData(){ -/// _employees.add(Employee(10001, 'James', 'Project Lead', 10000)); -/// _employees.add(Employee(10002, 'Kathryn', 'Manager', 10000)); -/// _employees.add(Employee(10003, 'Lara', 'Developer', 10000)); -/// _employees.add(Employee(10004, 'Michael', 'Designer', 10000)); -/// _employees.add(Employee(10005, 'Martin', 'Developer', 10000)); -/// _employees.add(Employee(10006, 'Newberry', 'Developer', 15000)); -/// _employees.add(Employee(10007, 'Balnc', 'Developer', 15000)); -/// _employees.add(Employee(10008, 'Perry', 'Developer', 15000)); -/// _employees.add(Employee(10009, 'Gable', 'Developer', 15000)); -/// _employees.add(Employee(10010, 'Grimes', 'Developer', 15000)); -/// } -/// } -/// -/// class Employee { -/// Employee(this.id, this.name, this.designation, this.salary); -/// final int id; -/// final String name; -/// final String designation; -/// final int salary; -/// } -/// -/// class EmployeeDataSource extends DataGridSource { -/// @override -/// List get rows => _employees -/// .map((dataRow) => DataGridRow(cells: [ -/// DataGridCell(columnName: 'id', value: dataRow.id), -/// DataGridCell(columnName: 'name', value: dataRow.name), -/// DataGridCell( -/// columnName: 'designation', value: dataRow.designation), -/// DataGridCell(columnName: 'salary', value: dataRow.salary), -/// ])) -/// .toList(); -/// -/// @override -/// DataGridRowAdapter? buildRow(DataGridRow row) { -/// return DataGridRowAdapter( -/// cells: row.getCells().map((dataCell) { -/// return Text(dataCell.value.toString()); -/// }).toList()); -/// } -/// } -/// -/// ``` -class SfDataGrid extends StatefulWidget { - /// Creates a widget describing a datagrid. - /// - /// The [columns] and [source] argument must be defined and must not be null. - const SfDataGrid( - {required this.source, - required this.columns, - Key? key, - this.rowHeight = double.nan, - this.headerRowHeight = double.nan, - this.defaultColumnWidth = double.nan, - this.gridLinesVisibility = GridLinesVisibility.horizontal, - this.headerGridLinesVisibility = GridLinesVisibility.horizontal, - this.columnWidthMode = ColumnWidthMode.none, - this.columnSizer, - this.columnWidthCalculationRange = - ColumnWidthCalculationRange.visibleRows, - this.selectionMode = SelectionMode.none, - this.navigationMode = GridNavigationMode.row, - this.frozenColumnsCount = 0, - this.footerFrozenColumnsCount = 0, - this.frozenRowsCount = 0, - this.footerFrozenRowsCount = 0, - this.allowSorting = false, - this.allowMultiColumnSorting = false, - this.allowTriStateSorting = false, - this.showSortNumbers = false, - this.sortingGestureType = SortingGestureType.tap, - this.stackedHeaderRows = const [], - this.selectionManager, - this.controller, - this.onQueryRowHeight, - this.onSelectionChanged, - this.onSelectionChanging, - this.onCellRenderersCreated, - this.onCurrentCellActivating, - this.onCurrentCellActivated, - this.onCellTap, - this.onCellDoubleTap, - this.onCellSecondaryTap, - this.onCellLongPress, - this.isScrollbarAlwaysShown = false, - this.horizontalScrollPhysics = const AlwaysScrollableScrollPhysics(), - this.verticalScrollPhysics = const AlwaysScrollableScrollPhysics(), - this.loadMoreViewBuilder, - this.allowPullToRefresh = false, - this.refreshIndicatorDisplacement = 40.0, - this.refreshIndicatorStrokeWidth = 2.0, - this.allowSwiping = false, - this.swipeMaxOffset = 200.0, - this.horizontalScrollController, - this.verticalScrollController, - this.onSwipeStart, - this.onSwipeUpdate, - this.onSwipeEnd, - this.startSwipeActionsBuilder, - this.endSwipeActionsBuilder, - this.highlightRowOnHover = true, - this.allowEditing = false, - this.editingGestureType = EditingGestureType.doubleTap, - this.footer, - this.footerHeight = 49.0}) - : assert(frozenColumnsCount >= 0), - assert(footerFrozenColumnsCount >= 0), - assert(frozenRowsCount >= 0), - assert(footerFrozenRowsCount >= 0), - super(key: key); - - /// The height of each row except the column header. - /// - /// Defaults to 49.0 - final double rowHeight; - - /// The height of the column header row. - /// - /// Defaults to 56.0 - final double headerRowHeight; - - /// The collection of the [GridColumn]. - /// - /// Each column associated with its own renderer and it controls the - /// corresponding column related operations. - /// - /// Defaults to null. - final List columns; - - /// The datasource that provides the data for each row in [SfDataGrid]. Must - /// be non-null. - /// - /// This object is expected to be long-lived, not recreated with each build. - /// - /// Defaults to null - final DataGridSource source; - - /// The width of each column. - /// - /// If the [columnWidthMode] is set for [SfDataGrid] or [GridColumn], or - /// [GridColumn.width] is set, [defaultColumnWidth] will not be considered. - /// - /// Defaults to 90.0 for Android & iOS and 100.0 for Web. - final double defaultColumnWidth; - - /// How the column widths are determined. - /// - /// Defaults to [ColumnWidthMode.none] - /// - /// Also refer [ColumnWidthMode] - final ColumnWidthMode columnWidthMode; - - /// How the row count should be considered when calculating the width of a - /// column. - /// - /// Provides options to consider only visible rows or all the rows which are - /// available in [SfDataGrid]. - /// - /// Defaults to [ColumnWidthCalculationRange.visibleRows] - /// - /// Also refer [ColumnWidthCalculationRange] - final ColumnWidthCalculationRange columnWidthCalculationRange; - - /// The [ColumnSizer] used to control the column width sizing operation of - /// each columns. - /// - /// You can override the available methods and customize the required - /// operations in the custom [ColumnSizer]. - final ColumnSizer? columnSizer; - - /// How the border should be visible. - /// - /// Decides whether vertical, horizontal, both the borders and no borders - /// should be drawn. - /// - /// Defaults to [GridLinesVisibility.horizontal] - /// - /// Also refer [GridLinesVisibility] - final GridLinesVisibility gridLinesVisibility; - - /// How the border should be visible in header cells. - /// - /// Decides whether vertical or horizontal or both the borders or no borders - /// should be drawn. - /// - /// [GridLinesVisibility.horizontal] will be useful if you are using - /// [stackedHeaderRows] to improve the readability. - /// - /// Defaults to [GridLinesVisibility.horizontal] - /// - /// Also refer [GridLinesVisibility]. - /// - /// See also, [gridLinesVisibility] – To set the border for cells other than - /// header cells. - final GridLinesVisibility headerGridLinesVisibility; - - /// Invoked when the row height for each row is queried. - final QueryRowHeightCallback? onQueryRowHeight; - - /// How the rows should be selected. - /// - /// Provides options to select single row or multiple rows. - /// - /// Defaults to [SelectionMode.none]. - /// - /// Also refer [SelectionMode] - final SelectionMode selectionMode; - - /// Invoked when the row is selected. - /// - /// This callback never be called when the [onSelectionChanging] is returned - /// as false. - final SelectionChangedCallback? onSelectionChanged; - - /// Invoked when the row is being selected or being unselected - /// - /// This callback's return type is [bool]. So, if you want to cancel the - /// selection on a row based on the condition, return false. - /// Otherwise, return true. - final SelectionChangingCallback? onSelectionChanging; - - /// The [SelectionManagerBase] used to control the selection operations - /// in [SfDataGrid]. - /// - /// You can override the available methods and customize the required - /// operations in the custom [RowSelectionManager]. - /// - /// Defaults to null - final SelectionManagerBase? selectionManager; - - /// The [DataGridController] used to control the current cell navigation and - /// selection operation. - /// - /// Defaults to null. - /// - /// This object is expected to be long-lived, not recreated with each build. - final DataGridController? controller; - - /// Called when the cell renderers are created for each column. - /// - /// This method is called once when the [SfDataGrid] is loaded. Users can - /// provide the custom cell renderer to the existing collection. - final CellRenderersCreatedCallback? onCellRenderersCreated; - - /// Decides whether the navigation in the [SfDataGrid] should be cell wise - /// or row wise. - final GridNavigationMode navigationMode; - - /// Invoked when the cell is activated. - /// - /// This callback never be called when the [onCurrentCellActivating] is - /// returned as false. - final CurrentCellActivatedCallback? onCurrentCellActivated; - - /// Invoked when the cell is being activated. - /// - /// This callback's return type is [bool]. So, if you want to cancel cell - /// activation based on the condition, return false. Otherwise, - /// return true. - final CurrentCellActivatingCallback? onCurrentCellActivating; - - /// Called when a tap with a cell has occurred. - final DataGridCellTapCallback? onCellTap; - - /// Called when user is tapped a cell with a primary button at the same cell - /// twice in quick succession. - final DataGridCellDoubleTapCallback? onCellDoubleTap; - - /// Called when a long press gesture with a primary button has been - /// recognized for a cell. - final DataGridCellTapCallback? onCellSecondaryTap; - - /// Called when a tap with a cell has occurred with a secondary button. - final DataGridCellLongPressCallback? onCellLongPress; - - /// The number of non-scrolling columns at the left side of [SfDataGrid]. - /// - /// In Right To Left (RTL) mode, this count refers to the number of - /// non-scrolling columns at the right side of [SfDataGrid]. - /// - /// Defaults to 0 - /// - /// See also: - /// - /// [footerFrozenColumnsCount] - /// [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the - /// width of the frozen line. - /// [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the - /// color of the frozen line - final int frozenColumnsCount; - - /// The number of non-scrolling columns at the right side of [SfDataGrid]. - /// - /// In Right To Left (RTL) mode, this count refers to the number of - /// non-scrolling columns at the left side of [SfDataGrid]. - /// - /// Defaults to 0 - /// - /// See also: - /// - /// [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the - /// width of the frozen line. - /// [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the - /// color of the frozen line. - final int footerFrozenColumnsCount; - - /// The number of non-scrolling rows at the top of [SfDataGrid]. - /// - /// Defaults to 0 - /// - /// See also: - /// - /// [footerFrozenRowsCount] - /// [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the - /// width of the frozen line. - /// [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the - /// color of the frozen line. - final int frozenRowsCount; - - /// The number of non-scrolling rows at the bottom of [SfDataGrid]. - /// - /// Defaults to 0 - /// - /// See also: - /// - /// [SfDataGridThemeData.frozenPaneLineWidth], which is used to customize the - /// width of the frozen line. - /// [SfDataGridThemeData.frozenPaneLineColor], which is used to customize the - /// color of the frozen line. - final int footerFrozenRowsCount; - - /// Decides whether user can sort the column simply by tapping the column - /// header. - /// - /// Defaults to false. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfDataGrid( - /// source: _employeeDataSource, - /// allowSorting: true, - /// columns: [ - /// GridColumn(columnName: 'id', label: Text('ID')), - /// GridColumn(columnName: 'name', label: Text('Name')), - /// GridColumn(columnName: 'designation', label: Text('Designation')), - /// GridColumn(columnName: 'salary', label: Text('Salary')), - /// ]); - /// } - /// - /// class EmployeeDataSource extends DataGridSource { - /// @override - /// List get rows => _employees - /// .map((dataRow) => DataGridRow(cells: [ - /// DataGridCell(columnName: 'id', value: dataRow.id), - /// DataGridCell(columnName: 'name', value: dataRow.name), - /// DataGridCell( - /// columnName: 'designation', value: dataRow.designation), - /// DataGridCell(columnName: 'salary', value: dataRow.salary), - /// ])) - /// .toList(); - /// - /// @override - /// DataGridRowAdapter? buildRow(DataGridRow row) { - /// return DataGridRowAdapter( - /// cells: row.getCells().map((dataCell) { - /// return Text(dataCell.value.toString()); - /// }).toList()); - /// } - /// } - /// - /// ``` - /// - /// - /// See also: - /// - /// * [GridColumn.allowSorting] - which allows users to sort the columns in - /// [SfDataGrid]. - /// * [sortingGestureType] – which allows users to sort the column in tap or - /// double tap. - /// * [DataGridSource.sortedColumns] - which is the collection of - /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. - /// * [DataGridSource.sort] - call this method when you are adding the - /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. - final bool allowSorting; - - /// Decides whether user can sort more than one column. - /// - /// Defaults to false. - /// - /// This is applicable only if the [allowSorting] is set as true. - /// - /// See also: - /// - /// * [DataGridSource.sortedColumns] - which is the collection of - /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. - /// * [DataGridSource.sort] - call this method when you are adding the - /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. - final bool allowMultiColumnSorting; - - /// Decides whether user can sort the column in three states: ascending, - /// descending, unsorted. - /// - /// Defaults to false. - /// - /// This is applicable only if the [allowSorting] is set as true. - /// - /// See also: - /// - /// * [DataGridSource.sortedColumns] - which is the collection of - /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. - /// * [DataGridSource.sort] - call this method when you are adding the - /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. - final bool allowTriStateSorting; - - /// Decides whether the sequence number should be displayed on the header cell - /// of sorted column during multi-column sorting. - /// - /// Defaults to false - /// - /// This is applicable only if the [allowSorting] and - /// [allowMultiColumnSorting] are set as true. - /// - /// See also: - /// - /// * [DataGridSource.sortedColumns] - which is the collection of - /// [SortColumnDetails] objects to sort the columns in [SfDataGrid]. - /// * [DataGridSource.sort] - call this method when you are adding the - /// [SortColumnDetails] programmatically to [DataGridSource.sortedColumns]. - final bool showSortNumbers; - - /// Decides whether the sorting should be applied on tap or double tap the - /// column header. - /// - /// Default to [SortingGestureType.tap] - /// - /// see also: - /// - /// [allowSorting] - final SortingGestureType sortingGestureType; - - /// The collection of [StackedHeaderRow]. - /// - /// Stacked headers enable you to display headers that span across multiple - /// columns and rows. These rows are displayed above to the regular column - /// headers. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return SfDataGrid(source: _employeeDataSource, columns: [ - /// GridColumn(columnName: 'id', label: Text('ID')), - /// GridColumn(columnName: 'name', label: Text('Name')), - /// GridColumn(columnName: 'designation', label: Text('Designation')), - /// GridColumn(columnName: 'salary', label: Text('Salary')) - /// ], stackedHeaderRows: [ - /// StackedHeaderRow(cells: [ - /// StackedHeaderCell( - /// columnNames: ['id', 'name', 'designation', 'salary'], - /// child: Center( - /// child: Text('Order Details'), - /// ), - /// ), - /// ]) - /// ]); - /// } - /// ``` - final List stackedHeaderRows; - - /// Indicates whether the horizontal and vertical scrollbars should always - /// be visible. When false, both the scrollbar will be shown during scrolling - /// and will fade out otherwise. When true, both the scrollbar will always be - /// visible and never fade out. - /// - /// Defaults to false - final bool isScrollbarAlwaysShown; - - /// How the horizontal scroll view should respond to user input. - /// For example, determines how the horizontal scroll view continues to animate after the user stops dragging the scroll view. - /// - /// Defaults to [AlwaysScrollableScrollPhysics]. - final ScrollPhysics horizontalScrollPhysics; - - /// How the vertical scroll view should respond to user input. - /// For example, determines how the vertical scroll view continues to animate after the user stops dragging the scroll view. - /// - /// Defaults to [AlwaysScrollableScrollPhysics]. - final ScrollPhysics verticalScrollPhysics; - - /// A builder that sets the widget to display at the bottom of the datagrid - /// when vertical scrolling reaches the end of the datagrid. - /// - /// You should override [DataGridSource.handleLoadMoreRows] method to load - /// more rows and then notify the datagrid about the changes. The - /// [DataGridSource.handleLoadMoreRows] can be called to load more rows from - /// this builder using `loadMoreRows` function which is passed as a parameter - /// to this builder. - /// - /// ## Infinite scrolling - /// - /// The example below demonstrates infinite scrolling by showing the circular - /// progress indicator until the rows are loaded when vertical scrolling - /// reaches the end of the datagrid, - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(title: Text('Syncfusion Flutter DataGrid')), - /// body: SfDataGrid( - /// source: employeeDataSource, - /// loadMoreViewBuilder: - /// (BuildContext context, LoadMoreRows loadMoreRows) { - /// Future loadRows() async { - /// await loadMoreRows(); - /// return Future.value('Completed'); - /// } - /// - /// return FutureBuilder( - /// initialData: 'loading', - /// future: loadRows(), - /// builder: (context, snapShot) { - /// if (snapShot.data == 'loading') { - /// return Container( - /// height: 98.0, - /// color: Colors.white, - /// width: double.infinity, - /// alignment: Alignment.center, - /// child: CircularProgressIndicator(valueColor: - /// AlwaysStoppedAnimation(Colors.deepPurple))); - /// } else { - /// return SizedBox.fromSize(size: Size.zero); - /// } - /// }, - /// ); - /// }, - /// columns: [ - /// GridColumn(columnName: 'id', label: Text('ID')), - /// GridColumn(columnName: 'name', label: Text('Name')), - /// GridColumn(columnName: 'designation', label: Text('Designation')), - /// GridColumn(columnName: 'salary', label: Text('Salary')), - /// ], - /// ), - /// ); - /// } - /// ``` - /// - /// ## Load more button - /// - /// The example below demonstrates how to show the button when vertical - /// scrolling is reached at the end of the datagrid and display the circular - /// indicator when you tap that button. In the onPressed flatbutton callback, - /// you can call the `loadMoreRows` function to add more rows. - /// - /// ```dart - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar(title: Text('Syncfusion Flutter DataGrid')), - /// body: SfDataGrid( - /// source: employeeDataSource, - /// loadMoreViewBuilder: - /// (BuildContext context, LoadMoreRows loadMoreRows) { - /// bool showIndicator = false; - /// return StatefulBuilder( - /// builder: (BuildContext context, StateSetter setState) { - /// if (showIndicator) { - /// return Container( - /// height: 98.0, - /// color: Colors.white, - /// width: double.infinity, - /// alignment: Alignment.center, - /// child: CircularProgressIndicator(valueColor: - /// AlwaysStoppedAnimation(Colors.deepPurple))); - /// } else { - /// return Container( - /// height: 98.0, - /// color: Colors.white, - /// width: double.infinity, - /// alignment: Alignment.center, - /// child: Container( - /// height: 36.0, - /// width: 142.0, - /// child: FlatButton( - /// color: Colors.deepPurple, - /// child: Text('LOAD MORE', - /// style: TextStyle(color: Colors.white)), - /// onPressed: () async { - /// if (context is StatefulElement && - /// context.state != null && - /// context.state.mounted) { - /// setState(() { - /// showIndicator = true; - /// }); - /// } - /// await loadMoreRows(); - /// if (context is StatefulElement && - /// context.state != null && - /// context.state.mounted) { - /// setState(() { - /// showIndicator = false; - /// }); - /// } - /// }, - /// ), - /// ), - /// ); - /// } - /// }); - /// }, - /// columns: [ - /// GridColumn(columnName: 'id', label: Text('ID')), - /// GridColumn(columnName: 'name', label: Text('Name')), - /// GridColumn(columnName: 'designation', label: Text('Designation')), - /// GridColumn(columnName: 'salary', label: Text('Salary')), - /// ], - /// ), - /// ); - /// } - /// ``` - final LoadMoreViewBuilder? loadMoreViewBuilder; - - /// Decides whether refresh indicator should be shown when datagrid is pulled - /// down. - /// - /// See also, - /// - /// [DataGridSource.handleRefresh] – This will be called when datagrid - /// is pulled down to refresh the data. - final bool allowPullToRefresh; - - /// The distance from the [SfDataGrid]’s top or bottom edge to where the refresh - /// indicator will settle. During the drag that exposes the refresh indicator, - /// its actual displacement may significantly exceed this value. - /// - /// By default, the value of `refreshIndicatorDisplacement` is 40.0. - final double refreshIndicatorDisplacement; - - /// Defines `strokeWidth` for `RefreshIndicator` used by [SfDataGrid]. - /// - /// By default, the value of `refreshIndicatorStrokeWidth` is 2.0 pixels. - final double refreshIndicatorStrokeWidth; - - /// Decides whether to 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 swipe view will be shown for custom actions. - /// - /// You can show the widgets for left or right swipe view using - /// [SfDataGrid.startSwipeActionsBuilder] and [SfDataGrid.endSwipeActionsBuilder]. - /// - /// See also, - /// - /// [SfDataGrid.onSwipeStart] - /// [SfDataGrid.onSwipeUpdate] - /// [SfDataGrid.onSwipeEnd] - final bool allowSwiping; - - /// Defines the maximum offset in which a row can be swiped. - /// - /// Defaults to 200. - final double swipeMaxOffset; - - /// Controls a horizontal scrolling in DataGrid. - /// - /// You can use addListener method to listen whenever you do the horizontal scrolling. - /// - final ScrollController? horizontalScrollController; - - /// Controls a vertical scrolling in DataGrid. - /// - /// You can use addListener method to listen whenever you do the vertical scrolling. - /// - final ScrollController? verticalScrollController; - - /// Called when row swiping is started. - /// - /// You can disable the swiping for specific row by returning false. - final DataGridSwipeStartCallback? onSwipeStart; - - /// Called when row is being swiped. - /// - /// You can disable the swiping for specific requirement on swiping itself by - /// returning false. - final DataGridSwipeUpdateCallback? onSwipeUpdate; - - /// Called when swiping of a row is ended (i.e. when reaches the max offset). - final DataGridSwipeEndCallback? onSwipeEnd; - - /// A builder that sets the widget for the background view in which a row is - /// swiped in the reading direction (e.g., from left to right in left-to-right - /// languages). - final DataGridSwipeActionsBuilder? startSwipeActionsBuilder; - - /// A builder that sets the widget for the background view in which a row is - /// swiped in the reverse of reading direction (e.g., from right to left in - /// left-to-right languages). - final DataGridSwipeActionsBuilder? endSwipeActionsBuilder; - - /// Decides whether to highlight a row when mouse hovers over it. - /// - /// see also, - /// [SfDataGridThemeData.rowHoverColor] – This helps you to change row highlighting color on hovering. - /// [SfDataGridThemeData.rowHoverTextStyle] – This helps you to change the [TextStyle] of row on hovering. - final bool highlightRowOnHover; - - /// Decides whether cell should be moved into edit mode based on - /// [editingGestureType]. - /// - /// Defaults to false. - /// - /// Editing can be enabled only if the [selectionMode] is other than none and - /// [navigationMode] is cell. - /// - /// You can load the required widget on editing using [DataGridSource.buildEditWidget] method. - /// - /// The following example shows how to load the [TextField] for `id` column - /// by overriding the `onCellSubmit` and `buildEditWidget` methods in - /// [DataGridSource] class. - /// - /// ```dart - /// - /// class EmployeeDataSource extends DataGridSource { - /// - /// TextEditingController editingController = TextEditingController(); - /// - /// dynamic newCellValue; - /// - /// /// Creates the employee data source class with required details. - /// EmployeeDataSource({required List employeeData}) { - /// employees = employeeData; - /// _employeeData = employeeData - /// .map((e) => DataGridRow(cells: [ - /// DataGridCell(columnName: 'id', value: e.id), - /// DataGridCell(columnName: 'name', value: e.name), - /// DataGridCell( - /// columnName: 'designation', value: e.designation), - /// DataGridCell(columnName: 'salary', value: e.salary), - /// ])) - /// .toList(); - /// } - /// - /// List _employeeData = []; - /// - /// List employees = []; - /// - /// @override - /// List get rows => _employeeData; - /// - /// @override - /// DataGridRowAdapter buildRow(DataGridRow row) { - /// return DataGridRowAdapter( - /// cells: row.getCells().map((e) { - /// return Container( - /// alignment: (e.columnName == 'id' || e.columnName == 'salary') - /// ? Alignment.centerRight - /// : Alignment.centerLeft, - /// padding: EdgeInsets.all(8.0), - /// child: Text(e.value.toString()), - /// ); - /// }).toList()); - /// } - /// - /// @override - /// Widget? buildEditWidget(DataGridRow dataGridRow, - /// RowColumnIndex rowColumnIndex, GridColumn column, submitCell) { - /// // To set the value for TextField when cell is moved into edit mode. - /// final String displayText = dataGridRow - /// .getCells() - /// .firstWhere((DataGridCell dataGridCell) => - /// dataGridCell.columnName == column.columnName) - /// .value - /// ?.toString() ?? - /// ''; - /// - /// /// Returning the TextField with the numeric keyboard configuration. - /// if (column.columnName == 'id') { - /// return Container( - /// padding: const EdgeInsets.all(8.0), - /// alignment: Alignment.centerRight, - /// child: TextField( - /// autofocus: true, - /// controller: editingController..text = displayText, - /// textAlign: TextAlign.right, - /// decoration: const InputDecoration( - /// contentPadding: EdgeInsets.all(0), - /// border: InputBorder.none, - /// isDense: true), - /// inputFormatters: [ - /// FilteringTextInputFormatter.allow(RegExp('[0-9]')) - /// ], - /// keyboardType: TextInputType.number, - /// onChanged: (String value) { - /// if (value.isNotEmpty) { - /// print(value); - /// newCellValue = int.parse(value); - /// } else { - /// newCellValue = null; - /// } - /// }, - /// onSubmitted: (String value) { - /// /// Call [CellSubmit] callback to fire the canSubmitCell and - /// /// onCellSubmit to commit the new value in single place. - /// submitCell(); - /// }, - /// )); - /// } - /// } - /// - /// @override - /// void onCellSubmit(DataGridRow dataGridRow, RowColumnIndex rowColumnIndex, - /// GridColumn column) { - /// final dynamic oldValue = dataGridRow - /// .getCells() - /// .firstWhereOrNull((DataGridCell dataGridCell) => - /// dataGridCell.columnName == column.columnName) - /// ?.value ?? - /// ''; - /// - /// final int dataRowIndex = rows.indexOf(dataGridRow); - /// - /// if (newCellValue == null || oldValue == newCellValue) { - /// return; - /// } - /// - /// if (column.columnName == 'id') { - /// rows[dataRowIndex].getCells()[rowColumnIndex.columnIndex] = - /// DataGridCell(columnName: 'id', value: newCellValue); - /// - /// // Save the new cell value to model collection also. - /// employees[dataRowIndex].id = newCellValue as int; - /// } - /// - /// // To reset the new cell value after successfully updated to DataGridRow - /// //and underlying mode. - /// newCellValue = null; - /// } - /// } - /// - /// ``` - /// The following example shows how to enable editing and set the - /// [DataGridSource] for [SfDataGrid]. - /// ```dart - /// - /// List employees = []; - /// - /// late EmployeeDataSource employeeDataSource; - /// - /// @override - /// void initState() { - /// super.initState(); - /// employees = getEmployeeData(); - /// employeeDataSource = EmployeeDataSource(employeeData: employees); - /// } - /// - /// @override - /// Widget build(BuildContext context) { - /// return Scaffold( - /// appBar: AppBar( - /// title: const Text('Syncfusion Flutter DataGrid'), - /// ), - /// body: SfDataGrid( - /// source: employeeDataSource, - /// allowEditing: true, - /// columnWidthMode: ColumnWidthMode.fill, - /// selectionMode: SelectionMode.single, - /// navigationMode: GridNavigationMode.cell, - /// columns: [ - /// GridColumn( - /// columnName: 'id', - /// label: Container( - /// padding: EdgeInsets.all(16.0), - /// alignment: Alignment.centerRight, - /// child: Text( - /// 'ID', - /// ))), - /// GridColumn( - /// columnName: 'name', - /// label: Container( - /// padding: EdgeInsets.all(8.0), - /// alignment: Alignment.centerLeft, - /// child: Text('Name'))), - /// GridColumn( - /// columnName: 'designation', - /// label: Container( - /// padding: EdgeInsets.all(8.0), - /// alignment: Alignment.centerLeft, - /// child: Text( - /// 'Designation', - /// overflow: TextOverflow.ellipsis, - /// ))), - /// GridColumn( - /// columnName: 'salary', - /// label: Container( - /// padding: EdgeInsets.all(8.0), - /// alignment: Alignment.centerRight, - /// child: Text('Salary'))), - /// ], - /// ), - /// ); - /// } - /// ``` - /// See also, - /// * [GridColumn.allowEditing] – You can enable or disable editing for an - /// individual column. - /// * [DataGridSource.onCellBeginEdit]- This will be triggered when a cell is - /// moved to edit mode. - /// * [DataGridSource.canSubmitCell]- This will be triggered before - /// [DataGridSource.onCellSubmit] method is called. You can use this method - /// if you want to not end the editing based on any criteria. - /// * [DataGridSource.onCellSubmit] – This will be triggered when the cell’s - /// editing is completed. - final bool allowEditing; - - /// Decides whether the editing should be triggered on tap or double tap - /// the cells. - /// - /// Defaults to [EditingGestureType.doubleTap]. - /// - /// See also, - /// * [allowEditing] – This will enable the editing option for cells. - final EditingGestureType editingGestureType; - - /// The widget to show over the bottom of the [SfDataGrid]. - /// - /// This footer will be displayed like normal row and shown below to last row. - /// - /// See also, - /// - /// [SfDataGrid.footerHeight] – This enables you to change the height of the - /// footer. - final Widget? footer; - - /// The height of the footer. - /// - /// Defaults to 49.0. - final double footerHeight; - - @override - State createState() => SfDataGridState(); -} - -/// Contains the state for a [SfDataGrid]. This class can be used to -/// programmatically show the refresh indicator, see the [refresh] -/// method. -class SfDataGridState extends State - with SingleTickerProviderStateMixin { - static const double _minWidth = 300.0; - static const double _minHeight = 300.0; - static const double _rowHeight = 49.0; - static const double _headerRowHeight = 56.0; - - late _RowGenerator _rowGenerator; - late _VisualContainerHelper _container; - late _DataGridStateDetails _dataGridStateDetails; - late _DataGridSettings _dataGridSettings; - late ColumnSizer _columnSizer; - late _CurrentCellManager _currentCell; - AnimationController? _swipingAnimationController; - - late Map _cellRenderers; - TextDirection _textDirection = TextDirection.ltr; - SfDataGridThemeData? _dataGridThemeData; - DataGridSource? _source; - List? _columns; - SelectionManagerBase? _rowSelectionManager; - DataGridController? _controller; - Animation? _swipingAnimation; - - @override - void initState() { - _columns = []; - _dataGridSettings = _DataGridSettings(); - _dataGridStateDetails = _onDataGridStateDetailsChanged; - _dataGridSettings.gridPaint = Paint(); - - _rowGenerator = _RowGenerator(dataGridStateDetails: _dataGridStateDetails); - _container = _VisualContainerHelper(rowGenerator: _rowGenerator); - _swipingAnimationController = AnimationController( - duration: const Duration(milliseconds: 200), vsync: this); - _setUp(); - _updateDataGridStateDetails(); - super.initState(); - } - - void _onDataGridTextDirectionChanged(TextDirection? newTextDirection) { - if (newTextDirection == null || _textDirection == newTextDirection) { - return; - } - - _textDirection = newTextDirection; - _dataGridSettings.textDirection = newTextDirection; - _container._needToSetHorizontalOffset = true; - } - - void _onDataGridThemeDataChanged(SfDataGridThemeData? newThemeData) { - if (newThemeData == null || _dataGridThemeData == newThemeData) { - return; - } - - final bool canUpdate = - _dataGridThemeData != null && _dataGridThemeData != newThemeData; - _dataGridThemeData = newThemeData; - _dataGridSettings.dataGridThemeData = newThemeData; - _updateDecoration(); - if (canUpdate) { - _container._refreshViewStyle(); - } - } - - void _onDataGridTextScaleFactorChanged(double textScaleFactor) { - if (textScaleFactor == _dataGridSettings.textScaleFactor) { - return; - } - - _dataGridSettings.textScaleFactor = textScaleFactor; - _dataGridSettings.headerRowHeight = widget.headerRowHeight.isNaN - ? (_dataGridSettings.textScaleFactor > 1.0) - ? _headerRowHeight * _dataGridSettings.textScaleFactor - : _headerRowHeight - : widget.headerRowHeight; - _dataGridSettings.rowHeight = widget.rowHeight.isNaN - ? (_dataGridSettings.textScaleFactor > 1.0) - ? _rowHeight * _dataGridSettings.textScaleFactor - : _rowHeight - : widget.rowHeight; - // Refreshes the default line size, column widths and row heights in initial - // `SfDataGrid` build. So, restricts the codes in initial loading by using - // the `_container._isGridLoaded` property. - if (_container._isGridLoaded) { - if (_dataGridSettings.columnWidthMode != ColumnWidthMode.none) { - _dataGridSettings.columnSizer._resetAutoCalculation(); - if (!_container._isDirty) { - // Refreshes the autofit columns only if don't have explicit width. - _dataGridSettings.columnSizer._refresh(0.0); - } - } - // Refresh header rows height. - _updateHeaderRowHeight(); - // Refresh data rows height. - _container.rowHeights.defaultLineSize = _dataGridSettings.rowHeight; - if (!_container._isDirty) { - _container.setRowHeights(); - } - } - } - - void _updateHeaderRowHeight() { - final _LineSizeCollection lineSizeCollection = - _container.columnWidths as _LineSizeCollection; - lineSizeCollection.suspendUpdates(); - final int headerIndex = - _GridIndexResolver.getHeaderIndex(_dataGridSettings); - if (_container.rowCount > 0) { - for (int i = 0; i <= headerIndex; i++) { - _container.rowHeights[i] = _dataGridSettings.headerRowHeight; - } - } - lineSizeCollection.resumeUpdates(); - _container.updateScrollBars(); - } - - void _setUp() { - _initializeDataGridDataSource(); - _initializeCellRendererCollection(); - - //DataGrid Controller - _controller = - _dataGridSettings.controller = widget.controller ?? DataGridController() - .._dataGridStateDetails = _dataGridStateDetails; - - _controller!._addDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); - - _dataGridSettings.verticalScrollController = - widget.verticalScrollController ?? ScrollController(); - _dataGridSettings.horizontalScrollController = - widget.horizontalScrollController ?? ScrollController(); - - //AutoFit controller initializing - _columnSizer = - _dataGridSettings.columnSizer = widget.columnSizer ?? ColumnSizer() - .._dataGridStateDetails = _dataGridStateDetails; - - //CurrentCell Manager initializing - _currentCell = _dataGridSettings.currentCell = - _CurrentCellManager(dataGridStateDetails: _dataGridStateDetails); - - //Selection Manager initializing - _rowSelectionManager = _dataGridSettings.rowSelectionManager = - widget.selectionManager ?? RowSelectionManager(); - _rowSelectionManager!._dataGridStateDetails = _dataGridStateDetails; - _controller!._addDataGridPropertyChangeListener( - _rowSelectionManager!._handleSelectionPropertyChanged); - - _initializeProperties(); - } - - @protected - void _gridLoaded() { - _container._refreshDefaultLineSize(); - _refreshContainerAndView(); - } - - @protected - void _refreshContainerAndView({bool isDataSourceChanged = false}) { - if (isDataSourceChanged) { - _rowSelectionManager! - ._updateSelectionController(isDataSourceChanged: isDataSourceChanged); - } - - _ensureSelectionProperties(); - _container - .._refreshHeaderLineCount() - .._updateRowAndColumnCount() - .._isGridLoaded = true; - } - - void _updateVisualDensity(VisualDensity visualDensity) { - if (_dataGridSettings.visualDensity == visualDensity) { - return; - } - - final Offset baseDensity = visualDensity.baseSizeAdjustment; - - _dataGridSettings - ..visualDensity = visualDensity - ..headerRowHeight += baseDensity.dy - ..rowHeight += baseDensity.dy; - - // Refreshes the default line size, and row heights in initial - // `SfDataGrid` build. So, restricts the codes in initial loading by using - // the `_container._isGridLoaded` property. - if (_container._isGridLoaded) { - // Refresh header rows height. - _updateHeaderRowHeight(); - // Refresh data rows height. - _container.rowHeights.defaultLineSize = _dataGridSettings.rowHeight; - } - } - - void _initializeDataGridDataSource() { - if (_source != widget.source) { - _removeDataGridSourceListeners(); - _source = widget.source; - _addDataGridSourceListeners(); - } - _source?._updateDataSource(); - } - - void _initializeProperties() { - if (!listEquals(_columns, widget.columns)) { - if (_columns != null) { - _columns! - ..clear() - ..addAll(widget.columns); - } - } - - _rowSelectionManager?._dataGridStateDetails = _dataGridStateDetails; - _currentCell._dataGridStateDetails = _dataGridStateDetails; - _columnSizer._dataGridStateDetails = _dataGridStateDetails; - _updateDataGridStateDetails(); - } - - void _initializeCellRendererCollection() { - _cellRenderers = {}; - _cellRenderers['TextField'] = - GridCellTextFieldRenderer(_dataGridStateDetails); - _cellRenderers['ColumnHeader'] = - GridHeaderCellRenderer(_dataGridStateDetails); - _cellRenderers['StackedHeader'] = - _GridStackedHeaderCellRenderer(_dataGridStateDetails); - - if (widget.onCellRenderersCreated != null) { - widget.onCellRenderersCreated!(_cellRenderers); - for (final MapEntry renderer - in _cellRenderers.entries) { - renderer.value._dataGridStateDetails = _dataGridStateDetails; - } - } - } - - void _processCellUpdate(RowColumnIndex rowColumnIndex) { - if (rowColumnIndex != RowColumnIndex(-1, -1)) { - final int rowIndex = _GridIndexResolver.resolveToRowIndex( - _dataGridSettings, rowColumnIndex.rowIndex); - final int columnIndex = _GridIndexResolver.resolveToScrollColumnIndex( - _dataGridSettings, rowColumnIndex.columnIndex); - - final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase dataRow) => dataRow.rowIndex == rowIndex); - - if (dataRow == null) { - return; - } - - dataRow._dataGridRow = _source!._effectiveRows[rowColumnIndex.rowIndex]; - dataRow._dataGridRowAdapter = _SfDataGridHelper.getDataGridRowAdapter( - _dataGridSettings, dataRow._dataGridRow!); - - final DataCellBase? dataCell = dataRow._visibleColumns.firstWhereOrNull( - (DataCellBase dataCell) => dataCell.columnIndex == columnIndex); - - if (dataCell == null) { - return; - } - - setState(() { - dataCell - .._isDirty = true - .._updateColumn(); - }); - } - } - - void _processUpdateDataSource() { - setState(() { - // Need to endEdit the editing [DataGridCell] before perform refreshing. - if (_dataGridSettings.currentCell.isEditing) { - _dataGridSettings.currentCell - ._onCellSubmit(_dataGridSettings, canRefresh: false); - } - - _initializeDataGridDataSource(); - _dataGridSettings.source = _source!; - - if (!listEquals(_columns, widget.columns)) { - if (widget.selectionMode != SelectionMode.none && - widget.navigationMode == GridNavigationMode.cell && - _rowSelectionManager != null) { - _rowSelectionManager!._onRowColumnChanged(-1, widget.columns.length); - } - - _resetColumn(); - } - - if (widget.selectionMode != SelectionMode.none && - widget.navigationMode == GridNavigationMode.cell && - _rowSelectionManager != null) { - _rowSelectionManager! - ._onRowColumnChanged(widget.source._effectiveRows.length, -1); - } - - if (widget.allowSwiping) { - _dataGridSettings.container.resetSwipeOffset(); - } - - if (widget.footer != null) { - final DataRowBase? footerRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase row) => - row.rowType == RowType.footerRow && row.rowIndex >= 0); - if (footerRow != null) { - // Need to reset the old footer row height in rowHeights collection. - _container.rowHeights[footerRow.rowIndex] = - _dataGridSettings.rowHeight; - } - } - - _container - .._updateRowAndColumnCount() - .._refreshView() - .._isDirty = true; - - // FLUT-3219 Need to reset the vertical offset when both the controller offset - // scrollbar offset are not identical - if (_dataGridSettings.verticalScrollController != null && - _dataGridSettings.verticalScrollController!.hasClients && - _dataGridSettings.verticalScrollController!.offset == 0 && - _dataGridSettings.container.verticalOffset > 0) { - _dataGridSettings.container.verticalOffset = 0; - _dataGridSettings.container.verticalScrollBar._value = 0; - } - }); - if (_dataGridSettings.source.shouldRecalculateColumnWidths()) { - _dataGridSettings.columnSizer._resetAutoCalculation(); - } - - if (_dataGridSettings.dataGridFocusNode != null && - !_dataGridSettings.dataGridFocusNode!.hasPrimaryFocus) { - _dataGridSettings.dataGridFocusNode!.requestFocus(); - } - } - - void _processSorting() { - setState(() { - _container - .._updateRowAndColumnCount() - .._refreshView() - .._isDirty = true; - }); - } - - void _resetColumn({bool clearEditing = true}) { - if (_columns != null) { - _columns! - ..clear() - ..addAll(widget.columns); - _dataGridSettings.columns = _columns!; - } - - for (final DataRowBase dataRow in _rowGenerator.items) { - if (!clearEditing && dataRow._isEditing) { - return; - } - - for (final DataCellBase dataCell in dataRow._visibleColumns) { - dataCell.columnIndex = -1; - } - } - - if (_textDirection == TextDirection.rtl) { - _container._needToSetHorizontalOffset = true; - } - _container._needToRefreshColumn = true; - } - - void _handleListeners() { - _processUpdateDataSource(); - } - - void _handleNotifyListeners({RowColumnIndex? rowColumnIndex}) { - if (rowColumnIndex != null) { - _processCellUpdate(rowColumnIndex); - } - if (rowColumnIndex == null) { - _processUpdateDataSource(); - } - } - - void _handleDataGridPropertyChangeListeners( - {RowColumnIndex? rowColumnIndex, - String? propertyName, - bool recalculateRowHeight = false}) { - if (propertyName == 'refreshRow') { - if (rowColumnIndex != null) { - // Need to endEdit before refreshing the row. - _dataGridSettings.currentCell - ._onCellSubmit(_dataGridSettings, canRefresh: false); - final int rowIndex = _GridIndexResolver.resolveToRowIndex( - _dataGridSettings, rowColumnIndex.rowIndex); - - final DataRowBase? dataRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase dataRow) => dataRow.rowIndex == rowIndex); - - if (dataRow == null) { - return; - } - setState(() { - dataRow - .._isDirty = true - .._rowIndexChanged(); - if (recalculateRowHeight) { - _dataGridSettings.container.rowHeightManager.setDirty(rowIndex); - _dataGridSettings.container - .._needToRefreshColumn = true - ..setRowHeights(); - } - }); - } - } - - if (rowColumnIndex == null && propertyName == 'Sorting') { - _processSorting(); - } - - if (propertyName == 'hoverOnCell') { - setState(() { - // To rebuild the datagrid on hovering the header cell. isDirtly already - // been set in header cell widget itself - }); - } - - if (propertyName == 'Swiping') { - setState(() { - // Need to end-edit the editing [DataGridCell] before swiping a - // [DataGridRow] or refreshing - _dataGridSettings.currentCell - ._onCellSubmit(_dataGridSettings, canRefresh: false); - _container._isDirty = true; - }); - } - - if (propertyName == 'editing' && rowColumnIndex != null) { - _processCellUpdate(rowColumnIndex); - } - } - - void _updateDataGridStateDetails() { - _dataGridSettings - ..source = _source! - ..columns = _columns! - ..textDirection = _textDirection - ..cellRenderers = _cellRenderers - ..container = _container - ..rowGenerator = _rowGenerator - ..visualDensity = _dataGridSettings.visualDensity - ..headerLineCount = _container._headerLineCount - ..onQueryRowHeight = widget.onQueryRowHeight - ..dataGridThemeData = _dataGridThemeData - ..gridLinesVisibility = widget.gridLinesVisibility - ..headerGridLinesVisibility = widget.headerGridLinesVisibility - ..columnWidthMode = widget.columnWidthMode - ..columnWidthCalculationRange = widget.columnWidthCalculationRange - ..selectionMode = widget.selectionMode - ..onSelectionChanged = widget.onSelectionChanged - ..onSelectionChanging = widget.onSelectionChanging - ..navigationMode = widget.navigationMode - ..onCurrentCellActivated = widget.onCurrentCellActivated - ..onCurrentCellActivating = widget.onCurrentCellActivating - ..onCellTap = widget.onCellTap - ..onCellDoubleTap = widget.onCellDoubleTap - ..onCellSecondaryTap = widget.onCellSecondaryTap - ..onCellLongPress = widget.onCellLongPress - ..frozenColumnsCount = widget.frozenColumnsCount - ..footerFrozenColumnsCount = widget.footerFrozenColumnsCount - ..frozenRowsCount = widget.frozenRowsCount - ..footerFrozenRowsCount = widget.footerFrozenRowsCount - ..allowSorting = widget.allowSorting - ..allowMultiColumnSorting = widget.allowMultiColumnSorting - ..allowTriStateSorting = widget.allowTriStateSorting - ..sortingGestureType = widget.sortingGestureType - ..showSortNumbers = widget.showSortNumbers - ..isControlKeyPressed = false - ..stackedHeaderRows = widget.stackedHeaderRows - ..isScrollbarAlwaysShown = widget.isScrollbarAlwaysShown - ..horizontalScrollPhysics = widget.horizontalScrollPhysics - ..verticalScrollPhysics = widget.verticalScrollPhysics - ..loadMoreViewBuilder = widget.loadMoreViewBuilder - ..refreshIndicatorDisplacement = widget.refreshIndicatorDisplacement - ..allowPullToRefresh = widget.allowPullToRefresh - ..refreshIndicatorStrokeWidth = widget.refreshIndicatorStrokeWidth - ..allowSwiping = widget.allowSwiping - ..swipeMaxOffset = widget.swipeMaxOffset - ..onSwipeStart = widget.onSwipeStart - ..onSwipeUpdate = widget.onSwipeUpdate - ..onSwipeEnd = widget.onSwipeEnd - ..startSwipeActionsBuilder = widget.startSwipeActionsBuilder - ..endSwipeActionsBuilder = widget.endSwipeActionsBuilder - ..swipingAnimationController ??= _swipingAnimationController - ..swipingAnimation ??= _swipingAnimation - ..highlightRowOnHover = widget.highlightRowOnHover - ..editingGestureType = widget.editingGestureType - ..allowEditing = widget.allowEditing - ..rowHeight = (widget.rowHeight.isNaN - ? _dataGridSettings.rowHeight.isNaN - ? _rowHeight - : _dataGridSettings.rowHeight - : widget.rowHeight) - ..headerRowHeight = (widget.headerRowHeight.isNaN - ? _dataGridSettings.headerRowHeight.isNaN - ? _headerRowHeight - : _dataGridSettings.headerRowHeight - : widget.headerRowHeight) - ..defaultColumnWidth = (widget.defaultColumnWidth.isNaN - ? _dataGridSettings.defaultColumnWidth - : widget.defaultColumnWidth) - ..footer = widget.footer - ..footerHeight = widget.footerHeight; - - if (widget.allowPullToRefresh) { - _dataGridSettings.refreshIndicatorKey ??= - GlobalKey(); - } - } - - _DataGridSettings _onDataGridStateDetailsChanged() => _dataGridSettings; - - void _updateProperties(SfDataGrid oldWidget) { - final bool isSourceChanged = widget.source != oldWidget.source; - final bool isDataSourceChanged = - !listEquals(oldWidget.source.rows, widget.source.rows); - final bool isColumnsChanged = - !listEquals(_columns, widget.columns); - final bool isSelectionManagerChanged = - oldWidget.selectionManager != widget.selectionManager || - oldWidget.selectionMode != widget.selectionMode; - final bool isColumnSizerChanged = - oldWidget.columnSizer != widget.columnSizer || - oldWidget.columnWidthMode != widget.columnWidthMode || - oldWidget.columnWidthCalculationRange != - widget.columnWidthCalculationRange; - final bool isDataGridControllerChanged = - oldWidget.controller != widget.controller; - final bool isFrozenColumnPaneChanged = oldWidget.frozenColumnsCount != - widget.frozenColumnsCount || - oldWidget.footerFrozenColumnsCount != widget.footerFrozenColumnsCount; - final bool isFrozenRowPaneChanged = - oldWidget.frozenRowsCount != widget.frozenRowsCount || - oldWidget.footerFrozenRowsCount != widget.footerFrozenRowsCount; - final bool isSortingChanged = oldWidget.allowSorting != widget.allowSorting; - final bool isMultiColumnSortingChanged = - oldWidget.allowMultiColumnSorting != widget.allowMultiColumnSorting; - final bool isShowSortNumbersChanged = - oldWidget.showSortNumbers != widget.showSortNumbers; - final bool isStackedHeaderRowsChanged = !listEquals( - oldWidget.stackedHeaderRows, widget.stackedHeaderRows); - final bool isPullToRefreshPropertiesChanged = - oldWidget.allowPullToRefresh != widget.allowPullToRefresh || - oldWidget.refreshIndicatorDisplacement != - widget.refreshIndicatorDisplacement || - oldWidget.refreshIndicatorStrokeWidth != - widget.refreshIndicatorStrokeWidth; - final bool isSwipingChanged = widget.allowSwiping != oldWidget.allowSwiping; - final bool isMaxSwipeOffsetChanged = - widget.swipeMaxOffset != oldWidget.swipeMaxOffset; - final bool isFooterRowChanged = widget.footer != oldWidget.footer || - widget.footerHeight != oldWidget.footerHeight; - - if (oldWidget.verticalScrollController != widget.verticalScrollController) { - _dataGridSettings.verticalScrollController = - widget.verticalScrollController ?? ScrollController(); - } - - if (oldWidget.horizontalScrollController != - widget.horizontalScrollController) { - _dataGridSettings.horizontalScrollController = - widget.horizontalScrollController ?? ScrollController(); - } - - final bool isEditingChanged = - oldWidget.allowEditing != widget.allowEditing || - oldWidget.editingGestureType != widget.editingGestureType; - - void refreshEditing() { - bool isEditingImpactAPIsChanged = isSourceChanged || - isDataSourceChanged || - oldWidget.stackedHeaderRows.length != widget.stackedHeaderRows.length; - - /// Need to end-edit the editing when sorting re-order the row on - /// refreshing - isEditingImpactAPIsChanged = - (isSortingChanged || isMultiColumnSortingChanged) && - (oldWidget.source.sortedColumns.isNotEmpty || - widget.source.sortedColumns.isNotEmpty || - oldWidget.source.sortedColumns.length != - widget.source.sortedColumns.length); - - if (isEditingChanged || - isEditingImpactAPIsChanged || - isSelectionManagerChanged || - oldWidget.navigationMode != widget.navigationMode) { - isEditingImpactAPIsChanged = isEditingImpactAPIsChanged || - isColumnsChanged || - isStackedHeaderRowsChanged; - - if (_dataGridSettings.currentCell.isEditing) { - _dataGridSettings.currentCell._onCellSubmit(_dataGridSettings, - canRefresh: !isEditingImpactAPIsChanged); - } - } - } - - void refreshFooterView() { - if (oldWidget.footer != null) { - final DataRowBase? footerRow = _rowGenerator.items.firstWhereOrNull( - (DataRowBase row) => - row.rowType == RowType.footerRow && row.rowIndex >= 0); - if (footerRow != null) { - if (isFooterRowChanged) { - // Need to reset the old footer row height in rowHeights collection. - _container.rowHeights[footerRow.rowIndex] = - _dataGridSettings.rowHeight; - // We remove the old footer view widget and recreate it in - // `ScrollViewWidget` when the footer property is changed. Thus updates - // the runtime changes of the footer view widget. - _rowGenerator.items.remove(footerRow); - } else if (isSourceChanged || - isDataSourceChanged || - isStackedHeaderRowsChanged) { - _container.rowHeights[footerRow.rowIndex] = - _dataGridSettings.rowHeight; - } - } - } - } - - if (isSourceChanged || - isColumnsChanged || - isDataSourceChanged || - isSelectionManagerChanged || - isColumnSizerChanged || - isDataGridControllerChanged || - isFrozenColumnPaneChanged || - isFrozenRowPaneChanged || - isSortingChanged || - isMultiColumnSortingChanged || - isShowSortNumbersChanged || - isStackedHeaderRowsChanged || - isSwipingChanged || - isFooterRowChanged || - oldWidget.rowHeight != widget.rowHeight || - oldWidget.headerRowHeight != widget.headerRowHeight || - oldWidget.defaultColumnWidth != widget.defaultColumnWidth || - oldWidget.navigationMode != widget.navigationMode) { - // Need to endEdit before refreshing - refreshEditing(); - - if (isSourceChanged) { - _initializeDataGridDataSource(); - } - if (isSortingChanged || isMultiColumnSortingChanged) { - if (!widget.allowSorting) { - _dataGridSettings.source - ..sortedColumns.clear() - .._updateDataSource(); - } else if (widget.allowSorting && !widget.allowMultiColumnSorting) { - while (_dataGridSettings.source.sortedColumns.length > 1) { - _dataGridSettings.source.sortedColumns.removeAt(0); - } - _dataGridSettings.source._updateDataSource(); - } - } - - if (isDataGridControllerChanged) { - oldWidget.controller?._addDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); - - _controller = - _dataGridSettings.controller = widget.controller ?? _controller! - .._dataGridStateDetails = _dataGridStateDetails; - - _controller?._addDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); - } - - if (oldWidget.columnSizer != widget.columnSizer) { - _columnSizer = - _dataGridSettings.columnSizer = widget.columnSizer ?? ColumnSizer() - .._dataGridStateDetails = _dataGridStateDetails; - } - - _initializeProperties(); - - if (isStackedHeaderRowsChanged || isColumnsChanged) { - _onStackedHeaderRowsPropertyChanged(oldWidget, widget); - } - - refreshFooterView(); - - _container._refreshDefaultLineSize(); - - _updateSelectionController(oldWidget, - isDataGridControlChanged: isDataGridControllerChanged, - isSelectionManagerChanged: isSelectionManagerChanged, - isSourceChanged: isSourceChanged, - isDataSourceChanged: isDataSourceChanged); - - _container._updateRowAndColumnCount(); - - if (isSourceChanged || - isColumnsChanged || - isColumnSizerChanged || - isFrozenColumnPaneChanged || - isSortingChanged || - isStackedHeaderRowsChanged || - widget.allowSorting && isMultiColumnSortingChanged || - widget.allowSorting && - widget.allowMultiColumnSorting && - isShowSortNumbersChanged) { - _resetColumn(clearEditing: false); - if (isColumnSizerChanged) { - _dataGridSettings.columnSizer._resetAutoCalculation(); - } - } - - if (isSourceChanged || - isDataSourceChanged || - isFrozenRowPaneChanged || - isStackedHeaderRowsChanged || - isSortingChanged || - widget.allowSorting && isMultiColumnSortingChanged) { - _container._refreshView(clearEditing: false); - } - - if (widget.allowSwiping || - (oldWidget.allowSwiping && !widget.allowSwiping)) { - if (isDataSourceChanged || - isColumnSizerChanged || - isMaxSwipeOffsetChanged || - isFrozenRowPaneChanged || - isFrozenColumnPaneChanged || - (oldWidget.allowSwiping && !widget.allowSwiping)) { - _container.resetSwipeOffset(); - } - } - - _container._isDirty = true; - } else { - if (oldWidget.gridLinesVisibility != widget.gridLinesVisibility || - oldWidget.allowTriStateSorting != widget.allowTriStateSorting || - oldWidget.headerGridLinesVisibility != - widget.headerGridLinesVisibility || - oldWidget.sortingGestureType != widget.sortingGestureType || - isEditingChanged) { - // Need to endEdit before refreshing - if (isEditingChanged && _dataGridSettings.currentCell.isEditing) { - _dataGridSettings.currentCell - ._onCellSubmit(_dataGridSettings, canRefresh: false); - } - _initializeProperties(); - _container._isDirty = true; - } else if (isPullToRefreshPropertiesChanged || isMaxSwipeOffsetChanged) { - _initializeProperties(); - } - } - } - - void _updateSelectionController(SfDataGrid oldWidget, - {bool isSelectionManagerChanged = false, - bool isDataGridControlChanged = false, - bool isSourceChanged = false, - bool isDataSourceChanged = false}) { - if (isSourceChanged) { - oldWidget.controller?._addDataGridPropertyChangeListener( - _rowSelectionManager!._handleSelectionPropertyChanged); - widget.controller?._addDataGridPropertyChangeListener( - _rowSelectionManager!._handleSelectionPropertyChanged); - } - - if (isSelectionManagerChanged) { - _rowSelectionManager = _dataGridSettings.rowSelectionManager = - widget.selectionManager ?? _rowSelectionManager! - .._dataGridStateDetails = _dataGridStateDetails; - } - - if (isSourceChanged) { - _rowSelectionManager!.handleDataGridSourceChanges(); - } - - _rowSelectionManager?._updateSelectionController( - isSelectionModeChanged: oldWidget.selectionMode != widget.selectionMode, - isNavigationModeChanged: - oldWidget.navigationMode != widget.navigationMode, - isDataSourceChanged: isDataSourceChanged); - - if (isDataGridControlChanged) { - _ensureSelectionProperties(); - } - } - - void _onStackedHeaderRowsPropertyChanged( - SfDataGrid oldWidget, SfDataGrid widget) { - _container._refreshHeaderLineCount(); - if (oldWidget.stackedHeaderRows.isNotEmpty) { - _rowGenerator.items.removeWhere( - (DataRowBase row) => row.rowType == RowType.stackedHeaderRow); - } - if (widget.onQueryRowHeight != null) { - _container.rowHeightManager.reset(); - } - - // FlUT-3851 Needs to reset the vertical and horizontal offset when both the - // controller's offset and scrollbar's offset are not identical. - if ((oldWidget.stackedHeaderRows.isNotEmpty && - widget.stackedHeaderRows.isEmpty) || - (oldWidget.stackedHeaderRows.isEmpty && - widget.stackedHeaderRows.isNotEmpty)) { - if (_dataGridSettings.verticalScrollController!.hasClients && - _dataGridSettings.container.verticalOffset > 0) { - _dataGridSettings.container.verticalOffset = 0; - _dataGridSettings.container.verticalScrollBar._value = 0; - } - if (_dataGridSettings.horizontalScrollController!.hasClients && - _dataGridSettings.container.horizontalOffset > 0) { - _dataGridSettings.container.horizontalOffset = 0; - _dataGridSettings.container.horizontalScrollBar._value = 0; - } - } - } - - void _ensureSelectionProperties() { - if (_controller!.selectedRows.isNotEmpty) { - _rowSelectionManager?.onSelectedRowsChanged(); - } - - if (_controller!.selectedRow != null) { - _rowSelectionManager?.onSelectedRowChanged(); - } - - if (_controller!.selectedIndex != -1) { - _rowSelectionManager?.onSelectedIndexChanged(); - } - } - - void _updateBoxPainter() { - if (widget.selectionMode == SelectionMode.multiple && - widget.navigationMode == GridNavigationMode.row) { - _dataGridSettings.configuration ??= - createLocalImageConfiguration(context); - if (_dataGridSettings.boxPainter == null) { - _updateDecoration(); - } - } - } - - void _updateDecoration() { - final BorderSide borderSide = - BorderSide(color: _dataGridThemeData!.currentCellStyle.borderColor); - final BoxDecoration decoration = BoxDecoration( - border: Border( - bottom: borderSide, - top: borderSide, - left: borderSide, - right: borderSide)); - - _dataGridSettings.boxPainter = decoration.createBoxPainter(); - } - - void _addDataGridSourceListeners() { - _source?._addDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); - _source?._addDataGridSourceListener(_handleNotifyListeners); - _source?.addListener(_handleListeners); - } - - void _removeDataGridSourceListeners() { - _source?._removeDataGridPropertyChangeListener( - _handleDataGridPropertyChangeListeners); - _source?._removeDataGridSourceListener(_handleNotifyListeners); - _source?.removeListener(_handleListeners); - } - - /// Show the refresh indicator and call the - /// [DataGridSource.handleRefresh]. - /// - /// To access this method, create the [SfDataGrid] with a - /// [GlobalKey]. - /// - /// The future returned from this method completes when the - /// [DataGridSource.handleRefresh] method’s future completes. - /// - /// By default, if you call this method without any parameter, - /// [RefreshIndicator] will be shown. If you want to disable the - /// [RefreshIndicator] and call the [DataGridSource.handleRefresh] method - /// alone, pass the parameter as `false`. - Future refresh([bool showRefreshIndicator = true]) async { - if (_dataGridSettings.allowPullToRefresh && - _dataGridSettings.refreshIndicatorKey != null) { - if (showRefreshIndicator) { - await _dataGridSettings.refreshIndicatorKey!.currentState?.show(); - } else { - await _dataGridSettings.source.handleRefresh(); - } - } - } - - @override - void didChangeDependencies() { - final ThemeData themeData = Theme.of(context); - _dataGridSettings._isDesktop = kIsWeb || - themeData.platform == TargetPlatform.macOS || - themeData.platform == TargetPlatform.windows || - themeData.platform == TargetPlatform.linux; - _onDataGridTextDirectionChanged(Directionality.of(context)); - _onDataGridThemeDataChanged(SfDataGridTheme.of(context)); - _onDataGridTextScaleFactorChanged(MediaQuery.textScaleFactorOf(context)); - _updateVisualDensity(themeData.visualDensity); - _dataGridSettings.defaultColumnWidth = widget.defaultColumnWidth.isNaN - ? _dataGridSettings._isDesktop - ? 100 - : 90 - : widget.defaultColumnWidth; - super.didChangeDependencies(); - } - - @override - void didUpdateWidget(SfDataGrid oldWidget) { - super.didUpdateWidget(oldWidget); - _updateProperties(oldWidget); - } - - @override - Widget build(BuildContext context) { - if (_dataGridSettings._isDesktop) { - _updateBoxPainter(); - } - - return LayoutBuilder( - builder: (BuildContext _context, BoxConstraints _constraints) { - final double _measuredHeight = _dataGridSettings.viewHeight = - _constraints.maxHeight.isInfinite - ? _minHeight - : _constraints.maxHeight; - final double _measuredWidth = _dataGridSettings.viewWidth = - _constraints.maxWidth.isInfinite ? _minWidth : _constraints.maxWidth; - - if (!_container._isGridLoaded) { - _gridLoaded(); - if (_textDirection == TextDirection.rtl) { - _container._needToSetHorizontalOffset = true; - } - _container._isDirty = true; - _columnSizer._isColumnSizerLoadedInitially = true; - } - - return _ScrollViewWidget( - dataGridStateDetails: _dataGridStateDetails, - width: _measuredWidth, - height: _measuredHeight, - ); - }); - } - - @override - void dispose() { - _removeDataGridSourceListeners(); - _controller?.removeListener(_handleDataGridPropertyChangeListeners); - _dataGridSettings - ..gridPaint = null - ..boxPainter = null - ..configuration = null; - _dataGridThemeData = null; - if (_swipingAnimationController != null) { - _swipingAnimationController!.dispose(); - _swipingAnimationController = null; - } - super.dispose(); - } -} - -class _DataGridSettings { - // late assignable values and non-null - late Map cellRenderers; - late DataGridSource source; - late List columns; - late _VisualContainerHelper container; - late _RowGenerator rowGenerator; - late ColumnWidthMode columnWidthMode; - late ColumnWidthCalculationRange columnWidthCalculationRange; - late ColumnSizer columnSizer; - late SelectionManagerBase rowSelectionManager; - late DataGridController controller; - late _CurrentCellManager currentCell; - late double viewWidth; - late double viewHeight; - late ScrollPhysics horizontalScrollPhysics; - late ScrollPhysics verticalScrollPhysics; - - int headerLineCount = 0; - int frozenColumnsCount = 0; - int footerFrozenColumnsCount = 0; - int frozenRowsCount = 0; - int footerFrozenRowsCount = 0; - double rowHeight = double.nan; - double headerRowHeight = double.nan; - double defaultColumnWidth = double.nan; - double swipingOffset = 0.0; - double refreshIndicatorDisplacement = 40.0; - double refreshIndicatorStrokeWidth = 2.0; - double swipeMaxOffset = 200.0; - double textScaleFactor = 1.0; - double footerHeight = 49.0; - - bool allowSorting = false; - bool allowMultiColumnSorting = false; - bool isControlKeyPressed = false; - bool allowTriStateSorting = false; - bool showSortNumbers = false; - bool _isDesktop = false; - bool isScrollbarAlwaysShown = false; - bool allowPullToRefresh = false; - bool allowSwiping = false; - // This flag is used to restrict the scrolling while updating the swipe offset - // of a row that already swiped in view. - bool isSwipingApplied = false; - bool highlightRowOnHover = true; - bool allowEditing = false; - - SortingGestureType sortingGestureType = SortingGestureType.tap; - GridNavigationMode navigationMode = GridNavigationMode.row; - TextDirection textDirection = TextDirection.ltr; - GridLinesVisibility gridLinesVisibility = GridLinesVisibility.horizontal; - GridLinesVisibility headerGridLinesVisibility = - GridLinesVisibility.horizontal; - SelectionMode selectionMode = SelectionMode.none; - ColumnResizeMode columnResizeMode = ColumnResizeMode.onResize; - ScrollDirection scrollingState = ScrollDirection.idle; - EditingGestureType editingGestureType = EditingGestureType.doubleTap; - - Paint? gridPaint; - BoxPainter? boxPainter; - FocusNode? dataGridFocusNode; - ImageConfiguration? configuration; - GlobalKey? refreshIndicatorKey; - Animation? swipingAnimation; - AnimationController? swipingAnimationController; - List stackedHeaderRows = []; - VisualDensity visualDensity = VisualDensity.adaptivePlatformDensity; - SfDataGridThemeData? dataGridThemeData; - ScrollController? verticalScrollController; - ScrollController? horizontalScrollController; - DataGridSwipeStartCallback? onSwipeStart; - DataGridSwipeUpdateCallback? onSwipeUpdate; - DataGridSwipeEndCallback? onSwipeEnd; - DataGridSwipeActionsBuilder? startSwipeActionsBuilder; - DataGridSwipeActionsBuilder? endSwipeActionsBuilder; - QueryRowHeightCallback? onQueryRowHeight; - SelectionChangingCallback? onSelectionChanging; - SelectionChangedCallback? onSelectionChanged; - CurrentCellActivatedCallback? onCurrentCellActivated; - CurrentCellActivatingCallback? onCurrentCellActivating; - DataGridCellTapCallback? onCellTap; - DataGridCellDoubleTapCallback? onCellDoubleTap; - DataGridCellTapCallback? onCellSecondaryTap; - DataGridCellLongPressCallback? onCellLongPress; - LoadMoreViewBuilder? loadMoreViewBuilder; - Widget? footer; -} diff --git a/packages/syncfusion_flutter_datagrid_export/CHANGELOG.md b/packages/syncfusion_flutter_datagrid_export/CHANGELOG.md new file mode 100644 index 000000000..d7e173de7 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/CHANGELOG.md @@ -0,0 +1,8 @@ +## Unreleased + +Intial release. + +**Features** + +* Provided the support to export the column headers, rows, stacked headers and table summary rows to Excel and Pdf format. +* Provided the support to fit all the columns on the page in Pdf exporting. \ No newline at end of file diff --git a/packages/syncfusion_flutter_datagrid_export/LICENSE b/packages/syncfusion_flutter_datagrid_export/LICENSE new file mode 100644 index 000000000..09ff7f559 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/LICENSE @@ -0,0 +1,12 @@ +Syncfusion License + +Syncfusion Flutter DataGrid Export package is available under the Syncfusion Essential Studio program, and can be licensed either under the Syncfusion Community License Program or the Syncfusion commercial license. + +To be qualified for the Syncfusion Community License Program you must have a gross revenue of less than one (1) million U.S. dollars ($1,000,000.00 USD) per year and have less than five (5) developers in your organization, and agree to be bound by Syncfusion’s terms and conditions. + +Customers who do not qualify for the community license can contact sales@syncfusion.com for commercial licensing options. + +Under no circumstances can you use this product without (1) either a Community License or a commercial license and (2) without agreeing and abiding by Syncfusion’s license containing all terms and conditions. + +The Syncfusion license that contains the terms and conditions can be found at +https://www.syncfusion.com/content/downloads/syncfusion_license.pdf diff --git a/packages/syncfusion_flutter_datagrid_export/README.md b/packages/syncfusion_flutter_datagrid_export/README.md new file mode 100644 index 000000000..53743cbaa --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/README.md @@ -0,0 +1,201 @@ +# Flutter DataGrid Export Library + +Syncfusion's Flutter DataGrid export library is used to export the Flutter [DataGrid](https://pub.dev/packages/syncfusion_flutter_datagrid) to Excel and PDF formats. It provides support to export DataGrid content, such as headers, rows, stacked header rows, and table summary rows, with the several customization options. + +**Disclaimer:** This is a commercial package. To use this package, you need to have either a Syncfusion commercial license or a [free Syncfusion Community License](https://www.syncfusion.com/products/communitylicense). For more details, please check the [LICENSE](https://github.com/syncfusion/flutter-examples/blob/master/LICENSE) file. + +## Table of contents +- [Get the demo application](#get-the-demo-application) +- [Useful links](#other-useful-links) +- [Installation](#installation) +- [Getting started](#getting-started) + - [Add the DataGrid to the application.](#add-the-datagrid-to-the-application) + - [Add GlobalKey for the DataGrid.](#add-globalkey-for-the-datagrid) + - [Export the DataGrid to Excel.](#export-the-datagrid-to-excel) + - [Export the DataGrid to PDF.](#export-the-datagrid-to-pdf) +- [Support and feedback](#support-and-feedback) +- [About Syncfusion](#about-syncfusion) + +## Get the demo application + +Explore the full capabilities of our Flutter widgets on your device by installing our sample browser applications from the following app stores, and view sample code in GitHub. + +

+ + + +

+

+ + + +

+

+ +

+ +## Useful links + +Check out the following resources to learn more about the Syncfusion Flutter DataGrid. + +* [Syncfusion Flutter DataGrid product page](https://www.syncfusion.com/flutter-widgets/flutter-datagrid) +* [User guide documentation](https://help.syncfusion.com/flutter/datagrid/overview) + +## Installation + +Install the latest version from [pub](https://pub.dartlang.org/packages/syncfusion_flutter_datagrid_export#-installing-tab-). + +## Getting started + +Import the following package. + +```dart +import 'package:syncfusion_flutter_datagrid_export/export.dart'; +``` + +### Add the DataGrid to the application + +Follow the steps provided in [this](https://help.syncfusion.com/flutter/datagrid/getting-started) link to add the DataGrid to your application. + +To export, add two buttons in the Row widget. Then, add the DataGrid in the Expanded widget and wrap both the buttons and DataGrid inside the Column widget. + +```dart + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + 'Syncfusion Flutter DataGrid Export', + overflow: TextOverflow.ellipsis, + ), + ), + body: Column( + children: [ + Container( + margin: const EdgeInsets.all(12.0), + child: Row( + children: [ + SizedBox( + height: 40.0, + width: 150.0, + child: MaterialButton( + color: Colors.blue, + child: const Center( + child: Text( + 'Export to Excel', + style: TextStyle(color: Colors.white), + )), + onPressed: exportDataGridToExcel), + ), + const Padding(padding: EdgeInsets.all(20)), + SizedBox( + height: 40.0, + width: 150.0, + child: MaterialButton( + color: Colors.blue, + child: const Center( + child: Text( + 'Export to PDF', + style: TextStyle(color: Colors.white), + )), + onPressed: exportDataGridToPdf), + ), + ], + ), + ), + Expanded( + child: SfDataGrid( + source: employeeDataSource, + columns: [ + GridColumn( + columnName: 'ID', + label: Container( + padding: const EdgeInsets.all(16.0), + alignment: Alignment.center, + child: const Text( + 'ID', + ))), + GridColumn( + columnName: 'Name', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text('Name'))), + GridColumn( + columnName: 'Designation', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text( + 'Designation', + overflow: TextOverflow.ellipsis, + ))), + GridColumn( + columnName: 'Salary', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text('Salary'))), + ], + ), + ), + ], + ), + ); + } + +``` + +### Add GlobalKey for the DataGrid + +After adding the DataGrid, create the GlobalKey with the [SfDataGridState](https://pub.dev/documentation/syncfusion_flutter_datagrid/latest/datagrid/SfDataGridState-class.html) class. Exporting related methods is available via the SfDataGridState class. + +Set the created GlobalKey to the DataGrid. + +```dart +final GlobalKey _key = GlobalKey(); + +... + child: SfDataGrid( + key: _key, +... +``` + +### Export the DataGrid to Excel + +Use the `exportToExcelWorkbook` method from `_key.currentState!` and call this method in a button click. + +```dart + Future exportDataGridToExcel() async { + final Workbook workbook = _key.currentState!.exportToExcelWorkbook(); + final List bytes = workbook.saveAsStream(); + File('DataGrid.xlsx').writeAsBytes(bytes); + workbook.dispose(); + } +``` + +### Export the DataGrid to PDF + +Use the `exportToPdfDocument` method from `_key.currentState!` and call this method in a button click. + +```dart + Future exportDataGridToPdf() async { + final PdfDocument document = + _key.currentState!.exportToPdfDocument(); + + final List bytes = document.save(); + File('DataGrid.pdf').writeAsBytes(bytes); + document.dispose(); + } +``` + +## Support and feedback + +* If you have any questions, you can contact the [Syncfusion support team](https://www.syncfusion.com/support/directtrac/incidents/newincident) or post them to the [community forums](https://www.syncfusion.com/forums). You can also submit a feature request or a bug report through our [feedback portal](https://www.syncfusion.com/feedback/flutter). +* To renew your subscription, click [renew](https://www.syncfusion.com/sales/products) or contact our sales team at sales@syncfusion.com | Toll Free: 1-888-9 DOTNET. + +## About Syncfusion + +Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 22,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. + +Today, we provide 1,600+ 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), and [Blazor](https://www.syncfusion.com/blazor-components)) , mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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_datagrid_export/analysis_options.yaml b/packages/syncfusion_flutter_datagrid_export/analysis_options.yaml new file mode 100644 index 000000000..e68d44916 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:syncfusion_flutter_core/analysis_options.yaml + +analyzer: + errors: + lines_longer_than_80_chars: ignore + include_file_not_found: ignore + invalid_dependency: ignore + avoid_as: ignore diff --git a/packages/syncfusion_flutter_datagrid_export/example/README.md b/packages/syncfusion_flutter_datagrid_export/example/README.md new file mode 100644 index 000000000..a13562602 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/.gitignore b/packages/syncfusion_flutter_datagrid_export/example/android/.gitignore new file mode 100644 index 000000000..0a741cb43 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/build.gradle b/packages/syncfusion_flutter_datagrid_export/example/android/app/build.gradle new file mode 100644 index 000000000..6ca23448e --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/build.gradle @@ -0,0 +1,59 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 30 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + minSdkVersion 16 + targetSdkVersion 30 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..34dd77efb --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 000000000..e793a000d --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 000000000..f74085f3f --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values-night/styles.xml b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..449a9f930 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..d74aa35c2 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000..c208884f3 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/build.gradle b/packages/syncfusion_flutter_datagrid_export/example/android/build.gradle new file mode 100644 index 000000000..9b6ed06eb --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + ext.kotlin_version = '1.3.50' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/gradle.properties b/packages/syncfusion_flutter_datagrid_export/example/android/gradle.properties new file mode 100644 index 000000000..94adc3a3f --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_datagrid_export/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..bc6a58afd --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/packages/syncfusion_flutter_datagrid_export/example/android/settings.gradle b/packages/syncfusion_flutter_datagrid_export/example/android/settings.gradle new file mode 100644 index 000000000..44e62bcf0 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/syncfusion_officechart/example/officechart_example.iml b/packages/syncfusion_flutter_datagrid_export/example/example.iml similarity index 100% rename from packages/syncfusion_officechart/example/officechart_example.iml rename to packages/syncfusion_flutter_datagrid_export/example/example.iml diff --git a/packages/syncfusion_flutter_datagrid_export/example/ios/.gitignore b/packages/syncfusion_flutter_datagrid_export/example/ios/.gitignore new file mode 100644 index 000000000..151026b91 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/ios/.gitignore @@ -0,0 +1,33 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000..9367d483e --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/packages/syncfusion_officechart/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/ios/Flutter/Debug.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/Debug.xcconfig diff --git a/packages/syncfusion_officechart/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/ios/Flutter/Release.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/ios/Flutter/Release.xcconfig diff --git a/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..c6759a6e8 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,471 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officechart/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/AppDelegate.swift similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/AppDelegate.swift rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/AppDelegate.swift diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/syncfusion_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/syncfusion_officechart/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Info.plist new file mode 100644 index 000000000..a060db61e --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/syncfusion_officechart/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Runner-Bridging-Header.h rename to packages/syncfusion_flutter_datagrid_export/example/ios/Runner/Runner-Bridging-Header.h diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart new file mode 100644 index 000000000..7733b6118 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_mobile.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:open_file/open_file.dart' as open_file; +import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart' + as path_provider_interface; + +///To save the Excel file in the Mobile and Desktop platforms. +Future saveAndLaunchFile(List bytes, String fileName) async { + String? path; + if (Platform.isAndroid || + Platform.isIOS || + Platform.isLinux || + Platform.isWindows) { + final Directory directory = + await path_provider.getApplicationSupportDirectory(); + path = directory.path; + } else { + path = await path_provider_interface.PathProviderPlatform.instance + .getApplicationSupportPath(); + } + + final String fileLocation = + Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'; + final File file = File(fileLocation); + await file.writeAsBytes(bytes, flush: true); + + if (Platform.isAndroid || Platform.isIOS) { + await open_file.OpenFile.open(fileLocation); + } else if (Platform.isWindows) { + await Process.run('start', [fileLocation], runInShell: true); + } else if (Platform.isMacOS) { + await Process.run('open', [fileLocation], runInShell: true); + } else if (Platform.isLinux) { + await Process.run('xdg-open', [fileLocation], runInShell: true); + } +} diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart new file mode 100644 index 000000000..59f773f34 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/helper/save_file_web.dart @@ -0,0 +1,12 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:html'; + +///To save the excel sheet in the web platform. +Future saveAndLaunchFile(List bytes, String fileName) async { + AnchorElement( + href: + 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') + ..setAttribute('download', fileName) + ..click(); +} diff --git a/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart b/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart new file mode 100644 index 000000000..175d72809 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/lib/main.dart @@ -0,0 +1,222 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +// External package imports +import 'package:syncfusion_flutter_datagrid/datagrid.dart'; +import 'package:syncfusion_flutter_datagrid_export/export.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; +import 'package:syncfusion_flutter_xlsio/xlsio.dart' + hide Alignment, Column, Row; + +// Local import +import 'helper/save_file_mobile.dart' + if (dart.library.html) 'helper/save_file_web.dart' as helper; + +void main() { + runApp(MyApp()); +} + +/// The application that contains datagrid on it. +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Syncfusion DataGrid Demo', + theme: ThemeData(primarySwatch: Colors.blue), + home: const MyHomePage(), + ); + } +} + +/// The home page of the application which hosts the datagrid. +class MyHomePage extends StatefulWidget { + /// Creates the home page. + const MyHomePage({Key? key}) : super(key: key); + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + List employees = []; + late EmployeeDataSource employeeDataSource; + + final GlobalKey _key = GlobalKey(); + + @override + void initState() { + super.initState(); + employees = getEmployeeData(); + employeeDataSource = EmployeeDataSource(employeeData: employees); + } + + Future exportDataGridToExcel() async { + final Workbook workbook = _key.currentState!.exportToExcelWorkbook(); + + final List bytes = workbook.saveAsStream(); + workbook.dispose(); + + await helper.saveAndLaunchFile(bytes, 'DataGrid.xlsx'); + } + + Future exportDataGridToPdf() async { + final PdfDocument document = + _key.currentState!.exportToPdfDocument(fitAllColumnsInOnePage: true); + + final List bytes = document.save(); + await helper.saveAndLaunchFile(bytes, 'DataGrid.pdf'); + document.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text( + 'Syncfusion Flutter DataGrid Export', + overflow: TextOverflow.ellipsis, + ), + ), + body: Column( + children: [ + Container( + margin: const EdgeInsets.all(12.0), + child: Row( + children: [ + SizedBox( + height: 40.0, + width: 150.0, + child: MaterialButton( + color: Colors.blue, + child: const Center( + child: Text( + 'Export to Excel', + style: TextStyle(color: Colors.white), + )), + onPressed: exportDataGridToExcel), + ), + const Padding(padding: EdgeInsets.all(20)), + SizedBox( + height: 40.0, + width: 150.0, + child: MaterialButton( + color: Colors.blue, + child: const Center( + child: Text( + 'Export to PDF', + style: TextStyle(color: Colors.white), + )), + onPressed: exportDataGridToPdf), + ), + ], + ), + ), + Expanded( + child: SfDataGrid( + key: _key, + source: employeeDataSource, + columns: [ + GridColumn( + columnName: 'ID', + label: Container( + padding: const EdgeInsets.all(16.0), + alignment: Alignment.center, + child: const Text( + 'ID', + ))), + GridColumn( + columnName: 'Name', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text('Name'))), + GridColumn( + columnName: 'Designation', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text( + 'Designation', + overflow: TextOverflow.ellipsis, + ))), + GridColumn( + columnName: 'Salary', + label: Container( + padding: const EdgeInsets.all(8.0), + alignment: Alignment.center, + child: const Text('Salary'))), + ], + ), + ), + ], + ), + ); + } + + List getEmployeeData() { + return [ + Employee(10001, 'James', 'Project Lead', 20000), + Employee(10002, 'Kathryn', 'Manager', 30000), + Employee(10003, 'Lara', 'Developer', 15000), + Employee(10004, 'Michael', 'Designer', 15000), + Employee(10005, 'Martin', 'Developer', 15000), + Employee(10006, 'Newberry', 'Developer', 15000), + Employee(10007, 'Balnc', 'Developer', 15000), + Employee(10008, 'Perry', 'Developer', 15000), + Employee(10009, 'Gable', 'Developer', 15000), + Employee(10010, 'Grimes', 'Developer', 15000) + ]; + } +} + +/// Custom business object class which contains properties to hold the detailed +/// information about the employee which will be rendered in datagrid. +class Employee { + /// Creates the employee class with required details. + Employee(this.id, this.name, this.designation, this.salary); + + /// Id of an employee. + final int id; + + /// Name of an employee. + final String name; + + /// Designation of an employee. + final String designation; + + /// Salary of an employee. + final int salary; +} + +/// An object to set the employee collection data source to the datagrid. This +/// is used to map the employee data to the datagrid widget. +class EmployeeDataSource extends DataGridSource { + /// Creates the employee data source class with required details. + EmployeeDataSource({required List employeeData}) { + _employeeData = employeeData + .map((Employee e) => DataGridRow(cells: [ + DataGridCell(columnName: 'ID', value: e.id), + DataGridCell(columnName: 'Name', value: e.name), + DataGridCell( + columnName: 'Designation', value: e.designation), + DataGridCell(columnName: 'Salary', value: e.salary), + ])) + .toList(); + } + + List _employeeData = []; + + @override + List get rows => _employeeData; + + @override + DataGridRowAdapter buildRow(DataGridRow row) { + return DataGridRowAdapter( + cells: row.getCells().map((DataGridCell cell) { + return Container( + alignment: Alignment.center, + padding: const EdgeInsets.all(8.0), + child: Text(cell.value.toString()), + ); + }).toList()); + } +} diff --git a/packages/syncfusion_officechart/example/linux/.gitignore b/packages/syncfusion_flutter_datagrid_export/example/linux/.gitignore similarity index 100% rename from packages/syncfusion_officechart/example/linux/.gitignore rename to packages/syncfusion_flutter_datagrid_export/example/linux/.gitignore diff --git a/packages/syncfusion_officechart/example/linux/CMakeLists.txt b/packages/syncfusion_flutter_datagrid_export/example/linux/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officechart/example/linux/CMakeLists.txt rename to packages/syncfusion_flutter_datagrid_export/example/linux/CMakeLists.txt diff --git a/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/CMakeLists.txt b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/CMakeLists.txt new file mode 100644 index 000000000..33fd5801e --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,87 @@ +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.cc similarity index 100% rename from packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.cc rename to packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.cc diff --git a/packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from packages/syncfusion_officechart/example/linux/flutter/generated_plugin_registrant.h rename to packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugin_registrant.h diff --git a/packages/syncfusion_officechart/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugins.cmake similarity index 100% rename from packages/syncfusion_officechart/example/linux/flutter/generated_plugins.cmake rename to packages/syncfusion_flutter_datagrid_export/example/linux/flutter/generated_plugins.cmake diff --git a/packages/syncfusion_officechart/example/linux/main.cc b/packages/syncfusion_flutter_datagrid_export/example/linux/main.cc similarity index 100% rename from packages/syncfusion_officechart/example/linux/main.cc rename to packages/syncfusion_flutter_datagrid_export/example/linux/main.cc diff --git a/packages/syncfusion_officechart/example/linux/my_application.cc b/packages/syncfusion_flutter_datagrid_export/example/linux/my_application.cc similarity index 100% rename from packages/syncfusion_officechart/example/linux/my_application.cc rename to packages/syncfusion_flutter_datagrid_export/example/linux/my_application.cc diff --git a/packages/syncfusion_officechart/example/linux/my_application.h b/packages/syncfusion_flutter_datagrid_export/example/linux/my_application.h similarity index 100% rename from packages/syncfusion_officechart/example/linux/my_application.h rename to packages/syncfusion_flutter_datagrid_export/example/linux/my_application.h diff --git a/packages/syncfusion_officechart/example/macos/.gitignore b/packages/syncfusion_flutter_datagrid_export/example/macos/.gitignore similarity index 100% rename from packages/syncfusion_officechart/example/macos/.gitignore rename to packages/syncfusion_flutter_datagrid_export/example/macos/.gitignore diff --git a/packages/syncfusion_officechart/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/packages/syncfusion_officechart/example/macos/Flutter/Flutter-Release.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/macos/Flutter/Flutter-Release.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 000000000..0d56f519c --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import path_provider_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) +} diff --git a/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.pbxproj rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcodeproj/project.pbxproj diff --git a/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officechart/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officechart/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/AppDelegate.swift similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/AppDelegate.swift rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/AppDelegate.swift diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/packages/syncfusion_officechart/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/packages/syncfusion_officechart/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/packages/syncfusion_officechart/example/macos/Runner/Configs/Debug.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Configs/Debug.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/Debug.xcconfig diff --git a/packages/syncfusion_officechart/example/macos/Runner/Configs/Release.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Configs/Release.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/Release.xcconfig diff --git a/packages/syncfusion_officechart/example/macos/Runner/Configs/Warnings.xcconfig b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Configs/Warnings.xcconfig rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/packages/syncfusion_officechart/example/macos/Runner/DebugProfile.entitlements b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/DebugProfile.entitlements rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/DebugProfile.entitlements diff --git a/packages/syncfusion_officechart/example/macos/Runner/Info.plist b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Info.plist similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Info.plist rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Info.plist diff --git a/packages/syncfusion_officechart/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/MainFlutterWindow.swift rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/MainFlutterWindow.swift diff --git a/packages/syncfusion_officechart/example/macos/Runner/Release.entitlements b/packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Release.entitlements similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner/Release.entitlements rename to packages/syncfusion_flutter_datagrid_export/example/macos/Runner/Release.entitlements diff --git a/packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml b/packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml new file mode 100644 index 000000000..9533d9489 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/example/pubspec.yaml @@ -0,0 +1,22 @@ +name: example +description: A new Flutter project. + +publish_to: "none" + +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + cupertino_icons: ^1.0.2 + flutter: + sdk: flutter + open_file: ^3.2.1 + path_provider: ^2.0.2 + syncfusion_flutter_datagrid_export: + path: ../ + + +flutter: + uses-material-design: true diff --git a/packages/syncfusion_officechart/example/web/favicon.png b/packages/syncfusion_flutter_datagrid_export/example/web/favicon.png similarity index 100% rename from packages/syncfusion_officechart/example/web/favicon.png rename to packages/syncfusion_flutter_datagrid_export/example/web/favicon.png diff --git a/packages/syncfusion_officechart/example/web/icons/Icon-192.png b/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-192.png similarity index 100% rename from packages/syncfusion_officechart/example/web/icons/Icon-192.png rename to packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-192.png diff --git a/packages/syncfusion_officechart/example/web/icons/Icon-512.png b/packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-512.png similarity index 100% rename from packages/syncfusion_officechart/example/web/icons/Icon-512.png rename to packages/syncfusion_flutter_datagrid_export/example/web/icons/Icon-512.png diff --git a/packages/syncfusion_officechart/example/web/index.html b/packages/syncfusion_flutter_datagrid_export/example/web/index.html similarity index 100% rename from packages/syncfusion_officechart/example/web/index.html rename to packages/syncfusion_flutter_datagrid_export/example/web/index.html diff --git a/packages/syncfusion_officechart/example/web/manifest.json b/packages/syncfusion_flutter_datagrid_export/example/web/manifest.json similarity index 100% rename from packages/syncfusion_officechart/example/web/manifest.json rename to packages/syncfusion_flutter_datagrid_export/example/web/manifest.json diff --git a/packages/syncfusion_officechart/example/windows/.gitignore b/packages/syncfusion_flutter_datagrid_export/example/windows/.gitignore similarity index 100% rename from packages/syncfusion_officechart/example/windows/.gitignore rename to packages/syncfusion_flutter_datagrid_export/example/windows/.gitignore diff --git a/packages/syncfusion_officechart/example/windows/CMakeLists.txt b/packages/syncfusion_flutter_datagrid_export/example/windows/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officechart/example/windows/CMakeLists.txt rename to packages/syncfusion_flutter_datagrid_export/example/windows/CMakeLists.txt diff --git a/packages/syncfusion_officechart/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officechart/example/windows/flutter/CMakeLists.txt rename to packages/syncfusion_flutter_datagrid_export/example/windows/flutter/CMakeLists.txt diff --git a/packages/syncfusion_officechart/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.cc similarity index 100% rename from packages/syncfusion_officechart/example/windows/flutter/generated_plugin_registrant.cc rename to packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.cc diff --git a/packages/syncfusion_officechart/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from packages/syncfusion_officechart/example/windows/flutter/generated_plugin_registrant.h rename to packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugin_registrant.h diff --git a/packages/syncfusion_officechart/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugins.cmake similarity index 100% rename from packages/syncfusion_officechart/example/windows/flutter/generated_plugins.cmake rename to packages/syncfusion_flutter_datagrid_export/example/windows/flutter/generated_plugins.cmake diff --git a/packages/syncfusion_officechart/example/windows/runner/CMakeLists.txt b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/CMakeLists.txt rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/CMakeLists.txt diff --git a/packages/syncfusion_officechart/example/windows/runner/Runner.rc b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/Runner.rc similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/Runner.rc rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/Runner.rc diff --git a/packages/syncfusion_officechart/example/windows/runner/flutter_window.cpp b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/flutter_window.cpp similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/flutter_window.cpp rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/flutter_window.cpp diff --git a/packages/syncfusion_officechart/example/windows/runner/flutter_window.h b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/flutter_window.h similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/flutter_window.h rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/flutter_window.h diff --git a/packages/syncfusion_officechart/example/windows/runner/main.cpp b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/main.cpp similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/main.cpp rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/main.cpp diff --git a/packages/syncfusion_officechart/example/windows/runner/resource.h b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/resource.h similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/resource.h rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/resource.h diff --git a/packages/syncfusion_officechart/example/windows/runner/resources/app_icon.ico b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/resources/app_icon.ico similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/resources/app_icon.ico rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/resources/app_icon.ico diff --git a/packages/syncfusion_officechart/example/windows/runner/run_loop.cpp b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/run_loop.cpp similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/run_loop.cpp rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/run_loop.cpp diff --git a/packages/syncfusion_officechart/example/windows/runner/run_loop.h b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/run_loop.h similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/run_loop.h rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/run_loop.h diff --git a/packages/syncfusion_officechart/example/windows/runner/runner.exe.manifest b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/runner.exe.manifest similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/runner.exe.manifest rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/runner.exe.manifest diff --git a/packages/syncfusion_officechart/example/windows/runner/utils.cpp b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/utils.cpp similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/utils.cpp rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/utils.cpp diff --git a/packages/syncfusion_officechart/example/windows/runner/utils.h b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/utils.h similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/utils.h rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/utils.h diff --git a/packages/syncfusion_officechart/example/windows/runner/win32_window.cpp b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/win32_window.cpp similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/win32_window.cpp rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/win32_window.cpp diff --git a/packages/syncfusion_officechart/example/windows/runner/win32_window.h b/packages/syncfusion_flutter_datagrid_export/example/windows/runner/win32_window.h similarity index 100% rename from packages/syncfusion_officechart/example/windows/runner/win32_window.h rename to packages/syncfusion_flutter_datagrid_export/example/windows/runner/win32_window.h diff --git a/packages/syncfusion_flutter_datagrid_export/lib/export.dart b/packages/syncfusion_flutter_datagrid_export/lib/export.dart new file mode 100644 index 000000000..d4a9787d0 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/lib/export.dart @@ -0,0 +1,8 @@ +/// The Flutter DataGrid Export library is used to export the DataGrid content +/// to Excel. +/// to Pdf. +library syncfusion_flutter_datagrid_export; + +export './src/enum.dart'; +export './src/export_to_excel.dart'; +export './src/export_to_pdf.dart'; diff --git a/packages/syncfusion_flutter_datagrid_export/lib/src/enum.dart b/packages/syncfusion_flutter_datagrid_export/lib/src/enum.dart new file mode 100644 index 000000000..0f6d83e9e --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/lib/src/enum.dart @@ -0,0 +1,14 @@ +/// Defines the cell type of the exporting cell. +enum DataGridExportCellType { + /// Specifies the column header cell. + columnHeader, + + /// Specifies the data row cell. + row, + + /// Specifies the stacked header cell. + stackedHeader, + + /// Specifies the table summary cell. + tableSummaryRow, +} diff --git a/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart new file mode 100644 index 000000000..71cd99148 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_excel.dart @@ -0,0 +1,758 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; + +import 'package:syncfusion_flutter_datagrid/datagrid.dart'; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; + +import 'enum.dart'; +import 'helper.dart'; + +/// Signature for `cellExport` callback which is passed as an argument in +/// `exportToExcelWorkbook` and `exportToExcelWorksheet` methods. +typedef DataGridCellExcelExportCallback = void Function( + DataGridCellExcelExportDetails details); + +/// Details for the callback that use [DataGridCellExcelExportDetails]. +class DataGridCellExcelExportDetails { + /// Creates the [DataGridCellExcelExportDetails]. + const DataGridCellExcelExportDetails( + this.cellValue, this.columnName, this.excelRange, this.cellType); + + /// The value of the cell. Typically, it is [DataGridCell.value]. + final Object? cellValue; + + /// The name of a column. + final String columnName; + + /// The range in the Excel worksheet. + final Range excelRange; + + /// The type of the cell. + final DataGridExportCellType cellType; +} + +/// A helper class to export the [SfDataGrid] to Excel. +/// +/// Override this class and required methods from this class to perform the +/// customized exporting operation. +class DataGridToExcelConverter { + /// A row index of [SfDataGrid] which keeps track of the row index + /// while exporting. + int excelRowIndex = 1; + + /// A column index of [SfDataGrid] which keeps track of the column index + /// while exporting. + int excelColumnIndex = 1; + + /// The start of the column index in Excel. The first column of the SfDataGrid + /// will begin at this column index in Excel + int startColumnIndex = 1; + + /// The start of the row index in Excel. The first row of the SfDataGrid will + /// begin at this row index in Excel. + int startRowIndex = 1; + + /// Decides whether the stacked headers should be exported to Excel. + /// + /// Defaults to true. + bool exportStackedHeaders = true; + + /// Decides whether the table summary rows should be exported to Excel. + /// + /// Defaults to true. + bool exportTableSummaries = true; + + /// The default width of the columns in Excel while exporting. + /// + /// Defaults to 90. + double defaultColumnWidth = 90.0; + + /// The default height of the rows in Excel while exporting. + /// + /// Defaults to 49. + double defaultRowHeight = 49.0; + + /// Decides whether the width of the columns in [SfDataGrid] should be + /// exported same in Excel. + /// + /// Defaults to true. + bool exportColumnWidth = true; + + /// Decides whether the height of the rows in [SfDataGrid] should be + /// exported same in Excel. + /// + /// Defaults to true. + bool exportRowHeight = true; + + /// Holds the cell export callback function. + DataGridCellExcelExportCallback? cellExport; + + /// Excludes certain columns to the exporting. + List excludeColumns = []; + + /// The collection of grid columns that proceeds to export. + List get columns => _columns; + List _columns = []; + + /// Exports the column headers to Excel. + @protected + void exportColumnHeaders(SfDataGrid dataGrid, Worksheet worksheet) { + if (columns.isEmpty) { + return; + } + + // Resets the column index when creating a new row. + excelColumnIndex = startColumnIndex; + + _setRowHeight(dataGrid, DataGridExportCellType.columnHeader, worksheet); + + for (final GridColumn column in columns) { + exportColumnHeader(dataGrid, column, column.columnName, worksheet); + excelColumnIndex++; + } + excelRowIndex++; + } + + /// Exports a column header to Excel. + @protected + void exportColumnHeader(SfDataGrid dataGrid, GridColumn column, + String columnName, Worksheet worksheet) { + Range range; + int rowSpan = 0; + + if (exportStackedHeaders && dataGrid.stackedHeaderRows.isNotEmpty) { + rowSpan = getRowSpan( + dataGrid: dataGrid, + isStackedHeader: false, + columnName: column.columnName, + rowIndex: excelRowIndex - startRowIndex - 1, + columnIndex: dataGrid.columns.indexOf(column)); + } + + if (rowSpan > 0) { + range = worksheet.getRangeByIndex(excelRowIndex - rowSpan, + excelColumnIndex, excelRowIndex, excelColumnIndex); + range.merge(); + } else { + range = worksheet.getRangeByIndex(excelRowIndex, excelColumnIndex); + } + + // Align cell content to the center position. + range.cellStyle + ..hAlign = HAlignType.center + ..vAlign = VAlignType.center; + + _exportCellToExcel(worksheet, range, column, + DataGridExportCellType.columnHeader, columnName); + } + + /// Exports all the data rows to Excel. + @protected + void exportRows( + SfDataGrid dataGrid, List rows, Worksheet worksheet) { + if (rows.isEmpty) { + return; + } + + for (final DataGridRow row in rows) { + // Resets the column index when creating a new row. + excelColumnIndex = startColumnIndex; + + _setRowHeight(dataGrid, DataGridExportCellType.row, worksheet); + + for (final GridColumn column in columns) { + exportRow(dataGrid, row, column, worksheet); + excelColumnIndex++; + } + excelRowIndex++; + } + } + + /// Exports a [DataGridRow] to Excel. + @protected + void exportRow(SfDataGrid dataGrid, DataGridRow row, GridColumn column, + Worksheet worksheet) { + final Object? cellValue = getCellValue(row, column); + final Range range = + worksheet.getRangeByIndex(excelRowIndex, excelColumnIndex); + _exportCellToExcel( + worksheet, range, column, DataGridExportCellType.row, cellValue); + } + + /// Exports all the stacked header rows to Excel. + @protected + void exportStackedHeaderRows(SfDataGrid dataGrid, Worksheet worksheet) { + if (dataGrid.stackedHeaderRows.isEmpty) { + return; + } + + for (final StackedHeaderRow row in dataGrid.stackedHeaderRows) { + exportStackedHeaderRow(dataGrid, row, worksheet); + excelRowIndex++; + } + } + + /// Exports a stacked header row to Excel. + @protected + void exportStackedHeaderRow(SfDataGrid dataGrid, + StackedHeaderRow stackedHeaderRow, Worksheet worksheet) { + _setRowHeight(dataGrid, DataGridExportCellType.stackedHeader, worksheet); + + for (final StackedHeaderCell column in stackedHeaderRow.cells) { + final List columnSequences = + getColumnSequences(dataGrid.columns, column); + for (final List indexes in getConsecutiveRanges(columnSequences)) { + final int columnIndex = indexes.reduce(min); + final int columnSpan = indexes.length - 1; + final int rowSpan = getRowSpan( + dataGrid: dataGrid, + isStackedHeader: true, + columnIndex: columnIndex, + stackedHeaderCell: column, + rowIndex: excelRowIndex - startRowIndex - 1); + + _exportStackedHeaderCell( + dataGrid, column, columnIndex, columnSpan, rowSpan, worksheet); + } + } + } + + /// Export table summary rows to the Excel. + @protected + void exportTableSummaryRows(SfDataGrid dataGrid, + GridTableSummaryRowPosition position, Worksheet worksheet) { + if (dataGrid.tableSummaryRows.isEmpty) { + return; + } + + final List summaryRows = dataGrid.tableSummaryRows + .where((GridTableSummaryRow row) => row.position == position) + .toList(); + + if (summaryRows.isEmpty) { + return; + } + + for (final GridTableSummaryRow summaryRow in summaryRows) { + exportTableSummaryRow(dataGrid, summaryRow, worksheet); + excelRowIndex++; + } + } + + /// Export table summary row to the Excel. + @protected + void exportTableSummaryRow(SfDataGrid dataGrid, + GridTableSummaryRow summaryRow, Worksheet worksheet) { + // Resets the column index when creating a new row. + excelColumnIndex = startColumnIndex; + + _setRowHeight(dataGrid, DataGridExportCellType.tableSummaryRow, worksheet); + + if (summaryRow.showSummaryInRow) { + _exportTableSummaryCell( + sheet: worksheet, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: columns.length); + } else { + int titleColumnCount = summaryRow.titleColumnSpan; + if (titleColumnCount > 0) { + // To consider the exclude columns in the `titleColumnCount`. + titleColumnCount = + getTitleColumnCount(summaryRow, dataGrid.columns, excludeColumns); + + if (titleColumnCount > 0) { + _exportTableSummaryCell( + sheet: worksheet, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: titleColumnCount); + } + } + + for (final GridSummaryColumn summaryColumn in summaryRow.columns) { + final GridColumn? column = dataGrid.columns.firstWhereOrNull( + (GridColumn element) => + element.columnName == summaryColumn.columnName); + final int columnIndex = getSummaryColumnIndex( + dataGrid.columns, summaryColumn.columnName, excludeColumns); + + // Restricts if the column doesn't exist or its column index is less + // than the `titleColumnCount`. because the `titleColumn` summary cell + // has already exported. + if (columnIndex < 0 || + (excelColumnIndex + columnIndex) < + (excelColumnIndex + titleColumnCount)) { + continue; + } + + _exportTableSummaryCell( + column: column!, + sheet: worksheet, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnIndex: columnIndex, + summaryColumn: summaryColumn); + } + } + } + + /// Gets the cell value required for data rows. + @protected + Object? getCellValue(DataGridRow row, GridColumn column) { + final DataGridCell cellValue = row.getCells().firstWhereOrNull( + (DataGridCell cell) => cell.columnName == column.columnName)!; + return cellValue.value; + } + + /// Exports the [SfDataGrid] to Excel [Worksheet]. + void exportToExcelWorksheet( + SfDataGrid dataGrid, List? rows, Worksheet worksheet) { + _columns = dataGrid.columns + .where( + (GridColumn column) => !excludeColumns.contains(column.columnName)) + .toList(); + + // Return if all the columns are `excludeColumns`. + if (columns.isEmpty) { + return; + } + + rows ??= dataGrid.source.rows; + + _setColumnWidths(exportColumnWidth, worksheet, defaultColumnWidth); + + if (exportStackedHeaders) { + // Exports the stacked header rows. + exportStackedHeaderRows(dataGrid, worksheet); + } + + // Exports the column header row. + exportColumnHeaders(dataGrid, worksheet); + + // Exports the top table summary rows. + if (exportTableSummaries) { + exportTableSummaryRows( + dataGrid, GridTableSummaryRowPosition.top, worksheet); + } + + // Exports the data rows. + exportRows(dataGrid, rows, worksheet); + + // Exports the bottom table summary rows. + if (exportTableSummaries) { + exportTableSummaryRows( + dataGrid, GridTableSummaryRowPosition.bottom, worksheet); + } + } + + /// Exports the [SfDataGrid] to Excel [Workbook]. + Workbook exportToExcelWorkbook(SfDataGrid dataGrid, List? rows) { + final Workbook workbook = Workbook(); + final Worksheet sheet = workbook.worksheets[0]; + exportToExcelWorksheet(dataGrid, rows, sheet); + return workbook; + } + + void _exportCellToExcel(Worksheet sheet, Range range, GridColumn column, + DataGridExportCellType cellType, Object? cellValue) { + range.setValue(cellValue); + if (cellExport != null) { + final DataGridCellExcelExportDetails details = + DataGridCellExcelExportDetails( + cellValue, column.columnName, range, cellType); + cellExport!(details); + } + } + + void _exportStackedHeaderCell(SfDataGrid dataGrid, StackedHeaderCell column, + int columnIndex, int columnSpan, int rowSpan, Worksheet sheet) { + Range range; + int firstColumnIndex = startColumnIndex + columnIndex; + int lastColumnIndex = firstColumnIndex + columnSpan; + + if (excludeColumns.isNotEmpty) { + final List startAndEndIndex = getSpannedCellStartAndEndIndex( + columnSpan: columnSpan, + columnIndex: columnIndex, + columns: dataGrid.columns, + excludeColumns: excludeColumns, + startColumnIndex: startColumnIndex); + + firstColumnIndex = startAndEndIndex[0]; + lastColumnIndex = startAndEndIndex[1]; + } + + if (firstColumnIndex <= lastColumnIndex) { + excelColumnIndex = firstColumnIndex; + if (rowSpan > 0 || lastColumnIndex > firstColumnIndex) { + range = sheet.getRangeByIndex(excelRowIndex - rowSpan, firstColumnIndex, + excelRowIndex, lastColumnIndex); + range.merge(); + } else { + range = sheet.getRangeByIndex(excelRowIndex, firstColumnIndex); + } + + // Align cell content to the center position. + range.cellStyle + ..hAlign = HAlignType.center + ..vAlign = VAlignType.center; + + _exportCellToExcel(sheet, range, dataGrid.columns[columnIndex], + DataGridExportCellType.stackedHeader, column.text); + } + } + + void _exportTableSummaryCell( + {int columnSpan = 0, + int columnIndex = 0, + GridColumn? column, + required Worksheet sheet, + required SfDataGrid dataGrid, + GridSummaryColumn? summaryColumn, + required GridTableSummaryRow summaryRow}) { + Range range; + final GridColumn? column = dataGrid.columns.firstWhereOrNull( + (GridColumn column) => column.columnName == summaryColumn?.columnName); + final RowColumnIndex rowColumnIndex = RowColumnIndex( + excelRowIndex - startRowIndex, + column != null ? dataGrid.columns.indexOf(column) : 0); + final String summaryValue = dataGrid.source.calculateSummaryValue( + summaryRow, summaryColumn ?? summaryRow.columns.first, rowColumnIndex); + + columnIndex += startColumnIndex; + excelColumnIndex = columnIndex; + + if (columnSpan > 0) { + range = sheet.getRangeByIndex(excelRowIndex, excelColumnIndex, + excelRowIndex, excelColumnIndex + columnSpan - 1); + range.merge(); + } else { + range = sheet.getRangeByIndex(excelRowIndex, excelColumnIndex); + } + + _exportCellToExcel(sheet, range, column ?? dataGrid.columns[0], + DataGridExportCellType.tableSummaryRow, summaryValue); + } + + void _setColumnWidths( + bool exportColumnWidth, Worksheet sheet, double columnWidth) { + excelColumnIndex = startColumnIndex; + for (final GridColumn column in columns) { + if (exportColumnWidth && !column.actualWidth.isNaN) { + columnWidth = column.actualWidth; + } + + sheet.setColumnWidthInPixels(excelColumnIndex, columnWidth.toInt()); + excelColumnIndex++; + } + } + + void _setRowHeight( + SfDataGrid dataGrid, DataGridExportCellType cellType, Worksheet sheet) { + if (exportRowHeight) { + switch (cellType) { + case DataGridExportCellType.columnHeader: + case DataGridExportCellType.stackedHeader: + final double height = + dataGrid.headerRowHeight.isNaN ? 56.0 : dataGrid.headerRowHeight; + sheet.setRowHeightInPixels(excelRowIndex, height); + break; + case DataGridExportCellType.row: + case DataGridExportCellType.tableSummaryRow: + final double height = + dataGrid.rowHeight.isNaN ? 49.0 : dataGrid.rowHeight; + sheet.setRowHeightInPixels(excelRowIndex, height); + break; + } + } else { + sheet.setRowHeightInPixels(excelRowIndex, defaultRowHeight); + } + } +} + +/// Provides the extension methods for the [SfDataGridState] to export the +/// [SfDataGrid] to Excel. +extension DataGridExcelExportExtensions on SfDataGridState { + // Initializes the properties to the converter. + void _initializeProperties(DataGridToExcelConverter converter, + {required int excelStartRowIndex, + required int excelStartColumnIndex, + required bool canExportStackedHeaders, + required bool canExportTableSummaries, + required bool canExportColumnWidth, + required bool canExportRowHeight, + required int columnWidth, + required int rowHeight, + required List excludeColumns, + DataGridCellExcelExportCallback? cellExport}) { + converter + ..defaultColumnWidth = columnWidth.toDouble() + ..defaultRowHeight = rowHeight.toDouble() + ..excelRowIndex = excelStartRowIndex + ..startRowIndex = excelStartRowIndex + ..excelColumnIndex = excelStartColumnIndex + ..startColumnIndex = excelStartColumnIndex + ..exportStackedHeaders = canExportStackedHeaders + ..exportTableSummaries = canExportTableSummaries + ..exportColumnWidth = canExportColumnWidth + ..exportRowHeight = canExportRowHeight + ..excludeColumns = excludeColumns + ..cellExport = cellExport; + } + + /// Exports the `SfDataGrid` to the given `Worksheet`. + /// + /// Use the `cellExport` argument as the callback, and it will be called for + /// each cell. You can customize the cell in an Excel sheet. + /// + /// Use `defaultColumnWidth` and `defaultRowHeight` arguments to set the default + /// column width and row height in Excel while exporting. + /// + /// Define the start of the row and column index in Excel sheet where DataGrid + /// content should be started using `startRowIndex` and `startColumnIndex`. + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// + /// Future exportDataGridToExcel() async { + /// final Workbook workbook = Workbook(); + /// final Worksheet worksheet = workbook.worksheets[0]; + /// _key.currentState!.exportToExcelWorksheet(worksheet); + /// final List bytes = workbook.saveAsStream(); + /// File('DataGrid.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text( + /// 'Syncfusion Flutter DataGrid Export', + /// overflow: TextOverflow.ellipsis, + /// ), + /// ), + /// body: Column( + /// children: [ + /// Container( + /// margin: const EdgeInsets.all(12.0), + /// child: const SizedBox( + /// height: 40.0, + /// width: 150.0, + /// child: MaterialButton( + /// color: Colors.blue, + /// child: Center( + /// child: Text( + /// 'Export to Excel', + /// style: TextStyle(color: Colors.white), + /// )), + /// onPressed: exportDataGridToExcel)), + /// ), + /// Expanded( + /// child: SfDataGrid( + /// key: _key, + /// source: employeeDataSource, + /// columns: [ + /// GridColumn( + /// columnName: 'ID', + /// label: Container( + /// padding: const EdgeInsets.all(16.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'ID', + /// ))), + /// GridColumn( + /// columnName: 'Name', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Name'))), + /// GridColumn( + /// columnName: 'Designation', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'Designation', + /// overflow: TextOverflow.ellipsis, + /// ))), + /// GridColumn( + /// columnName: 'Salary', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Salary'))), + /// ], + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + void exportToExcelWorksheet(Worksheet worksheet, + {List? rows, + bool exportStackedHeaders = true, + bool exportTableSummaries = true, + int defaultColumnWidth = 90, + int defaultRowHeight = 49, + bool exportColumnWidth = true, + bool exportRowHeight = true, + int startColumnIndex = 1, + int startRowIndex = 1, + List excludeColumns = const [], + DataGridToExcelConverter? converter, + DataGridCellExcelExportCallback? cellExport}) { + converter ??= DataGridToExcelConverter(); + + _initializeProperties( + converter, + cellExport: cellExport, + rowHeight: defaultRowHeight, + columnWidth: defaultColumnWidth, + excludeColumns: excludeColumns, + excelStartRowIndex: startRowIndex, + excelStartColumnIndex: startColumnIndex, + canExportRowHeight: exportRowHeight, + canExportColumnWidth: exportColumnWidth, + canExportTableSummaries: exportTableSummaries, + canExportStackedHeaders: exportStackedHeaders, + ); + + converter.exportToExcelWorksheet(widget, rows, worksheet); + } + + /// Exports the `SfDataGrid` to Excel `Workbook`. + /// + /// If the `rows` is set, the given list of DataGridRow collections is exported. + /// Typically, you can set this property to export the selected rows from + /// `SfDataGrid`. + /// + /// Use `cellExport` argument as the callback, and it will be called for + /// each cell. You can customize the cell in the Excel sheet. + /// + /// Set the customized Excel converter using the `converter` argument. + /// To customize the default Excel converter, override the + /// `DataGridToExcelConverter` class and override the necessary methods. + /// + /// Use `defaultColumnWidth` and `defaultRowHeight` arguments to set the + /// default column width and row height in Excel while exporting. + /// + /// Define the start of the row and column index in the Excel sheet where DataGrid + /// content should be started using `startRowIndex` and `startColumnIndex`. + /// + /// The following example shows how to export the SfDataGrid to the Excel workbook. + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// + /// Future exportDataGridToExcel() async { + /// final Workbook workbook = _key.currentState!.exportToExcelWorkbook(); + /// final List bytes = workbook.saveAsStream(); + /// File('DataGrid.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text( + /// 'Syncfusion Flutter DataGrid Export', + /// overflow: TextOverflow.ellipsis, + /// ), + /// ), + /// body: Column( + /// children: [ + /// Container( + /// margin: const EdgeInsets.all(12.0), + /// child: const SizedBox( + /// height: 40.0, + /// width: 150.0, + /// child: MaterialButton( + /// color: Colors.blue, + /// child: Center( + /// child: Text( + /// 'Export to Excel', + /// style: TextStyle(color: Colors.white), + /// )), + /// onPressed: exportDataGridToExcel), + /// ), + /// ), + /// Expanded( + /// child: SfDataGrid( + /// key: _key, + /// source: employeeDataSource, + /// columns: [ + /// GridColumn( + /// columnName: 'ID', + /// label: Container( + /// padding: const EdgeInsets.all(16.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'ID', + /// ))), + /// GridColumn( + /// columnName: 'Name', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Name'))), + /// GridColumn( + /// columnName: 'Designation', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'Designation', + /// overflow: TextOverflow.ellipsis, + /// ))), + /// GridColumn( + /// columnName: 'Salary', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Salary'))), + /// ], + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + Workbook exportToExcelWorkbook( + {List? rows, + bool exportStackedHeaders = true, + bool exportTableSummaries = true, + int defaultColumnWidth = 90, + int defaultRowHeight = 49, + bool exportRowHeight = true, + bool exportColumnWidth = true, + int startColumnIndex = 1, + int startRowIndex = 1, + List excludeColumns = const [], + DataGridToExcelConverter? converter, + DataGridCellExcelExportCallback? cellExport}) { + converter ??= DataGridToExcelConverter(); + + _initializeProperties( + converter, + cellExport: cellExport, + rowHeight: defaultRowHeight, + columnWidth: defaultColumnWidth, + excludeColumns: excludeColumns, + excelStartRowIndex: startRowIndex, + excelStartColumnIndex: startColumnIndex, + canExportRowHeight: exportRowHeight, + canExportColumnWidth: exportColumnWidth, + canExportTableSummaries: exportTableSummaries, + canExportStackedHeaders: exportStackedHeaders, + ); + + return converter.exportToExcelWorkbook(widget, rows); + } +} diff --git a/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart new file mode 100644 index 000000000..75d1ebd6a --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/lib/src/export_to_pdf.dart @@ -0,0 +1,763 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'package:syncfusion_flutter_datagrid/datagrid.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; + +import 'enum.dart'; +import 'helper.dart'; + +/// Exports the [SfDataGrid] to Pdf with the given rows and columns + +extension DataGridPdfExportExtensions on SfDataGridState { + // Initializes the properties to the converter. + void _initializeProperties(DataGridToPdfConverter converter, + {required bool canRepeatHeaders, + required bool exportStackedHeaders, + required bool fitAllColumnsInOnePage, + required List excludeColumns, + required bool autoColumnWidth, + required bool exportTableSummaries, + DataGridCellPdfExportCallback? cellExport, + DataGridPdfHeaderFooterExportCallback? headerFooterExport}) { + converter + ..canRepeatHeaders = canRepeatHeaders + ..exportStackedHeaders = exportStackedHeaders + ..autoColumnWidth = autoColumnWidth + ..fitAllColumnsInOnePage = fitAllColumnsInOnePage + ..cellExport = cellExport + ..excludeColumns = excludeColumns + ..headerFooterExport = headerFooterExport + ..exportTableSummaries = exportTableSummaries; + } + + /// Exports the `SfDataGrid` to `PdfDocument`. + /// + /// If the `rows` is set, the given list of DataGridRow collections is exported. + /// Typically, you can set this property to export the selected rows from `SfDataGrid`. + /// + /// Use `cellExport` argument as the callback, and it will be called for + /// each cell. You can customize the cell in Pdf document. + /// + /// Use `fitAllColumnsInOnePage` argument to fit all the columns in a page. + /// + /// Use the `converter` argument to set the customized Pdf converter. + /// To customize the default Pdf converter, override the `DataGridToPdfConverter` + /// class and override the necessary methods. + /// + /// Use the `headerFooterExport` argument to set the header and footer for each page. + /// + /// The following example shows how to export the SfDataGrid to PdfDocument. + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// + /// Future exportDataGridToPdf() async { + /// final PdfDocument document = + /// _key.currentState!.exportToPdfDocument(fitAllColumnsInOnePage: true); + /// final List bytes = document.save(); + /// File('DataGrid.pdf').writeAsBytes(bytes); + /// document.dispose(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text( + /// 'Syncfusion Flutter DataGrid Export', + /// overflow: TextOverflow.ellipsis, + /// ), + /// ), + /// body: Column( + /// children: [ + /// Container( + /// margin: const EdgeInsets.all(12.0), + /// child: const SizedBox( + /// height: 40.0, + /// width: 150.0, + /// child: MaterialButton( + /// color: Colors.blue, + /// child: Center( + /// child: Text( + /// 'Export to PDF', + /// style: TextStyle(color: Colors.white), + /// )), + /// onPressed: exportDataGridToPdf), + /// ), + /// ), + /// Expanded( + /// child: SfDataGrid( + /// key: _key, + /// source: employeeDataSource, + /// columns: [ + /// GridColumn( + /// columnName: 'ID', + /// label: Container( + /// padding: const EdgeInsets.all(16.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'ID', + /// ))), + /// GridColumn( + /// columnName: 'Name', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Name'))), + /// GridColumn( + /// columnName: 'Designation', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'Designation', + /// overflow: TextOverflow.ellipsis, + /// ))), + /// GridColumn( + /// columnName: 'Salary', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Salary'))), + /// ], + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + PdfDocument exportToPdfDocument( + {List? rows, + bool exportStackedHeaders = true, + bool canRepeatHeaders = true, + bool fitAllColumnsInOnePage = false, + bool autoColumnWidth = true, + bool exportTableSummaries = true, + List excludeColumns = const [], + DataGridToPdfConverter? converter, + DataGridCellPdfExportCallback? cellExport, + DataGridPdfHeaderFooterExportCallback? headerFooterExport}) { + converter ??= DataGridToPdfConverter(); + _initializeProperties(converter, + autoColumnWidth: autoColumnWidth, + fitAllColumnsInOnePage: fitAllColumnsInOnePage, + canRepeatHeaders: canRepeatHeaders, + exportStackedHeaders: exportStackedHeaders, + cellExport: cellExport, + headerFooterExport: headerFooterExport, + excludeColumns: excludeColumns, + exportTableSummaries: exportTableSummaries); + + return converter.exportToPdfDocument(widget, rows); + } + + /// Exports the `SfDataGrid` to `PdfGrid`. + /// + /// If the `rows` is set, the given list of DataGridRow collections is exported. + /// Typically, you can set this property to export the selected rows from `SfDataGrid`. + /// + /// Use the `cellExport` argument as the callback, and it will be called for + /// each cell. You can customize the cell in Pdf document. + /// + /// Use `fitAllColumnsInOnePage` argument to fit all the columns in a page. + /// + /// Use the `converter` argument to set the customized Pdf converter. + /// To customize the default Pdf converter, override the `DataGridToPdfConverter` + /// class and override the necessary methods. + /// + /// The following example shows how to export the SfDataGrid to PdfGrid. + /// + /// ```dart + /// final GlobalKey _key = GlobalKey(); + /// + /// Future exportDataGridToPdf() async { + /// final PdfDocument pdfDocument = PdfDocument(); + /// final PdfGrid pdfGrid = + /// _key.currentState!.exportToPdfGrid(fitAllColumnsInOnePage: true); + /// pdfGrid.draw( + /// page: pdfDocument.pages.add(), bounds: const Rect.fromLTWH(0, 0, 0, 0)); + /// final List bytes = pdfDocument.save(); + /// File('DataGrid.pdf').writeAsBytes(bytes); + /// pdfDocument.dispose(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// appBar: AppBar( + /// title: const Text( + /// 'Syncfusion Flutter DataGrid Export', + /// overflow: TextOverflow.ellipsis, + /// ), + /// ), + /// body: Column( + /// children: [ + /// Container( + /// margin: const EdgeInsets.all(12.0), + /// child: const SizedBox( + /// height: 40.0, + /// width: 150.0, + /// child: MaterialButton( + /// color: Colors.blue, + /// child: Center( + /// child: Text( + /// 'Export to PDF', + /// style: TextStyle(color: Colors.white), + /// )), + /// onPressed: exportDataGridToPdf), + /// ), + /// ), + /// Expanded( + /// child: SfDataGrid( + /// key: _key, + /// source: employeeDataSource, + /// columns: [ + /// GridColumn( + /// columnName: 'ID', + /// label: Container( + /// padding: const EdgeInsets.all(16.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'ID', + /// ))), + /// GridColumn( + /// columnName: 'Name', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Name'))), + /// GridColumn( + /// columnName: 'Designation', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text( + /// 'Designation', + /// overflow: TextOverflow.ellipsis, + /// ))), + /// GridColumn( + /// columnName: 'Salary', + /// label: Container( + /// padding: const EdgeInsets.all(8.0), + /// alignment: Alignment.center, + /// child: const Text('Salary'))), + /// ], + /// ), + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + PdfGrid exportToPdfGrid( + {List? rows, + bool exportStackedHeaders = true, + bool canRepeatHeaders = true, + bool fitAllColumnsInOnePage = false, + bool autoColumnWidth = true, + bool exportTableSummaries = true, + List excludeColumns = const [], + DataGridToPdfConverter? converter, + DataGridCellPdfExportCallback? cellExport}) { + converter ??= DataGridToPdfConverter(); + _initializeProperties(converter, + autoColumnWidth: autoColumnWidth, + fitAllColumnsInOnePage: fitAllColumnsInOnePage, + canRepeatHeaders: canRepeatHeaders, + cellExport: cellExport, + exportStackedHeaders: exportStackedHeaders, + excludeColumns: excludeColumns, + exportTableSummaries: exportTableSummaries); + + return converter.exportToPdfGrid(widget, rows); + } +} + +/// A helper class to export the [SfDataGrid] to Pdf. +/// +/// Override this class and required methods from this class to perform the customized exporting operation. + +class DataGridToPdfConverter { + /// Decides whether the headers should be repeated to all pages in the pdf document. + /// + /// Defaults to true. + bool canRepeatHeaders = true; + + /// Decides whether the Stacked headers should be exported in the pdf document. + /// + /// Defaults to true. + bool exportStackedHeaders = true; + + /// Decides whether the all columns should be fit into one page. + /// + /// Defaults to false. + bool fitAllColumnsInOnePage = false; + + /// Decides whether the all columns width should be set auto width(actual width). + /// + /// Defaults to true. + bool autoColumnWidth = true; + + /// A row index of [SfDataGrid] which keeps track of the row index while exporting. + int rowIndex = 0; + + /// A column index of [SfDataGrid] which keeps track of the column index while exporting. + int _columnIndex = 0; + + /// Excludes certain columns to the exporting. + List excludeColumns = []; + + /// The collection of grid columns that proceeds to export. + List get columns => _columns; + List _columns = []; + + /// Decides whether the table summary rows should be exported to Excel. + /// + /// Defaults to true. + bool exportTableSummaries = true; + + /// Details for the callback that use [DataGridCellPdfExportDetails] + DataGridCellPdfExportCallback? cellExport; + + /// Details for the callback that use [DataGridPdfHeaderFooterExportDetails] + DataGridPdfHeaderFooterExportCallback? headerFooterExport; + + //pdf pen style for draw borders width and color + final PdfPen _pdfPen = PdfPen(PdfColor(168, 168, 168), width: 0.2); + + /// Exports the column headers to Pdf. + @protected + void exportColumnHeaders( + SfDataGrid dataGrid, List columns, PdfGrid pdfGrid) { + if (columns.isEmpty) { + return; + } + _columnIndex = 0; + for (final GridColumn column in columns) { + exportColumnHeader(dataGrid, column, column.columnName, pdfGrid); + _columnIndex++; + } + } + + /// Exports a column header to Pdf + @protected + void exportColumnHeader(SfDataGrid dataGrid, GridColumn column, + String columnName, PdfGrid pdfGrid) { + int rowSpan = 0; + PdfGridRow columnHeader = pdfGrid.headers[rowIndex]; + PdfGridCell pdfCell = columnHeader.cells[_columnIndex]; + + if (dataGrid.stackedHeaderRows.isNotEmpty && exportStackedHeaders) { + rowSpan = getRowSpan( + dataGrid: dataGrid, + isStackedHeader: false, + columnName: columnName, + rowIndex: rowIndex - 1, + columnIndex: dataGrid.columns.indexOf(column)); + } + if (rowSpan > 0) { + columnHeader = pdfGrid.headers[rowIndex - 1]; + pdfCell = columnHeader.cells[_columnIndex]; + pdfCell.rowSpan = rowSpan + 1; + pdfCell.value = columnName; + } else { + pdfCell.value = columnName; + } + pdfCell.style.borders.all = _pdfPen; + _exportCellToPdf( + DataGridExportCellType.columnHeader, pdfCell, columnName, column); + } + + /// Exports all the data rows to Pdf. + @protected + void exportRows( + List columns, List rows, PdfGrid pdfGrid) { + for (final DataGridRow row in rows) { + exportRow(columns, row, pdfGrid); + rowIndex++; + } + } + + /// Exports a [DataGridRow] to Pdf. + @protected + void exportRow(List columns, DataGridRow row, PdfGrid pdfGrid) { + int cellIndex = 0; + final PdfGridRow pdfRow = pdfGrid.rows.add(); + + for (final GridColumn column in columns) { + final PdfGridCell pdfCell = pdfRow.cells[cellIndex]; + final Object cellValue = getCellValue(column, row) ?? ''; + pdfCell.value = cellValue.toString(); + pdfCell.style.borders.all = _pdfPen; + cellIndex++; + _exportCellToPdf(DataGridExportCellType.row, pdfCell, cellValue, column); + } + } + + /// Exports the [SfDataGrid] to [PdfDocument]. + /// + /// If the `rows` is set, the given list of DataGridRow collection is exported. Typically, you can set this property to export + PdfDocument exportToPdfDocument( + SfDataGrid dataGrid, List? rows) { + final PdfDocument pdfDocument = PdfDocument(); + + //adding page into pdf document + final PdfPage pdfPage = pdfDocument.pages.add(); + + _exportHeaderFooter(pdfPage, pdfDocument.template); + + //export pdf grid into pdf document + final PdfGrid pdfGrid = exportToPdfGrid(dataGrid, rows); + + //Draw the pdf grid into pdf document + pdfGrid.draw(page: pdfPage, bounds: const Rect.fromLTWH(0, 0, 0, 0)); + + return pdfDocument; + } + + /// Exports the [SfDataGrid] to [PdfGrid]. + /// + /// If the `rows` is set, the given list of DataGridRow collection is exported. Typically, you can set this property to export the selected rows from `SfDataGrid`. + /// + /// Use `cellExport` argument which is the callback and it will be called for each cell. You can customize the cell in pdf document. + + PdfGrid exportToPdfGrid(SfDataGrid dataGrid, List? rows) { + rows ??= dataGrid.source.rows; + final PdfGrid pdfGrid = PdfGrid(); + pdfGrid.style.cellPadding = PdfPaddings( + left: 3, + right: 3, + top: 2, + bottom: 2, + ); + + //final List columns = dataGrid.columns; + _columns = dataGrid.columns + .where( + (GridColumn column) => !excludeColumns.contains(column.columnName)) + .toList(); + + //if fit all columns in one page is false then horizontal overflow is true and type is next page + if (!fitAllColumnsInOnePage) { + pdfGrid.style.allowHorizontalOverflow = true; + pdfGrid.style.horizontalOverflowType = PdfHorizontalOverflowType.nextPage; + } + + //Get the total number of Column + final int columnCount = columns.length; + + //to add number of columns in grid column + pdfGrid.columns.add(count: columnCount); + + //default header count value is 1 (column header) + int headerCount = 1; + + //get the total column header count value if staked header is true + if (dataGrid.stackedHeaderRows.isNotEmpty && exportStackedHeaders) { + headerCount = dataGrid.stackedHeaderRows.length + headerCount; + } + + //add headers to grid + pdfGrid.headers.add(headerCount); + + //check if stacked header export or not + if (dataGrid.stackedHeaderRows.isNotEmpty && exportStackedHeaders) { + exportStackedHeaderRows(dataGrid, pdfGrid); + } + + //Export the column headers + exportColumnHeaders(dataGrid, columns, pdfGrid); + + // Exports the top table summary rows. + if (exportTableSummaries) { + exportTableSummaryRows( + dataGrid, GridTableSummaryRowPosition.top, pdfGrid); + } + + //Export the rows + exportRows(columns, rows, pdfGrid); + + // Exports the bottom table summary rows. + if (exportTableSummaries) { + exportTableSummaryRows( + dataGrid, GridTableSummaryRowPosition.bottom, pdfGrid); + } + + //Can Repeate header for all pages + pdfGrid.repeatHeader = canRepeatHeaders; + + //if fit all columns in one page is false then auto column width is true + if (!fitAllColumnsInOnePage && !autoColumnWidth) { + int _columnIndex = 0; + for (final GridColumn column in columns) { + pdfGrid.columns[_columnIndex].width = column.actualWidth; + _columnIndex++; + } + } + + return pdfGrid; + } + + /// Exports all the stacked header rows to Pdf. + @protected + void exportStackedHeaderRows(SfDataGrid dataGrid, PdfGrid pdfGrid) { + for (final StackedHeaderRow row in dataGrid.stackedHeaderRows) { + exportStackedHeaderRow(dataGrid, row, pdfGrid); + + rowIndex++; + } + } + + /// Exports a stacked header row to Pdf. + @protected + void exportStackedHeaderRow( + SfDataGrid dataGrid, StackedHeaderRow stackedHeaderRow, PdfGrid pdfGrid) { + for (final StackedHeaderCell column in stackedHeaderRow.cells) { + int columnSpanValue = 0; + final List columnSequences = + getColumnSequences(dataGrid.columns, column); + for (final List indexes in getConsecutiveRanges(columnSequences)) { + _columnIndex = indexes.reduce(min); + columnSpanValue = indexes.length; + final int rowSpan = getRowSpan( + dataGrid: dataGrid, + isStackedHeader: true, + columnIndex: _columnIndex, + stackedHeaderCell: column, + rowIndex: rowIndex - 1); + + _exportStackedHeaderCell( + dataGrid, column, _columnIndex, columnSpanValue, rowSpan, pdfGrid); + } + } + } + + /// Exports a stacked header cell to Pdf. + void _exportStackedHeaderCell(SfDataGrid dataGrid, StackedHeaderCell column, + int columnIndex, int columnSpan, int rowSpan, PdfGrid pdfGrid) { + PdfGridRow stakedHeaderRow = pdfGrid.headers[rowIndex]; + + int firstColumnIndex = columnIndex; + int lastColumnIndex = firstColumnIndex + columnSpan; + + if (excludeColumns.isNotEmpty) { + final List startAndEndIndex = getSpannedCellStartAndEndIndex( + columnSpan: columnSpan - 1, + columnIndex: columnIndex, + columns: dataGrid.columns, + excludeColumns: excludeColumns, + startColumnIndex: 0); + + firstColumnIndex = startAndEndIndex[0]; + lastColumnIndex = startAndEndIndex[1]; + columnSpan = lastColumnIndex - firstColumnIndex + 1; + } + PdfGridCell pdfCell = stakedHeaderRow.cells[firstColumnIndex]; + if (firstColumnIndex <= lastColumnIndex) { + if (rowSpan > 0) { + stakedHeaderRow = pdfGrid.headers[rowIndex - 1]; + pdfCell = stakedHeaderRow.cells[firstColumnIndex]; + pdfCell.columnSpan = columnSpan; + pdfCell.rowSpan = rowSpan + 1; + } else if (columnSpan >= 0) { + pdfCell = stakedHeaderRow.cells[firstColumnIndex]; + pdfCell.columnSpan = columnSpan; + } + pdfCell.value = column.text ?? ''; + } + pdfCell.style.borders.all = _pdfPen; + + _exportCellToPdf(DataGridExportCellType.stackedHeader, pdfCell, + pdfCell.value, dataGrid.columns[firstColumnIndex]); + } + + /// Gets the cell value required for data rows. + @protected + Object? getCellValue(GridColumn column, DataGridRow row) { + final DataGridCell cellValue = row.getCells().firstWhereOrNull( + (DataGridCell element) => element.columnName == column.columnName)!; + return cellValue.value; + } + + void _exportCellToPdf( + DataGridExportCellType cellType, + PdfGridCell pdfCell, + Object? cellValue, + GridColumn column, + ) { + if (cellExport != null) { + final DataGridCellPdfExportDetails details = DataGridCellPdfExportDetails( + cellType, pdfCell, cellValue, column.columnName); + cellExport!(details); + } + } + + void _exportHeaderFooter( + PdfPage pdfPage, PdfDocumentTemplate pdfDocumentTemplate) { + if (headerFooterExport != null) { + final DataGridPdfHeaderFooterExportDetails details = + DataGridPdfHeaderFooterExportDetails(pdfPage, pdfDocumentTemplate); + headerFooterExport!(details); + } + } + + /// Export table summary rows to the Excel. + @protected + void exportTableSummaryRows(SfDataGrid dataGrid, + GridTableSummaryRowPosition position, PdfGrid pdfGrid) { + if (dataGrid.tableSummaryRows.isEmpty) { + return; + } + + final List summaryRows = dataGrid.tableSummaryRows + .where((GridTableSummaryRow row) => row.position == position) + .toList(); + + if (summaryRows.isEmpty) { + return; + } + + for (final GridTableSummaryRow summaryRow in summaryRows) { + exportTableSummaryRow(dataGrid, summaryRow, pdfGrid); + + rowIndex++; + } + } + + /// Export table summary row to the Excel. + @protected + void exportTableSummaryRow( + SfDataGrid dataGrid, GridTableSummaryRow summaryRow, PdfGrid pdfGrid) { + final PdfGridRow tableSummaryRow = pdfGrid.rows.add(); + // Resets the column index when creating a new row. + _columnIndex = 0; + if (summaryRow.showSummaryInRow) { + _exportTableSummaryCell( + pdfGrid: pdfGrid, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: columns.length, + tableSummaryRow: tableSummaryRow); + } else { + int titleColumnCount = summaryRow.titleColumnSpan; + if (titleColumnCount > 0) { + // To consider the exclude columns in the `titleColumnCount`. + titleColumnCount = + getTitleColumnCount(summaryRow, dataGrid.columns, excludeColumns); + + if (titleColumnCount > 0) { + _exportTableSummaryCell( + pdfGrid: pdfGrid, + dataGrid: dataGrid, + summaryRow: summaryRow, + columnSpan: titleColumnCount, + tableSummaryRow: tableSummaryRow); + } + } + + for (final GridSummaryColumn summaryColumn in summaryRow.columns) { + final GridColumn? column = dataGrid.columns.firstWhereOrNull( + (GridColumn element) => + element.columnName == summaryColumn.columnName); + final int summaryColumnIndex = getSummaryColumnIndex( + dataGrid.columns, summaryColumn.columnName, excludeColumns); + + // Restricts if the column doesn't exist or its column index is less + // than the `titleColumnCount`. because the `titleColumn` summary cell + // has already exported. + if (summaryColumnIndex < 0 || (summaryColumnIndex < titleColumnCount)) { + continue; + } + + _exportTableSummaryCell( + column: column!, + pdfGrid: pdfGrid, + dataGrid: dataGrid, + summaryRow: summaryRow, + startColumnIndex: summaryColumnIndex, + summaryColumn: summaryColumn, + tableSummaryRow: tableSummaryRow); + } + } + } + + void _exportTableSummaryCell( + {int columnSpan = 0, + int startColumnIndex = 0, + GridColumn? column, + required PdfGrid pdfGrid, + required SfDataGrid dataGrid, + GridSummaryColumn? summaryColumn, + PdfGridRow? tableSummaryRow, + required GridTableSummaryRow summaryRow}) { + final GridColumn? column = dataGrid.columns.firstWhereOrNull( + (GridColumn column) => column.columnName == summaryColumn?.columnName); + final RowColumnIndex rowColumnIndex = RowColumnIndex( + rowIndex, column != null ? dataGrid.columns.indexOf(column) : 0); + + final String summaryValue = dataGrid.source.calculateSummaryValue( + summaryRow, summaryColumn ?? summaryRow.columns.first, rowColumnIndex); + + PdfGridCell pdfCell = tableSummaryRow!.cells[startColumnIndex]; + if (columnSpan > 0) { + pdfCell = tableSummaryRow.cells[startColumnIndex]; + pdfCell.columnSpan = columnSpan; + } else { + pdfCell = tableSummaryRow.cells[startColumnIndex]; + } + pdfCell.value = summaryValue; + + //Applying row cell borders style + for (int i = 0; i < tableSummaryRow.cells.count; i++) { + tableSummaryRow.cells[i].style.borders.all = _pdfPen; + } + _exportCellToPdf(DataGridExportCellType.tableSummaryRow, pdfCell, + pdfCell.value, column ?? dataGrid.columns[0]); + } +} + +/// Details for the callback that use [DataGridCellPdfExportDetails] +class DataGridCellPdfExportDetails { + ///the callback event [DataGridCellPdfExportDetails] + const DataGridCellPdfExportDetails( + this.cellType, this.pdfCell, this.cellValue, this.columnName); + + /// The type of the cell. + final DataGridExportCellType cellType; + + /// A corresponding cell in the Pdf document. + final PdfGridCell pdfCell; + + /// The value of the cell. Typically, it is [DataGridCell.value]. + final Object? cellValue; + + /// The name of a column. + final String columnName; +} + +/// Details for the callback that use [DataGridPdfHeaderFooterExportDetails] +class DataGridPdfHeaderFooterExportDetails { + /// + DataGridPdfHeaderFooterExportDetails(this.pdfPage, this.pdfDocumentTemplate); + + /// The [PdfPage] which is currently being exported. + final PdfPage pdfPage; + + /// The corresponding [PdfDocumentTemplate] to set the header and footer. + final PdfDocumentTemplate pdfDocumentTemplate; +} + +/// Signature for `cellExport` callback which is passed as an argument in +/// `exportToPdfGrid` and `exportToPdfDocument` methods. +typedef DataGridCellPdfExportCallback = void Function( + DataGridCellPdfExportDetails details); + +/// Signature for `headerFooterExport` callback which is passed as an argument +/// `exportToPdfGrid` and `exportToPdfDocument` methods. +typedef DataGridPdfHeaderFooterExportCallback = void Function( + DataGridPdfHeaderFooterExportDetails details); diff --git a/packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart b/packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart new file mode 100644 index 000000000..ed214eb91 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/lib/src/helper.dart @@ -0,0 +1,177 @@ +import 'dart:math'; + +import 'package:collection/collection.dart'; +import 'package:syncfusion_flutter_datagrid/datagrid.dart'; + +/// ----------------- Stacked header helper methods -------------------// + +/// Returns the column sequences for the stacked header column by using the +/// columnNames property. +List getColumnSequences(List columns, StackedHeaderCell cell) { + final List childSequences = []; + if (cell.columnNames.isNotEmpty) { + for (final String columnName in cell.columnNames) { + final GridColumn? column = columns.firstWhereOrNull( + (GridColumn column) => column.columnName == columnName); + if (column != null) { + childSequences.add(columns.indexOf(column)); + } + } + } + + if (childSequences.isNotEmpty) { + childSequences.sort(); + } + return childSequences; +} + +/// Returns the collection of consecutive ranges for the given column indexes +List> getConsecutiveRanges(List columnIndexes) { + int endIndex = 1; + final List> list = >[]; + if (columnIndexes.isEmpty) { + return list; + } + for (int i = 1; i <= columnIndexes.length; i++) { + if (i == columnIndexes.length || + columnIndexes[i] - columnIndexes[i - 1] != 1) { + if (endIndex == 1) { + list.add(columnIndexes.sublist(i - endIndex, (i - endIndex) + 1)); + } else { + list.add(columnIndexes.sublist(i - endIndex, i)); + } + endIndex = 1; + } else { + endIndex++; + } + } + return list; +} + +/// Returns the row span to the stacked header and column header row. +int getRowSpan( + {String? columnName, + required int rowIndex, + required int columnIndex, + required bool isStackedHeader, + required SfDataGrid dataGrid, + StackedHeaderCell? stackedHeaderCell}) { + int startIndex = 0, endIndex = 0, rowSpan = 0; + if (isStackedHeader && stackedHeaderCell != null) { + final List> columnIndexes = getConsecutiveRanges( + getColumnSequences(dataGrid.columns, stackedHeaderCell)); + final List? spannedColumn = columnIndexes + .singleWhereOrNull((List column) => column.first == columnIndex); + if (spannedColumn != null) { + startIndex = spannedColumn.reduce(min); + endIndex = startIndex + spannedColumn.length - 1; + } + } else { + if (rowIndex >= dataGrid.stackedHeaderRows.length) { + return rowSpan; + } + } + + while (rowIndex >= 0) { + final StackedHeaderRow stackedHeaderRow = + dataGrid.stackedHeaderRows[rowIndex]; + for (final StackedHeaderCell stackedColumn in stackedHeaderRow.cells) { + if (isStackedHeader) { + final List> columnIndexes = getConsecutiveRanges( + getColumnSequences(dataGrid.columns, stackedColumn)); + for (final List column in columnIndexes) { + if ((startIndex >= column.first && startIndex <= column.last) || + (endIndex >= column.first && endIndex <= column.last)) { + return rowSpan; + } + } + } else if (stackedColumn.columnNames.isNotEmpty) { + final List children = stackedColumn.columnNames; + for (int child = 0; child < children.length; child++) { + if (children[child] == columnName) { + return rowSpan; + } + } + } + } + + rowSpan += 1; + rowIndex -= 1; + } + + return rowSpan; +} + +/// Returns the start and end index of the given spanned columns when the +/// exclude columns are applied. +List getSpannedCellStartAndEndIndex( + {required int columnSpan, + required int columnIndex, + required int startColumnIndex, + required List columns, + required List excludeColumns}) { + int excludeColumnsCount = 0; + int firstColumnIndex = startColumnIndex + columnIndex; + int lastColumnIndex = firstColumnIndex + columnSpan; + final List excludeColumnIndexes = []; + + for (final String columnName in excludeColumns) { + final GridColumn? column = columns.firstWhereOrNull( + (GridColumn element) => element.columnName == columnName); + if (column != null) { + excludeColumnIndexes.add(columns.indexOf(column)); + } + } + + if (firstColumnIndex > startColumnIndex) { + // Updates the first and last column index if any exclude column exists + // before the `firstColumnIndex`. + excludeColumnsCount = + excludeColumnIndexes.where((int index) => index < columnIndex).length; + + firstColumnIndex = + max(startColumnIndex, firstColumnIndex - excludeColumnsCount); + lastColumnIndex = + max(startColumnIndex, lastColumnIndex - excludeColumnsCount); + } + + // To remove the in-between excluded columns from the `lastColumnIndex`. + excludeColumnsCount = excludeColumnIndexes + .where((int index) => + index >= columnIndex && index <= columnIndex + columnSpan) + .length; + lastColumnIndex -= excludeColumnsCount; + + return [firstColumnIndex, lastColumnIndex]; +} + +/// ----------------- Table summary row helper methods -------------------// + +/// Gets title column count of the given summary column. +int getTitleColumnCount(GridTableSummaryRow summaryRow, + List columns, List excludeColumns) { + int currentColumnSpan = 0; + for (int i = 0; i < summaryRow.titleColumnSpan; i++) { + if (i <= columns.length) { + if (!excludeColumns.contains(columns[i].columnName)) { + currentColumnSpan++; + } + } + } + return currentColumnSpan; +} + +/// Returns the column index to the summary column. +int getSummaryColumnIndex( + List columns, String columnName, List excludeColumns) { + if (excludeColumns.contains(columnName)) { + return -1; + } + + final List visibleColumns = columns + .where((GridColumn column) => !excludeColumns.contains(column.columnName)) + .toList(); + final GridColumn? column = visibleColumns.firstWhereOrNull( + (GridColumn element) => element.columnName == columnName); + return column != null ? visibleColumns.indexOf(column) : -1; +} diff --git a/packages/syncfusion_flutter_datagrid_export/pubspec.yaml b/packages/syncfusion_flutter_datagrid_export/pubspec.yaml new file mode 100644 index 000000000..78d770864 --- /dev/null +++ b/packages/syncfusion_flutter_datagrid_export/pubspec.yaml @@ -0,0 +1,26 @@ +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: 18.3.35-beta +homepage: https://github.com/syncfusion/flutter-widgets/tree/master/packages/syncfusion_flutter_datagrid_export + +environment: + sdk: ">=2.12.0 <3.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + + syncfusion_flutter_datagrid: + path: ../syncfusion_flutter_datagrid + + syncfusion_flutter_xlsio: + path: ../syncfusion_flutter_xlsio + + syncfusion_flutter_pdf: + path: ../syncfusion_flutter_pdf + + collection: ">=1.9.0 <=1.15.0" + + +flutter: null diff --git a/packages/syncfusion_flutter_datepicker/CHANGELOG.md b/packages/syncfusion_flutter_datepicker/CHANGELOG.md index 6ec118202..9aa3f9844 100644 --- a/packages/syncfusion_flutter_datepicker/CHANGELOG.md +++ b/packages/syncfusion_flutter_datepicker/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +**Features** +* Added a Today button in the Date Range Picker to move to today’s date. +* Added a selectable day predicate callback to decide whether a cell is selectable or not. + ## [19.2.44] **Features** * Provided support to display week numbers of the year. diff --git a/packages/syncfusion_flutter_datepicker/README.md b/packages/syncfusion_flutter_datepicker/README.md index 1e69b244e..1b1eea299 100644 --- a/packages/syncfusion_flutter_datepicker/README.md +++ b/packages/syncfusion_flutter_datepicker/README.md @@ -41,6 +41,14 @@ The Flutter Date Range Picker is a lightweight widget that allows users to easil ![week_numbers](https://cdn.syncfusion.com/content/images/FTControl/date%20range%20picker/datepicker-weeknumber.png) +* **Today button** - Displays a Today button at the bottom of the Date Range Picker, allowing you to quickly navigate to today’s date. + +![today_button](https://cdn.syncfusion.com/content/images/FTControl/Flutter/datepicker-today-button.png) + +* **Selectable day predicate** - Allows you to decide whether a cell is selectable or not. + +![selectable_day_predicate](https://cdn.syncfusion.com/content/images/FTControl/Flutter/datepicker-selectable-day-predicate.png) + * **Quick navigation** - Navigate back and forth the date-range views and between different view modes. * **Enable/disable built-in view switching** - Restrict users from navigating to different picker views by disabling view switching. Select values in terms of month, year, or decade with this feature enabled. @@ -205,4 +213,4 @@ Widget build(BuildContext context) { 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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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 +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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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_datepicker/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_datepicker/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.cc index 4bfa0f3a3..8b6d4680a 100644 --- a/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.h index 9846246b4..dc139d85a 100644 --- a/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_datepicker/example/windows/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ 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 b32303cd5..81679c135 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 @@ -1,22 +1,25 @@ import 'dart:ui'; + import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/gestures.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/core_internal.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_datepicker/datepicker.dart'; + import 'date_picker_manager.dart'; import 'hijri_date_picker_manager.dart'; import 'month_view.dart'; import 'picker_helper.dart'; import 'year_view.dart'; +/// Signature for callback that reports that the picker state value changed. typedef UpdatePickerState = void Function( PickerStateArgs updatePickerStateDetails); @@ -27,6 +30,10 @@ typedef UpdatePickerState = void Function( /// view changes available in the [DateRangePickerViewChangedArgs]. /// /// Used by [SfDateRangePicker.onViewChanged]. +/// +/// See also: +/// * [SfDateRangePicker.onViewChanged], which matches this signature. +/// * [SfDateRangePicker], which uses this signature in one of it's callback. typedef DateRangePickerViewChangedCallback = void Function( DateRangePickerViewChangedArgs dateRangePickerViewChangedArgs); @@ -37,6 +44,11 @@ typedef DateRangePickerViewChangedCallback = void Function( /// view changes available in the [HijriDatePickerViewChangedArgs]. /// /// Used by [SfHijriDateRangePicker.onViewChanged]. +/// +/// See also: +/// * [SfHijriDateRangePicker.onViewChanged], which matches this signature. +/// * [SfHijriDateRangePicker], which uses this signature in one of it's +/// callback. typedef HijriDatePickerViewChangedCallback = void Function( HijriDatePickerViewChangedArgs hijriDatePickerViewChangedArgs); @@ -48,6 +60,13 @@ typedef HijriDatePickerViewChangedCallback = void Function( /// /// Used by [SfDateRangePicker.onSelectionChanged] and /// [SfHijriDateRangePicker.onSelectionChanged]. +/// +/// See also: +/// * [SfDateRangePicker.onSelectionChanged], which matches this signature. +/// * [SfHijriDateRangePicker.onSelectionChanged], which matches this signature. +/// * [SfDateRangePicker], which uses this signature in one of it's callback. +/// * [SfHijriDateRangePicker], which uses this signature in one of it's +/// callback. typedef DateRangePickerSelectionChangedCallback = void Function( DateRangePickerSelectionChangedArgs dateRangePickerSelectionChangedArgs); @@ -127,12 +146,20 @@ void _raisePickerViewChangedCallback(_SfDateRangePicker picker, /// {@youtube 560 315 https://www.youtube.com/watch?v=3TyuUVExuPs} /// /// See also: -/// * [SfDateRangePickerThemeData] -/// * [DateRangePickerHeaderStyle] -/// * [DateRangePickerViewHeaderStyle] -/// * [DateRangePickerMonthViewSettings] -/// * [DateRangePickerYearCellStyle] -/// * [DateRangePickerMonthCellStyle] +/// * [SfDateRangePickerThemeData], which used to set consistent look for the +/// date range picker element. +/// * [DateRangePickerHeaderStyle], which used to customize the header view of +/// the date range picker. +/// * [DateRangePickerViewHeaderStyle], which used to customize the view header +/// view of the date range picker. +/// * [DateRangePickerMonthViewSettings], which used to customize the month view +/// of the date range picker. +/// * [DateRangePickerYearCellStyle], which used to customize the year, decade +/// and century views cell of the date range picker. +/// * [DateRangePickerMonthCellStyle], which used to customize the month cells +/// of month view in date range picker. +/// * [SfHijriDateRangePicker], Material widget, which used to display and +/// handle the hijri date time values. /// /// ``` dart ///class MyApp extends StatefulWidget { @@ -175,51 +202,53 @@ class SfDateRangePicker extends StatelessWidget { /// /// When the visible view changes, the widget will call the [onViewChanged] /// callback with the current view and the current view visible dates. - SfDateRangePicker({ - Key? key, - DateRangePickerView view = DateRangePickerView.month, - this.selectionMode = DateRangePickerSelectionMode.single, - this.headerHeight = 40, - this.todayHighlightColor, - this.backgroundColor, - DateTime? initialSelectedDate, - List? initialSelectedDates, - PickerDateRange? initialSelectedRange, - List? initialSelectedRanges, - this.toggleDaySelection = false, - this.enablePastDates = true, - this.showNavigationArrow = false, - this.confirmText = 'OK', - this.cancelText = 'CANCEL', - this.showActionButtons = false, - this.selectionShape = DateRangePickerSelectionShape.circle, - this.navigationDirection = DateRangePickerNavigationDirection.horizontal, - this.allowViewNavigation = true, - this.navigationMode = DateRangePickerNavigationMode.snap, - this.enableMultiView = false, - this.controller, - this.onViewChanged, - this.onSelectionChanged, - this.onCancel, - this.onSubmit, - this.headerStyle = const DateRangePickerHeaderStyle(), - this.yearCellStyle = const DateRangePickerYearCellStyle(), - this.monthViewSettings = const DateRangePickerMonthViewSettings(), - this.monthCellStyle = const DateRangePickerMonthCellStyle(), - DateTime? minDate, - DateTime? maxDate, - DateTime? initialDisplayDate, - double viewSpacing = 20, - this.selectionRadius = -1, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectionTextStyle, - this.rangeTextStyle, - this.monthFormat, - this.cellBuilder, - }) : assert(headerHeight >= -1), + SfDateRangePicker( + {Key? key, + DateRangePickerView view = DateRangePickerView.month, + this.selectionMode = DateRangePickerSelectionMode.single, + this.headerHeight = 40, + this.todayHighlightColor, + this.backgroundColor, + DateTime? initialSelectedDate, + List? initialSelectedDates, + PickerDateRange? initialSelectedRange, + List? initialSelectedRanges, + this.toggleDaySelection = false, + this.enablePastDates = true, + this.showNavigationArrow = false, + this.confirmText = 'OK', + this.cancelText = 'CANCEL', + this.showActionButtons = false, + this.selectionShape = DateRangePickerSelectionShape.circle, + this.navigationDirection = DateRangePickerNavigationDirection.horizontal, + this.allowViewNavigation = true, + this.navigationMode = DateRangePickerNavigationMode.snap, + this.enableMultiView = false, + this.controller, + this.onViewChanged, + this.onSelectionChanged, + this.onCancel, + this.onSubmit, + this.headerStyle = const DateRangePickerHeaderStyle(), + this.yearCellStyle = const DateRangePickerYearCellStyle(), + this.monthViewSettings = const DateRangePickerMonthViewSettings(), + this.monthCellStyle = const DateRangePickerMonthCellStyle(), + DateTime? minDate, + DateTime? maxDate, + DateTime? initialDisplayDate, + double viewSpacing = 20, + this.selectionRadius = -1, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectionTextStyle, + this.rangeTextStyle, + this.monthFormat, + this.cellBuilder, + this.showTodayButton = false, + this.selectableDayPredicate}) + : assert(headerHeight >= -1), assert(minDate == null || maxDate == null || minDate.isBefore(maxDate)), assert(minDate == null || maxDate == null || maxDate.isAfter(minDate)), assert(viewSpacing >= 0), @@ -259,7 +288,14 @@ class SfDateRangePicker extends StatelessWidget { /// null, then this property will be ignored and widget will display the view /// described in [controller.view] property. /// - /// Also refer [DateRangePickerView]. + /// See also: + /// * [DateRangePickerView], to know more about the available views in the + /// date range picker. + /// * [DateRangePickerController.view], which used to changed the view of + /// date range picker dynamically. + /// * [allowViewNavigation], which allows to navigate to different date range + /// picker views quick and easily by tapping on the header. + /// * [How to switch between the date range picker views when calendar has appointments](https://www.syncfusion.com/kb/11305/how-to-switch-between-the-date-range-picker-views-in-the-flutter-date-range-picker) /// /// ```dart ///Widget build(BuildContext context) { @@ -293,7 +329,66 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerMonthViewSettings.enableSwipeSelection] set as [false] the /// navigation through swiping will work as it is without any restriction. /// - /// See also: [DateRangePickerMonthViewSettings.enableSwipeSelection]. + /// See also: + /// * [DateRangePickerMonthViewSettings.enableSwipeSelection], which allows to + /// select the cells on swipe when the selection mode set as + /// [DateRangePickerSelectionMode.range], + /// [DateRangePickerSelectionMode.multiRange], and + /// [DateRangePickerSelectionMode.extendableRange]. + /// * [DateRangePickerSelectionMode], to know more about the available + /// selection modes in date range picker. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year, decade and century view of date range picker. + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * [toggleDaySelection], which allows to deselect a date when the selection + /// mode set as [DateRangePickerSelectionMode.single]. + /// * [showActionButtons], which displays action buttons on bottom of date + /// range picker, which allows to confirm and cancel the selection. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// /// /// ``` dart /// Widget build(BuildContext context) { @@ -319,7 +414,20 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerHeaderStyle.backgroundColor] of the header view in /// [SfDateRangePicker]. /// - /// See also: [DateRangePickerHeaderStyle] + /// See also: + /// * [DateRangePickerHeaderStyle], which contains options to customize the + /// header view of the date range picker. + /// * [headerHeight], which is the size of the header view in the date range + /// picker. + /// * [showNavigationArrow], which displays the navigation arrows on the + /// header view of the date range picker. + /// * [monthFormat], which allows to customize the month text in the header + /// view also in the year cell view of date range picker. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict the year view navigation when tapping on header view](https://www.syncfusion.com/kb/12113/how-to-restrict-the-year-view-navigation-while-tapping-header-of-the-flutter-date-range) + /// * Knowledge base: [How to customize the header in Flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) /// /// ``` dart /// Widget build(BuildContext context) { @@ -351,6 +459,18 @@ class SfDateRangePicker extends StatelessWidget { /// _Note:_ If [showNavigationArrows] set as true the arrows will shrink or /// grow based on the given header height value. /// + /// See also: + /// * [headerStyle], which allows to customize the header view of the + /// date range picker. + /// * [showNavigationArrow], which displays the navigation arrows on the + /// header view of the date range picker. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict the year view navigation when tapping on header view](https://www.syncfusion.com/kb/12113/how-to-restrict-the-year-view-navigation-while-tapping-header-of-the-flutter-date-range) + /// * Knowledge base: [How to customize the header in Flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// * Knowledge base: [How to navigate to the previous or next views using navigation arrows](https://www.syncfusion.com/kb/12270/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter-date) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -375,6 +495,16 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to null. /// + /// See also: + /// * [SfDateRangePickerThemeData], to handle theming with date range picker + /// for giving consistent look. + /// * [monthCellStyle], which allows to customize the month cells in the + /// date range picker. + /// * [yearCellStyle], which allows to customize the year cells in the date + /// range picker. + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -396,6 +526,11 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to null. /// + /// See also: + /// * [SfDateRangePickerThemeData], to handle theming with date range picker + /// for giving consistent look. + /// * Knowledge base: [How to add an image as background](https://www.syncfusion.com/kb/12233/how-to-add-an-image-as-background-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -423,6 +558,21 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to `false`. /// + /// See also: + /// * [selectionMode], which allows to set different selection modes for + /// date range picker. + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * Knowledge base: [How to deselect the selected date](https://www.syncfusion.com/kb/12138/how-to-deselect-the-selected-date-in-the-flutter-date-range-picker-sfdaterangepicker) /// ```dart /// /// Widget build(BuildContext context) { @@ -450,6 +600,13 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to `true`. /// + /// See also: + /// * [view], which allows to set different views which display initially on + /// date range picker. + /// * [DateRangePickerController.view], which allows to set different views + /// dynamically on date range picker. + /// * Knowledge base: [How to restrict the year view navigation while tapping header](https://www.syncfusion.com/kb/12113/how-to-restrict-the-year-view-navigation-while-tapping-header-of-the-flutter-date-range) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -469,6 +626,18 @@ class SfDateRangePicker extends StatelessWidget { /// decade and century views. The month cell, year cell, decade cell, /// century cell was differentiated by picker view. /// + /// See also: + /// * [monthViewSettings], which allows to customize the month view in the + /// date range picker. + /// * [monthCellStyle], which allows to customize the month cells in the date + /// range picker. + /// * [yearCellStyle], which allows to customize the year cells in the date + /// range picker. + /// * Knowledge base: [How to customize the leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select all days when clicking on day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// /// ```dart /// /// DateRangePickerController _controller = DateRangePickerController(); @@ -529,6 +698,65 @@ class SfDateRangePicker extends StatelessWidget { /// ``` final DateRangePickerCellBuilder? cellBuilder; + /// Displays the today button on the bottom of date range picker. + /// + /// The today button allows to navigate to the today date quickly in all + /// view of date range picker. + /// + /// Defaults to `false`. + /// + /// See also: + /// * [showActionButtons], which used to handle the selected value. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// showTodayButton: true, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final bool showTodayButton; + + /// An [selectableDayPredicate] callback to decide whether the cell is + /// selectable or not in date range picker. + /// + /// Note: This callback is not applicable when the [navigationMode] set as + /// [DateRangePickerNavigationMode.scroll]. + /// + /// Defaults to null. + /// + /// See also: + /// [DateRangePickerMonthViewSettings.blackoutDates], which allows to disable + /// interaction for specific dates. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfDateRangePicker( + /// initialDisplayDate: DateTime(2022), + /// selectableDayPredicate: (DateTime dateTime) { + /// if (dateTime != DateTime(2022)) { + /// return false; + /// } + /// return true; + /// }, + /// ), + /// )); + /// } + /// + /// ``` + final DateRangePickerSelectableDayPredicate? selectableDayPredicate; + /// Used to enable or disable showing multiple views /// /// When setting this [enableMultiView] property set to [true] displaying @@ -549,6 +777,15 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to `false`. /// + /// See also: + /// * [viewSpacing], which fills the space between the pickers in the date + /// range picker. + /// * [navigationDirection], which allows to arrange and navigate the + /// multiview in either in [DateRangePickerNavigationDirection.vertical] or + /// [DateRangePickerNavigationDirection.horizontal] in date range picker. + /// * Knowledge base: [How to show tow pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to use multiple picker](https://www.syncfusion.com/kb/11806/how-to-use-multiple-picker-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -573,6 +810,15 @@ class SfDateRangePicker extends StatelessWidget { /// This value not applicable on [SfDateRangePicker] when /// [navigationMode] is [DateRangePickerNavigationMode.scroll]. /// + /// See also: + /// * [enableMultiView], which allows displays multiple date picker side by + /// side in date range picker. + /// * [navigationDirection], which allows to arrange and navigate the + /// multiview in either in [DateRangePickerNavigationDirection.vertical] or + /// [DateRangePickerNavigationDirection.horizontal] in date range picker. + /// * Knowledge base: [How to show tow pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to use multiple picker](https://www.syncfusion.com/kb/11806/how-to-use-multiple-picker-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -598,6 +844,26 @@ class SfDateRangePicker extends StatelessWidget { /// /// ```dart /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// Widget build(BuildContext context) { /// return MaterialApp( /// home: Scaffold( @@ -622,6 +888,25 @@ class SfDateRangePicker extends StatelessWidget { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfda + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -661,8 +946,31 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.multiRange]. /// /// See also: - /// [PickerDateRange] - /// [DateRangePickerSelectionMode] + /// * [PickerDateRange], which used to holds the start and end date of the + /// selected range. + /// * [selectionMode], which allows to customize the selection modes with the + /// available modes. + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// /// ``` dart /// @@ -698,6 +1006,28 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.single] of /// [DateRangePickerSelectionMode.multiple]. /// + /// See more: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// @override @@ -732,6 +1062,29 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.range] of /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * [toggleDaySelection], which allows to deselect a date when the selection + /// mode set as [DateRangePickerSelectionMode.single]. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -766,6 +1119,27 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.range] of /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -799,6 +1173,27 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.range] of /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -833,7 +1228,30 @@ class SfDateRangePicker extends StatelessWidget { /// and [DateRangePickerMonthViewSettings.weekendDays] in month view of /// date range picker. /// - /// See also: [DateRangePickerMonthViewSettings] + /// See also: + /// * [DateRangePickerMonthViewSettings], to know more about available options + /// to customize the month view of date range picker + /// * [monthCellStyle], which allows to customize the month cell of the month + /// view of the date range picker + /// * [cellBuilder], which allows to set custom widget for the picker cells + /// in the date range picker. + /// * [yearCellStyle], which allows to customize the year cell of the year, + /// decade and century views of the date range picker. + /// * [backgroundColor], which fills the background of the date range picker. + /// * [todayHighlightColor], which highlights the today date cell in the date + /// range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the first day of week](https://www.syncfusion.com/kb/12221/how-to-change-the-first-day-of-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to change the week end dates](https://www.syncfusion.com/kb/12182/how-to-change-the-week-end-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12167/how-to-change-the-number-of-weeks-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to add active dates](https://www.syncfusion.com/kb/12075/how-to-add-active-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to create timeline date picker](https://www.syncfusion.com/kb/12474/how-to-create-timeline-date-picker-in-flutter) + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -887,7 +1305,35 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerYearCellStyle.disabledDatesDecoration] in year, decade and /// century view of the date range picker. /// - /// See also: [DateRangePickerYearCellStyle]. + /// See also: + /// * [DateRangePickerYearCellStyle], to know more about available options + /// to customize the year cells of date range picker + /// * [monthCellStyle], which allows to customize the month cell of the month + /// view of the date range picker + /// * [cellBuilder], which allows to set custom widget for the picker cells + /// in the date range picker. + /// * [monthViewSettings], which allows to customize the month view of the + /// date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * [backgroundColor], which fills the background of the date range picker. + /// * [todayHighlightColor], which highlights the today date cell in the date + /// range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -946,7 +1392,36 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerMonthCellStyle.weekendDatesDecoration] in the month cells /// of the date range picker. /// - /// See also: [DateRangePickerMonthCellStyle] + /// See also: + /// * [DateRangePickerMonthCellStyle] to know more about available options + /// to customize the month cell of date range picker + /// * [monthViewSettings], which allows to customize the month view of the + /// date range picker + /// * [cellBuilder], which allows to set custom widget for the picker cells + /// in the date range picker. + /// * [yearCellStyle], which allows to customize the year cell of the year, + /// decade and century views of the date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * [backgroundColor], which fills the background of the date range picker. + /// * [todayHighlightColor], which highlights the today date cell in the date + /// range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -995,6 +1470,33 @@ class SfDateRangePicker extends StatelessWidget { /// not [null] then this property will be ignored and the widget render the /// dates based on the date given in [controller.displayDate]. /// + /// See also: + /// * [DateRangePickerController.displayDate], which allows to move the + /// date range picker to specific date. + /// * [DateRangePickerController.forward], which allows to navigate to next + /// view of the date range picker programmatically. + /// * [DateRangePickerController.backward], which allows to navigate to + /// previous view of the date range picker programmatically. + /// * [onViewChanged], the callback which will notify that the current visible + /// dates were changed in date range picker. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on date range picker. + /// * [minDate], which is the least available date for the date range picker. + /// * [maxDate], which is the last available date for the date range picker. + /// * [showNavigationArrow], which display the navigation arrows on the header + /// view of the date range picker. + /// * Knowledge base: [How to navigate to the previous or next views using navigation arrows](https://www.syncfusion.com/kb/12270/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter-date) + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict date range picker within date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1024,6 +1526,43 @@ class SfDateRangePicker extends StatelessWidget { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.single]. /// + /// See also: + /// * [initialDisplayDate], which used to navigate the date range picker to + /// the specific date initially. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on date + /// range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to deselect the selected date](https://www.syncfusion.com/kb/12138/how-to-deselect-the-selected-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker)/// + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1055,9 +1594,15 @@ class SfDateRangePicker extends StatelessWidget { /// /// /// See also: - /// [initialDisplayDate]. - /// [maxDate]. - /// [controller.displayDate]. + /// * [initialDisplayDate], which used to navigate the date range picker to + /// the specific date on initially. + /// * [maxDate], which is last available date for the date range picker. + /// * [controller.displayDate], which used to navigate the date range picker + /// to specific date on dynamically. + /// * [enablePastDates], which allows to enable the dates that falls before + /// the today date for interaction. + /// * Knowledge base: [How to enable or disable the past dates](https://www.syncfusion.com/kb/12168/how-to-enable-or-disable-the-past-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict date range picker within the date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -1089,10 +1634,15 @@ class SfDateRangePicker extends StatelessWidget { /// this property. /// /// See also: - /// - /// [initialDisplayDate]. - /// [minDate]. - /// [controller.displayDate]. + /// * [initialDisplayDate], which used to navigate the date range picker to + /// the specific date on initially. + /// * [minDate], which is least available date for the date range picker. + /// * [controller.displayDate], which used to navigate the date range picker + /// to specific date on dynamically. + /// * [enablePastDates], which allows to enable the dates that falls before + /// the today date for interaction. + /// * Knowledge base: [How to enable or disable the past dates](https://www.syncfusion.com/kb/12168/how-to-enable-or-disable-the-past-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict date range picker within the date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -1118,6 +1668,13 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to `true`. /// + /// See also: + /// * [minDate], which is the least available date for the date range picker. + /// * [maxDate], which is last available date for the date range picker. + /// * Knowledge base: [How to enable or disable the past dates](https://www.syncfusion.com/kb/12168/how-to-enable-or-disable-the-past-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict date range picker within the date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) + /// + /// /// ``` dart /// ///Widget build(BuildContext context) { @@ -1148,6 +1705,41 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.multiple]. /// /// + /// See also: + /// * [initialDisplayDate], which used to navigate the date range picker to + /// the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on date + /// range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1184,7 +1776,47 @@ class SfDateRangePicker extends StatelessWidget { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.range]. /// - /// See also: [PickerDateRange]. + /// See also: + /// * [PickerDateRange], which is used to store the start and end date of the + /// range selection. + /// * [initialDisplayDate], which used to navigate the date range picker to + /// the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on date + /// range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -1219,7 +1851,47 @@ class SfDateRangePicker extends StatelessWidget { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiRange]. /// - /// See also: [PickerDateRange]. + /// See also: + /// * [PickerDateRange], which is used to store the start and end date of the + /// range selection. + /// * [initialDisplayDate], which used to navigate the date range picker to + /// the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on date + /// range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -1273,13 +1945,58 @@ class SfDateRangePicker extends StatelessWidget { /// select the dates or ranges programmatically on [SfDateRangePicker] on /// initial load and in run time. /// - /// See also: [DateRangePickerSelectionMode] /// /// Defaults to null. /// + /// See also: + /// * [DateRangePickerController], to know more about the controller and it's + /// usage with the date range picker. + /// * [initialDisplayDate], which used to navigate the date range picker to + /// the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onViewChanged], the callback which notifies when the current view + /// visible date changed on the date range picker. + /// * [onSelectionChanged], the callback which notifies when the selected cell + /// changed on the the date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on date + /// range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// This example demonstrates how to use the [SfDateRangePickerController] for /// [SfDateRangePicker]. /// + /// /// ``` dart /// ///class MyApp extends StatefulWidget { @@ -1346,6 +2063,13 @@ class SfDateRangePicker extends StatelessWidget { /// [false] the navigation arrows will be shown, only whn the /// [showNavigationArrow] property set as [true]. /// + /// See also: + /// * [DateRangePickerController.forward], which allows to navigate to next + /// view of the date range picker programmatically. + /// * [DateRangePickerController.backward], which allows to navigate to + /// previous view of the date range picker programmatically. + /// * Knowledge base: [How to navigate to the previous or next dates using navigation arrows](https://www.syncfusion.com/kb/12270/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter-date) + /// /// ``` dart /// ///Widget build(BuildContext context) { @@ -1372,6 +2096,17 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to `DateRangePickerNavigationDirection.horizontal`. /// + /// See also: + /// * [navigationMode], which allows to customize the navigation mode with + /// available options. + /// * [minDate], which is the least available date in the date range picker. + /// * [maxDate], which is the last available date in the date range picker. + /// * [enableMultiView], which allows to display multiple picker side by + /// side based on the navigation direction. + /// * Knowledge base: [How to show two pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the navigation direction](https://www.syncfusion.com/kb/12176/how-to-change-the-navigation-direction-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1406,6 +2141,32 @@ class SfDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionShape.circle], then the circle radius can be /// adjusted in month view by using the [selectionRadius] property. /// + /// See also: + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year, decade and century view of date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1435,6 +2196,18 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to null. /// + /// See also: + /// * [headerStyle], which used to customize the header view of the date range + /// picker. + /// * [headerHeight], which is the size of the header view in the date range + /// picker. + /// * [yearCellStyle], which is used to customize the year, decade and century + /// view cells in the date range picker. + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the header in the flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the month format](https://www.syncfusion.com/kb/12169/how-to-change-the-month-format-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1458,6 +2231,18 @@ class SfDateRangePicker extends StatelessWidget { /// /// Defaults to [DateRangePickerNavigationMode.snap] /// + /// See also: + /// * [navigationDirection], which allows to customize the navigation + /// direction of the date range picker with available options. + /// * [minDate], which is the least available date in the date range picker. + /// * [maxDate], which is the last available date in the date range picker. + /// * [enableMultiView], which allows to display multiple picker side by + /// side based on the navigation direction. + /// * Knowledge base: [How to show two pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the navigation direction](https://www.syncfusion.com/kb/12176/how-to-change-the-navigation-direction-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict the view navigation](https://www.syncfusion.com/kb/12500/how-to-restrict-the-view-navigation-in-the-flutter-date-range-picker) + /// + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1476,6 +2261,28 @@ class SfDateRangePicker extends StatelessWidget { /// The visible date range and the visible view which visible on view when the /// view changes available in the [DateRangePickerViewChangedArgs]. /// + /// See also: + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * [DateRangePickerViewChangedArgs], which contains the visible date range + /// details of the current visible view. + /// * [initialDisplayDate], which is used to navigate the date range picker to + /// specific date on initially. + /// * [DateRangePickerController.displayDate], which allows to move the + /// date range picker to specific date. + /// * [DateRangePickerController.forward], which allows to navigate to next + /// view of the date range picker programmatically. + /// * [DateRangePickerController.backward], which allows to navigate to + /// previous view of the date range picker programmatically. + /// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the header in flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// + /// + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1502,6 +2309,48 @@ class SfDateRangePicker extends StatelessWidget { /// The dates or ranges that selected when the selection changes available in /// the [DateRangePickerSelectionChangedArgs]. /// + /// See also: + /// * [onViewChanged], callback which notifies when the current view visible + /// dates changed on date range picker. + /// * [DateRangePickerMonthViewSettings.enableSwipeSelection], which allows to + /// select the cells on swipe when the selection mode set as + /// [DateRangePickerSelectionMode.range], + /// [DateRangePickerSelectionMode.multiRange], and + /// [DateRangePickerSelectionMode.extendableRange]. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year, decade and century view of date range picker. + /// * [toggleDaySelection], which allows to deselect a date when the selection + /// mode set as [DateRangePickerSelectionMode.single]. + /// * [showActionButtons], which displays action buttons on bottom of date + /// range picker, which allows to confirm and cancel the selection. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on date range picker. + /// * [DateRangePickerController.selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [DateRangePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -1542,9 +2391,17 @@ class SfDateRangePicker extends StatelessWidget { /// Text that displays on the confirm button. /// - /// See also - /// [showActionButtons] - /// [onSelectionChanged]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [cancelText], which is text that display on the cancel button. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -1566,9 +2423,17 @@ class SfDateRangePicker extends StatelessWidget { /// Text that displays on the cancel button. /// - /// See also - /// [showActionButtons] - /// [onCancel]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [confirmText], which is text that display on the confirm button. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -1594,6 +2459,31 @@ class SfDateRangePicker extends StatelessWidget { /// The [onSubmit] and [onCancel] callback is called based on the /// actions of the buttons. /// + /// See also: + /// * [DateRangePickerMonthViewSettings.enableSwipeSelection], which allows to + /// select the cells on swipe when the selection mode set as + /// [DateRangePickerSelectionMode.range], + /// [DateRangePickerSelectionMode.multiRange], and + /// [DateRangePickerSelectionMode.extendableRange]. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year, decade and century view of date range picker. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [selectionRadius], which is the radius for the selection view in the + /// date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// @override @@ -1616,8 +2506,16 @@ class SfDateRangePicker extends StatelessWidget { /// Called whenever the cancel button tapped on date range picker. /// It reset the selected values to confirmed selected values. /// - /// See also - /// [showActionButtons]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [cancelText], which is text that display on the cancel button. + /// * [confirmText], which is text that display on the confirm button + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -1642,8 +2540,16 @@ class SfDateRangePicker extends StatelessWidget { /// The dates or ranges that have been selected are confirmed and the /// selected value is available in the value argument. /// - /// See also - /// [showActionButtons]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on date range picker. + /// * [cancelText], which is text that display on the cancel button. + /// * [confirmText], which is text that display on the confirm button + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -1719,6 +2625,8 @@ class SfDateRangePicker extends StatelessWidget { confirmText: confirmText, cancelText: cancelText, showActionButtons: showActionButtons, + showTodayButton: showTodayButton, + selectableDayPredicate: selectableDayPredicate, ); } @@ -1795,6 +2703,11 @@ class SfDateRangePicker extends StatelessWidget { .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); properties.add(monthCellStyle.toDiagnosticsNode(name: 'monthCellStyle')); + + properties + .add(DiagnosticsProperty('showTodayButton', showTodayButton)); + properties.add(DiagnosticsProperty( + 'selectableDayPredicate', selectableDayPredicate)); } } @@ -1846,12 +2759,21 @@ class SfDateRangePicker extends StatelessWidget { /// [HijriDatePickerYearCellStyle], [HijriDatePickerMonthCellStyle] /// /// See also: -/// * [SfDateRangePickerThemeData] -/// * [DateRangePickerHeaderStyle] -/// * [DateRangePickerViewHeaderStyle] -/// * [HijriDatePickerMonthViewSettings] -/// * [HijriDatePickerYearCellStyle] -/// * [HijriDatePickerMonthCellStyle] +/// * [SfDateRangePickerThemeData], which used to set consistent look for the +/// hijri date range picker element. +/// * [DateRangePickerHeaderStyle], which used to customize the header view of +/// the hijri date range picker. +/// * [DateRangePickerViewHeaderStyle], which used to customize the view header +/// view of the hijri date range picker. +/// * [HijriDatePickerMonthViewSettings], which used to customize the month view +/// of the hijri date range picker. +/// * [HijriDatePickerYearCellStyle], which used to customize the year, decade +/// and century views cell of the hijri date range picker. +/// * [HijriDatePickerMonthCellStyle], which used to customize the month cells +/// of month view in hijri date range picker. +/// * [SfDateRangePicker], Material widget, which used to display and +/// handle the gregorian date time values. +/// * Knowledge base: [How to use hijri date picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) /// /// ``` dart ///class MyApp extends StatefulWidget { @@ -1938,6 +2860,8 @@ class SfHijriDateRangePicker extends StatelessWidget { this.rangeTextStyle, this.monthFormat, this.cellBuilder, + this.showTodayButton = false, + this.selectableDayPredicate, }) : initialSelectedDate = controller != null && controller.selectedDate != null ? controller.selectedDate @@ -1976,6 +2900,16 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Also refer [HijriDatePickerView]. /// + /// See also: + /// * [HijriDatePickerView], to know more about the available views in the + /// hijri date range picker. + /// * [HijriDatePickerController.view], which used to changed the view of + /// hijri date range picker dynamically. + /// * [allowViewNavigation], which allows to navigate to different hijri date + /// range picker views quick and easily by tapping on the header. + /// * [How to switch between the date range picker views when calendar has appointments](https://www.syncfusion.com/kb/11305/how-to-switch-between-the-date-range-picker-views-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to use hijri date picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) + /// /// ```dart ///Widget build(BuildContext context) { /// return MaterialApp( @@ -2007,7 +2941,67 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [false] the navigation through swiping will work as it is without any /// restriction. /// - /// See also: [HijriDatePickerMonthViewSettings.enableSwipeSelection]. + /// See also: + /// * [HijriDatePickerMonthViewSettings.enableSwipeSelection], which allows to + /// select the cells on swipe when the selection mode set as + /// [DateRangePickerSelectionMode.range], + /// [DateRangePickerSelectionMode.multiRange], and + /// [DateRangePickerSelectionMode.extendableRange]. + /// * [DateRangePickerSelectionMode], to know more about the available + /// selection modes in hijri date range picker. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year and decade view of the hijri date range picker. + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * [toggleDaySelection], which allows to deselect a date when the selection + /// mode set as [DateRangePickerSelectionMode.single]. + /// * [showActionButtons], which displays action buttons on bottom of hijri + /// date range picker, which allows to confirm and cancel the selection. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on hijri date range picker. + /// * Knowledge base: [How to use hijri date range picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to use hijri date picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) /// /// ``` dart /// Widget build(BuildContext context) { @@ -2033,7 +3027,19 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [DateRangePickerHeaderStyle.backgroundColor] of the header view in /// [SfHijriDateRangePicker]. /// - /// See also: [DateRangePickerHeaderStyle] + /// See also: + /// * [DateRangePickerHeaderStyle], which contains options to customize the + /// header view of the hijri date range picker. + /// * [headerHeight], which is the size of the header view in the hijri date + /// range picker. + /// * [showNavigationArrow], which displays the navigation arrows on the + /// header view of the hijri date range picker. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict the year view navigation when tapping on header view](https://www.syncfusion.com/kb/12113/how-to-restrict-the-year-view-navigation-while-tapping-header-of-the-flutter-date-range) + /// * Knowledge base: [How to customize the header in Flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// * Knowledge base: [How to use hijri date picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) /// /// ``` dart /// Widget build(BuildContext context) { @@ -2064,6 +3070,19 @@ class SfHijriDateRangePicker extends StatelessWidget { /// _Note:_ If [showNavigationArrows] set as true the arrows will shrink or /// grow based on the given header height value. /// + /// See also: + /// * [headerStyle], which allows to customize the header view of the hijri + /// date range picker. + /// * [showNavigationArrow], which displays the navigation arrows on the + /// header view of the hijri date range picker. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict the year view navigation when tapping on header view](https://www.syncfusion.com/kb/12113/how-to-restrict-the-year-view-navigation-while-tapping-header-of-the-flutter-date-range) + /// * Knowledge base: [How to customize the header in Flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// * Knowledge base: [How to navigate to the previous or next views using navigation arrows](https://www.syncfusion.com/kb/12270/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter-date) + /// * Knowledge base: [How to use hijri date picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2088,6 +3107,17 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to null. /// + /// See also: + /// * [SfDateRangePickerThemeData], to handle theming with hijri date range + /// picker for giving consistent look. + /// * [monthCellStyle], which allows to customize the month cells in the + /// hijri date range picker. + /// * [yearCellStyle], which allows to customize the year cells in the hijri + /// date range picker. + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to use hijri date picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2109,6 +3139,11 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to null. /// + /// See also: + /// * [SfDateRangePickerThemeData], to handle theming with hijri date range + /// picker for giving consistent look. + /// * Knowledge base: [How to add an image as background](https://www.syncfusion.com/kb/12233/how-to-add-an-image-as-background-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2136,6 +3171,22 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to `false`. /// + /// See also: + /// * [selectionMode], which allows to set different selection modes for + /// hijri date range picker. + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * Knowledge base: [How to deselect the selected date](https://www.syncfusion.com/kb/12138/how-to-deselect-the-selected-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2154,8 +3205,19 @@ class SfHijriDateRangePicker extends StatelessWidget { final bool toggleDaySelection; /// A builder that builds a widget that replaces the cell in a month, year, - /// decade and century views. The month cell, year cell, decade cell, - /// century cell was differentiated by picker view. + /// and decade views. The month cell, year cell, decade cell, + /// was differentiated by picker view. + /// + /// See also: + /// * [monthViewSettings], which allows to customize the month view in the + /// hijri date range picker. + /// * [monthCellStyle], which allows to customize the month cells in the hijri + /// date range picker. + /// * [yearCellStyle], which allows to customize the year cells in the hijri + /// date range picker. + /// * Knowledge base: [How to customize special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select all days when clicking on day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) /// /// ```dart /// @@ -2217,6 +3279,65 @@ class SfHijriDateRangePicker extends StatelessWidget { /// ``` final HijriDateRangePickerCellBuilder? cellBuilder; + /// Displays the today button on the bottom of the SfHijriDateRangePicker. + /// + /// The today button allows to navigate to the today date quickly in all view + /// of the SfHijriDateRangePicker. + /// + /// Defaults to `false`. + /// + /// See also: + /// * [showActionButtons], which used to handle the selected value. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// showTodayButton: true, + /// ), + /// ), + /// ); + /// } + /// + /// ``` + final bool showTodayButton; + + /// An [selectableDayPredicate] callback to decide whether the cell is + /// selectable or not in hijri date range picker. + /// + /// Note: This callback is not applicable when the [navigationMode] set as + /// [DateRangePickerNavigationMode.scroll]. + /// + /// Defaults to null. + /// + /// See also: + /// [HijriDatePickerMonthViewSettings.blackoutDates], which allows to + /// disable interaction for specific dates. + /// + /// ```dart + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// body: SfHijriDateRangePicker( + /// initialDisplayDate: HijriDateTime(1442, 1, 1), + /// selectableDayPredicate: (HijriDateTime dateTime) { + /// if (dateTime != HijriDateTime(1442, 1, 1)) { + /// return false; + /// } + /// return true; + /// }, + /// ), + /// )); + /// } + /// + /// ``` + final HijriDatePickerSelectableDayPredicate? selectableDayPredicate; + /// Used to enable or disable the view switching between /// [HijriDatePickerView] through interaction in the /// [SfHijriDateRangePicker] header. @@ -2228,6 +3349,13 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to `true`. /// + /// See also: + /// * [view], which allows to set different views which display initially on + /// hijri date range picker. + /// * [HijriDatePickerController.view], which allows to set different views + /// dynamically on hijri date range picker. + /// * Knowledge base: [How to restrict the year view navigation while tapping header](https://www.syncfusion.com/kb/12113/how-to-restrict-the-year-view-navigation-while-tapping-header-of-the-flutter-date-range) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2249,8 +3377,6 @@ class SfHijriDateRangePicker extends StatelessWidget { /// multiple views and provide quick navigation and dates selection. /// It is applicable for all the [HijriDatePickerView] types. /// - /// Decade view does not show trailing cells when the [enableMultiView] - /// property is enabled. /// /// Enabling this [enableMultiView] property is recommended for web /// browser and larger android and iOS devices(iPad, tablet, etc.,) @@ -2263,6 +3389,16 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to `false`. /// + /// See also: + /// * [viewSpacing], which fills the space between the pickers in the hijri + /// date range picker. + /// * [navigationDirection], which allows to arrange and navigate the + /// multiview in either in [DateRangePickerNavigationDirection.vertical] or + /// [DateRangePickerNavigationDirection.horizontal] in hijri date range + /// picker. + /// * Knowledge base: [How to show tow pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to use multiple picker](https://www.syncfusion.com/kb/11806/how-to-use-multiple-picker-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2288,6 +3424,16 @@ class SfHijriDateRangePicker extends StatelessWidget { /// This value not applicable on [SfHijriDateRangePicker] when /// [navigationMode] is [DateRangePickerNavigationMode.scroll]. /// + /// See also: + /// * [enableMultiView], which allows displays multiple date picker side by + /// side in hijri date range picker. + /// * [navigationDirection], which allows to arrange and navigate the + /// multiview in either in [DateRangePickerNavigationDirection.vertical] or + /// [DateRangePickerNavigationDirection.horizontal] in hijri date range + /// picker. + /// * Knowledge base: [How to show tow pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to use multiple picker](https://www.syncfusion.com/kb/11806/how-to-use-multiple-picker-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2311,6 +3457,26 @@ class SfHijriDateRangePicker extends StatelessWidget { /// _Note:_ This only applies if the [DateRangePickerSelectionMode] is set /// to [DateRangePickerSelectionMode.circle]. /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2337,6 +3503,25 @@ class SfHijriDateRangePicker extends StatelessWidget { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfda + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -2376,8 +3561,30 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.multiRange]. /// /// See also: - /// [HijriDateRange] - /// [DateRangePickerSelectionMode] + /// * [HijriDateRange], which used to holds the start and end date of the + /// selected range. + /// * [selectionMode], which allows to customize the selection modes with the + /// available modes. + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -2413,6 +3620,28 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.single] of /// [DateRangePickerSelectionMode.multiple]. /// + /// See more: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// @override @@ -2447,6 +3676,29 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.range] of /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * [toggleDaySelection], which allows to deselect a date when the selection + /// mode set as [DateRangePickerSelectionMode.single]. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -2482,6 +3734,27 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.range] of /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -2515,6 +3788,27 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionMode.range] of /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [selectionShape], which allows to customize the shape of the selection + /// view in hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -2546,7 +3840,28 @@ class SfHijriDateRangePicker extends StatelessWidget { /// and [HijriDatePickerMonthViewSettings.weekendDays] in month view of /// date range picker. /// - /// See also: [HijriDatePickerMonthViewSettings] + /// See also: + /// * [HijriDatePickerMonthViewSettings], to know more about available options + /// to customize the month view of hijri date range picker + /// * [monthCellStyle], which allows to customize the month cell of the month + /// view of the hijri date range picker + /// * [cellBuilder], which allows to set custom widget for the picker cells + /// in the hijri date range picker. + /// * [yearCellStyle], which allows to customize the year cell of the year, + /// and decade views of the hijri date range picker. + /// * [backgroundColor], which fills the background of the hijri date range + /// picker. + /// * [todayHighlightColor], which highlights the today date cell in the hijri + /// date range picker. + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the first day of week](https://www.syncfusion.com/kb/12221/how-to-change-the-first-day-of-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to change the week end dates](https://www.syncfusion.com/kb/12182/how-to-change-the-week-end-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to add active dates](https://www.syncfusion.com/kb/12075/how-to-add-active-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -2596,7 +3911,35 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [HijriDatePickerYearCellStyle.disabledDatesDecoration] in year and /// decade view of the date range picker. /// - /// See also: [HijriDatePickerYearCellStyle]. + /// See also: + /// * [HijriDatePickerYearCellStyle], to know more about available options + /// to customize the year cells of hijri date range picker + /// * [monthCellStyle], which allows to customize the month cell of the month + /// view of the hijri date range picker + /// * [cellBuilder], which allows to set custom widget for the picker cells + /// in the hijri date range picker. + /// * [monthViewSettings], which allows to customize the month view of the + /// hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * [backgroundColor], which fills the background of the hijri date range + /// picker. + /// * [todayHighlightColor], which highlights the today date cell in the hijri + /// date range picker. + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -2645,7 +3988,36 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [HijriDatePickerMonthCellStyle.weekendDatesDecoration] in the month /// cells of the date range picker. /// - /// See also: [HijriDatePickerMonthCellStyle] + /// See also: + /// * [HijriDatePickerMonthCellStyle] to know more about available options + /// to customize the month cell of hijri date range picker + /// * [monthViewSettings], which allows to customize the month view of the + /// hijri date range picker + /// * [cellBuilder], which allows to set custom widget for the picker cells + /// in the hijri date range picker. + /// * [yearCellStyle], which allows to customize the year cell of the year, + /// and decade views of the hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * [backgroundColor], which fills the background of the hijri date range + /// picker. + /// * [todayHighlightColor], which highlights the today date cell in the hijri + /// date range picker. + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -2689,6 +4061,35 @@ class SfHijriDateRangePicker extends StatelessWidget { /// not [null] then this property will be ignored and the widget render the /// dates based on the date given in [controller.displayDate]. /// + /// See also: + /// * [HijriDatePickerController.displayDate], which allows to move the + /// hijri date range picker to specific date. + /// * [HijriDatePickerController.forward], which allows to navigate to next + /// view of the hijri date range picker programmatically. + /// * [HijriDatePickerController.backward], which allows to navigate to + /// previous view of the date range picker programmatically. + /// * [onViewChanged], the callback which will notify that the current visible + /// dates were changed in hijri date range picker. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on hijri date range picker. + /// * [minDate], which is the least available date for the hijri date range + /// picker. + /// * [maxDate], which is the last available date for the hijri date range + /// picker. + /// * [showNavigationArrow], which display the navigation arrows on the header + /// view of the hijri date range picker. + /// * Knowledge base: [How to navigate to the previous or next views using navigation arrows](https://www.syncfusion.com/kb/12270/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter-date) + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict date range picker within date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2719,6 +4120,43 @@ class SfHijriDateRangePicker extends StatelessWidget { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.single]. /// + /// See also: + /// * [initialDisplayDate], which used to navigate the hijri date range picker + /// to the specific date initially. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on hijri date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the hijri date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the hijri date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on hijri date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on hijri + /// date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to deselect the selected date](https://www.syncfusion.com/kb/12138/how-to-deselect-the-selected-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker)/// + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -2751,10 +4189,16 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// /// See also: - /// [initialDisplayDate]. - /// [maxDate]. - /// [controller.displayDate]. - /// [HijriDateTime]. + /// * [initialDisplayDate], which used to navigate the hijri date range picker + /// to the specific date on initially. + /// * [maxDate], which is last available date for the hijri date range picker. + /// * [controller.displayDate], which used to navigate the hijri date range + /// picker to specific date on dynamically. + /// * [enablePastDates], which allows to enable the dates that falls before + /// the today date for interaction. + /// * [HijriDateTime], which handles the hijri date value details. + /// * Knowledge base: [How to enable or disable the past dates](https://www.syncfusion.com/kb/12168/how-to-enable-or-disable-the-past-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict date range picker within the date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -2786,11 +4230,17 @@ class SfHijriDateRangePicker extends StatelessWidget { /// set to this property. /// /// See also: - /// - /// [initialDisplayDate]. - /// [minDate]. - /// [controller.displayDate]. - /// [HijriDateTime]. + /// * [initialDisplayDate], which used to navigate the hijri date range picker + /// to the specific date on initially. + /// * [minDate], which is least available date for the hijri date range + /// picker. + /// * [controller.displayDate], which used to navigate the hijri date range + /// picker to specific date on dynamically. + /// * [enablePastDates], which allows to enable the dates that falls before + /// the today date for interaction. + /// * [HijriDateTime], which handles the date value in hijri type. + /// * Knowledge base: [How to enable or disable the past dates](https://www.syncfusion.com/kb/12168/how-to-enable-or-disable-the-past-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict date range picker within the date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -2816,6 +4266,13 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to `true`. /// + /// See also: + /// * [minDate], which is the least available date for the hijri date range + /// picker. + /// * [maxDate], which is last available date for the hijri date range picker. + /// * Knowledge base: [How to enable or disable the past dates](https://www.syncfusion.com/kb/12168/how-to-enable-or-disable-the-past-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict date range picker within the date limit](https://www.syncfusion.com/kb/11329/how-to-restrict-date-range-picker-within-the-date-limit-in-the-flutter-date-range-picker) + /// /// ``` dart /// ///Widget build(BuildContext context) { @@ -2846,6 +4303,40 @@ class SfHijriDateRangePicker extends StatelessWidget { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiple]. /// + /// See also: + /// * [initialDisplayDate], which used to navigate the hijri date range picker + /// to the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on hijri date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the hijri date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the hijri date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on hijri date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on hijri + /// date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -2883,7 +4374,47 @@ class SfHijriDateRangePicker extends StatelessWidget { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.range]. /// - /// See also: [HijriDateRange]. + /// See also: + /// * [HijriDateRange], which is used to store the start and end date of the + /// range selection. + /// * [initialDisplayDate], which used to navigate the hijri date range picker + /// to the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on hijri date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the hijri date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the hijri date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on hijri date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on hijri + /// date range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -2918,7 +4449,47 @@ class SfHijriDateRangePicker extends StatelessWidget { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiRange]. /// - /// See also: [HijriDateRange]. + /// See also: + /// * [HijriDateRange], which is used to store the start and end date of the + /// range selection. + /// * [initialDisplayDate], which used to navigate the hijri date range picker + /// to the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on hijri date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onSelectionChanged], the callback which notifies when the selection + /// cell changed on the hijri date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the hijri date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on hijri date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on hijri + /// date range picker. + /// * Knowledge base: [Range selection using multiple view picker](https://www.syncfusion.com/kb/11534/range-selection-using-the-multiple-view-picker-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -2976,10 +4547,54 @@ class SfHijriDateRangePicker extends StatelessWidget { /// programmatically on [SfHijriDateRangePicker] on initial load and in run /// time. /// - /// See also: [DateRangePickerSelectionMode] /// /// Defaults to null. /// + /// See also: + /// * [HijriDatePickerController], to know more about the controller and it's + /// usage with the hijri date range picker. + /// * [initialDisplayDate], which used to navigate the hijri date range picker + /// to the specific date initially. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on hijri date range picker. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [onViewChanged], the callback which notifies when the current view + /// visible date changed on the hijri date range picker. + /// * [onSelectionChanged], the callback which notifies when the selected cell + /// changed on the the hijri date range picker. + /// * [showActionButtons], which allows to cancel of confirm the selection in + /// the hijri date range picker. + /// * [onSubmit], the callback which notifies when the selected value + /// confirmed through confirm button on hijri date range picker. + /// * [onCancel], the callback which notifies when the selected value canceled + /// and reverted to previous confirmed value through cancel button on hijri + /// date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// This example demonstrates how to use the [HijriDatePickerController] /// for [SfHijriDateRangePicker]. /// @@ -3050,6 +4665,14 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [false] the navigation arrows will be shown, only whn the /// [showNavigationArrow] property set as [true]. /// + /// See also: + /// * [HijriDatePickerController.forward], which allows to navigate to next + /// view of the date range picker programmatically. + /// * [HijriDatePickerController.backward], which allows to navigate to + /// previous view of the date range picker programmatically. + /// * Knowledge base: [How to navigate to the previous or next dates using navigation arrows](https://www.syncfusion.com/kb/12270/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter-date) + /// + /// /// ``` dart /// ///Widget build(BuildContext context) { @@ -3076,6 +4699,18 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to `DateRangePickerNavigationDirection.horizontal`. /// + /// See also: + /// * [navigationMode], which allows to customize the navigation mode with + /// available options. + /// * [minDate], which is the least available date in the hijri date range + /// picker. + /// * [maxDate], which is the last available date in the hijri date range + /// picker. + /// * [enableMultiView], which allows to display multiple date side by + /// side based on the navigation direction. + /// * Knowledge base: [How to show two pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the navigation direction](https://www.syncfusion.com/kb/12176/how-to-change-the-navigation-direction-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -3110,6 +4745,32 @@ class SfHijriDateRangePicker extends StatelessWidget { /// [DateRangePickerSelectionShape.circle], then the circle radius can be /// adjusted in month view by using the [selectionRadius] property. /// + /// See also: + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year and decade view of hijri date range picker. + /// * [selectionColor], which fills the background of the selected cells in + /// the hijri date range picker. + /// * [startRangeSelectionColor], which fills the background of the first cell + /// of the range selection in hijri date range picker. + /// * [endRangeSelectionColor], which fills the background of the last cell of + /// the range selection in hijri date range picker. + /// * [rangeSelectionColor], which fills the background of the in between + /// cells of hijri date range picker in range selection. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [selectionTextStyle], which is used to set the text style for the text + /// in the selected cell of hijri date range picker. + /// * [rangeTextStyle], which is used to set text style for the text in the + /// selected range cell's of hijri date range picker. + /// * Knowledge base: [How to change the selection radius](https://www.syncfusion.com/kb/12230/how-to-change-the-selection-radius-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected range cells](https://www.syncfusion.com/kb/12148/how-to-customize-the-selected-range-cells-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to change the selection shape](https://www.syncfusion.com/kb/11900/how-to-change-the-selection-shape-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the selected month cell](https://www.syncfusion.com/kb/11441/how-to-customize-the-selected-month-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -3139,6 +4800,18 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to null. /// + /// See also: + /// * [headerStyle], which used to customize the header view of the hijri date + /// range picker. + /// * [headerHeight], which is the size of the header view in the hijri date + /// range picker. + /// * [yearCellStyle], which is used to customize the year and decade view + /// cells in the hijri date range picker. + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the header in the flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the month format](https://www.syncfusion.com/kb/12169/how-to-change-the-month-format-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -3162,6 +4835,19 @@ class SfHijriDateRangePicker extends StatelessWidget { /// /// Defaults to [DateRangePickerNavigationMode.snap] /// + /// See also: + /// * [navigationDirection], which allows to customize the navigation + /// direction of the hijri date range picker with available options. + /// * [minDate], which is the least available date in the hijri date range + /// picker. + /// * [maxDate], which is the last available date in the hijri date range + /// picker. + /// * [enableMultiView], which allows to display multiple picker side by + /// side based on the navigation direction. + /// * Knowledge base: [How to show two pickers vertically](https://www.syncfusion.com/kb/12193/how-to-show-two-pickers-vertically-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the navigation direction](https://www.syncfusion.com/kb/12176/how-to-change-the-navigation-direction-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to restrict the view navigation](https://www.syncfusion.com/kb/12500/how-to-restrict-the-view-navigation-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -3180,6 +4866,26 @@ class SfHijriDateRangePicker extends StatelessWidget { /// The visible date range and the visible view which visible on view when the /// view changes available in the [HijriDatePickerViewChangedArgs]. /// + /// See also: + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * [HijriDatePickerViewChangedArgs], which contains the visible date range + /// details of the current visible view. + /// * [initialDisplayDate], which is used to navigate the hijri date range + /// picker to specific date on initially. + /// * [HijriDatePickerController.displayDate], which allows to move the + /// hijri date range picker to specific date. + /// * [HijriDatePickerController.forward], which allows to navigate to next + /// view of the hijri date range picker programmatically. + /// * [HijriDatePickerController.backward], which allows to navigate to + /// previous view of the hijri date range picker programmatically. + /// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the header in flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -3207,6 +4913,48 @@ class SfHijriDateRangePicker extends StatelessWidget { /// The dates or ranges that selected when the selection changes available in /// the [DateRangePickerSelectionChangedArgs]. /// + /// See also: + /// * [onViewChanged], callback which notifies when the current view visible + /// dates changed on hijri date range picker. + /// * [HijriDatePickerMonthViewSettings.enableSwipeSelection], which allows to + /// select the cells on swipe when the selection mode set as + /// [DateRangePickerSelectionMode.range], + /// [DateRangePickerSelectionMode.multiRange], and + /// [DateRangePickerSelectionMode.extendableRange]. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year and decade view of hijri date range picker. + /// * [toggleDaySelection], which allows to deselect a date when the selection + /// mode set as [DateRangePickerSelectionMode.single]. + /// * [showActionButtons], which displays action buttons on bottom of date + /// range picker, which allows to confirm and cancel the selection. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * [initialSelectedDate], which allows to select date programmatically + /// initially on hijri date range picker. + /// * [initialSelectedDates], which allows to list of select date + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRange], which allows to select a range of dates + /// programmatically initially on hijri date range picker. + /// * [initialSelectedRanges], which allows to select a ranges of dates + /// programmatically initially on hijri date range picker. + /// * [HijriDatePickerController.selectedDate],which allows to select date + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedDates], which allows to select dates + /// programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRange], which allows to select range + /// of dates programmatically dynamically on hijri date range picker. + /// * [HijriDatePickerController.selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on hijri date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -3247,9 +4995,17 @@ class SfHijriDateRangePicker extends StatelessWidget { /// Text that displays on the confirm button. /// - /// See also - /// [showActionButtons] - /// [onSelectionChanged]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the hijri date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [cancelText], which is text that display on the cancel button. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -3271,9 +5027,17 @@ class SfHijriDateRangePicker extends StatelessWidget { /// Text that displays on the cancel button. /// - /// See also - /// [showActionButtons] - /// [onCancel]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the hijri date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [confirmText], which is text that display on the confirm button. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -3299,6 +5063,31 @@ class SfHijriDateRangePicker extends StatelessWidget { /// The [onSubmit] and [onCancel] callback is called based on the /// actions of the buttons. /// + /// See also: + /// * [HijriDatePickerMonthViewSettings.enableSwipeSelection], which allows to + /// select the cells on swipe when the selection mode set as + /// [DateRangePickerSelectionMode.range], + /// [DateRangePickerSelectionMode.multiRange], and + /// [DateRangePickerSelectionMode.extendableRange]. + /// * [selectionMode], which allows to customize the selection mode with + /// available mode options. + /// * [allowViewNavigation], which allows to navigate between views quickly, + /// and setting this property as `false`, allows to select the cells on + /// year and decade view of hijri date range picker. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [selectionRadius], which is the radius for the selection view in the + /// hijri date range picker when the selection shape set as + /// [DateRangePickerSelectionShape.circle]. + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// @override @@ -3321,8 +5110,16 @@ class SfHijriDateRangePicker extends StatelessWidget { /// Called whenever the cancel button tapped on date range picker. /// It reset the selected values to confirmed selected values. /// - /// See also - /// [showActionButtons]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the hijri date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [cancelText], which is text that display on the cancel button. + /// * [confirmText], which is text that display on the confirm button + /// * [onSubmit], callback which notifies when the selection confirmed + /// through the ok button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -3347,8 +5144,16 @@ class SfHijriDateRangePicker extends StatelessWidget { /// The dates or ranges that have been selected are confirmed and the /// selected value is available in the value argument. /// - /// See also - /// [showActionButtons]. + /// See also: + /// * [showActionButtons], which allows to display action buttons at the + /// bottom of the hijri date range picker to handle the selection. + /// * [onSelectionChanged], callback which notifies whenever the selection + /// changed on hijri date range picker. + /// * [cancelText], which is text that display on the cancel button. + /// * [confirmText], which is text that display on the confirm button + /// * [onCancel], callback which notifies when the selection canceled through + /// the cancel button of [showActionButtons]. + /// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) /// /// ```dart /// @@ -3425,6 +5230,8 @@ class SfHijriDateRangePicker extends StatelessWidget { cancelText: cancelText, showActionButtons: showActionButtons, isHijri: true, + showTodayButton: showTodayButton, + selectableDayPredicate: selectableDayPredicate, ); } @@ -3501,57 +5308,64 @@ class SfHijriDateRangePicker extends StatelessWidget { .add(monthViewSettings.toDiagnosticsNode(name: 'monthViewSettings')); properties.add(monthCellStyle.toDiagnosticsNode(name: 'monthCellStyle')); + + properties + .add(DiagnosticsProperty('showTodayButton', showTodayButton)); + properties.add(DiagnosticsProperty( + 'selectableDayPredicate', selectableDayPredicate)); } } @immutable class _SfDateRangePicker extends StatefulWidget { - const _SfDateRangePicker({ - Key? key, - required this.view, - required this.selectionMode, - this.isHijri = false, - required this.headerHeight, - this.todayHighlightColor, - this.backgroundColor, - this.initialSelectedDate, - this.initialSelectedDates, - this.initialSelectedRange, - this.initialSelectedRanges, - this.toggleDaySelection = false, - this.enablePastDates = true, - this.showNavigationArrow = false, - required this.selectionShape, - required this.navigationDirection, - this.controller, - this.onViewChanged, - this.onSelectionChanged, - this.onCancel, - this.onSubmit, - required this.headerStyle, - required this.yearCellStyle, - required this.monthViewSettings, - required this.initialDisplayDate, - this.confirmText = 'OK', - this.cancelText = 'CANCEL', - this.showActionButtons = false, - required this.minDate, - required this.maxDate, - required this.monthCellStyle, - this.allowViewNavigation = true, - this.enableMultiView = false, - required this.navigationMode, - required this.viewSpacing, - required this.selectionRadius, - this.selectionColor, - this.startRangeSelectionColor, - this.endRangeSelectionColor, - this.rangeSelectionColor, - this.selectionTextStyle, - this.rangeTextStyle, - this.monthFormat, - this.cellBuilder, - }) : super(key: key); + const _SfDateRangePicker( + {Key? key, + required this.view, + required this.selectionMode, + this.isHijri = false, + required this.headerHeight, + this.todayHighlightColor, + this.backgroundColor, + this.initialSelectedDate, + this.initialSelectedDates, + this.initialSelectedRange, + this.initialSelectedRanges, + this.toggleDaySelection = false, + this.enablePastDates = true, + this.showNavigationArrow = false, + required this.selectionShape, + required this.navigationDirection, + this.controller, + this.onViewChanged, + this.onSelectionChanged, + this.onCancel, + this.onSubmit, + required this.headerStyle, + required this.yearCellStyle, + required this.monthViewSettings, + required this.initialDisplayDate, + this.confirmText = 'OK', + this.cancelText = 'CANCEL', + this.showActionButtons = false, + required this.minDate, + required this.maxDate, + required this.monthCellStyle, + this.allowViewNavigation = true, + this.enableMultiView = false, + required this.navigationMode, + required this.viewSpacing, + required this.selectionRadius, + this.selectionColor, + this.startRangeSelectionColor, + this.endRangeSelectionColor, + this.rangeSelectionColor, + this.selectionTextStyle, + this.rangeTextStyle, + this.monthFormat, + this.cellBuilder, + this.showTodayButton = false, + this.selectableDayPredicate}) + : super(key: key); final DateRangePickerView view; @@ -3639,6 +5453,10 @@ class _SfDateRangePicker extends StatefulWidget { final Function(Object)? onSubmit; + final bool showTodayButton; + + final dynamic selectableDayPredicate; + @override _SfDateRangePickerState createState() => _SfDateRangePickerState(); } @@ -3739,21 +5557,23 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { final ThemeData themeData = Theme.of(context); _datePickerTheme = pickerTheme.copyWith( todayTextStyle: pickerTheme.todayTextStyle.color == null - ? pickerTheme.todayTextStyle.copyWith(color: themeData.accentColor) + ? pickerTheme.todayTextStyle + .copyWith(color: themeData.colorScheme.secondary) : pickerTheme.todayTextStyle, todayCellTextStyle: pickerTheme.todayCellTextStyle.color == null ? pickerTheme.todayCellTextStyle - .copyWith(color: themeData.accentColor) + .copyWith(color: themeData.colorScheme.secondary) : pickerTheme.todayCellTextStyle, - selectionColor: pickerTheme.selectionColor ?? themeData.accentColor, - startRangeSelectionColor: - pickerTheme.startRangeSelectionColor ?? themeData.accentColor, + selectionColor: + pickerTheme.selectionColor ?? themeData.colorScheme.secondary, + startRangeSelectionColor: pickerTheme.startRangeSelectionColor ?? + themeData.colorScheme.secondary, rangeSelectionColor: pickerTheme.rangeSelectionColor ?? - themeData.accentColor.withOpacity(0.1), - endRangeSelectionColor: - pickerTheme.endRangeSelectionColor ?? themeData.accentColor, + themeData.colorScheme.secondary.withOpacity(0.1), + endRangeSelectionColor: pickerTheme.endRangeSelectionColor ?? + themeData.colorScheme.secondary, todayHighlightColor: - pickerTheme.todayHighlightColor ?? themeData.accentColor); + pickerTheme.todayHighlightColor ?? themeData.colorScheme.secondary); _isRtl = direction == TextDirection.rtl; _isMobilePlatform = DateRangePickerHelper.isMobileLayout(Theme.of(context).platform); @@ -3946,11 +5766,12 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { ? _minPickerHeight : constraints.maxHeight; - final double actionButtonsHeight = widget.showActionButtons - ? _minHeight! * 0.1 < 50 - ? 50 - : _minHeight! * 0.1 - : 0; + final double actionButtonsHeight = + (widget.showActionButtons || widget.showTodayButton) + ? _minHeight! * 0.1 < 50 + ? 50 + : _minHeight! * 0.1 + : 0; _handleScrollViewSizeChanged(_minHeight!, _minWidth!, previousHeight, previousWidth, actionButtonsHeight); @@ -4071,6 +5892,8 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _scrollViewKey.currentState!._position = 0.0; _scrollViewKey.currentState!._children.clear(); _scrollViewKey.currentState!._updateVisibleDates(); + _scrollViewKey.currentState! + ._triggerSelectableDayPredicates(_currentViewVisibleDates); } }); } else if (value == 'displayDate') { @@ -4651,7 +6474,8 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { return Stack( children: [ scrollView, - _getActionsButton(topPosition + scrollViewHeight, actionButtonsHeight) + _getActionsButton( + topPosition + scrollViewHeight, actionButtonsHeight), ], ); } else { @@ -4849,6 +6673,7 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _datePickerTheme, null, _textScaleFactor, + null, getPickerStateDetails: _getPickerStateValues, updatePickerStateDetails: _updatePickerStateValues, isRtl: _isRtl, @@ -4957,10 +6782,9 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { } Widget _getActionsButton(double top, double actionButtonsHeight) { - if (!widget.showActionButtons) { + if (!widget.showActionButtons && !widget.showTodayButton) { return Container(width: 0, height: 0); } - Color textColor = widget.todayHighlightColor ?? _datePickerTheme.todayHighlightColor!; if (textColor == Colors.transparent) { @@ -4969,36 +6793,69 @@ class _SfDateRangePickerState extends State<_SfDateRangePicker> { _datePickerTheme.todayTextStyle; textColor = style.color != null ? style.color! : Colors.blue; } - + final Widget actionButtons = widget.showActionButtons + ? Container( + alignment: AlignmentDirectional.centerEnd, + constraints: const BoxConstraints(minHeight: 52.0), + padding: const EdgeInsets.symmetric(horizontal: 8), + child: OverflowBar( + spacing: 8, + children: [ + TextButton( + child: Text( + widget.cancelText, + style: TextStyle(color: textColor), + ), + onPressed: _handleCancel, + ), + TextButton( + child: Text( + widget.confirmText, + style: TextStyle(color: textColor), + ), + onPressed: _handleOk, + ), + ], + ), + ) + : Container(width: 0, height: 0); + final Widget todayButton = widget.showTodayButton + ? Container( + alignment: AlignmentDirectional.centerStart, + constraints: const BoxConstraints(minHeight: 52.0), + padding: const EdgeInsets.symmetric(horizontal: 8), + child: OverflowBar( + spacing: 8, + children: [ + TextButton( + child: Text( + _localizations.todayLabel, + style: TextStyle(color: textColor), + ), + onPressed: () { + if (widget.allowViewNavigation) { + _controller.view = widget.isHijri + ? HijriDatePickerView.month + : DateRangePickerView.month; + } + + _controller.displayDate = + DateRangePickerHelper.getToday(widget.isHijri); + }, + ), + ], + ), + ) + : Container(width: 0, height: 0); return Positioned( top: top, left: 0, right: 0, height: actionButtonsHeight, - child: Container( - alignment: AlignmentDirectional.centerEnd, - constraints: const BoxConstraints(minHeight: 52.0), - padding: const EdgeInsets.symmetric(horizontal: 8), - child: OverflowBar( - spacing: 8, - children: [ - TextButton( - child: Text( - widget.cancelText, - style: TextStyle(color: textColor), - ), - onPressed: _handleCancel, - ), - TextButton( - child: Text( - widget.confirmText, - style: TextStyle(color: textColor), - ), - onPressed: _handleOk, - ), - ], - ), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [todayButton, actionButtons]), ); } @@ -5857,7 +7714,8 @@ class _PickerHeaderViewState extends State<_PickerHeaderView> { headerWidth = widget.width - (arrowWidth * 2); } - Color arrowColor = widget.headerStyle.textStyle != null + Color arrowColor = widget.headerStyle.textStyle != null && + widget.headerStyle.textStyle!.color != null ? widget.headerStyle.textStyle!.color! : (widget.datePickerTheme.headerTextStyle.color!); arrowColor = arrowColor.withOpacity(arrowColor.opacity * 0.6); @@ -6541,12 +8399,19 @@ class _PickerViewHeaderPainter extends CustomPainter { @immutable class _PickerScrollView extends StatefulWidget { /// Constructor to create the picker scroll view instance. - const _PickerScrollView(this.picker, this.controller, this.width, this.height, - this.isRtl, this.datePickerTheme, this.locale, this.textScaleFactor, - {Key? key, - required this.getPickerStateValues, - required this.updatePickerStateValues}) - : super(key: key); + const _PickerScrollView( + this.picker, + this.controller, + this.width, + this.height, + this.isRtl, + this.datePickerTheme, + this.locale, + this.textScaleFactor, { + Key? key, + required this.getPickerStateValues, + required this.updatePickerStateValues, + }) : super(key: key); /// Holds the picker instance to access the picker details. final _SfDateRangePicker picker; @@ -6591,6 +8456,8 @@ class _PickerScrollViewState extends State<_PickerScrollView> // the three children which to be added into the layout final List<_PickerView> _children = <_PickerView>[]; + Map, List>? _disabledDates; + // holds the index of the current displaying view int _currentChildIndex = 1; @@ -6631,6 +8498,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> @override void initState() { _updateVisibleDates(); + _triggerSelectableDayPredicates(_currentViewVisibleDates); _animationController = AnimationController( duration: const Duration(milliseconds: 250), vsync: this, @@ -6662,7 +8530,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> _children.clear(); } - if (oldWidget.picker.controller != widget.picker.controller) { + if (oldWidget.picker.controller != widget.controller) { _position = 0; _children.clear(); _updateVisibleDates(); @@ -6673,6 +8541,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> _position = 0; _children.clear(); _updateVisibleDates(); + _triggerSelectableDayPredicates(_currentViewVisibleDates); } _updateSettings(oldWidget); @@ -6721,6 +8590,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> oldWidget.picker.isHijri))) { _children.clear(); _position = 0; + _triggerSelectableDayPredicates(_currentViewVisibleDates); } if (DateRangePickerHelper.getNumberOfWeeksInView( @@ -6731,6 +8601,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> oldWidget.picker.monthViewSettings.firstDayOfWeek) { _updateVisibleDates(); _position = 0; + _triggerSelectableDayPredicates(_currentViewVisibleDates); } /// Update the selection when [allowViewNavigation] property in @@ -6742,24 +8613,26 @@ class _PickerScrollViewState extends State<_PickerScrollView> pickerView != DateRangePickerView.month) { _position = 0; _children.clear(); + _triggerSelectableDayPredicates(_currentViewVisibleDates); } - if (oldWidget.picker.controller != widget.picker.controller || - widget.picker.controller == null) { + if (oldWidget.picker.controller != widget.controller || + widget.controller == null) { widget.getPickerStateValues(_pickerStateDetails); super.didUpdateWidget(oldWidget); return; } if (oldWidget.picker.controller?.displayDate != - widget.picker.controller?.displayDate || + widget.controller?.displayDate || !isSameDate( _pickerStateDetails.currentDate, widget.controller.displayDate)) { - _pickerStateDetails.currentDate = widget.picker.controller?.displayDate; + _pickerStateDetails.currentDate = widget.controller?.displayDate; _updateVisibleDates(); + _triggerSelectableDayPredicates(_currentViewVisibleDates); } - _drawSelection(oldWidget.picker.controller, widget.picker.controller); + _drawSelection(oldWidget.picker.controller, widget.controller); widget.getPickerStateValues(_pickerStateDetails); super.didUpdateWidget(oldWidget); } @@ -6981,6 +8854,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _tween.end = -widget.width; } + /// returns the disable dates collection when right to left swiping + _triggerSelectableDayPredicates(_getCurrentVisibleDates(true)); + _animationController.duration = const Duration(milliseconds: 500); _animationController .forward() @@ -7011,6 +8887,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> _tween.end = widget.width; } + /// returns the disable dates collection when left to right swiping + _triggerSelectableDayPredicates(_getCurrentVisibleDates(false)); + _animationController.duration = const Duration(milliseconds: 500); _animationController .forward() @@ -7287,6 +9166,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.datePickerTheme, _focusNode, widget.textScaleFactor, + DateRangePickerHelper.cloneList(_disabledDates?.values.first), key: key, getPickerStateDetails: (PickerStateArgs details) { _getPickerViewStateDetails(details); @@ -7342,6 +9222,14 @@ class _PickerScrollViewState extends State<_PickerScrollView> view = _getView(visibleDates, view.key!); _children[index] = view; } // check and update the visible appointments in the view + else if (_disabledDates != null && + _disabledDates!.isNotEmpty && + _disabledDates!.keys.first == viewDates && + !DateRangePickerHelper.isDateCollectionEquals( + view.disableDatePredicates, _disabledDates!.values.first)) { + view = _getView(viewDates, view.key!); + _children[index] = view; + } return view; } @@ -7569,27 +9457,32 @@ class _PickerScrollViewState extends State<_PickerScrollView> return _previousViewKey.currentState!; } - /// Updates the current view visible dates for picker in the swiping end - void _updateCurrentViewVisibleDates({bool isNextView = false}) { - final DateRangePickerView pickerView = - DateRangePickerHelper.getPickerView(widget.controller.view); + /// Return the current view visible dates for picker based on view index. + List _getCurrentVisibleDates(bool isNextView) { if (isNextView) { if (_currentChildIndex == 0) { - _currentViewVisibleDates = _visibleDates; + return _visibleDates; } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _nextViewVisibleDates; + return _nextViewVisibleDates; } else { - _currentViewVisibleDates = _previousViewVisibleDates; + return _previousViewVisibleDates; } } else { if (_currentChildIndex == 0) { - _currentViewVisibleDates = _nextViewVisibleDates; + return _nextViewVisibleDates; } else if (_currentChildIndex == 1) { - _currentViewVisibleDates = _previousViewVisibleDates; + return _previousViewVisibleDates; } else { - _currentViewVisibleDates = _visibleDates; + return _visibleDates; } } + } + + /// Updates the current view visible dates for picker in the swiping end + void _updateCurrentViewVisibleDates({bool isNextView = false}) { + final DateRangePickerView pickerView = + DateRangePickerHelper.getPickerView(widget.controller.view); + _currentViewVisibleDates = _getCurrentVisibleDates(isNextView); _pickerStateDetails.currentViewVisibleDates = _currentViewVisibleDates; _pickerStateDetails.currentDate = _currentViewVisibleDates[0]; @@ -8094,13 +9987,13 @@ class _PickerScrollViewState extends State<_PickerScrollView> widget.isRtl ? _moveToPreviousViewWithAnimation() : _moveToNextViewWithAnimation(); - result = KeyEventResult.handled; + return KeyEventResult.handled; } else if (event.logicalKey == LogicalKeyboardKey.arrowLeft && canMoveToPreviousView) { widget.isRtl ? _moveToNextViewWithAnimation() : _moveToPreviousViewWithAnimation(); - result = KeyEventResult.handled; + return KeyEventResult.handled; } result = KeyEventResult.ignored; } @@ -8145,6 +10038,10 @@ class _PickerScrollViewState extends State<_PickerScrollView> currentVisibleView.visibleDates, widget.picker.monthViewSettings.blackoutDates, selectedDate) || + DateRangePickerHelper.isDateWithInVisibleDates( + currentVisibleView.visibleDates, + currentVisibleView.disableDatePredicates, + selectedDate) || !DateRangePickerHelper.isEnabledDate( widget.picker.minDate, widget.picker.maxDate, @@ -8439,6 +10336,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> return; } + final bool isNextView = difference < 0; + _triggerSelectableDayPredicates(_getCurrentVisibleDates(isNextView)); + _position = difference; setState(() { /* Updates the widget navigated distance and moves the widget @@ -8589,6 +10489,7 @@ class _PickerScrollViewState extends State<_PickerScrollView> _animationController.reset(); } + _triggerSelectableDayPredicates(_currentViewVisibleDates); _animationController.duration = const Duration(milliseconds: 250); _animationController.forward(); } @@ -8641,6 +10542,9 @@ class _PickerScrollViewState extends State<_PickerScrollView> return; } + final bool isNextView = difference < 0; + _triggerSelectableDayPredicates(_getCurrentVisibleDates(isNextView)); + _position = difference; setState(() { /* Updates the widget navigated distance and moves the widget @@ -8784,12 +10688,85 @@ class _PickerScrollViewState extends State<_PickerScrollView> _animationController.reset(); } + _triggerSelectableDayPredicates(_currentViewVisibleDates); _animationController.duration = const Duration(milliseconds: 250); _animationController.forward(); } } } } + + /// Check the date cell is disable date or not, based on the callback returns + /// the value is false. If the date is disabled date, it holds the list of + /// dates collection. Which is the list of dates to restrict the interaction. + /// It is applicable for all views. + void _triggerSelectableDayPredicates(List visibleDates) { + if (widget.picker.selectableDayPredicate == null || + _disabledDates != null && + _disabledDates!.isNotEmpty && + _disabledDates!.keys.first == visibleDates) { + return; + } + + final DateRangePickerView view = + DateRangePickerHelper.getPickerView(widget.controller.view); + final int viewCount = _isMultiViewEnabled(widget.picker) ? 2 : 1; + + _disabledDates ??= , List>{}; + _disabledDates!.clear(); + + final List disabledDateCollection = []; + + switch (view) { + case DateRangePickerView.month: + final int datesCount = + visibleDates.length ~/ (widget.picker.enableMultiView ? 2 : 1); + for (int i = 0; i < viewCount; i++) { + int midDateIndex = datesCount ~/ 2; + if (i == 1) { + midDateIndex = datesCount + (datesCount ~/ 2); + } + for (int j = i * datesCount; j < ((i + 1) * datesCount); j++) { + final int numberOfWeeksInView = + DateRangePickerHelper.getNumberOfWeeksInView( + widget.picker.monthViewSettings, widget.picker.isHijri); + final bool showLeadingTrailingDates = + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, widget.picker.isHijri); + final bool isCurrentMonthDate = + DateRangePickerHelper.isDateAsCurrentMonthDate( + visibleDates[midDateIndex], + numberOfWeeksInView, + showLeadingTrailingDates, + visibleDates[j], + widget.picker.isHijri); + if (isCurrentMonthDate) { + final bool isSelectedDayPredicate = + widget.picker.selectableDayPredicate(visibleDates[j]) as bool; + if (!isSelectedDayPredicate) { + disabledDateCollection.add(visibleDates[j]); + } + } + } + } + break; + case DateRangePickerView.year: + case DateRangePickerView.century: + case DateRangePickerView.decade: + if (widget.picker.allowViewNavigation) { + break; + } + for (int i = 0; i < visibleDates.length; i++) { + final bool isSelectedDayPredicate = + widget.picker.selectableDayPredicate(visibleDates[i]) as bool; + if (!isSelectedDayPredicate) { + disabledDateCollection.add(visibleDates[i]); + } + } + } + + _disabledDates![visibleDates] = disabledDateCollection; + } } /// Holds the month, year, decade and century view and handle it interactions. @@ -8797,20 +10774,21 @@ class _PickerScrollViewState extends State<_PickerScrollView> class _PickerView extends StatefulWidget { /// Constructor to create picker view instance. const _PickerView( - this.picker, - this.controller, - this.visibleDates, - this.enableMultiView, - this.width, - this.height, - this.datePickerTheme, - this.focusNode, - this.textScaleFactor, - {Key? key, - required this.getPickerStateDetails, - required this.updatePickerStateDetails, - this.isRtl = false}) - : super(key: key); + this.picker, + this.controller, + this.visibleDates, + this.enableMultiView, + this.width, + this.height, + this.datePickerTheme, + this.focusNode, + this.textScaleFactor, + this.disableDatePredicates, { + Key? key, + required this.getPickerStateDetails, + required this.updatePickerStateDetails, + this.isRtl = false, + }) : super(key: key); /// Holds the visible dates for the picker view. final List visibleDates; @@ -8847,6 +10825,8 @@ class _PickerView extends StatefulWidget { /// Defines the text scale factor of [SfDateRangePicker]. final double textScaleFactor; + final List? disableDatePredicates; + @override _PickerViewState createState() => _PickerViewState(); } @@ -8995,44 +10975,46 @@ class _PickerViewState extends State<_PickerView> final int rowCount = DateRangePickerHelper.getNumberOfWeeksInView( widget.picker.monthViewSettings, widget.picker.isHijri); return MonthView( - widget.visibleDates, - rowCount, - widget.picker.monthCellStyle, - widget.picker.selectionTextStyle, - widget.picker.rangeTextStyle, - widget.picker.selectionColor, - widget.picker.startRangeSelectionColor, - widget.picker.endRangeSelectionColor, - widget.picker.rangeSelectionColor, - widget.datePickerTheme, - widget.isRtl, - widget.picker.todayHighlightColor, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - DateRangePickerHelper.canShowLeadingAndTrailingDates( - widget.picker.monthViewSettings, widget.picker.isHijri), - widget.picker.monthViewSettings.blackoutDates, - widget.picker.monthViewSettings.specialDates, - widget.picker.monthViewSettings.weekendDays, - widget.picker.selectionShape, - widget.picker.selectionRadius, - _mouseHoverPosition, - widget.enableMultiView, - widget.picker.viewSpacing, - ValueNotifier(false), - widget.textScaleFactor, - widget.picker.selectionMode, - widget.picker.isHijri, - localizations, - widget.picker.navigationDirection, - width, - height, - widget.getPickerStateDetails, - widget.picker.cellBuilder, - widget.picker.monthViewSettings.showWeekNumber, - widget.picker.monthViewSettings.weekNumberStyle, - _isMobilePlatform); + widget.visibleDates, + rowCount, + widget.picker.monthCellStyle, + widget.picker.selectionTextStyle, + widget.picker.rangeTextStyle, + widget.picker.selectionColor, + widget.picker.startRangeSelectionColor, + widget.picker.endRangeSelectionColor, + widget.picker.rangeSelectionColor, + widget.datePickerTheme, + widget.isRtl, + widget.picker.todayHighlightColor, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + DateRangePickerHelper.canShowLeadingAndTrailingDates( + widget.picker.monthViewSettings, widget.picker.isHijri), + widget.picker.monthViewSettings.blackoutDates, + widget.picker.monthViewSettings.specialDates, + widget.picker.monthViewSettings.weekendDays, + widget.picker.selectionShape, + widget.picker.selectionRadius, + _mouseHoverPosition, + widget.enableMultiView, + widget.picker.viewSpacing, + ValueNotifier(false), + widget.textScaleFactor, + widget.picker.selectionMode, + widget.picker.isHijri, + localizations, + widget.picker.navigationDirection, + width, + height, + widget.getPickerStateDetails, + widget.picker.cellBuilder, + widget.picker.monthViewSettings.showWeekNumber, + widget.picker.monthViewSettings.weekNumberStyle, + _isMobilePlatform, + widget.disableDatePredicates, + ); } Widget _getViewHeader(double viewHeaderHeight, Locale locale, @@ -9315,7 +11297,8 @@ class _PickerViewState extends State<_PickerView> localizations, widget.picker.navigationDirection, widget.width, - widget.height); + widget.height, + widget.disableDatePredicates); } GestureDragStartCallback? _getDragStartCallback() { @@ -9614,7 +11597,9 @@ class _PickerViewState extends State<_PickerView> } if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate)) { + widget.picker.monthViewSettings.blackoutDates, selectedDate) || + DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, widget.disableDatePredicates, selectedDate)) { return; } @@ -9670,7 +11655,9 @@ class _PickerViewState extends State<_PickerView> } if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate)) { + widget.picker.monthViewSettings.blackoutDates, selectedDate) || + DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, widget.disableDatePredicates, selectedDate)) { return; } @@ -10017,12 +12004,14 @@ class _PickerViewState extends State<_PickerView> final dynamic selectedDate = widget.visibleDates[index]; if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - selectedDate, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri)) { + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri) || + DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, widget.disableDatePredicates, selectedDate)) { return; } @@ -10045,12 +12034,14 @@ class _PickerViewState extends State<_PickerView> final dynamic selectedDate = widget.visibleDates[index]; if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - selectedDate, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri)) { + selectedDate, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri) || + DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, widget.disableDatePredicates, selectedDate)) { return; } @@ -10096,7 +12087,9 @@ class _PickerViewState extends State<_PickerView> } if (DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, - widget.picker.monthViewSettings.blackoutDates, selectedDate)) { + widget.picker.monthViewSettings.blackoutDates, selectedDate) || + DateRangePickerHelper.isDateWithInVisibleDates(widget.visibleDates, + widget.disableDatePredicates, selectedDate)) { return; } @@ -10279,12 +12272,14 @@ class _PickerViewState extends State<_PickerView> widget.getPickerStateDetails(_pickerStateDetails); if (!widget.picker.allowViewNavigation) { if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - date, - widget.picker.minDate, - widget.picker.maxDate, - widget.picker.enablePastDates, - widget.controller.view, - widget.picker.isHijri)) { + date, + widget.picker.minDate, + widget.picker.maxDate, + widget.picker.enablePastDates, + widget.controller.view, + widget.picker.isHijri) || + DateRangePickerHelper.isDateWithInVisibleDates( + widget.visibleDates, widget.disableDatePredicates, date)) { return; } 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 5f41d9b0a..88cb1a8f4 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 @@ -1,6 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_datepicker/datepicker.dart'; import 'picker_helper.dart'; /// Sets the style for customizing the [SfDateRangePicker] header view. @@ -8,6 +9,23 @@ import 'picker_helper.dart'; /// Allows to customize the [textStyle], [textAlign] and [backgroundColor] of /// the header view in [SfDateRangePicker]. /// +/// See also: +/// * [DateRangePickerMonthViewSettings], which allows to customize the month +/// view of the date range picker. +/// * [DateRangePickerViewHeaderStyle], which allows to customize the view +/// header view of the month view in date range picker. +/// * [SfDateRangePicker.headerHeight], which is the size of the header view in +/// the date range picker. +/// * [SfDateRangePicker.showNavigationArrow], which displays the navigation +/// arrows on the header view of the date range picker. +/// * [SfDateRangePicker.monthFormat], which allows to customize the month text +/// in the header view also in the year cell view of date range picker. +/// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to restrict the year view navigation when tapping on header view](https://www.syncfusion.com/kb/12113/how-to-restrict-the-year-view-navigation-while-tapping-header-of-the-flutter-date-range) +/// * Knowledge base: [How to customize the header in Flutter multi date range picker](https://www.syncfusion.com/kb/11897/how-to-customize-the-header-in-the-flutter-multi-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) +/// /// ``` dart /// Widget build(BuildContext context) { /// return MaterialApp( @@ -40,6 +58,17 @@ class DateRangePickerHeaderStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [SfDateRangePicker.monthFormat], which allows to customize the month + /// text in the header view also in the year cell view of date range picker. + /// * [textAlign], which aligns the text in the header view of the date + /// range picker. + /// * [textStyle], which fills the background of the header view in the date + /// range picker. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -67,6 +96,18 @@ class DateRangePickerHeaderStyle with Diagnosticable { /// /// Defaults to `TextAlign.left`. /// + /// See also: + /// * [textStyle], which allows to set style for the header text in the + /// date range picker. + /// * [SfDateRangePicker.showNavigationArrow], which displays the navigation + /// arrows on the header view of the date range picker. + /// * [SfDateRangePicker.monthFormat], which allows to customize the month + /// text in the header view also in the year cell view of date range picker. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// * Knowledge base: [How to navigate to the previous or next dates using navigation arrows](https://www.syncfusion.com/kb/12270/how-to-navigate-to-the-previous-or-next-views-using-navigation-arrows-in-the-flutter-date) + /// + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -93,6 +134,16 @@ class DateRangePickerHeaderStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [SfDateRangePicker.monthFormat], which allows to customize the month + /// text in the header view also in the year cell view of date range picker. + /// * [textAlign], which aligns the text in the header view of the date + /// range picker. + /// * [textStyle], which fills the background of the header view in the date + /// range picker. + /// * Knowledge base: [How to style a header](https://www.syncfusion.com/kb/12342/how-to-style-a-header-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the header view](https://www.syncfusion.com/kb/11427/how-to-customize-the-header-view-of-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -153,6 +204,15 @@ class DateRangePickerHeaderStyle with Diagnosticable { /// Allows to customize the [textStyle] and [backgroundColor] of the view header /// view in month view of [SfDateRangePicker]. /// +/// See also: +/// * [DateRangePickerHeaderStyle], which used to customize the header view of +/// the date range picker. +/// * [SfDateRangePicker.todayHighlightColor], which highlights the today date +/// in the date range picker. +/// * [DateRangePickerMonthViewSettings.viewHeaderHeight], which is the size +/// for the view header view in date range picker. +/// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) +/// /// ```dart /// /// Widget build(BuildContext context) { @@ -189,6 +249,15 @@ class DateRangePickerViewHeaderStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [textStyle], which used to style the text in the view header view of the + /// month view in date range picker. + /// * [DateRangePickerMonthViewSettings.viewHeaderHeight], which is the size + /// for the view header view in date range picker. + /// * [SfDateRangePicker.todayHighlightColor], which highlights the today date + /// in the date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) + /// /// ```dart /// ///Widget build(BuildContext context) { @@ -218,6 +287,15 @@ class DateRangePickerViewHeaderStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [backgroundColor], which fills the background of the view header view + /// in the date range picker. + /// * [DateRangePickerMonthViewSettings.viewHeaderHeight], which is the size + /// for the view header view in date range picker. + /// * [SfDateRangePicker.todayHighlightColor], which highlights the today date + /// in the date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -280,6 +358,12 @@ class DateRangePickerViewHeaderStyle with Diagnosticable { /// Allows to customize the [backgroundColor], [textStyle] /// week number in month view of date Range Picker. /// +/// See also: +/// * [DateRangePickerMonthViewSettings], which is used to customize the month +/// view of the date range picker. +/// * [HijriDatePickerMonthViewSettings], which is used to customize the month +/// view of the hijri date range picker. +/// /// ```dart /// Widget build(BuildContext context) { /// return Scaffold( @@ -311,6 +395,10 @@ class DateRangePickerWeekNumberStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [textStyle], which used to customize the style for the text in the + /// week number view of month view in date range picker. + /// /// ```dart /// Widget build(BuildContext context) { /// return Scaffold( @@ -337,6 +425,10 @@ class DateRangePickerWeekNumberStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [backgroundColor], which fills the background of the week number view in + /// the date range picker. + /// /// ```dart /// Widget build(BuildContext context) { /// return Scaffold( @@ -396,6 +488,34 @@ class DateRangePickerWeekNumberStyle with Diagnosticable { /// [viewHeaderStyle], [enableSwipeSelection], [blackoutDates], [specialDates] /// and [weekendDays] in month view of date range picker. /// +/// See also: +/// * [HijriDatePickerMonthViewSettings], which used to customize the month view +/// of the hijri date range picker. +/// * [DateRangePickerMonthCellStyle], which used to customize the month cell of +/// the month view in the date range picker. +/// * [HijriDatePickerMonthCellStyle], which used to customize the month cell of +/// the month view in the hijri date range picker. +/// * [SfDateRangePicker.cellBuilder],which allows to set custom widget for the +/// picker cells in the date range picker. +/// * [DateRangePickerYearCellStyle], which allows to customize the year cell of +/// the year, decade and century views of the date range picker. +/// * [SfDateRangePicker.backgroundColor], which fills the background of the +/// date range picker. +/// * [SfDateRangePicker.todayHighlightColor], which highlights the today date +/// cell in the date range picker. +/// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) +/// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to change the first day of week](https://www.syncfusion.com/kb/12221/how-to-change-the-first-day-of-week-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) +/// * Knowledge base: [How to change the week end dates](https://www.syncfusion.com/kb/12182/how-to-change-the-week-end-dates-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12167/how-to-change-the-number-of-weeks-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to add active dates](https://www.syncfusion.com/kb/12075/how-to-add-active-dates-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to create timeline date picker](https://www.syncfusion.com/kb/12474/how-to-create-timeline-date-picker-in-flutter) +/// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) +/// /// ```dart /// ///Widget build(BuildContext context) { @@ -464,6 +584,17 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// /// Defaults to `EE`. /// + /// See also: + /// * [viewHeaderStyle], which used to customize the view header view of the + /// date range picker. + /// * [SfDateRangePicker.backgroundColor], which fills the background of the + /// date range picker. + /// * [SfDateRangePicker.todayHighlightColor], which highlights the today date + /// in the date range picker. + /// * [viewHeaderHeight], which is the size for the view header view in the + /// month view of date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -498,6 +629,14 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// See also: [DateRangePickerMonthCellStyle] to know about leading and /// trailing dates style. /// + /// See also: + /// * [showTrailingAndLeadingDates], which used to display the previous month + /// and next month dates on the month view of the date range picker. + /// * [firstDayOfWeek], which used to customize the week start day of the + /// month view in date range picker. + /// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12167/how-to-change-the-number-of-weeks-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to create timeline date picker](https://www.syncfusion.com/kb/12474/how-to-create-timeline-date-picker-in-flutter) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -529,6 +668,14 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// as [DateRangePickerSelectionMode.range] or /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [DateRangePickerSelectionMode], which contains different selection modes + /// for the date range picker. + /// * [SfDateRangePicker.enableMultiView], which displays two date range + /// picker side by side based on the [SfDateRangePicker.navigationDirection] + /// value. + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -556,6 +703,15 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// /// Defaults to `7` which indicates `DateTime.sunday`. /// + /// See also: + /// * [showWeekNumber], which displays the week number of the year in the + /// month view of the date range picker. + /// * [showTrailingAndLeadingDates], which used to display the previous month + /// and next month dates on the month view of the date range picker. + /// * [numberOfWeeksInView], which used to customize the displaying week count + /// in the month view of date range picker. + /// * Knowledge base: [How to change the first day of week](https://www.syncfusion.com/kb/12221/how-to-change-the-first-day-of-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -580,7 +736,16 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// Allows to customize the [textStyle] and [backgroundColor] of the view /// header view in month view of [SfDateRangePicker]. /// - /// See also: [DateRangePickerViewHeaderStyle]. + /// See also: + /// * [DateRangePickerViewHeaderStyle], to know more about the available + /// option for customize the view header view of month view. + /// * [viewHeaderHeight], which is the size for the view header view in the + /// month view of date range picker. + /// * [dayFormat], which is used to customize the format of the view header + /// text in the month view of date range picker. + /// * [SfDateRangePicker.todayHighlightColor], which highlights the today date + /// cell in the date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) /// /// ```dart /// @@ -614,6 +779,15 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// /// ![view header with height as 100](https://help.syncfusion.com/flutter/daterangepicker/images/headers/viewheaderheight.png) /// + /// See also: + /// * [viewHeaderStyle], which used to customize the view header view of the + /// month view in date range picker. + /// * [dayFormat], which is used to customize the format of the view header + /// text in the month view of date range picker. + /// * [SfDateRangePicker.todayHighlightColor], which highlights the today date + /// cell in the date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -668,6 +842,22 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// _Note:_ This property not applicable when the /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. /// + /// See also: + /// * [numberOfWeeksInView], which used to customize the displaying week count + /// displayed in the month view of date range picker. + /// * [firstDayOfWeek], which used to customize the first week day of the + /// month view in date range picker. + /// * [DateRangePickerMonthCellStyle.trailingDatesTextStyle], which used to + /// customize the trailing dates cell text in the month view. + /// * [DateRangePickerMonthCellStyle.leadingDatesTextStyle], which used to + /// customize the leading dates cell text in the month view. + /// * [DateRangePickerMonthCellStyle.trailingDatesDecoration], which used to + /// customize the trailing dates with decoration in the month view. + /// * [DateRangePickerMonthCellStyle.leadingDatesDecoration], which used to + /// customize the leading dates with decoration in the month view. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -697,8 +887,19 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// customize the appearance of blackout dates in month view. /// /// See also: - /// [DateRangePickerMonthCellStyle.blackoutDateTextStyle] - /// [DateRangePickerMonthCellStyle.blackoutDatesDecoration]. + /// * [DateRangePickerMonthCellStyle.blackoutDateTextStyle], which used to set + /// text style for the black out date cell in the month view. + /// * [DateRangePickerMonthCellStyle.blackoutDatesDecoration], which used to + /// set the decoration for the black out date cell in the month view. + /// * [specialDates], which is the list of dates to highlight the specific + /// dates in the month view. + /// * [weekendDays], which used to change the week end days in the month view. + /// * [SfDateRangePicker.enablePastDates], which allows to enable the dates + /// that falls before the today date for interaction. + /// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to add active dates](https://www.syncfusion.com/kb/12075/how-to-add-active-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// /// ```dart /// @@ -734,8 +935,14 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// customize the appearance of blackout dates in month view. /// /// See also: - /// [DateRangePickerMonthCellStyle.specialDatesTextStyle] - /// [DateRangePickerMonthCellStyle.specialDatesDecoration]. + /// [DateRangePickerMonthCellStyle.specialDatesTextStyle],which used to set + /// text style for the text in special dates cell in the month view. + /// [DateRangePickerMonthCellStyle.specialDatesDecoration], which used to set + /// decoration for the special dates cell in the month view. + /// * [blackoutDates], which is the list of dates to restrict the interaction + /// for specific dates in month view. + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -770,6 +977,21 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// using the [DateRangePickerMonthCellStyle.weekendTextStyle] or /// [DateRangePickerMonthCellStyle.weekendDatesDecoration] property. /// + /// See also: + /// * [DateRangePickerMonthCellStyle.weekendTextStyle], which used to set + /// text style for the week end cell text in month view. + /// * [DateRangePickerMonthCellStyle.weekendDatesDecoration], which used to + /// set decoration for the week end cell text in month view. + /// * [specialDates], which used to highlight specific dates in the month view + /// of date range picker. + /// * [blackoutDates], which used to restrict the user interaction for the + /// specific dates in teh month view of date range picker. + /// * [numberOfWeeksInView], which allows to customize the displaying week + /// count in month view of date range picker. + /// * Knowledge base: [How to change the week end dates](https://www.syncfusion.com/kb/12182/how-to-change-the-week-end-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12167/how-to-change-the-number-of-weeks-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -797,7 +1019,13 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// /// Defaults to false /// - /// see also: [weekNumberStyle] + /// see also: + /// * [weekNumberStyle], which used to customize the week number view of the + /// month view in the date range picker. + /// * [numberOfWeeksInView], which allows to customize the displaying week + /// count in month view of date range picker. + /// * [firstDayOfWeek], which used to customize the start day of the week in + /// month view of date range picker. /// /// ``` dart /// Widget build(BuildContext context) { @@ -817,7 +1045,13 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// /// Defaults to null /// - /// see also: [showWeekNumber] + /// see also: + /// * [showWeekNumber], which allows to display the week number of the year in + /// the month view of the date range picker. + /// * [numberOfWeeksInView], which allows to customize the displaying week + /// count in month view of date range picker. + /// * [firstDayOfWeek], which used to customize the start day of the week in + /// month view of date range picker. /// /// ``` dart /// Widget build(BuildContext context) { @@ -910,6 +1144,35 @@ class DateRangePickerMonthViewSettings with Diagnosticable { /// [disabledDatesDecoration] in year, decade and century view of the /// [SfDateRangePicker]. /// +/// See also: +/// * [DateRangePickerMonthCellStyle], which allows to customize the month cell +/// of the month view of the date range picker +/// * [SfDateRangePicker.cellBuilder], which allows to set custom widget for the +/// picker cells in the date range picker. +/// * [DateRangePickerMonthViewSettings], which allows to customize the month +/// view of the date range picker. +/// * [SfDateRangePicker.selectionColor], which fills the background of the +/// selected cells in the date range picker. +/// * [SfDateRangePicker.startRangeSelectionColor], which fills the background +/// of the first cell of the range selection in date range picker. +/// * [SfDateRangePicker.endRangeSelectionColor], which fills the background of +/// the last cell of the range selection in date range picker. +/// * [SfDateRangePicker.rangeSelectionColor], which fills the background of the +/// in between cells of date range picker in range selection. +/// * [SfDateRangePicker.selectionTextStyle], which is used to set the text +/// style for the text in the selected cell of date range picker. +/// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for +/// the text in the selected range cell's of date range picker. +/// * [SfDateRangePicker.backgroundColor], which fills the background of the +/// date range picker. +/// * [SfDateRangePicker.todayHighlightColor], which highlights the today date +/// cell in the date range picker. +/// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) +/// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) +/// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) +/// /// ``` dart /// /// Widget build(BuildContext context) { @@ -966,6 +1229,22 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [cellDecoration], which used to set decoration for the year cells in the + /// year view of the date range picker. + /// * [todayTextStyle], which used to set text style for the today cell in the + /// year view of the date range picker. + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// cell in the year view of the date range picker. + /// * [leadingDatesTextStyle], which used to set text style for the leading + /// date cells in the year views of the dat range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -997,6 +1276,22 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayCellDecoration], which used to set decoration for the today cell + /// in the year views of date range picker. + /// * [textStyle], which used to set text style for the year cell in the + /// year views of the date range picker. + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// cell in the year views of the date range picker. + /// * [leadingDatesTextStyle], which used to set text style for the leading + /// date cells in the year views of the dat range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1031,6 +1326,22 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// _Note:_ This property not applicable when the /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. /// + /// See also: + /// * [leadingDatesDecoration], which used to set decoration for the leading + /// date cells in the year views of date range picker. + /// * [textStyle], which used to set text style for the year cell in the + /// year views of the date range picker. + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// cell in the year views of the date range picker. + /// * [leadingDatesTextStyle], which used to set text style for the leading + /// date cells in the year views of the dat range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1064,6 +1375,22 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the year views of date range picker. + /// * [textStyle], which used to set text style for the year cell in the + /// year views of the date range picker. + /// * [todayTextStyle], which used to set text style for the today date + /// cell in the year views of the date range picker. + /// * [leadingDatesTextStyle], which used to set text style for the leading + /// date cells in the year views of the dat range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1097,6 +1424,26 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// date cells in the year views of date range picker. + /// * [cellDecoration], which used to set decoration for the year cell in the + /// year views of the date range picker. + /// * [todayCellDecoration], which used to set decoration for the today date + /// cell in the year views of the date range picker. + /// * [leadingDatesDecoration], which used to set decoration for the leading + /// date cells in the year views of the dat range picker. + /// * [SfDateRangePicker.selectionColor], which fills the background of the + /// selected cells in the date range picker. + /// * [SfDateRangePicker.startRangeSelectionColor], which fills the background + /// of the first cell of the range selection in date range picker. + /// * [SfDateRangePicker.endRangeSelectionColor], which fills the background + /// of the last cell of the range selection in date range picker. + /// * [SfDateRangePicker.rangeSelectionColor], which fills the background of + /// the in between cells of date range picker in range selection. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1129,6 +1476,26 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [textStyle], which used to set text style for the year cells in the + /// year views of date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// year cells in the year views of the date range picker. + /// * [todayCellDecoration], which used to set decoration for the today date + /// cell in the year views of the date range picker. + /// * [leadingDatesDecoration], which used to set decoration for the leading + /// date cells in the year views of the dat range picker. + /// * [SfDateRangePicker.selectionColor], which fills the background of the + /// selected cells in the date range picker. + /// * [SfDateRangePicker.startRangeSelectionColor], which fills the background + /// of the first cell of the range selection in date range picker. + /// * [SfDateRangePicker.endRangeSelectionColor], which fills the background + /// of the last cell of the range selection in date range picker. + /// * [SfDateRangePicker.rangeSelectionColor], which fills the background of + /// the in between cells of date range picker in range selection. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1161,6 +1528,26 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayTextStyle], which used to set text style for the today date cell + /// in the year views of date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// year cells in the year views of the date range picker. + /// * [todayCellDecoration], which used to set decoration for the today date + /// cell in the year views of the date range picker. + /// * [leadingDatesDecoration], which used to set decoration for the leading + /// date cells in the year views of the dat range picker. + /// * [SfDateRangePicker.selectionColor], which fills the background of the + /// selected cells in the date range picker. + /// * [SfDateRangePicker.startRangeSelectionColor], which fills the background + /// of the first cell of the range selection in date range picker. + /// * [SfDateRangePicker.endRangeSelectionColor], which fills the background + /// of the last cell of the range selection in date range picker. + /// * [SfDateRangePicker.rangeSelectionColor], which fills the background of + /// the in between cells of date range picker in range selection. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1196,6 +1583,26 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// _Note:_ This property not applicable when the /// [SfDateRangePicker.pickerMode] set as [DateRangePickerMode.hijri]. /// + /// See also: + /// * [leadingDatesTextStyle], which used to set text style for the leading + /// date cell in the year views of date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// year cells in the year views of the date range picker. + /// * [todayCellDecoration], which used to set decoration for the today date + /// cell in the year views of the date range picker. + /// * [cellDecoration], which used to set decoration for the year cells in the + /// year views of the dat range picker. + /// * [SfDateRangePicker.selectionColor], which fills the background of the + /// selected cells in the date range picker. + /// * [SfDateRangePicker.startRangeSelectionColor], which fills the background + /// of the first cell of the range selection in date range picker. + /// * [SfDateRangePicker.endRangeSelectionColor], which fills the background + /// of the last cell of the range selection in date range picker. + /// * [SfDateRangePicker.rangeSelectionColor], which fills the background of + /// the in between cells of date range picker in range selection. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1289,6 +1696,36 @@ class DateRangePickerYearCellStyle with Diagnosticable { /// [leadingDatesDecoration], and [weekendDatesDecoration] in the month cells /// of the date range picker. /// +/// See also: +/// * [DateRangePickerMonthViewSettings], which allows to customize the month +/// view of the date range picker +/// * [SfDateRangePicker.cellBuilder], which allows to set custom widget for the +/// picker cells in the date range picker. +/// * [DateRangePickerYearCellStyle], which allows to customize the year cell of +/// the year, decade and century views of the date range picker. +/// * [SfDateRangePicker.selectionColor], which fills the background of the +/// selected cells in the date range picker. +/// * [SfDateRangePicker.startRangeSelectionColor], which fills the background +/// of the first cell of the range selection in date range picker. +/// * [SfDateRangePicker.endRangeSelectionColor], which fills the background of +/// the last cell of the range selection in date range picker. +/// * [SfDateRangePicker.rangeSelectionColor], which fills the background of the +/// in between cells of date range picker in range selection. +/// * [SfDateRangePicker.selectionTextStyle], which is used to set the text +/// style for the text in the selected cell of date range picker. +/// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for +/// the text in the selected range cell's of date range picker. +/// * [SfDateRangePicker.backgroundColor], which fills the background of the +/// date range picker. +/// * [SfDateRangePicker.todayHighlightColor], which highlights the today date +/// cell in the date range picker. +/// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) +/// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) +/// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) +/// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1370,6 +1807,34 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [cellDecoration], which used to set decoration for the month cells in + /// the month view of the date range picker. + /// * [todayTextStyle], which used to set text style for the today date cell + /// in the month view of the date range picker. + /// * [leadingDatesTextStyle], which used to set the text style for the + /// leading dates cells in the month view of the date range picker. + /// * [trailingDatesTextStyle], which used to set the text style for the + /// trailing dates cells in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled cells in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1403,6 +1868,34 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayCellDecoration], which used to set decoration for the today month + /// cell in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [leadingDatesTextStyle], which used to set the text style for the + /// leading dates cells in the month view of the date range picker. + /// * [trailingDatesTextStyle], which used to set the text style for the + /// trailing dates cells in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled cells in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -1443,7 +1936,35 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// This property not applicable when the [SfDateRangePicker.pickerMode] set /// as [DateRangePickerMode.hijri]. /// - /// See also:[DateRangePickerMonthViewSettings.showTrailingAndLeadingDates]. + /// See also: + /// * [DateRangePickerMonthViewSettings.showTrailingAndLeadingDates], which + /// used to display the previous and next month cell dates in the month view. + /// * [trailingDatesDecoration], which used to set decoration for the trailing + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [leadingDatesTextStyle], which used to set the text style for the + /// leading dates cells in the month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled cells in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -1485,7 +2006,35 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// This property not applicable when the [SfDateRangePicker.pickerMode] set /// as [DateRangePickerMode.hijri]. /// - /// See also:[DateRangePickerMonthViewSettings.showTrailingAndLeadingDates]. + /// See also: + /// * [DateRangePickerMonthViewSettings.showTrailingAndLeadingDates], which + /// used to display the previous and next month cell dates in the month view. + /// * [leadingDatesDecoration], which used to set decoration for the leading + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [trailingDatesTextStyle], which used to set the text style for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled cells in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -1521,9 +2070,38 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// appearance of various components of the date range picker. /// /// See also: - /// [SfDateRangePicker.minDate]. - /// [SfDateRangePicker.maxDate]. - /// [SfDateRangePicker.enablePastDates]. + /// * [SfDateRangePicker.minDate], which is the minimum available date for the + /// date range picker. + /// * [SfDateRangePicker.maxDate], which is the last available date for the + /// date range picker. + /// * [SfDateRangePicker.enablePastDates], which allows to enable the dates + /// that falls before the today date for interaction. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [trailingDatesTextStyle], which used to set the text style for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [leadingDatesTextStyle], which used to set the text style for the + /// leading date cells in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -1634,7 +2212,37 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// - /// See also: [DateRangePickerMonthViewSettings.blackoutDates]. + /// See also: + /// * [DateRangePickerMonthViewSettings.blackoutDates], which is used to + /// disable interaction for the specific month dates in month view. + /// * [SfDateRangePicker.enablePastDates], which allows to enable the dates + /// that falls before the today date for interaction. + /// * [blackoutDatesDecoration], which used to set decoration for the black + /// out date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [trailingDatesTextStyle], which used to set the text style for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [leadingDatesTextStyle], which used to set the text style for the + /// leading date cells in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -1668,7 +2276,35 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// - /// See also: [DateRangePickerMonthViewSettings.weekendDays]. + /// See also: + /// * [DateRangePickerMonthViewSettings.weekendDays], which is used change the + /// week ends for month. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [trailingDatesTextStyle], which used to set the text style for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [leadingDatesTextStyle], which used to set the text style for the + /// leading date cells in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -1702,7 +2338,35 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// - /// See also: [DateRangePickerMonthViewSettings.specialDates]. + /// See also: + /// * [DateRangePickerMonthViewSettings.specialDates], which is used highlight + /// the specific dates in the month view. + /// * [specialDatesDecoration], which used to set decoration for the special + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [trailingDatesTextStyle], which used to set the text style for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [leadingDatesTextStyle], which used to set the text style for the + /// leading date cells in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled date cells in the month view of date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set the text style for the weekend + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -1735,6 +2399,36 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [DateRangePickerMonthViewSettings.specialDates], which is used highlight + /// the specific dates in the month view. + /// * [specialDatesTextStyle], which used to set text style for the special + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [trailingDatesDecoration], which used to set the decoration for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [leadingDatesDecoration], which used to set the decoration for the + /// leading date cells in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set the decoration for the + /// disabled date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set the decoration for the + /// weekend date cells in the month view of date range picker. + /// * [blackoutDatesDecoration], which used to set decoration for the blackout + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1768,6 +2462,36 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [DateRangePickerMonthViewSettings.weekendDates], which is used to change + /// the weekends for the month in month view. + /// * [weekendTextStyle], which used to set text style for the special + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [trailingDatesDecoration], which used to set the decoration for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [leadingDatesDecoration], which used to set the decoration for the + /// leading date cells in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set the decoration for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [blackoutDatesDecoration], which used to set decoration for the blackout + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1801,6 +2525,36 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [DateRangePickerMonthViewSettings.blackoutDates], which is used to + /// disable interactions for specific dates in month view. + /// * [blackoutDateTextStyle], which used to set text style for the blackout + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [trailingDatesDecoration], which used to set the decoration for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [leadingDatesDecoration], which used to set the decoration for the + /// leading date cells in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set the decoration for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1838,8 +2592,36 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// appearance of various components of the date range picker. /// /// See also: - /// [SfDateRangePicker.minDate]. - /// [SfDateRangePicker.maxDate]. + /// * [SfDateRangePicker.minDate], which is the least available date in the + /// date range picker. + /// * [SfDateRangePicker.maxDate], which is the last available date in the + /// date range picker. + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [trailingDatesDecoration], which used to set the decoration for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [leadingDatesDecoration], which used to set the decoration for the + /// leading date cells in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -1873,6 +2655,34 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [trailingDatesDecoration], which used to set the decoration for the + /// trailing dates cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [leadingDatesDecoration], which used to set the decoration for the + /// leading date cells in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1905,6 +2715,34 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayTextStyle], which used to set text style for the today date cell + /// in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [trailingDatesDecoration], which used to set the decoration for the + /// trailing dates cells in the month view of the date range picker. + /// * [cellDecoration], which used to set the decoration for the month cells + /// in the month view of the date range picker. + /// * [leadingDatesDecoration], which used to set the decoration for the + /// leading date cells in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1945,7 +2783,35 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// This property not applicable when the [SfDateRangePicker.pickerMode] set /// as [DateRangePickerMode.hijri]. /// - /// See also:[DateRangePickerMonthViewSettings.showTrailingAndLeadingDates]. + /// See also: + /// * [DateRangePickerMonthViewSettings.showTrailingAndLeadingDates], which + /// used to display the previous and next month dates in the month view. + /// * [trailingDatesTextStyle], which used to set text style for the trailing + /// date cell in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [cellDecoration], which used to set the decoration for the month cells + /// in the month view of the date range picker. + /// * [leadingDatesDecoration], which used to set the decoration for the + /// leading date cells in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -1987,7 +2853,36 @@ class DateRangePickerMonthCellStyle with Diagnosticable { /// This property not applicable when the [SfDateRangePicker.pickerMode] set /// as [DateRangePickerMode.hijri]. /// - /// See also:[DateRangePickerMonthViewSettings.showLeadingAndTrailingDate]. + /// See also: + /// * [DateRangePickerMonthViewSettings.showTrailingAndLeadingDates], which + /// used to display the previous and next month dates in the month view. + /// * [leadingDatesTextStyle], which used to set text style for the leading + /// date cell in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [cellDecoration], which used to set the decoration for the month cells + /// in the month view of the date range picker. + /// * [trailingDatesDecoration], which used to set the decoration for the + /// trailing date cells in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfDateRangePicker.selectionTextStyle], which is used to set the text + /// style for the text in the selected cell of date range picker. + /// * [SfDateRangePicker.rangeTextStyle], which is used to set text style for + /// the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// /// ```dart /// @@ -2246,6 +3141,8 @@ class DateRangePickerMonthCellStyle with Diagnosticable { } } +/// Signature for the callback that reports when the picker controller value +/// changed. typedef DateRangePickerValueChangedCallback = void Function(String); /// Notifier used to notify the when the objects properties changed. @@ -2341,10 +3238,51 @@ class DateRangePickerValueChangeNotifier with Diagnosticable { /// select the dates or ranges programmatically on [SfDateRangePicker] on /// initial load and in run time. /// -/// See also: [DateRangePickerSelectionMode] -/// /// Defaults to null. /// +/// See also: +/// * [SfDateRangePicker.initialDisplayDate], which used to navigate the date +/// range picker to the specific date initially. +/// * [SfDateRangePicker.initialSelectedDate], which allows to select date +/// programmatically initially on date range picker. +/// * [SfDateRangePicker.initialSelectedDates], which allows to list of select +/// date programmatically initially on date range picker. +/// * [SfDateRangePicker.initialSelectedRange], which allows to select a range +/// of dates programmatically initially on date range picker. +/// * [SfDateRangePicker.initialSelectedRanges], which allows to select a ranges +/// of dates programmatically initially on date range picker. +/// * [selectedDate],which allows to select date +/// programmatically dynamically on date range picker. +/// * [selectedDates], which allows to select dates +/// programmatically dynamically on date range picker. +/// * [selectedRange], which allows to select range +/// of dates programmatically dynamically on date range picker. +/// * [selectedRanges], which allows to select +/// ranges of dates programmatically dynamically on date range picker. +/// * [SfDateRangePicker.selectionMode], which allows to customize the selection +/// mode with available mode options. +/// * [SfDateRangePicker.onViewChanged], the callback which notifies when the +/// current view visible date changed on the date range picker. +/// * [SfDateRangePicker.onSelectionChanged], the callback which notifies when +/// the selected cell changed on the the date range picker. +/// * [SfDateRangePicker.showActionButtons], which allows to cancel of confirm +/// the selection in the date range picker. +/// * [SfDateRangePicker.onSubmit], the callback which notifies when the +/// selected value confirmed through confirm button on date range picker. +/// * [SfDateRangePicker.onCancel], the callback which notifies when the +/// selected value canceled and reverted to previous confirmed value through +/// cancel button on date range picker. +/// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) +/// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) +/// /// This example demonstrates how to use the [SfDateRangePickerController] for /// [SfDateRangePicker]. /// @@ -2423,6 +3361,29 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [DateRangePickerSelectionMode] set as /// [DateRangePickerSelectionMode.single]. /// + /// See also: + /// * [SfDateRangePicker.initialSelectedDate], which allows to select date + /// programmatically initially on date range picker. + /// * [selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [SfDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfDateRangePicker.onSelectionChanged], the callback which notifies when + /// the selected cell changed on the the date range picker. + /// * [SfDateRangePicker.showActionButtons], which allows to cancel of confirm + /// the selection in the date range picker. + /// * [SfDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -2482,6 +3443,31 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiple]. /// + /// See also: + /// * [SfDateRangePicker.initialSelectedDates], which allows to list of select + /// date programmatically initially on date range picker. + /// * [selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [SfDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfDateRangePicker.onSelectionChanged], the callback which notifies when + /// the selected cell changed on the the date range picker. + /// * [SfDateRangePicker.showActionButtons], which allows to cancel of confirm + /// the selection in the date range picker. + /// * [SfDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -2543,6 +3529,31 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.range]. /// + /// See also: + /// * [SfDateRangePicker.initialSelectedRange], which allows to select a range + /// of dates programmatically initially on date range picker. + /// * [selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [SfDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfDateRangePicker.onSelectionChanged], the callback which notifies when + /// the selected cell changed on the the date range picker. + /// * [SfDateRangePicker.showActionButtons], which allows to cancel of confirm + /// the selection in the date range picker. + /// * [SfDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -2601,6 +3612,32 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [SfDateRangePicker.initialSelectedRanges], which allows to select a + /// ranges of dates programmatically initially on date range picker. + /// * [selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [SfDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfDateRangePicker.onSelectionChanged], the callback which notifies when + /// the selected cell changed on the the date range picker. + /// * [SfDateRangePicker.showActionButtons], which allows to cancel of confirm + /// the selection in the date range picker. + /// * [SfDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -2658,6 +3695,15 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// [SfDateRangePicker.maxDate] the widget will move the widgets min or max /// date. /// + /// See also: + /// * [SfDateRangePicker.initialDisplayDate], which used to navigate the date + /// range picker to the specific date initially. + /// * [SfDateRangePicker.onViewChanged], the callback which notifies when the + /// current view visible date changed on the date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// /// ``` dart /// @@ -2702,6 +3748,17 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// /// The [SfDateRangePicker] will display the view sets to this property. /// + /// See also: + /// * [SfDateRangePicker.view], which used to display the required view on + /// the date range picker initially. + /// * [SfDateRangePicker.onViewChanged], the callback which notifies when the + /// current view visible date changed on the date range picker. + /// * [DateRangePickerView], to know more about the available view options in + /// date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// ```dart /// /// class MyAppState extends State { @@ -2743,6 +3800,17 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// _Note:_ If the current view has the maximum date range, it will not move /// to the next view. /// + /// See also: + /// * [SfDateRangePicker.showNavigationArrow], which allows to display the + /// navigation arrows on the header view of the date range picker. + /// * [backward], which used to navigate to the previous view of the date + /// range picker programmatically. + /// * [SfDateRangePicker.onViewChanged], the callback which notifies when the + /// current view visible date changed on the date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// ```dart /// /// class MyApp extends StatefulWidget { @@ -2794,6 +3862,17 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { /// _Note:_ If the current view has the minimum date range, it will not move /// to the previous view. /// + /// See also: + /// * [SfDateRangePicker.showNavigationArrow], which allows to display the + /// navigation arrows on the header view of the date range picker. + /// * [forward], which used to navigate to the next view of the date + /// range picker programmatically. + /// * [SfDateRangePicker.onViewChanged], the callback which notifies when the + /// current view visible date changed on the date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// ```dart /// /// class MyApp extends StatefulWidget { @@ -2854,6 +3933,9 @@ class DateRangePickerController extends DateRangePickerValueChangeNotifier { } /// Selection modes for [SfDateRangePicker]. +/// +/// [DateRangePickerSelectionShape], which used to set different shape for the +/// selection view in date range picker. enum DateRangePickerSelectionMode { /// - DateRangePickerSelectionMode.single, Allows to select a single date, /// selecting a new date will remove the selection for previous date and @@ -2895,14 +3977,18 @@ enum DateRangePickerSelectionMode { /// [DateRangePickerNavigationMode.scroll]. /// /// See also: - /// - /// [pickerDateRange] - /// - /// [HijriDateRange] + /// * [pickerDateRange], which used to store the start and end date of the + /// range in date range picker. + /// [HijriDateRange], which used to store the start and end date of the + /// range in hijri date range picker. extendableRange, } /// Available views for [SfDateRangePicker]. +/// +/// See also: +/// * [HijriDatePickerView], which used to set different views for hijri date +/// range picker. enum DateRangePickerView { /// - DateRangePickerView.month, Displays the month view. month, @@ -2921,6 +4007,10 @@ enum DateRangePickerView { } /// The shape for the selection view in [SfDateRangePicker]. +/// +/// See also: +/// * [DateRangePickerSelectionMode], which used to set different selectio modes +/// for date range picker. enum DateRangePickerSelectionShape { /// - DateRangePickerSelectionShape.circle, Draws the date selection in circle /// shape. @@ -2932,6 +4022,10 @@ enum DateRangePickerSelectionShape { } /// A direction in which the [SfDateRangePicker] navigates. +/// +/// See also: +/// * [DateRangePickerNavigationMode], which used to set different navigation +/// modes for calendar. enum DateRangePickerNavigationDirection { /// - DateRangePickerNavigationDirection.vertical, Navigates in top and bottom /// direction. @@ -2943,6 +4037,10 @@ enum DateRangePickerNavigationDirection { } /// A type specifies how the date picker navigation interaction works. +/// +/// See also: +/// * [DateRangePickerNavigationDirection], which allows to navigate through +/// date picker views with different modes. enum DateRangePickerNavigationMode { /// Disables the next or previous view dates to be shown by scrolling or /// swipe interaction in [SfDateRangePicker] and [SfHijriDateRangePicker]. @@ -2979,6 +4077,12 @@ enum DateRangePickerNavigationMode { /// /// Details for [DateRangePickerViewChangedCallback], such as [visibleDateRange] /// and [view]. +/// +/// See also: +/// * [SfDateRangePicker.onViewChanged], which receives the information. +/// * [SfDateRangePicker], which passes the information to one of its receiver. +/// * [DateRangePickerViewChangedCallback], signature when the current visible +/// dates changed in date range picker. @immutable class DateRangePickerViewChangedArgs { /// Creates details for [DateRangePickerViewChangedCallback]. @@ -2999,6 +4103,12 @@ class DateRangePickerViewChangedArgs { /// /// Details for [DateRangePickerSelectionChangedCallback], such as selected /// value. +/// +/// See also: +/// * [SfDateRangePicker.onSelectionChanged], which receives the information. +/// * [SfDateRangePicker], which passes the information to one of its receiver. +/// * [DateRangePickerSelectionChangedCallback], signature when the selectoin +/// changed in date range picker. @immutable class DateRangePickerSelectionChangedArgs { /// Creates details for [DateRangePickerSelectionChangedCallback]. @@ -3023,6 +4133,19 @@ class DateRangePickerSelectionChangedArgs { /// Defines a range of dates, covers the dates in between the given [startDate] /// and [endDate] as a range. +/// +/// See also: +/// * [HijriDateRange], which is used to store the range value for hijri date +/// range picker. +/// * [DateRangePickerSelectionMode], which is used to handle different +/// available selection modes in date range picker. +/// * [SfDateRangePicker.initialSelectedRange], which allows to select a range +/// of dates programmatically initially on date range picker. +/// * [SfDateRangePicker.initialSelectedRanges], which allows to select a ranges +/// of dates programmatically initially on date range picker. +/// * [DateRangePickerController.selectedRange], which allows to select range +/// of dates programmatically dynamically on date range picker. +/// * [DateRangePickerController.selectedRanges], which allows to select @immutable class PickerDateRange with Diagnosticable { /// Creates a picker date range with the given start and end date. @@ -3044,10 +4167,24 @@ class PickerDateRange with Diagnosticable { /// Signature for a function that creates a widget based on date range picker /// cell details. +/// +/// See also: +/// * [SfDateRangePicker.cellBuilder], which matches this signature. +/// * [SfDateRangePicker], which uses this signature in one of it's callback. typedef DateRangePickerCellBuilder = Widget Function( BuildContext context, DateRangePickerCellDetails cellDetails); +/// Signature for predicating dates for enabled date selections. +/// +/// [SelectableDayPredicate] parameter used to specify allowable days in the +/// SfDateRangePicker. +typedef DateRangePickerSelectableDayPredicate = bool Function(DateTime date); + /// Contains the details that needed on calendar cell builder. +/// +/// See also: +/// * [SfDateRangePicker.cellBuilder], which matches this signature. +/// * [SfDateRangePicker], which uses this signature in one of it's callback. class DateRangePickerCellDetails { /// Constructor to store the details that needed on calendar cell builder. DateRangePickerCellDetails( 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 70cc4be94..bb115c8ae 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 @@ -10,6 +10,35 @@ import 'picker_helper.dart'; /// [viewHeaderStyle], [enableSwipeSelection], [blackoutDates], [specialDates] /// and [weekendDays] in month view of date range picker. /// +/// See also: +/// * [DateRangePickerMonthViewSettings], which used to customize the month view +/// of the date range picker. +/// * [DateRangePickerMonthCellStyle], which used to customize the month cell of +/// the month view in the date range picker. +/// * [HijriDatePickerMonthCellStyle], which used to customize the month cell of +/// the month view in the hijri date range picker. +/// * [SfHijriDateRangePicker.cellBuilder],which allows to set custom widget for +/// the picker cells in the date range picker. +/// * [HijriDatePickerYearCellStyle], which allows to customize the year cell of +/// the year, decade and century views of the date range picker. +/// * [SfHijriDateRangePicker.backgroundColor], which fills the background of +/// the date range picker. +/// * [SfHijriDateRangePicker.todayHighlightColor], which highlights the today +/// date cell in the date range picker. +/// * Knowledge base: [How to use hijri date range picker](https://www.syncfusion.com/kb/12200/how-to-use-hijri-date-range-picker-sfhijridaterangepicker-in-flutter) +/// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) +/// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to change the first day of week](https://www.syncfusion.com/kb/12221/how-to-change-the-first-day-of-week-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) +/// * Knowledge base: [How to change the week end dates](https://www.syncfusion.com/kb/12182/how-to-change-the-week-end-dates-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12167/how-to-change-the-number-of-weeks-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to add active dates](https://www.syncfusion.com/kb/12075/how-to-add-active-dates-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to create timeline date picker](https://www.syncfusion.com/kb/12474/how-to-create-timeline-date-picker-in-flutter) +/// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) +/// /// ```dart /// ///Widget build(BuildContext context) { @@ -70,6 +99,17 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// /// Defaults to `EE`. /// + /// See also: + /// * [viewHeaderStyle], which used to customize the view header view of the + /// hijri date range picker. + /// * [SfHijriDateRangePicker.backgroundColor], which fills the background of + /// the hijri date range picker. + /// * [SfHijriDateRangePicker.todayHighlightColor], which highlights the today + /// date in the hijri date range picker. + /// * [viewHeaderHeight], which is the size for the view header view in the + /// month view of date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -101,6 +141,14 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// set as [DateRangePickerSelectionMode.range] or /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [DateRangePickerSelectionMode], which contains different selection modes + /// for the hijri date range picker. + /// * [SfHijriDateRangePicker.enableMultiView], which displays two date range + /// picker side by side based on the + /// [SfHijriDateRangePicker.navigationDirection] value. + /// * Knowledge base: [How to restrict swipe gesture for range selection](https://www.syncfusion.com/kb/12117/how-to-restrict-swipe-gesture-for-range-selection-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -128,6 +176,11 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// /// Defaults to `7` which indicates `DateTime.sunday`. /// + /// See also: + /// * [showWeekNumber], which displays the week number of the year in the + /// month view of the hijri date range picker. + /// * Knowledge base: [How to change the first day of week](https://www.syncfusion.com/kb/12221/how-to-change-the-first-day-of-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -153,7 +206,16 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// Allows to customize the [textStyle] and [backgroundColor] of the view /// header view in month view of [SfHijriDateRangePicker]. /// - /// See also: [DateRangePickerViewHeaderStyle]. + /// See also: + /// * [DateRangePickerViewHeaderStyle], to know more about the available + /// option for customize the view header view of month view. + /// * [viewHeaderHeight], which is the size for the view header view in the + /// month view of hijri date range picker. + /// * [dayFormat], which is used to customize the format of the view header + /// text in the month view of hijri date range picker. + /// * [SfHijriDateRangePicker.todayHighlightColor], which highlights the today + /// date cell in the hijri date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) /// /// ```dart /// @@ -185,6 +247,15 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// /// Defaults to `30`. /// + /// See also: + /// * [viewHeaderStyle], which used to customize the view header view of the + /// month view in hijri date range picker. + /// * [dayFormat], which is used to customize the format of the view header + /// text in the month view of hijri date range picker. + /// * [SfHijriDateRangePicker.todayHighlightColor], which highlights the today + /// date cell in the hijri date range picker. + /// * Knowledge base: [How to replace the view header with the custom widget](https://www.syncfusion.com/kb/12098/how-to-replace-the-view-header-with-the-custom-widget-in-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -214,8 +285,18 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// customize the appearance of blackout dates in month view. /// /// See also: - /// [HijriDatePickerMonthCellStyle.blackoutDateTextStyle] - /// [HijriDatePickerMonthCellStyle.blackoutDatesDecoration]. + /// * [HijriDatePickerMonthCellStyle.blackoutDateTextStyle], which used to set + /// text style for the black out date cell in the month view. + /// * [HijriDatePickerMonthCellStyle.blackoutDatesDecoration], which used to + /// set the decoration for the black out date cell in the month view. + /// * [specialDates], which is the list of dates to highlight the specific + /// dates in the month view. + /// * [weekendDays], which used to change the week end days in the month view. + /// * [SfHijriDateRangePicker.enablePastDates], which allows to enable the + /// dates that falls before the today date for interaction. + /// * Knowledge base: [How to update blackout dates using onViewChanged callback](https://www.syncfusion.com/kb/12372/how-to-update-blackout-dates-using-onviewchanged-callback-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to add active dates](https://www.syncfusion.com/kb/12075/how-to-add-active-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -252,8 +333,14 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// customize the appearance of blackout dates in month view. /// /// See also: - /// [HijriDatePickerMonthCellStyle.specialDatesTextStyle] - /// [HijriDatePickerMonthCellStyle.specialDatesDecoration]. + /// [HijriDatePickerMonthCellStyle.specialDatesTextStyle],which used to set + /// text style for the text in special dates cell in the month view. + /// [HijriDatePickerMonthCellStyle.specialDatesDecoration], which used to set + /// decoration for the special dates cell in the month view. + /// * [blackoutDates], which is the list of dates to restrict the interaction + /// for specific dates in month view. + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -288,7 +375,11 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// /// Defaults to false /// - /// see also: [weekNumberStyle] + /// see also: + /// * [weekNumberStyle], which used to customize the week number view of the + /// month view in the hijri date range picker. + /// * [firstDayOfWeek], which used to customize the start day of the week in + /// month view of hijri date range picker. /// /// ``` dart /// Widget build(BuildContext context) { @@ -308,7 +399,11 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// /// Defaults to null /// - /// see also: [showWeekNumber] + /// see also: + /// * [showWeekNumber], which allows to display the week number of the year in + /// the month view of the hijri date range picker. + /// * [firstDayOfWeek], which used to customize the start day of the week in + /// month view of hijri date range picker. /// /// ``` dart /// Widget build(BuildContext context) { @@ -335,6 +430,19 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// using the [HijriDatePickerMonthCellStyle.weekendTextStyle] or /// [HijriDatePickerMonthCellStyle.weekendDatesDecoration] property. /// + /// See also: + /// * [HijriDatePickerMonthCellStyle.weekendTextStyle], which used to set + /// text style for the week end cell text in month view. + /// * [HijriDatePickerMonthCellStyle.weekendDatesDecoration], which used to + /// set decoration for the week end cell text in month view. + /// * [specialDates], which used to highlight specific dates in the month view + /// of hijri date range picker. + /// * [blackoutDates], which used to restrict the user interaction for the + /// specific dates in teh month view of date range picker. + /// * Knowledge base: [How to change the week end dates](https://www.syncfusion.com/kb/12182/how-to-change-the-week-end-dates-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to change the number of weeks](https://www.syncfusion.com/kb/12167/how-to-change-the-number-of-weeks-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -421,6 +529,36 @@ class HijriDatePickerMonthViewSettings with Diagnosticable { /// [disabledDatesDecoration] in year and decade view of the /// [SfHijriDateRangePicker]. /// +/// See also: +/// * [HijriDatePickerMonthCellStyle], which allows to customize the month cell +/// of the month view of the hijri date range picker +/// * [SfHijriDateRangePicker.cellBuilder], which allows to set custom widget +/// for the picker cells in the dhijri ate range picker. +/// * [HijriDatePickerMonthViewSettings], which allows to customize the month +/// view of the hijri date range picker. +/// * [SfHijriDateRangePicker.selectionColor], which fills the background of the +/// selected cells in the hijri date range picker. +/// * [SfHijriDateRangePicker.startRangeSelectionColor], which fills the +/// background of the first cell of the range selection in hijri date range +/// picker. +/// * [SfHijriDateRangePicker.endRangeSelectionColor], which fills the +/// background of the last cell of the range selection in hijri date range +/// picker. +/// * [SfHijriDateRangePicker.rangeSelectionColor], which fills the background +/// of the in between cells of hijri date range picker in range selection. +/// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the text +/// style for the text in the selected cell of hijri date range picker. +/// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style +/// for the text in the selected range cell's of hijri date range picker. +/// * [SfHijriDateRangePicker.backgroundColor], which fills the background of +/// the hijri date range picker. +/// * [SfHijriDateRangePicker.todayHighlightColor], which highlights the today +/// date cell in the hijri date range picker. +/// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) +/// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) +/// /// ``` dart /// /// Widget build(BuildContext context) { @@ -471,6 +609,20 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [cellDecoration], which used to set decoration for the year cells in the + /// year view of the date range picker. + /// * [todayTextStyle], which used to set text style for the today cell in the + /// year view of the date range picker. + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// cell in the year view of the date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -502,6 +654,20 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayCellDecoration], which used to set decoration for the today cell + /// in the year views of date range picker. + /// * [textStyle], which used to set text style for the year cell in the + /// year views of the date range picker. + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// cell in the year views of the date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -536,6 +702,20 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the year views of date range picker. + /// * [textStyle], which used to set text style for the year cell in the + /// year views of the date range picker. + /// * [todayTextStyle], which used to set text style for the today date + /// cell in the year views of the date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -569,6 +749,24 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// date cells in the year views of date range picker. + /// * [cellDecoration], which used to set decoration for the year cell in the + /// year views of the date range picker. + /// * [todayCellDecoration], which used to set decoration for the today date + /// cell in the year views of the date range picker. + /// * [SfHijriDateRangePicker.selectionColor], which fills the background of + /// the selected cells in the date range picker. + /// * [SfHijriDateRangePicker.startRangeSelectionColor], which fills the + /// background of the first cell of the range selection in date range picker. + /// * [SfHijriDateRangePicker.endRangeSelectionColor], which fills the + /// background of the last cell of the range selection in date range picker. + /// * [SfHijriDateRangePicker.rangeSelectionColor], which fills the background + /// of the in between cells of date range picker in range selection. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -601,6 +799,24 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [textStyle], which used to set text style for the year cells in the + /// year views of date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// year cells in the year views of the date range picker. + /// * [todayCellDecoration], which used to set decoration for the today date + /// cell in the year views of the date range picker. + /// * [SfHijriDateRangePicker.selectionColor], which fills the background of + /// the selected cells in the date range picker. + /// * [SfHijriDateRangePicker.startRangeSelectionColor], which fills the + /// background of the first cell of the range selection in date range picker. + /// * [SfHijriDateRangePicker.endRangeSelectionColor], which fills the + /// background of the last cell of the range selection in date range picker. + /// * [SfHijriDateRangePicker.rangeSelectionColor], which fills the background + /// of the in between cells of date range picker in range selection. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -633,6 +849,24 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayTextStyle], which used to set text style for the today date cell + /// in the year views of date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// year cells in the year views of the date range picker. + /// * [todayCellDecoration], which used to set decoration for the today date + /// cell in the year views of the date range picker. + /// * [SfHijriDateRangePicker.selectionColor], which fills the background of + /// the selected cells in the date range picker. + /// * [SfHijriDateRangePicker.startRangeSelectionColor], which fills the + /// background of the first cell of the range selection in date range picker. + /// * [SfHijriDateRangePicker.endRangeSelectionColor], which fills the + /// background of the last cell of the range selection in date range picker. + /// * [SfHijriDateRangePicker.rangeSelectionColor], which fills the background + /// of the in between cells of date range picker in range selection. + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the year, decade and century views](https://www.syncfusion.com/kb/12321/how-to-style-the-year-decade-century-views-in-the-flutter-date-range-picker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -711,6 +945,36 @@ class HijriDatePickerYearCellStyle with Diagnosticable { /// [disabledDatesDecoration],and [weekendDatesDecoration] in the month cells /// of the date range picker. /// +/// See also: +/// * [HijriDatePickerMonthViewSettings], which allows to customize the month +/// view of the date range picker +/// * [SfHijriDateRangePicker.cellBuilder], which allows to set custom widget +/// for the picker cells in the date range picker. +/// * [HijriDatePickerYearCellStyle], which allows to customize the year cell of +/// the year, decade and century views of the date range picker. +/// * [SfHijriDateRangePicker.selectionColor], which fills the background of the +/// selected cells in the date range picker. +/// * [SfHijriDateRangePicker.startRangeSelectionColor], which fills the +/// background of the first cell of the range selection in date range picker. +/// * [SfHijriDateRangePicker.endRangeSelectionColor], which fills the +/// background of the last cell of the range selection in date range picker. +/// * [SfHijriDateRangePicker.rangeSelectionColor], which fills the background +/// of the in between cells of date range picker in range selection. +/// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the text +/// style for the text in the selected cell of date range picker. +/// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style +/// for the text in the selected range cell's of date range picker. +/// * [SfHijriDateRangePicker.backgroundColor], which fills the background of +/// the date range picker. +/// * [SfHijriDateRangePicker.todayHighlightColor], which highlights the today +/// date cell in the date range picker. +/// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) +/// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) +/// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) +/// /// ``` dart /// /// Widget build(BuildContext context) { @@ -766,6 +1030,31 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [cellDecoration], which used to set decoration for the month cells in + /// the month view of the date range picker. + /// * [todayTextStyle], which used to set text style for the today date cell + /// in the month view of the date range picker. + /// trailing dates cells in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled cells in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -799,6 +1088,30 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayCellDecoration], which used to set decoration for the today month + /// cell in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled cells in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// Widget build(BuildContext context) { @@ -833,9 +1146,34 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// appearance of various components of the date range picker. /// /// See also: - /// [SfHijriDateRangePicker.minDate]. - /// [SfHijriDateRangePicker.maxDate]. - /// [SfHijriDateRangePicker.enablePastDates]. + /// * [SfHijriDateRangePicker.minDate], which is the minimum available date + /// for the date range picker. + /// * [SfHijriDateRangePicker.maxDate], which is the last available date for + /// the date range picker. + /// * [SfHijriDateRangePicker.enablePastDates], which allows to enable the + /// dates that falls before the today date for interaction. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -869,7 +1207,33 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// - /// See also: [HijriDatePickerMonthViewSettings.blackoutDates]. + /// See also: + /// * [HijriDatePickerMonthViewSettings.blackoutDates], which is used to + /// disable interaction for the specific month dates in month view. + /// * [SfHijriDateRangePicker.enablePastDates], which allows to enable the + /// dates that falls before the today date for interaction. + /// * [blackoutDatesDecoration], which used to set decoration for the black + /// out date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set text style for the the week end + /// date cells in the date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -903,7 +1267,31 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// - /// See also: [HijriDatePickerMonthViewSettings.weekendDays]. + /// See also: + /// * [HijriDatePickerMonthViewSettings.weekendDays], which is used change the + /// week ends for month. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesTextStyle], which used to set the text style for the + /// special dates cells in the month view of date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -937,7 +1325,31 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// - /// See also: [HijriDatePickerMonthViewSettings.specialDates]. + /// See also: + /// * [HijriDatePickerMonthViewSettings.specialDates], which is used highlight + /// the specific dates in the month view. + /// * [specialDatesDecoration], which used to set decoration for the special + /// date cells in the month view of the date range picker. + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [todayTextStyle], which used to set the text style for the today date + /// cell in the month view of the date range picker. + /// * [disabledDatesTextStyle], which used to set the text style for the + /// disabled date cells in the month view of date range picker. + /// * [blackoutDateTextStyle], which used to set the text style for the black + /// out dates cells in the month view of date range picker. + /// * [weekendTextStyle], which used to set the text style for the weekend + /// date cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ``` dart /// @@ -970,6 +1382,32 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [HijriDatePickerMonthViewSettings.specialDates], which is used highlight + /// the specific dates in the month view. + /// * [specialDatesTextStyle], which used to set text style for the special + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set the decoration for the + /// disabled date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set the decoration for the + /// weekend date cells in the month view of date range picker. + /// * [blackoutDatesDecoration], which used to set decoration for the blackout + /// date cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1003,6 +1441,32 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [HijriDatePickerMonthViewSettings.weekendDates], which is used to change + /// the weekends for the month in month view. + /// * [weekendTextStyle], which used to set text style for the special + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set the decoration for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [blackoutDatesDecoration], which used to set decoration for the blackout + /// date cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1036,6 +1500,32 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [HijriDatePickerMonthViewSettings.blackoutDates], which is used to + /// disable interactions for specific dates in month view. + /// * [blackoutDateTextStyle], which used to set text style for the blackout + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set the decoration for the + /// disabled date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1073,8 +1563,32 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// appearance of various components of the date range picker. /// /// See also: - /// [SfHijriDateRangePicker.minDate]. - /// [SfHijriDateRangePicker.maxDate]. + /// * [SfHijriDateRangePicker.minDate], which is the least available date in + /// the date range picker. + /// * [SfHijriDateRangePicker.maxDate], which is the last available date in + /// the date range picker. + /// * [disabledDatesTextStyle], which used to set text style for the disabled + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set decoration for the month cells + /// in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) /// /// ```dart /// @@ -1108,6 +1622,30 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [textStyle], which used to set text style for the month cells in the + /// month view of the date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [todayCellDecoration], which used to set the decoration for the today + /// date cell in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1141,6 +1679,30 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// Using a [SfDateRangePickerTheme] gives more fine-grained control over the /// appearance of various components of the date range picker. /// + /// See also: + /// * [todayTextStyle], which used to set text style for the today date cell + /// in the month view of the date range picker. + /// * [disabledDatesDecoration], which used to set decoration for the disabled + /// date cells in the month view of the date range picker. + /// * [cellDecoration], which used to set the decoration for the month cells + /// in the month view of the date range picker. + /// * [blackoutDatesDecoration], which used to set the decoration for the + /// blackout date cells in the month view of date range picker. + /// * [specialDatesDecoration], which used to set the decoration for the + /// special date cells in the month view of date range picker. + /// * [weekendDatesDecoration], which used to set decoration for the weekend + /// date cells in the month view of date range picker. + /// * [SfHijriDateRangePicker.selectionTextStyle], which is used to set the + /// text style for the text in the selected cell of date range picker. + /// * [SfHijriDateRangePicker.rangeTextStyle], which is used to set text style + /// for the text in the selected range cell's of date range picker. + /// * Knowledge base: [How to customize leading and trailing dates using cell builder](https://www.syncfusion.com/kb/12674/how-to-customize-leading-and-trailing-dates-using-cell-builder-in-the-flutter-date-range) + /// * Knowledge base: [How to customize the special dates using builder](https://www.syncfusion.com/kb/12374/how-to-customize-the-special-dates-using-builder-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to customize the date range picker cells using builder](https://www.syncfusion.com/kb/12208/how-to-customize-the-date-range-picker-cells-using-builder-in-the-flutter-sfdaterangepicker) + /// * Knowledge base: [How to apply theming](https://www.syncfusion.com/kb/11898/how-to-apply-theming-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to style the current month date cell](https://www.syncfusion.com/kb/12190/how-to-style-the-current-month-date-cell-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to customize the month cell](https://www.syncfusion.com/kb/11307/how-to-customize-the-month-cell-of-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ```dart /// /// Widget build(BuildContext context) { @@ -1271,10 +1833,51 @@ class HijriDatePickerMonthCellStyle with Diagnosticable { /// select the dates or ranges programmatically on [SfHijriDateRangePicker] on /// initial load and in run time. /// -/// See also: [HijriDateRangePickerSelectionMode] -/// /// Defaults to null. /// +/// See also: +/// * [SfHijriDateRangePicker.initialDisplayDate], which used to navigate the +/// date range picker to the specific date initially. +/// * [SfHijriDateRangePicker.initialSelectedDate], which allows to select date +/// programmatically initially on date range picker. +/// * [SfHijriDateRangePicker.initialSelectedDates], which allows to list of +/// select date programmatically initially on date range picker. +/// * [SfHijriDateRangePicker.initialSelectedRange], which allows to select a +/// range of dates programmatically initially on date range picker. +/// * [SfHijriDateRangePicker.initialSelectedRanges], which allows to select a +/// ranges of dates programmatically initially on date range picker. +/// * [selectedDate],which allows to select date +/// programmatically dynamically on date range picker. +/// * [selectedDates], which allows to select dates +/// programmatically dynamically on date range picker. +/// * [selectedRange], which allows to select range +/// of dates programmatically dynamically on date range picker. +/// * [selectedRanges], which allows to select +/// ranges of dates programmatically dynamically on date range picker. +/// * [SfHijriDateRangePicker.selectionMode], which allows to customize the +/// selection mode with available mode options. +/// * [SfHijriDateRangePicker.onViewChanged], the callback which notifies when +/// the current view visible date changed on the date range picker. +/// * [SfHijriDateRangePicker.onSelectionChanged], the callback which notifies +/// when the selected cell changed on the the date range picker. +/// * [SfHijriDateRangePicker.showActionButtons], which allows to cancel of +/// confirm the selection in the date range picker. +/// * [SfHijriDateRangePicker.onSubmit], the callback which notifies when the +/// selected value confirmed through confirm button on date range picker. +/// * [SfHijriDateRangePicker.onCancel], the callback which notifies when the +/// selected value canceled and reverted to previous confirmed value through +/// cancel button on date range picker. +/// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to select all days when clicking on the day header](https://www.syncfusion.com/kb/12353/how-to-select-all-days-when-clicking-on-the-day-header-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to confirm or cancel the selection](https://www.syncfusion.com/kb/12546/how-to-confirm-or-cancel-the-selection-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) +/// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) +/// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) +/// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) +/// /// This example demonstrates how to use the [HijriDatePickerController] for /// [SfHijriDateRangePicker]. /// @@ -1354,6 +1957,29 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [DateRangePickerSelectionMode] set as /// [DateRangePickerSelectionMode.single]. /// + /// See also: + /// * [SfHijriDateRangePicker.initialSelectedDate], which allows to select + /// date programmatically initially on date range picker. + /// * [selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [SfHijriDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfHijriDateRangePicker.onSelectionChanged], the callback which notifies + /// when the selected cell changed on the the date range picker. + /// * [SfHijriDateRangePicker.showActionButtons], which allows to cancel of + /// confirm the selection in the date range picker. + /// * [SfHijriDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfHijriDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -1412,6 +2038,31 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiple]. /// + /// See also: + /// * [SfHijriDateRangePicker.initialSelectedDates], which allows to list of + /// select date programmatically initially on date range picker. + /// * [selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [SfHijriDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfHijriDateRangePicker.onSelectionChanged], the callback which notifies + /// when the selected cell changed on the the date range picker. + /// * [SfHijriDateRangePicker.showActionButtons], which allows to cancel of + /// confirm the selection in the date range picker. + /// * [SfHijriDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfHijriDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -1474,6 +2125,31 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [HijriDateRangePickerSelectionMode.range]. /// + /// See also: + /// * [SfHijriDateRangePicker.initialSelectedRange], which allows to select a + /// range of dates programmatically initially on date range picker. + /// * [selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [selectedRanges], which allows to select + /// ranges of dates programmatically dynamically on date range picker. + /// * [SfHijriDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfHijriDateRangePicker.onSelectionChanged], the callback which notifies + /// when the selected cell changed on the the date range picker. + /// * [SfHijriDateRangePicker.showActionButtons], which allows to cancel of + /// confirm the selection in the date range picker. + /// * [SfHijriDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfHijriDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -1532,6 +2208,32 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// It is only applicable when the [selectionMode] set as /// [DateRangePickerSelectionMode.multiRange]. /// + /// See also: + /// * [SfHijriDateRangePicker.initialSelectedRanges], which allows to select a + /// ranges of dates programmatically initially on date range picker. + /// * [selectedDate],which allows to select date + /// programmatically dynamically on date range picker. + /// * [selectedDates], which allows to select dates + /// programmatically dynamically on date range picker. + /// * [selectedRange], which allows to select range + /// of dates programmatically dynamically on date range picker. + /// * [SfHijriDateRangePicker.selectionMode], which allows to customize the + /// selection mode with available mode options. + /// * [SfHijriDateRangePicker.onSelectionChanged], the callback which notifies + /// when the selected cell changed on the the date range picker. + /// * [SfHijriDateRangePicker.showActionButtons], which allows to cancel of + /// confirm the selection in the date range picker. + /// * [SfHijriDateRangePicker.onSubmit], the callback which notifies when the + /// selected value confirmed through confirm button on date range picker. + /// * [SfHijriDateRangePicker.onCancel], the callback which notifies when the + /// selected value canceled and reverted to previous confirmed value through + /// cancel button on date range picker. + /// * Knowledge base: [How to get the selected date](https://www.syncfusion.com/kb/11410/how-to-get-the-selected-date-from-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select a week](https://www.syncfusion.com/kb/11412/how-to-select-a-week-in-the-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to select previous or next dates bases on selected date](https://www.syncfusion.com/kb/12354/how-to-select-previous-or-next-dates-based-on-the-selected-date-in-the-flutter-date-range) + /// * Knowledge base: [How to get the start and end date of the selected range](https://www.syncfusion.com/kb/12248/how-to-get-the-start-and-end-date-of-the-selected-range-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically select the date](https://www.syncfusion.com/kb/12114/how-to-programmatically-select-the-date-in-the-flutter-date-range-picker-sfdaterangepicker) + /// /// ``` dart /// /// class MyAppState extends State { @@ -1591,6 +2293,14 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// [SfHijriDateRangePicker.maxDate] the widget will move the widgets min or /// max date. /// + /// See also: + /// * [SfHijriDateRangePicker.initialDisplayDate], which used to navigate the + /// date range picker to the specific date initially. + /// * [SfHijriDateRangePicker.onViewChanged], the callback which notifies when + /// the current view visible date changed on the date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) /// /// ``` dart /// @@ -1636,6 +2346,17 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// /// The [SfHijriDateRangePicker] will display the view sets to this property. /// + /// See also: + /// * [SfHijriDateRangePicker.view], which used to display the required view + /// on the date range picker initially. + /// * [SfHijriDateRangePicker.onViewChanged], the callback which notifies when + /// the current view visible date changed on the date range picker. + /// * [HijriDatePickerView], to know more about the available view options in + /// the hijri date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// ```dart /// /// class MyAppState extends State { @@ -1677,6 +2398,17 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// _Note:_ If the current view has the maximum date range, it will not move /// to the next view. /// + /// See also: + /// * [SfHijriDateRangePicker.showNavigationArrow], which allows to display + /// the navigation arrows on the header view of the date range picker. + /// * [backward], which used to navigate to the previous view of the date + /// range picker programmatically. + /// * [SfHijriDateRangePicker.onViewChanged], the callback which notifies when + /// the current view visible date changed on the date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// ```dart /// /// class MyApp extends StatefulWidget { @@ -1728,6 +2460,17 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { /// _Note:_ If the current view has the minimum date range, it will not move /// to the previous view. /// + /// See also: + /// * [SfHijriDateRangePicker.showNavigationArrow], which allows to display + /// the navigation arrows on the header view of the date range picker. + /// * [forward], which used to navigate to the next view of the date + /// range picker programmatically. + /// * [SfHijriDateRangePicker.onViewChanged], the callback which notifies when + /// the current view visible date changed on the date range picker. + /// * Knowledge base: [How to do programmatic navigation](https://www.syncfusion.com/kb/12140/how-to-do-programmatic-navigation-using-flutter-date-range-picker-sfdaterangepicker) + /// * Knowledge base: [How to programmatically navigate to adjacent dates](https://www.syncfusion.com/kb/12137/how-to-programmatically-navigate-to-the-adjacent-dates-in-the-flutter-date-range-picker) + /// * Knowledge base: [How to programmatically navigate](https://www.syncfusion.com/kb/12135/how-to-programmatically-navigate-to-the-date-in-the-flutter-date-range-picker) + /// /// ```dart /// /// class MyApp extends StatefulWidget { @@ -1790,6 +2533,10 @@ class HijriDatePickerController extends DateRangePickerValueChangeNotifier { } /// Available views for [SfHijriDateRangePicker]. +/// +/// See also: +/// * [DateRangePickerView], which used to set different views for date range +/// picker. enum HijriDatePickerView { /// - HijriDatePickerView.month, Displays the month view. month, @@ -1805,6 +2552,13 @@ enum HijriDatePickerView { /// /// Details for [HijriDatePickerViewChangedCallback], such as /// [visibleDateRange] and [view]. +/// +/// See also: +/// * [SfHijriDateRangePicker.onViewChanged], which receives the information. +/// * [SfHijriDateRangePicker], which passes the information to one of its +/// receiver. +/// * [HijriDatePickerViewChangedCallback], signature when the current visible +/// dates changed in date range picker. @immutable class HijriDatePickerViewChangedArgs { /// Creates details for [DateRangePickerViewChangedCallback]. @@ -1824,6 +2578,19 @@ class HijriDatePickerViewChangedArgs { /// Defines a range of dates, covers the dates in between the given [startDate] /// and [endDate] as a range. +/// +/// See also: +/// * [PickerDateRange], which is used to store the range value for hijri date +/// range picker. +/// * [DateRangePickerSelectionMode], which is used to handle different +/// available selection modes in date range picker. +/// * [SfHijriDateRangePicker.initialSelectedRange], which allows to select a +/// range of dates programmatically initially on date range picker. +/// * [SfHijriDateRangePicker.initialSelectedRanges], which allows to select a +/// ranges of dates programmatically initially on date range picker. +/// * [HijriDatePickerController.selectedRange], which allows to select range +/// of dates programmatically dynamically on date range picker. +/// * [HijriDatePickerController.selectedRanges], which allows to select @immutable class HijriDateRange with Diagnosticable { /// Creates a picker date range with the given start and end date. @@ -1845,10 +2612,27 @@ class HijriDateRange with Diagnosticable { /// Signature for a function that creates a widget based on date range picker /// cell details. +/// +/// See also: +/// * [SfHijriDateRangePicker.cellBuilder], which matches this signature. +/// * [SfHijriDateRangePicker], which uses this signature in one of it's +/// callback. typedef HijriDateRangePickerCellBuilder = Widget Function( BuildContext context, HijriDateRangePickerCellDetails cellDetails); +/// Signature for predicating dates for enabled date selections. +/// +/// [SelectableDayPredicate] parameter used to specify allowable days in the +/// SfHijriDateRangePicker. +typedef HijriDatePickerSelectableDayPredicate = bool Function( + HijriDateTime date); + /// Contains the details that needed on calendar cell builder. +/// +/// See also: +/// * [SfHijriDateRangePicker.cellBuilder], which matches this signature. +/// * [SfHijriDateRangePicker], which uses this signature in one of it's +/// callback. class HijriDateRangePickerCellDetails { /// Constructor to store the details that needed on calendar cell builder. HijriDateRangePickerCellDetails( diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart index 3afaed966..bcd95459d 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/month_view.dart @@ -1,4 +1,3 @@ -import 'dart:math'; import 'dart:ui'; import 'package:flutter/foundation.dart'; @@ -7,8 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:syncfusion_flutter_core/core.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; + import '../../datepicker.dart'; import 'date_picker_manager.dart'; import 'picker_helper.dart'; @@ -54,7 +54,8 @@ class MonthView extends StatefulWidget { this.cellBuilder, this.showWeekNumber, this.weekNumberStyle, - this.isMobilePlatform); + this.isMobilePlatform, + this.disableDatesCollection); /// Defines the month row count. final int rowCount; @@ -167,6 +168,9 @@ class MonthView extends StatefulWidget { /// Defines the week number style for [SfDateRangePicker]. final DateRangePickerWeekNumberStyle weekNumberStyle; + /// Holds the list of dates for selectable day predicate. + final List? disableDatesCollection; + @override _MonthViewState createState() => _MonthViewState(); } @@ -432,6 +436,7 @@ class _MonthViewState extends State { widget.isMobilePlatform, widget.weekNumberStyle, weekNumberPanelWidth, + widget.disableDatesCollection, widgets: _children); } case DateRangePickerSelectionMode.multiple: @@ -472,6 +477,7 @@ class _MonthViewState extends State { widget.isMobilePlatform, widget.weekNumberStyle, weekNumberPanelWidth, + widget.disableDatesCollection, widgets: _children); } case DateRangePickerSelectionMode.range: @@ -511,6 +517,7 @@ class _MonthViewState extends State { widget.isMobilePlatform, widget.weekNumberStyle, weekNumberPanelWidth, + widget.disableDatesCollection, widgets: _children); case DateRangePickerSelectionMode.extendableRange: { @@ -550,6 +557,7 @@ class _MonthViewState extends State { widget.isMobilePlatform, widget.weekNumberStyle, weekNumberPanelWidth, + widget.disableDatesCollection, widgets: _children); } case DateRangePickerSelectionMode.multiRange: @@ -590,6 +598,7 @@ class _MonthViewState extends State { widget.isMobilePlatform, widget.weekNumberStyle, weekNumberPanelWidth, + widget.disableDatesCollection, widgets: _children); } } @@ -634,6 +643,7 @@ class _MonthViewSingleSelectionRenderWidget this.isMobilePlatform, this.weekNumberStyle, this.weekNumberPanelWidth, + this.disableDatesCollection, {List widgets = const []}) : super(children: widgets); @@ -707,6 +717,8 @@ class _MonthViewSingleSelectionRenderWidget final double weekNumberPanelWidth; + final List? disableDatesCollection; + @override _MonthViewSingleSelectionRenderObject createRenderObject( BuildContext context) { @@ -745,7 +757,8 @@ class _MonthViewSingleSelectionRenderWidget selectedDate, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); } @override @@ -786,7 +799,8 @@ class _MonthViewSingleSelectionRenderWidget ..selectedDate = selectedDate ..isMobilePlatform = isMobilePlatform ..weekNumberStyle = weekNumberStyle - ..weekNumberPanelWidth = weekNumberPanelWidth; + ..weekNumberPanelWidth = weekNumberPanelWidth + ..disableDatesCollection = disableDatesCollection; } } @@ -828,6 +842,7 @@ class _MonthViewMultiSelectionRenderWidget this.isMobilePlatform, this.weekNumberStyle, this.weekNumberPanelWidth, + this.disableDatesCollection, {List widgets = const []}) : super(children: widgets); final DateRangePickerWeekNumberStyle weekNumberStyle; @@ -900,6 +915,8 @@ class _MonthViewMultiSelectionRenderWidget final double weekNumberPanelWidth; + final List? disableDatesCollection; + @override _MonthViewMultiSelectionRenderObject createRenderObject( BuildContext context) { @@ -938,7 +955,8 @@ class _MonthViewMultiSelectionRenderWidget selectedDates, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); } @override @@ -979,7 +997,8 @@ class _MonthViewMultiSelectionRenderWidget ..selectedDates = selectedDates ..isMobilePlatform = isMobilePlatform ..weekNumberStyle = weekNumberStyle - ..weekNumberPanelWidth = weekNumberPanelWidth; + ..weekNumberPanelWidth = weekNumberPanelWidth + ..disableDatesCollection = disableDatesCollection; } } @@ -1021,6 +1040,7 @@ class _MonthViewRangeSelectionRenderWidget this.isMobilePlatform, this.weekNumberStyle, this.weekNumberPanelWidth, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -1094,6 +1114,8 @@ class _MonthViewRangeSelectionRenderWidget final double weekNumberPanelWidth; + final List? disableDatesCollection; + @override _MonthViewRangeSelectionRenderObject createRenderObject( BuildContext context) { @@ -1132,7 +1154,8 @@ class _MonthViewRangeSelectionRenderWidget selectedRange, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); } @override @@ -1173,7 +1196,8 @@ class _MonthViewRangeSelectionRenderWidget ..selectedRange = selectedRange ..isMobilePlatform = isMobilePlatform ..weekNumberStyle = weekNumberStyle - ..weekNumberPanelWidth = weekNumberPanelWidth; + ..weekNumberPanelWidth = weekNumberPanelWidth + ..disableDatesCollection = disableDatesCollection; } } @@ -1215,6 +1239,7 @@ class _MonthViewExtendableRangeSelectionRenderWidget this.isMobilePlatform, this.weekNumberStyle, this.weekNumberPanelWidth, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -1288,6 +1313,8 @@ class _MonthViewExtendableRangeSelectionRenderWidget final DateRangePickerWeekNumberStyle weekNumberStyle; + final List? disableDatesCollection; + @override _MonthViewExtendableRangeSelectionRenderObject createRenderObject( BuildContext context) { @@ -1326,7 +1353,8 @@ class _MonthViewExtendableRangeSelectionRenderWidget selectedRange, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); } @override @@ -1367,7 +1395,8 @@ class _MonthViewExtendableRangeSelectionRenderWidget ..selectedRange = selectedRange ..isMobilePlatform = isMobilePlatform ..weekNumberStyle = weekNumberStyle - ..weekNumberPanelWidth = weekNumberPanelWidth; + ..weekNumberPanelWidth = weekNumberPanelWidth + ..disableDatesCollection = disableDatesCollection; } } @@ -1409,6 +1438,7 @@ class _MonthViewMultiRangeSelectionRenderWidget this.isMobilePlatform, this.weekNumberStyle, this.weekNumberPanelWidth, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -1482,6 +1512,8 @@ class _MonthViewMultiRangeSelectionRenderWidget final double weekNumberPanelWidth; + final List? disableDatesCollection; + @override _MonthViewMultiRangeSelectionRenderObject createRenderObject( BuildContext context) { @@ -1520,7 +1552,8 @@ class _MonthViewMultiRangeSelectionRenderWidget selectedRanges, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); } @override @@ -1561,7 +1594,8 @@ class _MonthViewMultiRangeSelectionRenderWidget ..selectedRanges = selectedRanges ..isMobilePlatform = isMobilePlatform ..weekNumberStyle = weekNumberStyle - ..weekNumberPanelWidth = weekNumberPanelWidth; + ..weekNumberPanelWidth = weekNumberPanelWidth + ..disableDatesCollection = disableDatesCollection; } } @@ -1603,7 +1637,8 @@ abstract class _IMonthView extends RenderBox this.localizations, this.isMobilePlatform, this._weekNumberStyle, - this._weekNumberPanelWidth); + this._weekNumberPanelWidth, + this._disableDatesCollection); bool isMobilePlatform; @@ -2151,6 +2186,23 @@ abstract class _IMonthView extends RenderBox SfLocalizations localizations; + List? _disableDatesCollection; + + List? get disableDatesCollection => _disableDatesCollection; + + set disableDatesCollection(List? value) { + if (_disableDatesCollection == value) { + return; + } + + _disableDatesCollection = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + /// Used to paint the selection of month cell on all the selection mode. final Paint _selectionPainter = Paint(); @@ -2235,6 +2287,9 @@ abstract class _IMonthView extends RenderBox /// value(date, dates, range, ranges). List getSelectedIndexValues(int viewStartIndex, int viewEndIndex); + /// Check and return that the given date index is between range or not. + bool isBetweenRange(int index); + /// Draw the highlight for selected month cell based on it selection mode /// value(date, dates, range, ranges). TextStyle drawSelection(Canvas canvas, double x, double y, int index, @@ -2409,7 +2464,9 @@ abstract class _IMonthView extends RenderBox top = leftAndTopValue['top']!; continue; } else if (!DateRangePickerHelper.isEnabledDate( - _minDate, _maxDate, _enablePastDates, currentDate, _isHijri)) { + _minDate, _maxDate, _enablePastDates, currentDate, _isHijri) || + DateRangePickerHelper.isDateWithInVisibleDates( + visibleDates, disableDatesCollection, currentDate)) { semanticsBuilder.add(CustomPainterSemantics( rect: Rect.fromLTWH(viewXStartPosition + left, viewYStartPosition + top, cellWidth, cellHeight), @@ -2494,7 +2551,8 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { this._selectedDate, bool isMobilePlatform, DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth) + double weekNumberPanelWidth, + List? disableDatesCollection) : super( visibleDates, rowCount, @@ -2529,7 +2587,8 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { localizations, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); dynamic _selectedDate; @@ -2630,6 +2689,11 @@ class _MonthViewSingleSelectionRenderObject extends _IMonthView { selectedDate = details.selectedDate; selectionNotifier.value = !selectionNotifier.value; } + + @override + bool isBetweenRange(int index) { + return false; + } } class _MonthViewMultiSelectionRenderObject extends _IMonthView { @@ -2668,7 +2732,8 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { this._selectedDates, bool isMobilePlatform, DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth) + double weekNumberPanelWidth, + List? disableDatesCollection) : super( visibleDates, rowCount, @@ -2703,7 +2768,8 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { localizations, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); List? _selectedDates; @@ -2809,6 +2875,11 @@ class _MonthViewMultiSelectionRenderObject extends _IMonthView { selectedDates = DateRangePickerHelper.cloneList(details.selectedDates); selectionNotifier.value = !selectionNotifier.value; } + + @override + bool isBetweenRange(int index) { + return false; + } } class _MonthViewRangeSelectionRenderObject extends _IMonthView { @@ -2847,7 +2918,8 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { this._selectedRange, bool isMobilePlatform, DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth) + double weekNumberPanelWidth, + List? disableDatesCollection) : super( visibleDates, rowCount, @@ -2882,7 +2954,8 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { localizations, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); dynamic _selectedRange; @@ -3074,6 +3147,16 @@ class _MonthViewRangeSelectionRenderObject extends _IMonthView { selectedRange = details.selectedRange; selectionNotifier.value = !selectionNotifier.value; } + + @override + bool isBetweenRange(int index) { + /// This method will return list of boolean indicating whether selected date + /// index is either it is start or end range or selected date and is between + /// range as a list, in this list the isBetweenRange boolean index is 3, + /// hence we used 3 here. + final List selectionDetails = _getSelectedRangePosition(index); + return selectionDetails[3]; + } } class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { @@ -3112,7 +3195,8 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { this._selectedRange, bool isMobilePlatform, DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth) + double weekNumberPanelWidth, + List? disableDatesCollection) : super( visibleDates, rowCount, @@ -3147,7 +3231,8 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { localizations, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); dynamic _selectedRange; @@ -3341,6 +3426,17 @@ class _MonthViewExtendableRangeSelectionRenderObject extends _IMonthView { selectedRange = details.selectedRange; selectionNotifier.value = !selectionNotifier.value; } + + @override + bool isBetweenRange(int index) { + /// This method will return list of boolean indicating whether selected date + /// index is either it is start or end range or selected date and is between + /// range as a list, in this list the isBetweenRange boolean index is 3, + /// hence we used 3 here. + final List selectionDetails = + _getSelectedRangePosition(index, _selectedIndex); + return selectionDetails[3]; + } } class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { @@ -3379,7 +3475,8 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { this._selectedRanges, bool isMobilePlatform, DateRangePickerWeekNumberStyle weekNumberStyle, - double weekNumberPanelWidth) + double weekNumberPanelWidth, + List? disableDatesCollection) : super( visibleDates, rowCount, @@ -3414,7 +3511,8 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { localizations, isMobilePlatform, weekNumberStyle, - weekNumberPanelWidth); + weekNumberPanelWidth, + disableDatesCollection); List? _selectedRanges; @@ -3625,6 +3723,16 @@ class _MonthViewMultiRangeSelectionRenderObject extends _IMonthView { selectedRanges = DateRangePickerHelper.cloneList(details.selectedRanges); selectionNotifier.value = !selectionNotifier.value; } + + @override + bool isBetweenRange(int index) { + /// This method will return list of boolean indicating whether selected date + /// index is either it is start or end range or selected date and is between + /// range as a list, in this list the isBetweenRange boolean index is 3, + /// hence we used 3 here. + final List selectionDetails = _getSelectedRangePosition(index); + return selectionDetails[3]; + } } void _drawSelectedDate( @@ -3957,6 +4065,17 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, final dynamic date = monthView.visibleDates[currentIndex]; final int currentDateMonth = date.month as int; + /// Check the x position reaches view end position then draw the + /// date on next cell. + /// Padding 1 value used to avoid decimal value difference. + /// eg., if view end position as 243 and x position as 242.499 then + /// round method in decimal return 242 rather than 243, so it does + /// not move the next line for draw date value. + if (xPosition + 1 >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + if (weekNumberPanelWidth != 0 && hideLeadingAndTrailingDates && ((i <= DateTime.daysPerWeek && @@ -3997,17 +4116,6 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, viewEndPosition); } - /// Check the x position reaches view end position then draw the - /// date on next cell. - /// Padding 1 value used to avoid decimal value difference. - /// eg., if view end position as 243 and x position as 242.499 then - /// round method in decimal return 242 rather than 243, so it does - /// not move the next line for draw date value. - if (xPosition + 1 >= viewEndPosition) { - xPosition = viewStartPosition; - yPosition += cellHeight; - } - if (hideLeadingAndTrailingDates && currentDateMonth != currentMonth) { xPosition += cellWidth; continue; @@ -4023,10 +4131,14 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, DateRangePickerHelper.isDateWithInVisibleDates( monthView.visibleDates, monthView.blackoutDates, date); final bool isSelectedDate = selectedIndex.contains(currentIndex); + final bool isDisabledDate = + DateRangePickerHelper.isDateWithInVisibleDates( + monthView.visibleDates, monthView.disableDatesCollection, date); if (isSelectedDate && !isBlackedDate && isEnableDate && + !isDisabledDate && (!monthView.enableMultiView || (monthView.rowCount != 6 || (currentMonth == currentDateMonth)))) { @@ -4041,8 +4153,8 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, date.weekday == DateTime.monday && (!hideLeadingAndTrailingDates || (hideLeadingAndTrailingDates && - (i >= (DateTime.daysPerWeek * 2)) && - (i <= datesCount - (DateTime.daysPerWeek * 2))))) { + (i > (DateTime.daysPerWeek * 2)) && + (i < datesCount - (DateTime.daysPerWeek * 2))))) { _drawWeekNumber( canvas, size, @@ -4057,7 +4169,10 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, if (monthView.mouseHoverPosition.value != null && monthView.mouseHoverPosition.value!.offset != null) { - if (isSelectedDate || isBlackedDate || !isEnableDate) { + if ((isSelectedDate && (!monthView.isBetweenRange(currentIndex))) || + isBlackedDate || + !isEnableDate || + isDisabledDate) { xPosition += cellWidth; continue; } @@ -4164,6 +4279,17 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, final dynamic date = monthView.visibleDates[currentIndex]; final int currentDateMonth = date.month as int; + /// Check the x position reaches view end position then draw the + /// date on next cell. + /// Padding 1 value used to avoid decimal value difference. + /// eg., if view end position as 243 and x position as 242.499 then + /// round method in decimal return 242 rather than 243, so it does + /// not move the next line for draw date value. + if (xPosition + 1 >= viewEndPosition) { + xPosition = viewStartPosition; + yPosition += cellHeight; + } + if (weekNumberPanelWidth != 0 && hideLeadingAndTrailingDates && ((i <= DateTime.daysPerWeek && @@ -4203,17 +4329,6 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, viewEndPosition); } - /// Check the x position reaches view end position then draw the - /// date on next cell. - /// Padding 1 value used to avoid decimal value difference. - /// eg., if view end position as 243 and x position as 242.499 then - /// round method in decimal return 242 rather than 243, so it does - /// not move the next line for draw date value. - if (xPosition + 1 >= viewEndPosition) { - xPosition = viewStartPosition; - yPosition += cellHeight; - } - bool isNextMonth = false; bool isPreviousMonth = false; if (monthView.rowCount == 6 || monthView.isHijri) { @@ -4236,8 +4351,8 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, date.weekday == DateTime.monday && (!hideLeadingAndTrailingDates || (hideLeadingAndTrailingDates && - (i >= (DateTime.daysPerWeek * 2)) && - (i <= datesCount - (DateTime.daysPerWeek * 2))))) { + (i > (DateTime.daysPerWeek * 2)) && + (i < datesCount - (DateTime.daysPerWeek * 2))))) { _drawWeekNumber( canvas, size, @@ -4263,9 +4378,20 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, DateRangePickerHelper.isWeekend(monthView.weekendDays, date); final bool isSpecialDate = DateRangePickerHelper.isDateWithInVisibleDates( monthView.visibleDates, monthView.specialDates, date); + final bool isDisabledDate = + DateRangePickerHelper.isDateWithInVisibleDates( + monthView.visibleDates, monthView.disableDatesCollection, date); - textStyle = _updateTextStyle(monthView, isNextMonth, isPreviousMonth, - isCurrentDate, isEnableDate, isBlackedDate, isWeekEnd, isSpecialDate); + textStyle = _updateTextStyle( + monthView, + isNextMonth, + isPreviousMonth, + isCurrentDate, + isEnableDate, + isBlackedDate, + isWeekEnd, + isSpecialDate, + isDisabledDate); dateDecoration = _updateDecoration( isNextMonth, isPreviousMonth, @@ -4275,12 +4401,14 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, isBlackedDate, date, isWeekEnd, - isSpecialDate); + isSpecialDate, + isDisabledDate); final bool isSelectedDate = selectedIndex.contains(currentIndex); if (isSelectedDate && !isBlackedDate && isEnableDate && + !isDisabledDate && (!monthView.enableMultiView || (monthView.rowCount != 6 || (currentMonthDate.month == currentDateMonth)))) { @@ -4322,7 +4450,10 @@ void _drawMonthCellsAndSelection(PaintingContext context, Size size, } if (monthView.mouseHoverPosition.value != null) { - if (isSelectedDate || isBlackedDate || !isEnableDate) { + if ((isSelectedDate && (!monthView.isBetweenRange(currentIndex))) || + isBlackedDate || + !isEnableDate || + isDisabledDate) { xPosition += cellWidth; continue; } @@ -4415,7 +4546,6 @@ void _addRangeHoverEffect(Canvas canvas, double xPosition, double yPosition, final List hoveringDetails = rangeSelectionMonthView ._getSelectedRangePosition(currentIndex, hoveringIndex); - final bool isSelectedDate = hoveringDetails[0]; final bool isStartRange = hoveringDetails[1]; final bool isEndRange = hoveringDetails[2]; final bool isBetweenRange = hoveringDetails[3]; @@ -4435,60 +4565,43 @@ void _addRangeHoverEffect(Canvas canvas, double xPosition, double yPosition, break; } - late Rect rect; - final Path path = Path(); - if (isStartRange || isSelectedDate) { - rect = Rect.fromLTRB( - xPosition + rangeSelectionMonthView._centerXPosition, - yPosition + heightDifference, - xPosition + rangeSelectionMonthView._cellWidth, - yPosition + rangeSelectionMonthView._cellHeight - heightDifference); + Rect rect = const Rect.fromLTRB(0, 0, 0, 0); + if (isStartRange) { switch (monthView.selectionShape) { case DateRangePickerSelectionShape.circle: - path.addArc( - Rect.fromCenter( - center: Offset(rect.left, rect.top + (rect.height / 2)), - width: radius * 2, - height: radius * 2), - pi / 2, - pi); + rect = Rect.fromLTRB( + xPosition + rangeSelectionMonthView._centerXPosition, + yPosition + heightDifference, + xPosition + rangeSelectionMonthView._cellWidth, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference); break; case DateRangePickerSelectionShape.rectangle: - path.addRRect(RRect.fromRectAndCorners( - Rect.fromLTRB( - xPosition, - yPosition + heightDifference, - xPosition + monthView._centerXPosition, - yPosition + monthView._cellHeight - heightDifference), - topLeft: Radius.circular(cornerRadius), - bottomLeft: Radius.circular(cornerRadius))); + final double rightPosition = + xPosition + rangeSelectionMonthView._cellWidth; + rect = Rect.fromLTRB( + rightPosition - cornerRadius, + yPosition + heightDifference, + rightPosition, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference); } } else if (isEndRange) { - rect = Rect.fromLTRB( - xPosition, - yPosition + heightDifference, - xPosition + rangeSelectionMonthView._centerXPosition, - yPosition + rangeSelectionMonthView._cellHeight - heightDifference); switch (monthView.selectionShape) { case DateRangePickerSelectionShape.circle: - path.addArc( - Rect.fromCenter( - center: Offset(rect.right, rect.top + (rect.height / 2)), - width: radius * 2, - height: radius * 2), - -(pi / 2), - pi); + rect = Rect.fromLTRB( + xPosition, + yPosition + heightDifference, + xPosition + rangeSelectionMonthView._centerXPosition, + yPosition + rangeSelectionMonthView._cellHeight - heightDifference); break; case DateRangePickerSelectionShape.rectangle: { - path.addRRect(RRect.fromRectAndCorners( - Rect.fromLTRB( - xPosition + monthView._centerXPosition, - yPosition + heightDifference, - xPosition + monthView._cellWidth - heightDifference, - yPosition + monthView._cellHeight - heightDifference), - bottomRight: Radius.circular(cornerRadius), - topRight: Radius.circular(cornerRadius))); + rect = Rect.fromLTRB( + xPosition, + yPosition + heightDifference, + xPosition + cornerRadius, + yPosition + + rangeSelectionMonthView._cellHeight - + heightDifference); } } } else if (isBetweenRange) { @@ -4508,17 +4621,6 @@ void _addRangeHoverEffect(Canvas canvas, double xPosition, double yPosition, rect.left, rect.top, rect.right, canvas, monthView._selectionPainter); DateRangePickerHelper.drawDashedLine( rect.left, rect.bottom, rect.right, canvas, monthView._selectionPainter); - - if (isEndRange || isStartRange) { - canvas.drawPath( - DateRangePickerHelper.getDashedPath( - path, - isStartRange, - isEndRange, - monthView.selectionShape == - DateRangePickerSelectionShape.rectangle), - monthView._selectionPainter); - } } void _addHoveringEffect(Canvas canvas, _IMonthView monthView, double xPosition, @@ -4639,7 +4741,8 @@ TextStyle _updateTextStyle( bool isEnableDate, bool isBlackedDate, bool isWeekEnd, - bool isSpecialDate) { + bool isSpecialDate, + bool isDisabledDate) { final TextStyle currentDatesTextStyle = monthView.cellStyle.textStyle as TextStyle? ?? monthView.datePickerTheme.activeDatesTextStyle; @@ -4655,7 +4758,7 @@ TextStyle _updateTextStyle( monthView.datePickerTheme.specialDatesTextStyle; } - if (!isEnableDate) { + if (!isEnableDate || isDisabledDate) { return monthView.cellStyle.disabledDatesTextStyle as TextStyle? ?? monthView.datePickerTheme.disabledDatesTextStyle; } @@ -4692,7 +4795,8 @@ Decoration? _updateDecoration( bool isBlackedDate, dynamic date, bool isWeekEnd, - bool isSpecialDate) { + bool isSpecialDate, + bool isDisabledDate) { final Decoration? dateDecoration = monthView.cellStyle.cellDecoration as Decoration?; @@ -4704,7 +4808,7 @@ Decoration? _updateDecoration( return monthView.cellStyle.specialDatesDecoration as Decoration?; } - if (!isEnableDate) { + if (!isEnableDate || isDisabledDate) { return monthView.cellStyle.disabledDatesDecoration as Decoration?; } diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart index 3f68c552b..f17cdebc3 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/picker_helper.dart @@ -6,6 +6,7 @@ import 'package:syncfusion_flutter_core/localizations.dart'; import 'date_picker_manager.dart'; import 'hijri_date_picker_manager.dart'; +// ignore: avoid_classes_with_only_static_members /// Holds the static helper methods of the date picker. class DateRangePickerHelper { /// Return the index value based on RTL. @@ -930,15 +931,15 @@ class DateRangePickerHelper { } } -/// +/// Holds the hovering data details class HoveringDetails { - /// + /// Create instance of hovering details. HoveringDetails(this.hoveringRange, this.offset); - /// + /// Holds the current hovering range for extendable range selection final dynamic hoveringRange; - /// + /// Current hovering offset. final Offset? offset; } diff --git a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart index b2df8236b..4f611f49a 100644 --- a/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart +++ b/packages/syncfusion_flutter_datepicker/lib/src/date_picker/year_view.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -48,7 +46,8 @@ class YearView extends StatefulWidget { this.localizations, this.navigationDirection, this.width, - this.height); + this.height, + this.disableDatesCollection); /// Defines the year cell style. final dynamic cellStyle; @@ -155,6 +154,9 @@ class YearView extends StatefulWidget { /// Defines the year view maximum row count. static const int maxRowCount = 4; + /// Holds the list of dates for selectable day predicate. + final List? disableDatesCollection; + @override _YearViewState createState() => _YearViewState(); } @@ -332,6 +334,7 @@ class _YearViewState extends State { widget.isHijri, widget.localizations, widget.navigationDirection, + widget.disableDatesCollection, widgets: _children); } case DateRangePickerSelectionMode.multiple: @@ -367,6 +370,7 @@ class _YearViewState extends State { widget.isHijri, widget.localizations, widget.navigationDirection, + widget.disableDatesCollection, widgets: _children); } case DateRangePickerSelectionMode.range: @@ -402,6 +406,7 @@ class _YearViewState extends State { widget.isHijri, widget.localizations, widget.navigationDirection, + widget.disableDatesCollection, widgets: _children); } case DateRangePickerSelectionMode.extendableRange: @@ -437,6 +442,7 @@ class _YearViewState extends State { widget.isHijri, widget.localizations, widget.navigationDirection, + widget.disableDatesCollection, widgets: _children); } case DateRangePickerSelectionMode.multiRange: @@ -472,6 +478,7 @@ class _YearViewState extends State { widget.isHijri, widget.localizations, widget.navigationDirection, + widget.disableDatesCollection, widgets: _children); } } @@ -591,6 +598,7 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -678,6 +686,8 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Specifies the localizations. final SfLocalizations localizations; + final List? disableDatesCollection; + @override _SingleSelectionRenderObject createRenderObject(BuildContext context) { return _SingleSelectionRenderObject( @@ -709,7 +719,8 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { isHijri, navigationDirection, localizations, - selectedDate); + selectedDate, + disableDatesCollection); } @override @@ -744,7 +755,8 @@ class _SingleSelectionRenderWidget extends MultiChildRenderObjectWidget { ..navigationDirection = navigationDirection ..monthFormat = monthFormat ..locale = locale - ..view = view; + ..view = view + ..disableDatesCollection = disableDatesCollection; } } @@ -780,6 +792,7 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -867,6 +880,8 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Specifies the localizations. final SfLocalizations localizations; + final List? disableDatesCollection; + @override _MultipleSelectionRenderObject createRenderObject(BuildContext context) { return _MultipleSelectionRenderObject( @@ -898,7 +913,8 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { isHijri, navigationDirection, localizations, - selectedDates); + selectedDates, + disableDatesCollection); } @override @@ -933,7 +949,8 @@ class _MultiSelectionRenderWidget extends MultiChildRenderObjectWidget { ..navigationDirection = navigationDirection ..monthFormat = monthFormat ..locale = locale - ..view = view; + ..view = view + ..disableDatesCollection = disableDatesCollection; } } @@ -969,6 +986,7 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -1056,6 +1074,8 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Specifies the localizations. final SfLocalizations localizations; + final List? disableDatesCollection; + @override _RangeSelectionRenderObject createRenderObject(BuildContext context) { return _RangeSelectionRenderObject( @@ -1087,7 +1107,8 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { isHijri, navigationDirection, localizations, - selectedRange); + selectedRange, + disableDatesCollection); } @override @@ -1122,7 +1143,8 @@ class _RangeSelectionRenderWidget extends MultiChildRenderObjectWidget { ..navigationDirection = navigationDirection ..monthFormat = monthFormat ..locale = locale - ..view = view; + ..view = view + ..disableDatesCollection = disableDatesCollection; } } @@ -1159,6 +1181,7 @@ class _ExtendableRangeSelectionRenderWidget this.isHijri, this.localizations, this.navigationDirection, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -1246,6 +1269,8 @@ class _ExtendableRangeSelectionRenderWidget /// Specifies the localizations. final SfLocalizations localizations; + final List? disableDatesCollection; + @override _ExtendableRangeSelectionRenderObject createRenderObject( BuildContext context) { @@ -1278,7 +1303,8 @@ class _ExtendableRangeSelectionRenderWidget isHijri, navigationDirection, localizations, - selectedRange); + selectedRange, + disableDatesCollection); } @override @@ -1313,7 +1339,8 @@ class _ExtendableRangeSelectionRenderWidget ..navigationDirection = navigationDirection ..monthFormat = monthFormat ..locale = locale - ..view = view; + ..view = view + ..disableDatesCollection = disableDatesCollection; } } @@ -1349,6 +1376,7 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { this.isHijri, this.localizations, this.navigationDirection, + this.disableDatesCollection, {required List widgets}) : super(children: widgets); @@ -1437,6 +1465,8 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { /// Specifies the localizations. final SfLocalizations localizations; + final List? disableDatesCollection; + @override _MultiRangeSelectionRenderObject createRenderObject(BuildContext context) { return _MultiRangeSelectionRenderObject( @@ -1468,7 +1498,8 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { isHijri, navigationDirection, localizations, - selectedRanges); + selectedRanges, + disableDatesCollection); } @override @@ -1503,7 +1534,8 @@ class _MultiRangeSelectionRenderWidget extends MultiChildRenderObjectWidget { ..monthFormat = monthFormat ..locale = locale ..navigationDirection = navigationDirection - ..view = view; + ..view = view + ..disableDatesCollection = disableDatesCollection; } } @@ -1539,7 +1571,8 @@ abstract class _IYearViewRenderObject extends RenderBox this._view, this._isHijri, this._navigationDirection, - this.localizations); + this.localizations, + this._disableDatesCollection); DateRangePickerNavigationDirection _navigationDirection; @@ -1988,6 +2021,23 @@ abstract class _IYearViewRenderObject extends RenderBox } } + List? _disableDatesCollection; + + List? get disableDatesCollection => _disableDatesCollection; + + set disableDatesCollection(List? value) { + if (_disableDatesCollection == value) { + return; + } + + _disableDatesCollection = value; + if (childCount == 0) { + markNeedsPaint(); + } else { + markNeedsLayout(); + } + } + /// Specifies the localizations. SfLocalizations localizations; @@ -2146,6 +2196,9 @@ abstract class _IYearViewRenderObject extends RenderBox /// draw selection when the cell have custom widget. void drawCustomCellSelection(Canvas canvas, Rect rect, int index); + /// Check and return that the given date index is between range or not. + bool isBetweenRange(int index); + List _getSemanticsBuilder(Size size) { final List semanticsBuilder = []; @@ -2197,7 +2250,9 @@ abstract class _IYearViewRenderObject extends RenderBox } if (!DateRangePickerHelper.isBetweenMinMaxDateCell( - date, _minDate, _maxDate, _enablePastDates, _view, _isHijri)) { + date, _minDate, _maxDate, _enablePastDates, _view, _isHijri) || + DateRangePickerHelper.isDateWithInVisibleDates( + visibleDates, disableDatesCollection, date)) { semanticsBuilder.add(CustomPainterSemantics( rect: Rect.fromLTWH(startXPosition + left, startYPosition + top, cellWidth, cellHeight), @@ -2409,7 +2464,6 @@ abstract class _IYearViewRenderObject extends RenderBox final List hoveringDetails = rangeSelectionYearView ._getSelectedRangePosition(currentIndex, hoveringIndex); - final bool isSelectedDate = hoveringDetails[0]; final bool isStartRange = hoveringDetails[1]; final bool isEndRange = hoveringDetails[2]; final bool isBetweenRange = hoveringDetails[3]; @@ -2420,99 +2474,38 @@ abstract class _IYearViewRenderObject extends RenderBox highlightPadding = maximumHighlight; } - final Rect rect = Rect.fromLTRB( - xPosition, - yPosition + centerYPosition - highlightPadding - textHalfHeight, - xPosition + cellWidth, - yPosition + centerYPosition + highlightPadding + textHalfHeight); + Rect rect = const Rect.fromLTRB(0, 0, 0, 0); + final double startYPosition = + yPosition + centerYPosition - highlightPadding - textHalfHeight; + final double endYPosition = + yPosition + centerYPosition + highlightPadding + textHalfHeight; + final double endXPosition = xPosition + cellWidth; final double cornerRadius = isBetweenRange ? 0 : (selectionShape == DateRangePickerSelectionShape.circle - ? rect.height / 2 + ? (endYPosition - startYPosition) / 2 : 3); - final double leftRadius = isStartRange || isSelectedDate ? cornerRadius : 0; - final double rightRadius = isEndRange || isSelectedDate ? cornerRadius : 0; - - final RRect roundRect = RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(leftRadius), - bottomLeft: Radius.circular(leftRadius), - bottomRight: Radius.circular(rightRadius), - topRight: Radius.circular(rightRadius)); _todayHighlightPaint.style = PaintingStyle.stroke; _todayHighlightPaint.strokeWidth = 1.0; _todayHighlightPaint.color = selectionColor != null ? selectionColor!.withOpacity(0.4) : datePickerTheme.selectionColor!.withOpacity(0.4); - final Path path = Path(); - if (isStartRange || isSelectedDate) { - switch (selectionShape) { - case DateRangePickerSelectionShape.circle: - path.addArc( - Rect.fromCenter( - center: Offset(roundRect.left + leftRadius, - roundRect.top + (roundRect.height / 2)), - width: leftRadius * 1.5, - height: roundRect.height), - pi / 2, - pi); - break; - case DateRangePickerSelectionShape.rectangle: - path.addRRect(RRect.fromLTRBR( - rect.left + selectionPadding, - rect.top, - rect.right - selectionPadding, - rect.bottom, - Radius.circular(leftRadius))); - } - canvas.drawPath( - DateRangePickerHelper.getDashedPath( - path, - isStartRange || isSelectedDate, - isEndRange, - selectionShape == DateRangePickerSelectionShape.rectangle), - _todayHighlightPaint); + if (isStartRange) { + rect = Rect.fromLTRB(endXPosition - cornerRadius, startYPosition, + endXPosition, endYPosition); } else if (isEndRange) { - switch (selectionShape) { - case DateRangePickerSelectionShape.circle: - path.addArc( - Rect.fromCenter( - center: Offset(roundRect.right - rightRadius, - roundRect.top + (roundRect.height / 2)), - width: rightRadius * 1.5, - height: roundRect.height), - -(pi / 2), - pi); - break; - case DateRangePickerSelectionShape.rectangle: - path.addRRect(RRect.fromLTRBR( - rect.left + selectionPadding, - rect.top, - rect.right - selectionPadding, - rect.bottom, - Radius.circular(rightRadius))); - } - canvas.drawPath( - DateRangePickerHelper.getDashedPath( - path, - isStartRange || isSelectedDate, - isEndRange, - selectionShape == DateRangePickerSelectionShape.rectangle), - _todayHighlightPaint); + rect = Rect.fromLTRB( + xPosition, startYPosition, xPosition + cornerRadius, endYPosition); + } else if (isBetweenRange) { + rect = Rect.fromLTRB( + xPosition, startYPosition, xPosition + cellWidth, endYPosition); } DateRangePickerHelper.drawDashedLine( - roundRect.left + leftRadius, - roundRect.top, - roundRect.right - rightRadius, - canvas, - _todayHighlightPaint); + rect.left, rect.top, rect.right, canvas, _todayHighlightPaint); DateRangePickerHelper.drawDashedLine( - roundRect.left + leftRadius, - roundRect.bottom, - roundRect.right - rightRadius, - canvas, - _todayHighlightPaint); + rect.left, rect.bottom, rect.right, canvas, _todayHighlightPaint); } void _drawTodayHighlight( @@ -2576,8 +2569,8 @@ abstract class _IYearViewRenderObject extends RenderBox } TextStyle _updateCellTextStyle(int j, bool isCurrentDate, bool isSelected, - bool isEnableDate, bool isActiveDate) { - if (!isEnableDate) { + bool isEnableDate, bool isActiveDate, bool isDisabledDate) { + if (!isEnableDate || isDisabledDate) { return cellStyle.disabledDatesTextStyle as TextStyle? ?? datePickerTheme.disabledCellTextStyle; } @@ -2599,9 +2592,9 @@ abstract class _IYearViewRenderObject extends RenderBox return cellStyle.textStyle as TextStyle? ?? datePickerTheme.cellTextStyle; } - Decoration? _updateCellDecoration( - int j, bool isCurrentDate, bool isEnableDate, bool isActiveDate) { - if (!isEnableDate) { + Decoration? _updateCellDecoration(int j, bool isCurrentDate, + bool isEnableDate, bool isActiveDate, bool isDisabledDate) { + if (!isEnableDate || isDisabledDate) { return cellStyle.disabledDatesDecoration as Decoration?; } @@ -2648,7 +2641,8 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { bool isHijri, DateRangePickerNavigationDirection navigationDirection, SfLocalizations localizations, - this._selectedDate) + this._selectedDate, + List? disableDatesCollection) : super( visibleDates, cellStyle, @@ -2677,7 +2671,8 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { view, isHijri, navigationDirection, - localizations); + localizations, + disableDatesCollection); dynamic _selectedDate; @@ -2763,6 +2758,11 @@ class _SingleSelectionRenderObject extends _IYearViewRenderObject { return selectedIndex; } + + @override + bool isBetweenRange(int index) { + return false; + } } class _MultipleSelectionRenderObject extends _IYearViewRenderObject { @@ -2795,7 +2795,8 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { bool isHijri, DateRangePickerNavigationDirection navigationDirection, SfLocalizations localizations, - this._selectedDates) + this._selectedDates, + List? disableDatesCollection) : super( visibleDates, cellStyle, @@ -2824,7 +2825,8 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { view, isHijri, navigationDirection, - localizations); + localizations, + disableDatesCollection); List? _selectedDates; @@ -2911,6 +2913,11 @@ class _MultipleSelectionRenderObject extends _IYearViewRenderObject { return selectedIndex; } + + @override + bool isBetweenRange(int index) { + return false; + } } class _RangeSelectionRenderObject extends _IYearViewRenderObject { @@ -2943,7 +2950,8 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { bool isHijri, DateRangePickerNavigationDirection navigationDirection, SfLocalizations localizations, - this._selectedRange) + this._selectedRange, + List? disableDatesCollection) : super( visibleDates, cellStyle, @@ -2972,7 +2980,8 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { view, isHijri, navigationDirection, - localizations); + localizations, + disableDatesCollection); dynamic _selectedRange; @@ -3148,6 +3157,16 @@ class _RangeSelectionRenderObject extends _IYearViewRenderObject { return _selectedIndex; } + + @override + bool isBetweenRange(int index) { + /// This method will return list of boolean indicating whether selected date + /// index is either it is start or end range or selected date and is between + /// range as a list, in this list the isBetweenRange boolean index is 3, + /// hence we used 3 here. + final List selectionDetails = _getSelectedRangePosition(index); + return selectionDetails[3]; + } } class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { @@ -3180,7 +3199,8 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { bool isHijri, DateRangePickerNavigationDirection navigationDirection, SfLocalizations localizations, - this._selectedRange) + this._selectedRange, + List? disableDatesCollection) : super( visibleDates, cellStyle, @@ -3209,7 +3229,8 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { view, isHijri, navigationDirection, - localizations); + localizations, + disableDatesCollection); dynamic _selectedRange; @@ -3387,6 +3408,17 @@ class _ExtendableRangeSelectionRenderObject extends _IYearViewRenderObject { return _selectedIndex; } + + @override + bool isBetweenRange(int index) { + /// This method will return list of boolean indicating whether selected date + /// index is either it is start or end range or selected date and is between + /// range as a list, in this list the isBetweenRange boolean index is 3, + /// hence we used 3 here. + final List selectionDetails = + _getSelectedRangePosition(index, _selectedIndex); + return selectionDetails[3]; + } } class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { @@ -3419,7 +3451,8 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { bool isHijri, DateRangePickerNavigationDirection navigationDirection, SfLocalizations localizations, - this._selectedRanges) + this._selectedRanges, + List? disableDatesCollection) : super( visibleDates, cellStyle, @@ -3448,7 +3481,8 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { view, isHijri, navigationDirection, - localizations); + localizations, + disableDatesCollection); List? _selectedRanges; @@ -3638,6 +3672,16 @@ class _MultiRangeSelectionRenderObject extends _IYearViewRenderObject { return selectedIndex; } + + @override + bool isBetweenRange(int index) { + /// This method will return list of boolean indicating whether selected date + /// index is either it is start or end range or selected date and is between + /// range as a list, in this list the isBetweenRange boolean index is 3, + /// hence we used 3 here. + final List selectionDetails = _getSelectedRangePosition(index); + return selectionDetails[3]; + } } /// Check the date cell placed in current view or not. @@ -3748,8 +3792,11 @@ void _drawYearCells( yearView.enablePastDates, view, yearView.isHijri); + final bool isDisabledDate = + DateRangePickerHelper.isDateWithInVisibleDates( + yearView.visibleDates, yearView.disableDatesCollection, date); - if (isSelected && isEnableDate) { + if (isSelected && isEnableDate && !isDisabledDate) { yearView.drawCustomCellSelection( canvas, Rect.fromLTRB(xPosition, yPosition, xPosition + cellWidth, @@ -3759,8 +3806,9 @@ void _drawYearCells( child!.paint(context, Offset(xPosition, yPosition)); - if (!isSelected && + if ((!isSelected || (yearView.isBetweenRange(currentIndex))) && isEnableDate && + !isDisabledDate && yearView.mouseHoverPosition.value != null && yearView.mouseHoverPosition.value!.offset != null) { if (xPosition <= yearView.mouseHoverPosition.value!.offset!.dx && @@ -3863,10 +3911,13 @@ void _drawYearCells( yearView.isHijri); final bool isActiveDate = _isCurrentViewDateCell( date, j, yearView.visibleDates, yearView.enableMultiView, view); - final TextStyle style = yearView._updateCellTextStyle( - j, isCurrentDate, isSelected, isEnableDate, isActiveDate); + final bool isDisabledDate = + DateRangePickerHelper.isDateWithInVisibleDates( + yearView.visibleDates, yearView.disableDatesCollection, date); + final TextStyle style = yearView._updateCellTextStyle(j, isCurrentDate, + isSelected, isEnableDate, isActiveDate, isDisabledDate); final Decoration? yearDecoration = yearView._updateCellDecoration( - j, isCurrentDate, isEnableDate, isActiveDate); + j, isCurrentDate, isEnableDate, isActiveDate, isDisabledDate); final TextSpan yearText = TextSpan( text: yearView._getCellText(date), @@ -3879,7 +3930,7 @@ void _drawYearCells( final double highlightPadding = yearView.selectionRadius == -1 ? 10 : yearView.selectionRadius; final double textHalfHeight = yearView._textPainter.height / 2; - if (isSelected && isEnableDate) { + if (isSelected && isEnableDate && !isDisabledDate) { yearView.drawSelection( canvas, cellWidth, @@ -3933,8 +3984,9 @@ void _drawYearCells( cellHeight); } - if (!isSelected && + if ((!isSelected || (yearView.isBetweenRange(currentIndex))) && isEnableDate && + !isDisabledDate && yearView.mouseHoverPosition.value != null && yearView.mouseHoverPosition.value!.offset != null) { yearView._addMouseHovering( diff --git a/packages/syncfusion_flutter_gauges/CHANGELOG.md b/packages/syncfusion_flutter_gauges/CHANGELOG.md index 0462cb6c0..be0e4c69c 100644 --- a/packages/syncfusion_flutter_gauges/CHANGELOG.md +++ b/packages/syncfusion_flutter_gauges/CHANGELOG.md @@ -1,3 +1,23 @@ +## Unreleased + +## Linear Gauge + +**Features** + +* Pointer drag behavior - Provides an option to change the dragging behavior of the marker pointers. The available drag behaviors are `free` and `constraint`. +* Added `onChangeStart` and `onChangeEnd` callbacks in the [`LinearMarkerPointer`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/LinearMarkerPointer-class.html) to notify the user about the marker pointer start and end actions. + +**Breaking changes** + +* The `onValueChanged` callback has been renamed to `onChanged` in the [`LinearMarkerPointer`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/LinearMarkerPointer-class.html) class. + +## Radial Gauge + +**Enhancements** + +* Now, the pointer can be freely dragged beyond the end-angle for circular radial gauge. For the non-circular radial gauge, the pointers can be dragged between the start and end angle only. +* The [`GaugeRange`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/GaugeRange/GaugeRange.html) will always apply in clockwise direction even the [`startValue`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/GaugeRange/startValue.html) is greater than the [`endValue`](https://pub.dev/documentation/syncfusion_flutter_gauges/latest/gauges/GaugeRange/endValue.html). + ## [18.3.35] - 10/01/2020 **Features** diff --git a/packages/syncfusion_flutter_gauges/README.md b/packages/syncfusion_flutter_gauges/README.md index 2faba11c7..bfcee8b04 100644 --- a/packages/syncfusion_flutter_gauges/README.md +++ b/packages/syncfusion_flutter_gauges/README.md @@ -46,6 +46,8 @@ The Linear Gauge is used to display data on a linear scale, while the Radial Gau ![linear gauge pointer interaction](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/interaction.gif) +* **Drag behavior** - Provides an option to change the dragging behavior of the marker pointers, when the linear gauge has multiple pointers. The available drag behaviors are `free` and `constraint`. + * **Animation** - All the gauge elements can be animated in a visually appealing way. Animate the gauge elements when they are loading, or when their values change. ![linear gauge animation](https://cdn.syncfusion.com/content/images/Flutter/pub_images/linear_gauge_images/animation.gif) @@ -221,4 +223,4 @@ The following screenshot illustrates the result of the above code sample. 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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 +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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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_gauges/lib/gauges.dart b/packages/syncfusion_flutter_gauges/lib/gauges.dart index 0744d8f4e..732647142 100644 --- a/packages/syncfusion_flutter_gauges/lib/gauges.dart +++ b/packages/syncfusion_flutter_gauges/lib/gauges.dart @@ -21,7 +21,7 @@ export './src/linear_gauge/pointers/linear_marker_pointer.dart'; export './src/linear_gauge/pointers/linear_shape_pointer.dart'; export './src/linear_gauge/pointers/linear_widget_pointer.dart'; export './src/linear_gauge/range/linear_gauge_range.dart'; -export './src/linear_gauge/utils/enum.dart'; +export './src/linear_gauge/utils/enum.dart' hide ConstrainedBy; export './src/linear_gauge/utils/linear_gauge_typedef.dart'; // export circular gauge library export './src/radial_gauge/annotation/gauge_annotation.dart'; diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_label.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_label.dart index 6253b1ad1..a54401f2f 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_label.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_label.dart @@ -8,7 +8,7 @@ class LinearAxisLabel { /// An axis value as a text /// - /// Defaults to [String.Empty]. + /// Defaults to [String.isEmpty]. /// /// ```dart /// 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 5b71e23f0..9a1d6f76a 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 @@ -847,9 +847,9 @@ class RenderLinearAxis extends RenderBox { final double tickSize = getTickSize() - tickMarginSize; final double axisSize = getAxisLineThickness(); final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition(tickPosition, isMirrored); + getEffectiveElementPosition(tickPosition, isMirrored); final LinearLabelPosition labelPlacement = - LinearGaugeHelper.getEffectiveLabelPosition(labelPosition, isMirrored); + getEffectiveLabelPosition(labelPosition, isMirrored); switch (position) { case LinearElementPosition.inside: @@ -896,9 +896,9 @@ class RenderLinearAxis extends RenderBox { final double labelSize = getEffectiveLabelSize() - labelMarginSize; final double axisSize = getAxisLineThickness(); final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition(tickPosition, isMirrored); + getEffectiveElementPosition(tickPosition, isMirrored); final LinearLabelPosition labelPlacement = - LinearGaugeHelper.getEffectiveLabelPosition(labelPosition, isMirrored); + getEffectiveLabelPosition(labelPosition, isMirrored); switch (position) { case LinearElementPosition.inside: @@ -1350,7 +1350,7 @@ class RenderLinearAxis extends RenderBox { void _drawMinorTicks(double minorTickLeftPosition, double top, int majorTickIndex, Canvas canvas) { final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition(tickPosition, isMirrored); + getEffectiveElementPosition(tickPosition, isMirrored); if (_isHorizontalOrientation) { if (position == LinearElementPosition.outside) { @@ -1537,14 +1537,14 @@ class RenderLinearAxis extends RenderBox { _axisLineRect, Radius.circular(thickness))); break; case LinearEdgeStyle.startCurve: - _path.addRRect(LinearGaugeHelper.getStartCurve( + _path.addRRect(getStartCurve( isHorizontal: _isHorizontalOrientation, isAxisInversed: isAxisInversed, rect: _axisLineRect, radius: thickness / 2)); break; case LinearEdgeStyle.endCurve: - _path.addRRect(LinearGaugeHelper.getEndCurve( + _path.addRRect(getEndCurve( isHorizontal: _isHorizontalOrientation, isAxisInversed: isAxisInversed, rect: _axisLineRect, diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_track_style.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_track_style.dart index f6d662fe3..c7c572ca4 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_track_style.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_axis_track_style.dart @@ -58,7 +58,7 @@ class LinearAxisTrackStyle { /// /// SfLinearGauge( /// axisLineStyle: LinearAxisLineStyle( - /// borderColor: Colors.Brown ) + /// borderColor: Colors.brown ) /// ) /// ``` final Color? borderColor; @@ -89,7 +89,7 @@ class LinearAxisTrackStyle { /// /// SfLinearGauge( /// axisLineStyle: LinearAxisLineStyle( - /// edgeStyle: LinearEdgeStyle.endCurce) + /// edgeStyle: LinearEdgeStyle.endCurve) /// ) /// ``` /// @@ -111,7 +111,7 @@ class LinearAxisTrackStyle { /// Colors.green, /// ], stops: [ /// 0.3, - /// 0.5 + /// 0.5, /// 0.8 ]),)) /// ``` /// diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_tick_style.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_tick_style.dart index 6f54398dd..e14e0fa7f 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_tick_style.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/axis/linear_tick_style.dart @@ -10,7 +10,7 @@ class LinearTickStyle { /// Specifies the length of major and minor ticks. /// - /// Defaults to 8.0 for [majorTicks] and 4.0 for [minorTicks]. + /// Defaults to 8.0 for `majorTicks `and 4.0 for `minorTicks`. /// /// This snippet shows how to set tick length. /// @@ -52,9 +52,9 @@ class LinearTickStyle { /// ```dart /// /// SfLinearGauge(majorTickStyle: LinearTickStyle( - /// color: Colors.Blue) + /// color: Colors.blue), /// minorTickStyle: LinearTickStyle( - /// color: Colors.Green,) + /// color: Colors.green), /// ) /// ``` /// diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge.dart index 6aad36c09..0c21a2851 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge.dart @@ -77,14 +77,14 @@ class SfLinearGauge extends StatefulWidget { ///Customizes each bar pointer by adding it to the [barPointers] collection. /// - /// Also refer [LinearGaugePointer] + /// Also refer [LinearMarkerPointer] /// final List? barPointers; /// Add a list of gauge shape and widget pointer to the linear gauge and customize - /// each pointer by adding it to the [pointers] collection. + /// each pointer by adding it to the [markerPointers] collection. /// - /// Also refer [LinearGaugePointer] + /// Also refer [LinearMarkerPointer] /// final List? markerPointers; @@ -599,8 +599,7 @@ class _SfLinearGaugeState extends State final Animation animation = Tween(begin: 0, end: 1).animate( CurvedAnimation( parent: pointerController, - curve: Interval(0, 1, - curve: LinearGaugeHelper.getCurveAnimation(animationType)))); + curve: Interval(0, 1, curve: getCurveAnimation(animationType)))); _pointerAnimations.add(animation); _pointerAnimationControllers.add(pointerController); diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_render_widget.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_render_widget.dart index a03c7a89d..321f80d0b 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_render_widget.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/gauge/linear_gauge_render_widget.dart @@ -7,13 +7,13 @@ import 'package:flutter/gestures.dart' GestureArenaTeam, HitTestTarget, HorizontalDragGestureRecognizer, - TapGestureRecognizer, VerticalDragGestureRecognizer; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import '../../linear_gauge/axis/linear_axis_renderer.dart'; import '../../linear_gauge/pointers/linear_bar_renderer.dart'; +import '../../linear_gauge/pointers/linear_marker_pointer.dart'; import '../../linear_gauge/pointers/linear_shape_renderer.dart'; import '../../linear_gauge/pointers/linear_widget_renderer.dart'; import '../../linear_gauge/range/linear_gauge_range_renderer.dart'; @@ -114,28 +114,29 @@ class RenderLinearGauge extends RenderBox _barPointers = []; _shapePointers = []; _widgetPointers = []; - _markerPointers = []; + _markerPointers = []; _verticalDragGestureRecognizer = VerticalDragGestureRecognizer() ..team = _gestureArenaTeam ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd ..dragStartBehavior = DragStartBehavior.start; _horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() ..team = _gestureArenaTeam ..onStart = _handleDragStart ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd ..dragStartBehavior = DragStartBehavior.start; - - _tapGestureRecognizer = TapGestureRecognizer()..onTapDown = _handleTapDown; } final GestureArenaTeam _gestureArenaTeam; - late TapGestureRecognizer _tapGestureRecognizer; late VerticalDragGestureRecognizer _verticalDragGestureRecognizer; late HorizontalDragGestureRecognizer _horizontalDragGestureRecognizer; + double? _dragStartValue; + double _axisLineActualSize = 0, _axisTop = 0, _axisWidgetThickness = 0, @@ -157,12 +158,12 @@ class RenderLinearGauge extends RenderBox late List _shapePointers; late List _widgetPointers; late List _ranges; - late List _markerPointers; + late List _markerPointers; late BoxConstraints _parentConstraints; late BoxConstraints _childConstraints; - late dynamic _markerRenderObject; + late RenderLinearPointerBase _markerRenderObject; /// Gets the axis assigned to [_RenderLinearGaugeRenderObject]. RenderLinearAxis? get axis => _axis; @@ -271,11 +272,9 @@ class RenderLinearGauge extends RenderBox final double tickSize = axis!.getTickSize(); final double axisLineSize = axis!.getAxisLineThickness(); final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition( - axis!.tickPosition, axis!.isMirrored); + getEffectiveElementPosition(axis!.tickPosition, axis!.isMirrored); final LinearLabelPosition labelPosition = - LinearGaugeHelper.getEffectiveLabelPosition( - axis!.labelPosition, axis!.isMirrored); + getEffectiveLabelPosition(axis!.labelPosition, axis!.isMirrored); final bool isInsideLabel = labelPosition == LinearLabelPosition.inside; late double _insideElementSize; @@ -330,11 +329,9 @@ class RenderLinearGauge extends RenderBox final double tickSize = axis!.getTickSize(); final double axisSize = axis!.getAxisLineThickness(); final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition( - axis!.tickPosition, axis!.isMirrored); + getEffectiveElementPosition(axis!.tickPosition, axis!.isMirrored); final LinearLabelPosition labelPlacement = - LinearGaugeHelper.getEffectiveLabelPosition( - axis!.labelPosition, axis!.isMirrored); + getEffectiveLabelPosition(axis!.labelPosition, axis!.isMirrored); final bool isInsideLabel = labelPlacement == LinearLabelPosition.inside; switch (position) { @@ -418,8 +415,7 @@ class RenderLinearGauge extends RenderBox required double thickness, required double offset}) { final LinearElementPosition pointerPosition = - LinearGaugeHelper.getEffectiveElementPosition( - position, axis!.isMirrored); + getEffectiveElementPosition(position, axis!.isMirrored); switch (pointerPosition) { case LinearElementPosition.inside: @@ -463,8 +459,7 @@ class RenderLinearGauge extends RenderBox final double markerSize = _isHorizontalOrientation ? size.height : size.width; final LinearElementPosition pointerPosition = - LinearGaugeHelper.getEffectiveElementPosition( - elementPosition, axis!.isMirrored); + getEffectiveElementPosition(elementPosition, axis!.isMirrored); switch (pointerPosition) { case LinearElementPosition.inside: return _outsideWidgetElementSize + @@ -504,13 +499,16 @@ class RenderLinearGauge extends RenderBox } } - void _updatePointerPositionOnDrag(dynamic pointer, + void _updatePointerPositionOnDrag(RenderLinearPointerBase pointer, {bool isDragCall = false}) { double animationValue = 1; if (!isDragCall) { if (pointer.pointerAnimation != null) { - animationValue = pointer.pointerAnimation.value as double; + animationValue = pointer.pointerAnimation!.value; + } + if (pointer.dragBehavior == LinearMarkerDragBehavior.constrained) { + _findDraggableRange(pointer); } } @@ -524,6 +522,31 @@ class RenderLinearGauge extends RenderBox offset: pointer.offset, size: Size(pointer.size.width, pointer.size.height))!; + /// _pointX calculation is depends on animation, so the constrained marker + /// goes beyond the reference marker even though it's constrained. To avoid + /// this, restricting the pixel values by a min and max draggable range. + if (pointer.dragBehavior == LinearMarkerDragBehavior.constrained && + pointer.constrainedBy != ConstrainedBy.none) { + final double minimum = axis!.valueToPixel(pointer.dragRangeMin!); + final double maximum = axis!.valueToPixel(pointer.dragRangeMax!); + if ((_isHorizontalOrientation && _isAxisInversed) || + (!_isHorizontalOrientation && !_isAxisInversed)) { + if (_pointX < minimum) { + _pointX = maximum; + } + if (_pointX > maximum) { + _pointX = minimum; + } + } else if ((_isHorizontalOrientation && !_isAxisInversed) || + (!_isHorizontalOrientation && _isAxisInversed)) { + if (_pointX < minimum) { + _pointX = minimum; + } + if (_pointX > maximum) { + _pointX = maximum; + } + } + } _positionChildElement(pointer); } @@ -547,9 +570,10 @@ class RenderLinearGauge extends RenderBox _pointerEndPadding = 0; _markerPointers.clear(); - _markerPointers = >[_shapePointers, _widgetPointers] - .expand((List x) => x) - .toList(); + _markerPointers = >[ + _shapePointers, + _widgetPointers + ].expand((List x) => x).toList(); final double width = constraints.hasBoundedWidth ? constraints.maxWidth @@ -628,8 +652,7 @@ class RenderLinearGauge extends RenderBox final double rangeThickness = _isHorizontalOrientation ? range.size.height : range.size.width; final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition( - range.position, range.isMirrored); + getEffectiveElementPosition(range.position, range.isMirrored); switch (position) { case LinearElementPosition.inside: @@ -737,8 +760,7 @@ class RenderLinearGauge extends RenderBox _pointX = axis!.valueToPixel(range.startValue).abs(); final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition( - range.position, range.isMirrored); + getEffectiveElementPosition(range.position, range.isMirrored); final double axisSize = axis!.showAxisTrack ? axis!.thickness : 0.0; switch (position) { @@ -780,8 +802,7 @@ class RenderLinearGauge extends RenderBox : barPointer.size.height; final LinearElementPosition position = - LinearGaugeHelper.getEffectiveElementPosition( - barPointer.position, axis!.isMirrored); + getEffectiveElementPosition(barPointer.position, axis!.isMirrored); switch (position) { case LinearElementPosition.inside: @@ -878,7 +899,7 @@ class RenderLinearGauge extends RenderBox if (child is RenderLinearShapePointer || child is RenderLinearWidgetPointer) { _isMarkerPointerInteraction = true; - _markerRenderObject = child; + _markerRenderObject = child as RenderLinearPointerBase; } else { _isMarkerPointerInteraction = false; } @@ -887,7 +908,7 @@ class RenderLinearGauge extends RenderBox return isHit; } - ///Get the value from position. + /// Get the value from position. double _getValueFromPosition(Offset localPosition) { final double actualAxisPadding = axis!.getChildPadding(); @@ -904,28 +925,119 @@ class RenderLinearGauge extends RenderBox return axis!.factorToValue(visualPosition.clamp(0.0, 1.0)); } - /// Handles the drag update callback. + void _applyConstraintBehavior( + RenderLinearPointerBase markerRenderObject, double currentValue) { + if (currentValue > _markerRenderObject.dragRangeMin! && + currentValue < _markerRenderObject.dragRangeMax!) { + _markerRenderObject.constrainedBy = ConstrainedBy.none; + } else { + if (currentValue <= _markerRenderObject.dragRangeMin!) { + currentValue = _markerRenderObject.dragRangeMin!; + _markerRenderObject.constrainedBy = ConstrainedBy.min; + } else if (currentValue >= _markerRenderObject.dragRangeMax!) { + currentValue = _markerRenderObject.dragRangeMax!; + _markerRenderObject.constrainedBy = ConstrainedBy.max; + } + } + markerRenderObject.onChanged!(currentValue); + } + + // This method for pull drag behavior for markers. + // ignore: unused_element + void _applyPullBehavior( + RenderLinearPointerBase markerRenderObject, double currentValue) { + for (final RenderLinearPointerBase markerPointer in _markerPointers) { + if (markerPointer != markerRenderObject) { + if (currentValue < _dragStartValue!) { + if (markerPointer.value > currentValue && + markerPointer.value < _dragStartValue!) { + markerPointer.onChanged?.call(currentValue); + } + } else if (currentValue > _dragStartValue!) { + if (markerPointer.value < currentValue && + markerPointer.value > _dragStartValue!) { + markerPointer.onChanged?.call(currentValue); + } + } + } + markerRenderObject.onChanged!(currentValue); + } + } + void _handleDragUpdate(DragUpdateDetails details) { - final double _currentValue = _getValueFromPosition(details.localPosition); - if (_markerRenderObject.onValueChanged != null && - _markerRenderObject.value != _currentValue) { - _markerRenderObject.oldValue = _currentValue; - _markerRenderObject.onValueChanged(_currentValue); + final double currentValue = _getValueFromPosition(details.localPosition); + if (_markerRenderObject.onChanged != null && + _markerRenderObject.value != currentValue) { + _markerRenderObject.oldValue = currentValue; + + switch (_markerRenderObject.dragBehavior) { + case LinearMarkerDragBehavior.free: + _markerRenderObject.onChanged!(currentValue); + break; + case LinearMarkerDragBehavior.constrained: + _applyConstraintBehavior(_markerRenderObject, currentValue); + break; + } _updatePointerPositionOnDrag(_markerRenderObject, isDragCall: true); - markNeedsPaint(); } } - void _handleDragStart(DragStartDetails details) {} + void _findDraggableRange(RenderLinearPointerBase pointer) { + pointer.dragRangeMin = pointer.constrainedBy == ConstrainedBy.min + ? pointer.value + : axis!.minimum; + pointer.dragRangeMax = pointer.constrainedBy == ConstrainedBy.max + ? pointer.value + : axis!.maximum; + for (int i = 0; i < _markerPointers.length; i++) { + final double currentValue = _markerPointers[i].value; + if (pointer.constrainedBy != ConstrainedBy.min && + currentValue < pointer.value) { + if (pointer.dragRangeMin! < currentValue) { + pointer.dragRangeMin = currentValue; + } + } else if (pointer.constrainedBy != ConstrainedBy.max && + currentValue > pointer.value) { + if (pointer.dragRangeMax! > currentValue) { + pointer.dragRangeMax = currentValue; + } + } + } + } + + void _handleDragStart(DragStartDetails details) { + _markerRenderObject.onChangeStart?.call(_markerRenderObject.value); + _dragStartValue = _markerRenderObject.value; + if (_markerRenderObject.dragBehavior == + LinearMarkerDragBehavior.constrained) { + int count = 0; + for (final RenderLinearPointerBase markerPointer in _markerPointers) { + if (markerPointer.constrainedBy != ConstrainedBy.none) { + for (final RenderLinearPointerBase pointer in _markerPointers) { + if (markerPointer.value == pointer.value) { + count++; + } + } + } + if (count <= 1) { + markerPointer.constrainedBy = ConstrainedBy.none; + } + count = 0; + } + _findDraggableRange(_markerRenderObject); + } + } - void _handleTapDown(TapDownDetails details) {} + void _handleDragEnd(DragEndDetails details) { + _markerRenderObject.onChangeEnd?.call(_markerRenderObject.value); + _dragStartValue = null; + } @override void handleEvent(PointerEvent event, BoxHitTestEntry entry) { assert(debugHandleEvent(event, entry)); if (event is PointerDownEvent && _isMarkerPointerInteraction) { _restrictHitTestPointerChange = true; - _tapGestureRecognizer.addPointer(event); _horizontalDragGestureRecognizer.addPointer(event); _verticalDragGestureRecognizer.addPointer(event); } else if (event is PointerUpEvent || event is PointerCancelEvent) { 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 df25309e9..33f664a1d 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 @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import '../../linear_gauge/gauge/linear_gauge.dart'; import '../../linear_gauge/gauge/linear_gauge_scope.dart'; import '../../linear_gauge/pointers/linear_bar_renderer.dart'; import '../../linear_gauge/utils/enum.dart'; @@ -29,7 +30,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { : offset = offset > 0 ? offset : 0, super(key: key, child: child); - /// Specifies the pointer value of [barPointer]. + /// Specifies the pointer value of [SfLinearGauge.barPointers]. /// This value must be between the min and max value of an axis track. /// /// Defaults to 0. @@ -99,7 +100,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// color: Colors.Red, + /// color: Colors.red, /// )]) /// ``` /// @@ -117,7 +118,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// borderColor: Colors.Blue, + /// borderColor: Colors.blue, /// )]) /// ``` /// @@ -150,7 +151,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// value: 40 + /// value: 40, /// thickness: 80, /// )]) /// ``` @@ -170,7 +171,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// value: 40 + /// value: 40, /// offset: 10, /// )]) /// ``` @@ -188,7 +189,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// value: 40 + /// value: 40, /// position: LinearElementPosition.outside, /// )]) /// ``` @@ -206,7 +207,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// )]) /// ``` @@ -225,7 +226,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// animationDuration: 4000 /// )]) @@ -244,7 +245,7 @@ class LinearBarPointer extends SingleChildRenderObjectWidget { /// SfLinearGauge ( /// barPointers: [ /// LinearBarPointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// animationType: LinearAnimationType.bounceOut /// )]) diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_renderer.dart index 5f9d39157..862bcbfcc 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_bar_renderer.dart @@ -378,14 +378,14 @@ class RenderLinearBarPointer extends RenderOpacity { RRect.fromRectAndRadius(_barRect, Radius.circular(thickness / 2))); break; case LinearEdgeStyle.startCurve: - _path.addRRect(LinearGaugeHelper.getStartCurve( + _path.addRRect(getStartCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: _barRect, radius: thickness / 2)); break; case LinearEdgeStyle.endCurve: - _path.addRRect(LinearGaugeHelper.getEndCurve( + _path.addRRect(getEndCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: _barRect, diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_marker_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_marker_pointer.dart index 8878f6d37..fbb8a9b05 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_marker_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_marker_pointer.dart @@ -1,20 +1,24 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import '../../../gauges.dart'; import '../../linear_gauge/utils/enum.dart'; -/// [LinearGaugePointer] has properties for customizing linear gauge pointers. +/// [LinearMarkerPointer] has properties for customizing linear gauge pointers. abstract class LinearMarkerPointer { /// Creates a pointer for linear axis with the default or required properties. LinearMarkerPointer( {required this.value, - this.onValueChanged, + this.onChanged, + this.onChangeStart, + this.onChangeEnd, this.enableAnimation = false, this.animationDuration = 1000, this.animationType = LinearAnimationType.ease, this.offset = 0.0, this.markerAlignment = LinearMarkerAlignment.center, this.position = LinearElementPosition.cross, + this.dragBehavior = LinearMarkerDragBehavior.free, this.onAnimationCompleted}); /// Specifies the linear axis value to place the pointer. @@ -39,7 +43,7 @@ abstract class LinearMarkerPointer { final LinearAnimationType animationType; /// Specifies the offset value which represent the gap from the linear axis. - /// Origin of this property will be relates to [LinearGaugePointer.position]. + /// Origin of this property will be relates to [LinearMarkerPointer.position]. /// /// Defaults to 0. final double offset; @@ -48,9 +52,14 @@ abstract class LinearMarkerPointer { /// final LinearElementPosition position; - /// Signature for callbacks that report that an underlying value has changed. - /// - final ValueChanged? onValueChanged; + /// Signature for a callback that report that a value was changed for a marker pointer. + final ValueChanged? onChanged; + + /// Signature for a callback that reports the value of a marker pointer has started to change. + final ValueChanged? onChangeStart; + + /// Signature for a callback that reports the value changes are ended for a marker pointer. + final ValueChanged? onChangeEnd; /// Specifies the marker alignment. /// @@ -60,4 +69,252 @@ abstract class LinearMarkerPointer { /// Specifies the animation completed callback. /// final VoidCallback? onAnimationCompleted; + + /// Specifies the drag behavior for the pointer. + /// + /// Defaults to [LinearMarkerDragBehavior.free]. + /// + /// ```dart + /// double _startMarkerValue = 30.0; + /// double _endMarkerValue = 60.0; + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: const Text('Drag behavior'), + /// ), + /// body: SfLinearGauge( + /// markerPointers: [ + /// LinearShapePointer( + /// value: _startMarkerValue, + /// dragBehavior: LinearMarkerDragBehavior.constrained, + /// onChanged: (double value) { + /// setState(() { + /// _startMarkerValue = value; + /// }); + /// }, + /// ), + /// LinearShapePointer( + /// value: _endMarkerValue, + /// onChanged: (double value) { + /// setState(() { + /// _endMarkerValue = value; + /// }); + /// }, + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + final LinearMarkerDragBehavior dragBehavior; +} + +/// Represents the render object base class for shape and widget pointer. +class RenderLinearPointerBase extends RenderProxyBox { + /// Creates a instance for [RenderLinearPointerBase] + RenderLinearPointerBase( + {required double value, + ValueChanged? onChanged, + this.onChangeStart, + this.onChangeEnd, + required double offset, + required LinearElementPosition position, + required LinearMarkerAlignment markerAlignment, + required bool isAxisInversed, + required bool isMirrored, + Animation? pointerAnimation, + VoidCallback? onAnimationCompleted, + required LinearMarkerDragBehavior dragBehavior, + this.animationController}) + : _value = value, + _onChanged = onChanged, + _offset = offset, + _position = position, + _dragBehavior = dragBehavior, + _markerAlignment = markerAlignment, + _pointerAnimation = pointerAnimation, + _isAxisInversed = isAxisInversed, + _isMirrored = isMirrored, + _onAnimationCompleted = onAnimationCompleted; + + /// Gets or sets the shape pointer old value. + double? oldValue; + + /// Gets or sets the animation controller assigned to [RenderLinearPointerBase]. + AnimationController? animationController; + + /// Sets the pointers constrained value. + ConstrainedBy constrainedBy = ConstrainedBy.none; + + /// Gets or sets the onChangeStart assigned to [RenderLinearPointerBase]. + ValueChanged? onChangeStart; + + /// Gets or sets the onChangeEnd assigned to [RenderLinearPointerBase]. + ValueChanged? onChangeEnd; + + /// Sets the minimum value to the pointer for dragging. + double? dragRangeMin; + + /// Sets the maximum value to the pointer for dragging. + double? dragRangeMax; + + /// Gets and sets the value assigned to [RenderLinearPointerBase]. + double get value => _value; + double _value; + set value(double value) { + if (value == _value) { + return; + } + if (animationController != null && animationController!.isAnimating) { + oldValue = _value; + animationController!.stop(); + } + _value = value; + if (animationController != null && oldValue != value) { + animationController!.forward(from: 0.01); + } + markNeedsLayout(); + } + + /// Gets and sets the onChanged assigned to [RenderLinearPointerBase]. + ValueChanged? get onChanged => _onChanged; + ValueChanged? _onChanged; + set onChanged(ValueChanged? value) { + if (value == _onChanged) { + return; + } + _onChanged = value; + } + + /// Gets and sets the offset assigned to [RenderLinearPointerBase]. + double get offset => _offset; + double _offset; + set offset(double value) { + if (value == _offset) { + return; + } + _offset = value; + markNeedsLayout(); + } + + /// Gets and sets the position assigned to [RenderLinearPointerBase]. + LinearElementPosition get position => _position; + LinearElementPosition _position; + set position(LinearElementPosition value) { + if (value == _position) { + return; + } + _position = value; + markNeedsLayout(); + } + + /// Gets and sets the animation assigned to [RenderLinearPointerBase]. + Animation? get pointerAnimation => _pointerAnimation; + Animation? _pointerAnimation; + set pointerAnimation(Animation? value) { + if (value == _pointerAnimation) { + return; + } + _removeAnimationListener(); + _pointerAnimation = value; + _addAnimationListener(); + } + + void _addAnimationListener() { + if (pointerAnimation == null) { + return; + } + pointerAnimation!.addListener(markNeedsPaint); + pointerAnimation!.addStatusListener(_animationStatusListener); + } + + /// Gets and sets the Marker Alignment assigned to [RenderLinearPointerBase]. + LinearMarkerAlignment? get markerAlignment => _markerAlignment; + LinearMarkerAlignment? _markerAlignment; + set markerAlignment(LinearMarkerAlignment? value) { + if (value == _markerAlignment) { + return; + } + _markerAlignment = value; + markNeedsLayout(); + } + + /// Gets and sets the isAxisInversed assigned to [RenderLinearPointerBase]. + bool get isAxisInversed => _isAxisInversed; + bool _isAxisInversed; + set isAxisInversed(bool value) { + if (value == _isAxisInversed) { + return; + } + + _isAxisInversed = value; + markNeedsLayout(); + } + + /// Gets and sets the isMirrored to [RenderLinearPointerBase]. + bool get isMirrored => _isMirrored; + bool _isMirrored; + set isMirrored(bool value) { + if (value == _isMirrored) { + return; + } + + _isMirrored = value; + markNeedsPaint(); + } + + void _removeAnimationListener() { + if (pointerAnimation == null) { + return; + } + pointerAnimation!.removeListener(markNeedsPaint); + pointerAnimation!.removeStatusListener(_animationStatusListener); + } + + void _animationStatusListener(AnimationStatus status) { + if (status == AnimationStatus.completed) { + if (onAnimationCompleted != null) { + onAnimationCompleted!(); + } + if (oldValue != value) { + oldValue = value; + } + } + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _addAnimationListener(); + } + + @override + void detach() { + _removeAnimationListener(); + super.detach(); + } + + /// Gets and sets the drag behavior assigned to [RenderLinearPointerBase]. + LinearMarkerDragBehavior get dragBehavior => _dragBehavior; + LinearMarkerDragBehavior _dragBehavior; + set dragBehavior(LinearMarkerDragBehavior value) { + if (value == _dragBehavior) { + return; + } + _dragBehavior = value; + } + + /// Gets and sets the animation completed callback. + VoidCallback? get onAnimationCompleted => _onAnimationCompleted; + VoidCallback? _onAnimationCompleted; + set onAnimationCompleted(VoidCallback? value) { + if (value == _onAnimationCompleted) { + return; + } + _onAnimationCompleted = value; + } } 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 17d36b702..6cf36751b 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 @@ -15,7 +15,9 @@ class LinearShapePointer extends LeafRenderObjectWidget const LinearShapePointer( {Key? key, required this.value, - this.onValueChanged, + this.onChanged, + this.onChangeStart, + this.onChangeEnd, this.enableAnimation = true, this.animationDuration = 1000, this.animationType = LinearAnimationType.ease, @@ -26,6 +28,7 @@ class LinearShapePointer extends LeafRenderObjectWidget this.markerAlignment = LinearMarkerAlignment.center, this.position = LinearElementPosition.outside, this.shapeType = LinearShapePointerType.invertedTriangle, + this.dragBehavior = LinearMarkerDragBehavior.free, this.color, this.borderColor, this.borderWidth = 0.0, @@ -34,7 +37,7 @@ class LinearShapePointer extends LeafRenderObjectWidget : offset = offset > 0 ? offset : 0, super(key: key); - /// Specifies the pointer value of [ShapePointer]. + /// Specifies the pointer value of [LinearShapePointer]. /// This value must be between the min and max value of an axis track. /// /// Defaults to 0. @@ -65,7 +68,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// )]) /// ``` @@ -85,7 +88,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// animationDuration: 4000 /// )]) @@ -105,7 +108,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// animationType: LinearAnimationType.linear /// )]) @@ -125,7 +128,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 + /// value: 50, /// width: 30 /// )]) /// ``` @@ -145,7 +148,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 + /// value: 50, /// margin: 10 /// )]) /// ``` @@ -164,7 +167,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 + /// value: 50, /// height: 30 /// )]) /// ``` @@ -182,7 +185,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 + /// value: 50, /// position: LinearElementPosition.inside /// )]) /// ``` @@ -190,25 +193,64 @@ class LinearShapePointer extends LeafRenderObjectWidget @override final LinearElementPosition position; - /// Signature for callbacks that report that an underlying value has changed. + /// Signature for a callback that report that a value was changed for a marker pointer. /// - /// This snippet shows how to call onvaluechange function in [SfLinearGauge]. + /// This snippet shows how to call onChanged function in [SfLinearGauge]. /// /// ```dart /// /// SfLinearGauge( /// markerPointers: [ /// LinearShapePointer( - /// value: _pointerValue - /// onValueChanged: (value) => - /// {setState(() => - /// {_pointerValue = value})}, + /// value: _pointerValue, + /// onChanged: (double value){ + /// setState((){ + /// _pointerValue = value; + /// }) + /// } /// )]) /// /// ``` + @override + final ValueChanged? onChanged; + + /// Signature for a callback that reports the value of a marker pointer has started to change. + /// + /// This snippet shows how to call onChangeStart callback in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearShapePointer( + /// value: _pointerValue, + /// onChangeStart: (double startValue) { + /// print('Start value $startValue'); + /// }, + ///)]) + /// + /// ``` + @override + final ValueChanged? onChangeStart; + + /// Signature for a callback that reports the value changes are ended for a marker pointer. + /// + /// This snippet shows how to call onChangeEnd callback in [SfLinearGauge]. /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearShapePointer( + /// value: _pointerValue, + /// onChangeEnd: (double endValue) { + /// print('End value $endValue'); + /// }, + ///)]) + /// + /// ``` @override - final ValueChanged? onValueChanged; + final ValueChanged? onChangeEnd; /// Specifies the built-in shape of shape marker pointer. /// @@ -221,7 +263,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 + /// value: 50, /// shapeType: LinearShapePointerType.triangle /// )]) /// ``` @@ -240,7 +282,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// color: Colors.Grey + /// color: Colors.grey /// )]) /// ``` /// @@ -258,7 +300,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// borderColor: Colors.Orange + /// borderColor: Colors.orange /// )]) /// ``` /// @@ -292,7 +334,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 + /// value: 50, /// elevation: 2 /// )]) /// ``` @@ -301,7 +343,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// Specifies elevation color for shape pointers with [elevation] /// - /// Defaults to [Colors.Black]. + /// Defaults to [Colors.black]. /// /// This snippet shows how to set elevation color for shape pointers. /// @@ -310,7 +352,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 + /// value: 50, /// elevationColor: Colors.grey[50] /// )]) /// ``` @@ -319,7 +361,7 @@ class LinearShapePointer extends LeafRenderObjectWidget /// Specifies the alignment for shape marker pointer. /// - /// Defaults to [MarkerAlignment.center]. + /// Defaults to [LinearMarkerAlignment.center]. /// /// This snippet shows how to set a shape to shape-pointer. /// @@ -328,8 +370,8 @@ class LinearShapePointer extends LeafRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearShapePointer( - /// value: 50 - /// markerAlignment: MarkerAlignment.end + /// value: 50, + /// markerAlignment: LinearMarkerAlignment.end /// )]) /// ``` /// @@ -354,6 +396,51 @@ class LinearShapePointer extends LeafRenderObjectWidget @override final VoidCallback? onAnimationCompleted; + /// Specifies the drag behavior for shape marker pointer. + /// + /// Defaults to [LinearMarkerDragBehavior.free]. + /// + /// This snippet shows how to set drag behavior for a shape-pointer. + /// + /// ```dart + /// double _startMarkerValue = 30.0; + /// double _endMarkerValue = 60.0; + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: const Text('Drag behavior'), + /// ), + /// body: SfLinearGauge( + /// markerPointers: [ + /// LinearShapePointer( + /// value: _startMarkerValue, + /// dragBehavior: LinearMarkerDragBehavior.constrained, + /// onChanged: (double value) { + /// setState(() { + /// _startMarkerValue = value; + /// }); + /// }, + /// ), + /// LinearShapePointer( + /// value: _endMarkerValue, + /// onChanged: (double value) { + /// setState(() { + /// _endMarkerValue = value; + /// }); + /// }, + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + @override + final LinearMarkerDragBehavior dragBehavior; + @override RenderObject createRenderObject(BuildContext context) { final LinearGaugeScope linearGaugeScope = LinearGaugeScope.of(context); @@ -361,7 +448,9 @@ class LinearShapePointer extends LeafRenderObjectWidget final bool isDarkTheme = theme.brightness == Brightness.dark; return RenderLinearShapePointer( value: value, - onValueChanged: onValueChanged, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, color: color ?? (isDarkTheme ? Colors.white70 : Colors.black54), borderColor: borderColor ?? (isDarkTheme ? Colors.white70 : Colors.black54), @@ -376,8 +465,10 @@ class LinearShapePointer extends LeafRenderObjectWidget elevationColor: elevationColor, orientation: linearGaugeScope.orientation, isMirrored: linearGaugeScope.isMirrored, + isAxisInversed: linearGaugeScope.isAxisInversed, markerAlignment: markerAlignment, animationController: linearGaugeScope.animationController, + dragBehavior: dragBehavior, onAnimationCompleted: onAnimationCompleted, pointerAnimation: linearGaugeScope.animation); } @@ -391,7 +482,9 @@ class LinearShapePointer extends LeafRenderObjectWidget renderObject ..value = value - ..onValueChanged = onValueChanged + ..onChanged = onChanged + ..onChangeStart = onChangeStart + ..onChangeEnd = onChangeEnd ..color = color ?? (isDarkTheme ? Colors.white70 : Colors.black54) ..borderColor = borderColor ?? (isDarkTheme ? Colors.white70 : Colors.black54) @@ -406,8 +499,10 @@ class LinearShapePointer extends LeafRenderObjectWidget ..elevationColor = elevationColor ..orientation = linearGaugeScope.orientation ..isMirrored = linearGaugeScope.isMirrored + ..isAxisInversed = linearGaugeScope.isAxisInversed ..markerAlignment = markerAlignment ..onAnimationCompleted = onAnimationCompleted + ..dragBehavior = dragBehavior ..animationController = linearGaugeScope.animationController ..pointerAnimation = linearGaugeScope.animation; diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_renderer.dart index 757bdf597..66754053d 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_shape_renderer.dart @@ -2,35 +2,38 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/core.dart' as core; import '../../linear_gauge/utils/enum.dart'; +import 'linear_marker_pointer.dart'; /// Represents the render object of shape pointer. -class RenderLinearShapePointer extends RenderBox { +class RenderLinearShapePointer extends RenderLinearPointerBase { /// Creates a instance for [RenderLinearShapePointer]. - RenderLinearShapePointer( - {required double value, - ValueChanged? onValueChanged, - required Color color, - required Color borderColor, - required double borderWidth, - required double width, - required double height, - required double offset, - required LinearElementPosition position, - required LinearShapePointerType shapeType, - required double elevation, - required LinearGaugeOrientation orientation, - required Color elevationColor, - required LinearMarkerAlignment markerAlignment, - required bool isMirrored, - Animation? pointerAnimation, - VoidCallback? onAnimationCompleted, - this.animationController}) - : _value = value, - _onValueChanged = onValueChanged, - _color = color, + RenderLinearShapePointer({ + required double value, + ValueChanged? onChanged, + ValueChanged? onChangeStart, + ValueChanged? onChangeEnd, + required Color color, + required Color borderColor, + required double borderWidth, + required double width, + required double height, + required double offset, + required LinearElementPosition position, + required LinearShapePointerType shapeType, + required double elevation, + required LinearGaugeOrientation orientation, + required Color elevationColor, + required LinearMarkerAlignment markerAlignment, + required bool isMirrored, + required bool isAxisInversed, + Animation? pointerAnimation, + VoidCallback? onAnimationCompleted, + required LinearMarkerDragBehavior dragBehavior, + AnimationController? animationController, + }) : _color = color, _borderColor = borderColor, _borderWidth = borderWidth, _shapeType = shapeType, @@ -39,12 +42,20 @@ class RenderLinearShapePointer extends RenderBox { _elevationColor = elevationColor, _width = width, _height = height, - _offset = offset, - _position = position, - _markerAlignment = markerAlignment, - _isMirrored = isMirrored, - _onAnimationCompleted = onAnimationCompleted, - _pointerAnimation = pointerAnimation { + super( + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + offset: offset, + position: position, + dragBehavior: dragBehavior, + markerAlignment: markerAlignment, + isMirrored: isMirrored, + isAxisInversed: isAxisInversed, + pointerAnimation: pointerAnimation, + animationController: animationController, + onAnimationCompleted: onAnimationCompleted) { _shapePaint = Paint(); _borderPaint = Paint(); } @@ -54,12 +65,6 @@ class RenderLinearShapePointer extends RenderBox { Rect _shapeRect = Rect.zero; - /// Shape Pointer old value. - double? oldValue; - - /// Gets or Sets the animation controller assigned to [RenderLinearShapePointer]. - AnimationController? animationController; - /// Gets the orientation to [RenderLinearShapePointer]. LinearGaugeOrientation? get orientation => _orientation; LinearGaugeOrientation? _orientation; @@ -89,43 +94,6 @@ class RenderLinearShapePointer extends RenderBox { markNeedsPaint(); } - /// Gets the value assigned to [RenderLinearShapePointer]. - double get value => _value; - double _value; - - /// Sets the value for [RenderLinearShapePointer]. - set value(double value) { - if (value == _value) { - return; - } - - if (animationController != null && animationController!.isAnimating) { - oldValue = _value; - animationController!.stop(); - } - - _value = value; - - if (animationController != null && oldValue != value) { - animationController!.forward(from: 0.01); - } - - markNeedsLayout(); - } - - /// Gets the onValueChanged assigned to [RenderLinearShapePointer]. - ValueChanged? get onValueChanged => _onValueChanged; - ValueChanged? _onValueChanged; - - /// Sets the onValueChanged callback for [RenderLinearShapePointer]. - set onValueChanged(ValueChanged? value) { - if (value == _onValueChanged) { - return; - } - - _onValueChanged = value; - } - /// Gets the width assigned to [RenderLinearShapePointer]. double get width => _width; double _width; @@ -196,34 +164,6 @@ class RenderLinearShapePointer extends RenderBox { markNeedsPaint(); } - /// Gets the offset assigned to [RenderLinearShapePointer]. - double get offset => _offset; - double _offset; - - /// Sets the offset for [RenderLinearShapePointer]. - set offset(double value) { - if (value == _offset) { - return; - } - - _offset = value; - markNeedsLayout(); - } - - /// Gets the position assigned to [RenderLinearShapePointer]. - LinearElementPosition get position => _position; - LinearElementPosition _position; - - /// Sets the position for [RenderLinearShapePointer]. - set position(LinearElementPosition value) { - if (value == _position) { - return; - } - - _position = value; - markNeedsLayout(); - } - /// Gets the elevation assigned to [RenderLinearShapePointer]. double get elevation => _elevation; double _elevation; @@ -252,62 +192,6 @@ class RenderLinearShapePointer extends RenderBox { markNeedsPaint(); } - /// Gets the animation assigned to [RenderLinearShapePointer]. - Animation? get pointerAnimation => _pointerAnimation; - Animation? _pointerAnimation; - - /// Sets the animation animation for [RenderLinearShapePointer]. - set pointerAnimation(Animation? value) { - if (value == _pointerAnimation) { - return; - } - - _removeAnimationListener(); - _pointerAnimation = value; - _addAnimationListener(); - } - - /// Gets the Marker Alignment assigned to [RenderLinearShapePointer]. - LinearMarkerAlignment? get markerAlignment => _markerAlignment; - LinearMarkerAlignment? _markerAlignment; - - /// Sets the Marker Alignment for [RenderLinearShapePointer]. - set markerAlignment(LinearMarkerAlignment? value) { - if (value == _markerAlignment) { - return; - } - - _markerAlignment = value; - markNeedsLayout(); - } - - /// Gets the animation completed callback. - VoidCallback? get onAnimationCompleted => _onAnimationCompleted; - VoidCallback? _onAnimationCompleted; - - /// Sets the animation completed callback. - set onAnimationCompleted(VoidCallback? value) { - if (value == _onAnimationCompleted) { - return; - } - - _onAnimationCompleted = value; - } - - /// Gets the isMirrored to [RenderLinearShapePointer]. - bool get isMirrored => _isMirrored; - bool _isMirrored; - - /// Sets the isMirrored for [RenderLinearShapePointer]. - set isMirrored(bool value) { - if (value == _isMirrored) { - return; - } - - _isMirrored = value; - markNeedsPaint(); - } - void _animationStatusListener(AnimationStatus status) { if (status == AnimationStatus.completed) { if (onAnimationCompleted != null) { @@ -388,35 +272,35 @@ class RenderLinearShapePointer extends RenderBox { } } - late ShapeMarkerType markerType; + late core.ShapeMarkerType markerType; switch (gaugeShapeType) { case LinearShapePointerType.circle: - markerType = ShapeMarkerType.circle; + markerType = core.ShapeMarkerType.circle; break; case LinearShapePointerType.rectangle: - markerType = ShapeMarkerType.rectangle; + markerType = core.ShapeMarkerType.rectangle; break; case LinearShapePointerType.triangle: - markerType = ShapeMarkerType.triangle; + markerType = core.ShapeMarkerType.triangle; if (orientation == LinearGaugeOrientation.vertical) { - markerType = ShapeMarkerType.verticalTriangle; + markerType = core.ShapeMarkerType.verticalTriangle; } break; case LinearShapePointerType.invertedTriangle: - markerType = ShapeMarkerType.invertedTriangle; + markerType = core.ShapeMarkerType.invertedTriangle; if (orientation == LinearGaugeOrientation.vertical) { - markerType = ShapeMarkerType.verticalInvertedTriangle; + markerType = core.ShapeMarkerType.verticalInvertedTriangle; } break; case LinearShapePointerType.diamond: - markerType = ShapeMarkerType.diamond; + markerType = core.ShapeMarkerType.diamond; break; default: break; } - ShapePainter.paint( + core.paint( canvas: canvas, rect: _shapeRect, elevation: elevation, diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_pointer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_pointer.dart index 282aaf644..92cf7efb1 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_pointer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_pointer.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import '../../linear_gauge/gauge/linear_gauge.dart'; import '../../linear_gauge/gauge/linear_gauge_scope.dart'; import '../../linear_gauge/pointers/linear_marker_pointer.dart'; import '../../linear_gauge/pointers/linear_widget_renderer.dart'; @@ -13,7 +14,9 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget const LinearWidgetPointer( {Key? key, required this.value, - this.onValueChanged, + this.onChanged, + this.onChangeStart, + this.onChangeEnd, this.enableAnimation = true, this.animationDuration = 1000, this.animationType = LinearAnimationType.ease, @@ -21,11 +24,12 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget double offset = 0.0, this.position = LinearElementPosition.cross, this.markerAlignment = LinearMarkerAlignment.center, + this.dragBehavior = LinearMarkerDragBehavior.free, required Widget child}) : offset = offset > 0 ? offset : 0, super(key: key, child: child); - /// Specifies the pointer value for [WidgetPointer]. + /// Specifies the pointer value for [LinearWidgetPointer]. /// This value must be between the min and max value of an axis track. /// /// Defaults to 0. @@ -57,7 +61,7 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearWidgetPointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// )]) /// ``` @@ -77,7 +81,7 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearWidgetPointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// animationDuration: 4000 /// )]) @@ -98,7 +102,7 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearWidgetPointer( - /// value: 20 + /// value: 20, /// enableAnimation: true, /// animationType: LinearAnimationType.linear /// )]) @@ -121,7 +125,7 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget /// SfLinearGauge ( /// markerPointers: [ /// LinearWidgetPointer( - /// value: 50 + /// value: 50, /// margin: 15 /// )]) /// ``` @@ -149,29 +153,80 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget @override final LinearElementPosition position; - /// Signature for callbacks that report that an underlying value has changed. + /// Signature for a callback that report that a value was changed for a marker pointer. + /// + /// This snippet shows how to call onChanged function in[SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: _pointerValue, + /// child: Container( + /// height: 20, + /// width: 20, + /// color: Colors.red), + /// onChanged: (double value){ + /// setState((){ + /// _pointerValue = value; + /// }) + /// } + /// )]) + /// + /// ``` + @override + final ValueChanged? onChanged; + + /// Signature for a callback that reports the value of a marker pointer has started to change. /// - /// This snippet shows how to call onvaluechange function in[SfLinearGauge]. + /// This snippet shows how to call onChangeStart function in [SfLinearGauge]. /// /// ```dart /// /// SfLinearGauge( /// markerPointers: [ /// LinearWidgetPointer( - /// value: _pointerValue - /// onValueChanged: (value) => - /// {setState(() => - /// {_pointerValue = value})}, + /// value: _pointerValue, + /// child: Container( + /// height: 20, + /// width: 20, + /// color: Colors.red), + /// onChangeStart: (double startValue) { + /// print('Start value $startValue'); + /// }, /// )]) /// /// ``` + @override + final ValueChanged? onChangeStart; + + /// Signature for a callback that reports the value changes are ended for a marker pointer. /// + /// This snippet shows how to call onChangeEnd function in [SfLinearGauge]. + /// + /// ```dart + /// + /// SfLinearGauge( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: _pointerValue, + /// child: Container( + /// height: 20, + /// width: 20, + /// color: Colors.red), + /// onChangeEnd: (double endValue) { + /// print('End value $endValue'); + /// }, + /// )]) + /// + /// ``` @override - final ValueChanged? onValueChanged; + final ValueChanged? onChangeEnd; /// Specifies the marker alignment. /// - /// Defaults to [MarkerAlignment.center]. + /// Defaults to [LinearMarkerAlignment.center]. /// /// This snippet shows how to set marker alignment in[SfLinearGauge]. /// @@ -181,7 +236,7 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget /// markerPointers: [ /// LinearWidgetPointer( /// value: 30, - /// markerAlignment: MarkerAlignment.end + /// markerAlignment: LinearMarkerAlignment.end /// )]) /// /// ``` @@ -207,18 +262,78 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget @override final VoidCallback? onAnimationCompleted; + /// Specifies the drag behavior for widget marker pointer. + /// + /// Defaults to [LinearMarkerDragBehavior.free]. + /// + /// This snippet shows how to set drag behavior for widget pointer. + /// + /// ```dart + /// double _startMarkerValue = 30.0; + /// double _endMarkerValue = 60.0; + /// + /// @override + /// Widget build(BuildContext context) { + /// return MaterialApp( + /// home: Scaffold( + /// appBar: AppBar( + /// title: const Text('Drag behavior'), + /// ), + /// body: SfLinearGauge( + /// markerPointers: [ + /// LinearWidgetPointer( + /// value: _startMarkerValue, + /// dragBehavior: LinearMarkerDragBehavior.constrained, + /// onChanged: (double value) { + /// setState(() { + /// _startMarkerValue = value; + /// }); + /// }, + /// child: Container( + /// height: 20.0, + /// width: 20.0, + /// color: Colors.red, + /// ), + /// ), + /// LinearWidgetPointer( + /// value: _endMarkerValue, + /// onChanged: (double value) { + /// setState(() { + /// _endMarkerValue = value; + /// }); + /// }, + /// child: Container( + /// height: 20.0, + /// width: 20.0, + /// color: Colors.red, + /// ), + /// ) + /// ], + /// ), + /// ), + /// ); + /// } + /// ``` + @override + final LinearMarkerDragBehavior dragBehavior; + @override RenderObject createRenderObject(BuildContext context) { final LinearGaugeScope scope = LinearGaugeScope.of(context); return RenderLinearWidgetPointer( value: value, - onValueChanged: onValueChanged, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, offset: offset, position: position, markerAlignment: markerAlignment, animationController: scope.animationController, onAnimationCompleted: onAnimationCompleted, - pointerAnimation: scope.animation); + pointerAnimation: scope.animation, + isAxisInversed: scope.isAxisInversed, + isMirrored: scope.isMirrored, + dragBehavior: dragBehavior); } @override @@ -228,11 +343,16 @@ class LinearWidgetPointer extends SingleChildRenderObjectWidget renderObject ..value = value - ..onValueChanged = onValueChanged + ..onChanged = onChanged + ..onChangeStart = onChangeStart + ..onChangeEnd = onChangeEnd ..offset = offset ..position = position ..markerAlignment = markerAlignment + ..dragBehavior = dragBehavior ..onAnimationCompleted = onAnimationCompleted + ..isAxisInversed = scope.isAxisInversed + ..isMirrored = scope.isMirrored ..animationController = scope.animationController ..pointerAnimation = scope.animation; diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_renderer.dart index 0d378ed3d..81d4fec7a 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/pointers/linear_widget_renderer.dart @@ -2,182 +2,42 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:syncfusion_flutter_gauges/src/linear_gauge/pointers/linear_marker_pointer.dart'; import '../../linear_gauge/utils/enum.dart'; /// Represents the render object of shape pointer. -class RenderLinearWidgetPointer extends RenderProxyBox { +class RenderLinearWidgetPointer extends RenderLinearPointerBase { /// Creates a instance for [RenderLinearWidgetPointer]. RenderLinearWidgetPointer( {required double value, - ValueChanged? onValueChanged, + ValueChanged? onChanged, + ValueChanged? onChangeStart, + ValueChanged? onChangeEnd, required double offset, required LinearElementPosition position, required LinearMarkerAlignment markerAlignment, + required bool isAxisInversed, + required bool isMirrored, Animation? pointerAnimation, VoidCallback? onAnimationCompleted, - this.animationController}) - : _value = value, - _onValueChanged = onValueChanged, - _offset = offset, - _position = position, - _markerAlignment = markerAlignment, - _pointerAnimation = pointerAnimation, - _onAnimationCompleted = onAnimationCompleted; - - /// Gets or sets the shape pointer old value. - double? oldValue; - - /// Gets or sets the animation controller assigned to [RenderLinearShapePointer]. - AnimationController? animationController; - - /// Gets the value assigned to [RenderLinearWidgetPointer]. - double get value => _value; - double _value; - - /// Sets the value for [RenderLinearWidgetPointer]. - set value(double value) { - if (value == _value) { - return; - } - - if (animationController != null && animationController!.isAnimating) { - oldValue = _value; - animationController!.stop(); - } - - _value = value; - - if (animationController != null && oldValue != value) { - animationController!.forward(from: 0.01); - } - - markNeedsLayout(); - } - - /// Gets the onValueChanged assigned to [RenderLinearWidgetPointer]. - ValueChanged? get onValueChanged => _onValueChanged; - ValueChanged? _onValueChanged; - - /// Sets the onValueChanged callback for [RenderLinearWidgetPointer]. - set onValueChanged(ValueChanged? value) { - if (value == _onValueChanged) { - return; - } - - _onValueChanged = value; - } - - /// Gets the offset assigned to [RenderLinearWidgetPointer]. - double get offset => _offset; - double _offset; - - /// Sets the offset for [RenderLinearWidgetPointer]. - set offset(double value) { - if (value == _offset) { - return; - } - - _offset = value; - markNeedsLayout(); - } - - /// Gets the position assigned to [RenderLinearWidgetPointer]. - LinearElementPosition get position => _position; - LinearElementPosition _position; - - /// Sets the position for [RenderLinearWidgetPointer]. - set position(LinearElementPosition value) { - if (value == _position) { - return; - } - - _position = value; - markNeedsLayout(); - } - - /// Gets the animation assigned to [RenderLinearWidgetPointer]. - Animation? get pointerAnimation => _pointerAnimation; - Animation? _pointerAnimation; - - /// Sets the animation for [RenderLinearWidgetPointer]. - set pointerAnimation(Animation? value) { - if (value == _pointerAnimation) { - return; - } - - _removeAnimationListener(); - _pointerAnimation = value; - _addAnimationListener(); - } - - /// Gets the Marker Alignment assigned to [RenderLinearWidgetPointer]. - LinearMarkerAlignment get markerAlignment => _markerAlignment; - LinearMarkerAlignment _markerAlignment; - - /// Sets the Marker Alignment for [RenderLinearWidgetPointer]. - set markerAlignment(LinearMarkerAlignment value) { - if (value == _markerAlignment) { - return; - } - _markerAlignment = value; - markNeedsLayout(); - } - - /// Gets the animation completed callback. - VoidCallback? get onAnimationCompleted => _onAnimationCompleted; - VoidCallback? _onAnimationCompleted; - - /// Sets the animation completed callback. - set onAnimationCompleted(VoidCallback? value) { - if (value == _onAnimationCompleted) { - return; - } - - _onAnimationCompleted = value; - } - - void _animationStatusListener(AnimationStatus status) { - if (status == AnimationStatus.completed) { - if (onAnimationCompleted != null) { - onAnimationCompleted!(); - } - - if (oldValue != value) { - oldValue = value; - } - } - } - - void _addAnimationListener() { - if (pointerAnimation == null) { - return; - } - - pointerAnimation!.addListener(markNeedsPaint); - pointerAnimation!.addStatusListener(_animationStatusListener); - } - - void _removeAnimationListener() { - if (pointerAnimation == null) { - return; - } - - pointerAnimation!.removeListener(markNeedsPaint); - pointerAnimation!.removeStatusListener(_animationStatusListener); - } - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - _addAnimationListener(); - } - - @override - void detach() { - _removeAnimationListener(); - super.detach(); - } + required LinearMarkerDragBehavior dragBehavior, + AnimationController? animationController}) + : super( + value: value, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + offset: offset, + position: position, + dragBehavior: dragBehavior, + markerAlignment: markerAlignment, + isAxisInversed: isAxisInversed, + isMirrored: isMirrored, + pointerAnimation: pointerAnimation, + animationController: animationController, + onAnimationCompleted: onAnimationCompleted, + ); @override bool hitTestSelf(Offset position) { diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range_renderer.dart index 6543dbeb0..628c1e9ec 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/range/linear_gauge_range_renderer.dart @@ -341,7 +341,7 @@ class RenderLinearRange extends RenderOpacity { void _getRangeOffsets() { final LinearElementPosition rangeElementPosition = - LinearGaugeHelper.getEffectiveElementPosition(position, isMirrored); + getEffectiveElementPosition(position, isMirrored); double _bottom = _rangeOffset.dy + _rangeRect.height; if (orientation == LinearGaugeOrientation.vertical) { @@ -408,14 +408,14 @@ class RenderLinearRange extends RenderOpacity { rangeRect, Radius.circular(startThickness / 2))); break; case LinearEdgeStyle.startCurve: - _path.addRRect(LinearGaugeHelper.getStartCurve( + _path.addRRect(getStartCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: rangeRect, radius: startThickness / 2)); break; case LinearEdgeStyle.endCurve: - _path.addRRect(LinearGaugeHelper.getEndCurve( + _path.addRRect(getEndCurve( isHorizontal: _isHorizontal, isAxisInversed: isAxisInversed, rect: rangeRect, diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart index b00dcbc18..c77520321 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/enum.dart @@ -105,3 +105,25 @@ enum LinearMarkerAlignment { /// LinearMarkerAlignment.end points the axis from outside position. end } + +/// Apply the different drag behavior for marker pointers. +enum LinearMarkerDragBehavior { + /// LinearMarkerDragBehavior.free default drag behavior for marker pointer. + free, + + /// LinearMarkerDragBehavior.constrained the current marker pointer is not + /// go beyond the reference marker pointer. + constrained, +} + +/// Find the Constrained state of the marker pointer. +enum ConstrainedBy { + /// ConstrainedBy.min constrained the marker pointer in the minimum value. + min, + + /// ConstrainedBy.max constrained the marker pointer in the maximum value. + max, + + /// ConstrainedBy.none when the marker pointer is not constrained with any pointers. + none, +} diff --git a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_helper.dart b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_helper.dart index b68179ee4..1d86b032b 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_helper.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/linear_gauge/utils/linear_gauge_helper.dart @@ -3,118 +3,114 @@ import 'package:flutter/rendering.dart'; import '../../linear_gauge/utils/enum.dart'; -/// This class provide the start and end curve paths. -class LinearGaugeHelper { - static RRect _getHorizontalStartCurve(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(radius), bottomLeft: Radius.circular(radius)); - } +RRect _getHorizontalStartCurve(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + topLeft: Radius.circular(radius), bottomLeft: Radius.circular(radius)); +} - static RRect _getHorizontalEndCurvePath(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - topRight: Radius.circular(radius), - bottomRight: Radius.circular(radius)); - } +RRect _getHorizontalEndCurvePath(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + topRight: Radius.circular(radius), bottomRight: Radius.circular(radius)); +} - static RRect _getVerticalStartCurve(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - topLeft: Radius.circular(radius), topRight: Radius.circular(radius)); - } +RRect _getVerticalStartCurve(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + topLeft: Radius.circular(radius), topRight: Radius.circular(radius)); +} - static RRect _getVerticalEndCurvePath(Rect rect, double radius) { - return RRect.fromRectAndCorners(rect, - bottomLeft: Radius.circular(radius), - bottomRight: Radius.circular(radius)); - } +RRect _getVerticalEndCurvePath(Rect rect, double radius) { + return RRect.fromRectAndCorners(rect, + bottomLeft: Radius.circular(radius), + bottomRight: Radius.circular(radius)); +} - /// Returns the start curve path. - static RRect getStartCurve( - {required bool isHorizontal, - required bool isAxisInversed, - required Rect rect, - required double radius}) { - if (isHorizontal) { - return !isAxisInversed - ? _getHorizontalStartCurve(rect, radius) - : _getHorizontalEndCurvePath(rect, radius); - } else { - return !isAxisInversed - ? _getVerticalEndCurvePath(rect, radius) - : _getVerticalStartCurve(rect, radius); - } +/// Returns the start curve path. +RRect getStartCurve( + {required bool isHorizontal, + required bool isAxisInversed, + required Rect rect, + required double radius}) { + if (isHorizontal) { + return !isAxisInversed + ? _getHorizontalStartCurve(rect, radius) + : _getHorizontalEndCurvePath(rect, radius); + } else { + return !isAxisInversed + ? _getVerticalEndCurvePath(rect, radius) + : _getVerticalStartCurve(rect, radius); } +} - /// Returns the end curve path. - static RRect getEndCurve( - {required bool isHorizontal, - required bool isAxisInversed, - required Rect rect, - required double radius}) { - if (isHorizontal) { - return !isAxisInversed - ? _getHorizontalEndCurvePath(rect, radius) - : _getHorizontalStartCurve(rect, radius); - } else { - return !isAxisInversed - ? _getVerticalStartCurve(rect, radius) - : _getVerticalEndCurvePath(rect, radius); - } +/// Returns the end curve path. +RRect getEndCurve( + {required bool isHorizontal, + required bool isAxisInversed, + required Rect rect, + required double radius}) { + if (isHorizontal) { + return !isAxisInversed + ? _getHorizontalEndCurvePath(rect, radius) + : _getHorizontalStartCurve(rect, radius); + } else { + return !isAxisInversed + ? _getVerticalStartCurve(rect, radius) + : _getVerticalEndCurvePath(rect, radius); } +} - /// Returns the effective element position. - static LinearElementPosition getEffectiveElementPosition( - LinearElementPosition position, bool isMirrored) { - if (isMirrored) { - return (position == LinearElementPosition.inside) - ? LinearElementPosition.outside - : (position == LinearElementPosition.outside) - ? LinearElementPosition.inside - : LinearElementPosition.cross; - } - - return position; +/// Returns the effective element position. +LinearElementPosition getEffectiveElementPosition( + LinearElementPosition position, bool isMirrored) { + if (isMirrored) { + return (position == LinearElementPosition.inside) + ? LinearElementPosition.outside + : (position == LinearElementPosition.outside) + ? LinearElementPosition.inside + : LinearElementPosition.cross; } - /// Returns the effective label position. - static LinearLabelPosition getEffectiveLabelPosition( - LinearLabelPosition labelPlacement, bool isMirrored) { - if (isMirrored) { - labelPlacement = (labelPlacement == LinearLabelPosition.inside) - ? LinearLabelPosition.outside - : LinearLabelPosition.inside; - } + return position; +} - return labelPlacement; +/// Returns the effective label position. +LinearLabelPosition getEffectiveLabelPosition( + LinearLabelPosition labelPlacement, bool isMirrored) { + if (isMirrored) { + labelPlacement = (labelPlacement == LinearLabelPosition.inside) + ? LinearLabelPosition.outside + : LinearLabelPosition.inside; } - /// Returns the curve animation function based on the animation type - static Curve getCurveAnimation(LinearAnimationType type) { - Curve curve = Curves.linear; - switch (type) { - case LinearAnimationType.bounceOut: - curve = Curves.bounceOut; - break; - case LinearAnimationType.ease: - curve = Curves.ease; - break; - case LinearAnimationType.easeInCirc: - curve = Curves.easeInCirc; - break; - case LinearAnimationType.easeOutBack: - curve = Curves.easeOutBack; - break; - case LinearAnimationType.elasticOut: - curve = Curves.elasticOut; - break; - case LinearAnimationType.linear: - curve = Curves.linear; - break; - case LinearAnimationType.slowMiddle: - curve = Curves.slowMiddle; - break; - default: - break; - } - return curve; + return labelPlacement; +} + +/// Returns the curve animation function based on the animation type +Curve getCurveAnimation(LinearAnimationType type) { + Curve curve = Curves.linear; + switch (type) { + case LinearAnimationType.bounceOut: + curve = Curves.bounceOut; + break; + case LinearAnimationType.ease: + curve = Curves.ease; + break; + case LinearAnimationType.easeInCirc: + curve = Curves.easeInCirc; + break; + case LinearAnimationType.easeOutBack: + curve = Curves.easeOutBack; + break; + case LinearAnimationType.elasticOut: + curve = Curves.elasticOut; + break; + case LinearAnimationType.linear: + curve = Curves.linear; + break; + case LinearAnimationType.slowMiddle: + curve = Curves.slowMiddle; + break; + default: + break; } + return curve; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart index d24526525..aee370f79 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/axis/radial_axis_parent_widget.dart @@ -486,8 +486,12 @@ class RenderRadialAxisParent extends RenderBox /// Method to update the drag value void _updateDragValue(double x, double y, dynamic pointer) { - final double actualCenterX = size.width * axis!.centerX; - final double actualCenterY = size.height * axis!.centerY; + final double actualCenterX = axis!.canScaleToFit + ? axis!.getAxisCenter().dx + : size.width * axis!.centerX; + final double actualCenterY = axis!.canScaleToFit + ? axis!.getAxisCenter().dy + : size.height * axis!.centerY; double angle = math.atan2(y - actualCenterY, x - actualCenterX) * (180 / math.pi) + 360; @@ -501,6 +505,24 @@ class RenderRadialAxisParent extends RenderBox angle %= 360; } + // Restricts the dragging of pointer once the maximum or minimum + // value of axis is reached, if it is not a full circle + if (axis!.startAngle != axis!.endAngle) { + final double pointerAngle = angle > 360 ? angle % 360 : angle; + final double startAngle = + axis!.startAngle > 360 ? axis!.startAngle % 360 : axis!.startAngle; + final double endAngle = + axis!.endAngle > 360 ? axis!.endAngle % 360 : axis!.endAngle; + final bool isPointerInsideRange = startAngle < endAngle + ? pointerAngle > (startAngle == 360 ? 0 : startAngle) && + pointerAngle < (endAngle == 0 ? 360 : endAngle) + : pointerAngle > (startAngle == 360 ? 0 : startAngle) || + pointerAngle < (endAngle == 0 ? 360 : endAngle); + if (!isPointerInsideRange) { + _checkPointerIsDragged(); + } + } + if (angle >= axis!.startAngle && angle <= endAngle) { double dragValue = 0; 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 bd518ab8b..b7a2f7c02 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 @@ -1339,7 +1339,9 @@ class RenderRadialAxisWidget extends RenderBox { late double begin, end; if (pointer is RenderRangePointer) { animationStartValue = pointer.animationStartValue ?? 0; - animationEndValue = pointer.getSweepAngle(); + animationEndValue = isInversed + ? _getSweepAngle(pointer.getSweepAngle()) + : pointer.getSweepAngle(); pointer.animationStartValue = animationEndValue; } else { animationStartValue = _getSweepAngle(pointer.oldValue ?? minimum); @@ -2087,7 +2089,9 @@ class RenderRadialAxisWidget extends RenderBox { ///Method to calculate teh sweep angle of axis double getAxisSweepAngle() { final double actualEndAngle = endAngle > 360 ? endAngle % 360 : endAngle; - double totalAngle = actualEndAngle - startAngle; + final double actualStartAngle = + startAngle > 360 ? startAngle % 360 : startAngle; + double totalAngle = actualEndAngle - actualStartAngle; totalAngle = totalAngle <= 0 ? (totalAngle + 360) : totalAngle; _sweepAngle = totalAngle; return _sweepAngle; 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 f555321bb..972ad8fdb 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 @@ -225,7 +225,7 @@ class SfRadialGaugeState extends State /// Method to convert the [SfRadialGauge] as an image. /// - /// Returns the [dart:ui.image] + /// Returns the `dart:ui.image` /// ///As this method is in the widget’s state class, you have to use a global ///key to access the state to call this method. 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 b413335e3..db2462acd 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 @@ -4,7 +4,7 @@ import 'dart:ui' as dart_ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/core.dart' as core; import 'package:syncfusion_flutter_core/theme.dart'; import '../../radial_gauge/axis/radial_axis_widget.dart'; @@ -76,7 +76,7 @@ class RenderMarkerPointer extends RenderBox { late double _angle; late Offset _offset; late Rect _markerRect; - late ShapeMarkerType _shapeMarkerType; + late core.ShapeMarkerType _shapeMarkerType; bool _isAnimating = false; bool _isInitialLoading = true; late double _radius; @@ -375,7 +375,7 @@ class RenderMarkerPointer extends RenderBox { } _imageUrl = value; - markNeedsPaint(); + _loadImage(); } /// Gets the textStyle assigned to [RenderMarkerPointer]. @@ -527,9 +527,7 @@ class RenderMarkerPointer extends RenderBox { _angle = _getPointerAngle(); _radian = getDegreeToRadian(_angle); final Offset offset = _getMarkerOffset(_radian); - if (markerType == MarkerType.image && imageUrl != null) { - _loadImage(); - } else if (markerType == MarkerType.text && text != null) { + if (markerType == MarkerType.text && text != null) { _textSize = getTextSize(text!, textStyle); } @@ -543,6 +541,9 @@ class RenderMarkerPointer extends RenderBox { @override void performLayout() { size = Size(constraints.maxWidth, constraints.maxHeight); + if (markerType == MarkerType.image && imageUrl != null) { + _loadImage(); + } } @override @@ -598,7 +599,10 @@ class RenderMarkerPointer extends RenderBox { /// To load the image from the image url // ignore: avoid_void_async void _loadImage() async { - await _renderImage(); + await _renderImage().then((void value) { + WidgetsBinding.instance! + .addPostFrameCallback((Duration duration) => markNeedsPaint()); + }); } /// Renders the image from the image url @@ -608,7 +612,6 @@ class RenderMarkerPointer extends RenderBox { await dart_ui.instantiateImageCodec(imageData.buffer.asUint8List()); final dart_ui.FrameInfo frameInfo = await imageCodec.getNextFrame(); _image = frameInfo.image; - markNeedsPaint(); } /// Method to draw pointer the marker pointer. @@ -693,7 +696,7 @@ class RenderMarkerPointer extends RenderBox { } if (markerType != MarkerType.text && markerType != MarkerType.image) { - ShapePainter.paint( + core.paint( canvas: canvas, rect: _markerRect, paint: paint, @@ -761,7 +764,7 @@ class RenderMarkerPointer extends RenderBox { canvas.drawOval(overlayRect, overlayPaint); } - _shapeMarkerType = ShapeMarkerType.circle; + _shapeMarkerType = core.ShapeMarkerType.circle; } /// Renders the MarkerShape.rectangle @@ -792,7 +795,7 @@ class RenderMarkerPointer extends RenderBox { canvas.drawRect(overlayRect, overlayPaint); } - _shapeMarkerType = ShapeMarkerType.rectangle; + _shapeMarkerType = core.ShapeMarkerType.rectangle; } /// Renders the MarkerShape.image @@ -840,7 +843,7 @@ class RenderMarkerPointer extends RenderBox { canvas.drawPath(overlayPath, overlayPaint); } - _shapeMarkerType = ShapeMarkerType.diamond; + _shapeMarkerType = core.ShapeMarkerType.diamond; } /// Renders the triangle and the inverted triangle @@ -880,7 +883,7 @@ class RenderMarkerPointer extends RenderBox { canvas.drawPath(overlayPath, overlayPaint); } - _shapeMarkerType = ShapeMarkerType.triangle; + _shapeMarkerType = core.ShapeMarkerType.triangle; } @override 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 17b58af71..4460f5746 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 @@ -551,7 +551,11 @@ class RenderRangePointer extends RenderBox { /// Returns the sweep radian for pointer. double _getPointerSweepRadian(double sweepRadian) { if (pointerAnimation != null && _isAnimating) { - return getDegreeToRadian(_sweepAngle * _pointerAnimation!.value); + if (axisRenderer != null && axisRenderer!.isInversed) { + return sweepRadian * _pointerAnimation!.value; + } else { + return getDegreeToRadian(_sweepAngle * _pointerAnimation!.value); + } } else { return sweepRadian; } diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart index 3b1a1566f..063a6377e 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/range/gauge_range_renderer.dart @@ -443,6 +443,23 @@ class RenderGaugeRange extends RenderBox { getDegreeToRadian((endFactor * _sweepAngle) + axisRenderer!.startAngle); _rangeEndRadian = endRadian - _rangeStartRadian; + // To render the range in clock wise if the start value is greater than the end value + // for full circle axis track. + if (axisRenderer!.startAngle == axisRenderer!.endAngle && + startValue > endValue) { + final double midFactor = (axisRenderer!.renderer != null && + axisRenderer!.renderer!.valueToFactor(axisRenderer!.maximum) != + null) + ? axisRenderer!.renderer!.valueToFactor(axisRenderer!.maximum) ?? + axisRenderer!.valueToFactor(axisRenderer!.maximum) + : axisRenderer!.valueToFactor(axisRenderer!.maximum); + final double midRadian = getDegreeToRadian( + (midFactor * _sweepAngle) + axisRenderer!.startAngle); + final double startRadian = getDegreeToRadian(axisRenderer!.startAngle); + _rangeEndRadian = + (midRadian - _rangeStartRadian) + (endRadian - startRadian); + } + _rangeRect = Rect.fromLTRB( -(_radius - (_actualStartWidth / 2 + _totalOffset)), -(_radius - (_actualStartWidth / 2 + _totalOffset)), diff --git a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer.dart b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer.dart index 5b9597f91..91ad7a9d6 100644 --- a/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer.dart +++ b/packages/syncfusion_flutter_gauges/lib/src/radial_gauge/renderers/radial_axis_renderer.dart @@ -6,7 +6,7 @@ abstract class GaugeAxisRenderer { /// Represents the gauge axis late RadialAxis axis; - /// Returns the visible labels on [GaugeAxis] + /// Returns the visible labels on [RadialAxis] /// /// Modify the actual labels generated, which are calculated on the basis /// of scale range and interval. diff --git a/packages/syncfusion_flutter_maps/CHANGELOG.md b/packages/syncfusion_flutter_maps/CHANGELOG.md index c823ec4ba..6166e0e16 100644 --- a/packages/syncfusion_flutter_maps/CHANGELOG.md +++ b/packages/syncfusion_flutter_maps/CHANGELOG.md @@ -1,9 +1,22 @@ ## Unreleased +**Features** + +* Vector line stroke cap - Provides a stroke cap option to customize the map vector lines and polylines. +* Marker alignment - Marker can be aligned in various alignment positions based on its coordinate point. +* Custom bounds - Provides an option to specify the visual limits for viewing a specific region in Maps. + +**Enhancements** + +* Support has been provided to increase or decrease the duration of the tooltip visibility. The duration will be measured in seconds. +* Improved the hover color of the shapes, bubbles, and vector layer shapes. + +## [19.2.44-beta] - 06/29/2021 + ### Features -* #I323663, I318077 - Provided multiple tile layer support in the maps. -* **Legend pointer** - Show a pointer at the top of the legend while hovering. +* #I323663, I318077 - Provided multiple tile layer support in the Maps. +* **Legend pointer** - Show a pointer at the top of the gradient legend while hovering. ## [19.1.54-beta] - 03/30/2021 diff --git a/packages/syncfusion_flutter_maps/README.md b/packages/syncfusion_flutter_maps/README.md index 0cde9bde8..1b815732c 100644 --- a/packages/syncfusion_flutter_maps/README.md +++ b/packages/syncfusion_flutter_maps/README.md @@ -35,6 +35,8 @@ Create a highly interactive and customizable Flutter Maps that has features set ![maps marker](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/map_marker.png) +**Marker alignment** - Marker can be aligned in various alignment positions based on its coordinate point. + **Shape selection** - Select a shape in order to highlight that area on a map. You can use the callback for doing any action during shape selection. ![maps selection](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/map_selection.png) @@ -57,6 +59,8 @@ Add a shape sublayer with GeoJSON data on another shape layer to show more detai **Vector layer** Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer in the shape layer. +**Vector line stroke cap** - Provides a stroke cap option to customize the map vector lines and polylines. + **Zooming and panning** - Zoom in shape layer for a closer look at a specific region by pinching, scrolling the mouse wheel or track pad, or using the toolbar on the web. Pan the map to navigate across the regions. ![maps zoompan](https://cdn.syncfusion.com/content/images/zoompan.gif) @@ -71,6 +75,8 @@ Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer i ![tile layer marker](https://cdn.syncfusion.com/content/images/Tile_withmarker.png) +**Marker alignment** - Marker can be aligned in various alignment positions based on its coordinate point. + **Shape sublayer** Add a shape sublayer with GeoJSON data on the tile layer to show more details about a particular region. @@ -83,6 +89,8 @@ Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer i ![tile layer polyline](https://cdn.syncfusion.com/content/images/FTControl/polyline.jpg) +**Vector line stroke cap** - Provides a stroke cap option to customize the map vector lines and polylines. + **Zooming and panning** - Zoom in tile layer for a closer look at a specific region by pinching, scrolling the mouse wheel or track pad, or using the toolbar on the web. Pan the map to navigate across the regions. **Inverted circle** - Support has been provided for applying color to the inverted circle with the inner circle being transparent and the outer portion covered by an overlay color. @@ -93,6 +101,8 @@ Add shapes such as polylines, lines, polygons, circles, and arcs as a sublayer i ![inverted polygon](https://cdn.syncfusion.com/content/images/Flutter/pub_images/maps_images/inverted-polygon.png) +**Custom bounds** - Provides an option to specify the visual limits for viewing a specific region in Maps. + ## Get the demo application Explore the full capability of our Flutter widgets on your device by installing our sample browser application from the following app stores. View sample codes in GitHub. @@ -337,4 +347,4 @@ The following screenshot illustrates the result of the above code sample. 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_flutter_maps/example/pubspec.yaml b/packages/syncfusion_flutter_maps/example/pubspec.yaml index ce03b104f..1e9ec3371 100644 --- a/packages/syncfusion_flutter_maps/example/pubspec.yaml +++ b/packages/syncfusion_flutter_maps/example/pubspec.yaml @@ -28,7 +28,6 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/syncfusion_flutter_maps/lib/maps.dart b/packages/syncfusion_flutter_maps/lib/maps.dart index 7ecf4e412..2a94cdc11 100644 --- a/packages/syncfusion_flutter_maps/lib/maps.dart +++ b/packages/syncfusion_flutter_maps/lib/maps.dart @@ -1,12 +1,25 @@ +/// Syncfusion Flutter Maps package is a data visualization library for creating +/// beautiful, interactive, and customizable maps from shape files or WMTS +/// services to visualize the geographical area. +/// +/// To use, import `package:syncfusion_flutter_maps/maps.dart`. +/// +/// See also: +/// * [Syncfusion Flutter Maps product page](https://www.syncfusion.com/flutter-widgets/flutter-maps) +/// * [User guide documentation](https://help.syncfusion.com/flutter/maps/overview) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter/sfmaps) library maps; import 'dart:convert'; import 'package:flutter/foundation.dart' show DiagnosticableTree, ObjectFlagProperty; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:http/http.dart' as http; +import 'package:syncfusion_flutter_core/theme.dart'; import 'src/common.dart'; import 'src/controller/map_controller.dart'; @@ -14,10 +27,11 @@ import 'src/elements/legend.dart'; import 'src/layer/layer_base.dart'; import 'src/layer/shape_layer.dart'; import 'src/layer/tile_layer.dart'; +import 'src/layer/zoomable.dart'; import 'src/settings.dart'; import 'src/utils.dart'; -export 'maps.dart' hide BehaviorViewRenderObjectWidget; +export 'maps.dart' hide BehaviorView, BehaviorViewRenderObjectWidget; export 'src/controller/layer_controller.dart'; export 'src/elements/legend.dart' hide Legend; export 'src/elements/marker.dart' hide MarkerContainer; @@ -191,8 +205,8 @@ Future _fetchResponse(String url) { /// Tile layer which renders the tiles returned from the Web Map Tile /// Services (WMTS) like OpenStreetMap, Bing Maps, Google Maps, TomTom etc. /// -/// The [urlTemplate] accepts the URL in WMTS format i.e. {z} — zoom level, {x} -/// and {y} — tile coordinates. +/// The [MapTileLayer.urlTemplate] accepts the URL in WMTS format i.e. +/// {z} — zoom level, {x} and {y} — tile coordinates. /// /// This URL might vary slightly depends on the providers. The formats can be, /// https://exampleprovider/{z}/{x}/{y}.png, @@ -203,9 +217,10 @@ Future _fetchResponse(String url) { /// current center point and the zoom level. /// /// The subscription key may be needed for some of the providers. Please include -/// them in the [urlTemplate] itself as mentioned in above example. Please note -/// that the format may vary between the each map provider. You can check the -/// exact URL format needed for the providers in their official websites. +/// them in the [MapTileLayer.urlTemplate] itself as mentioned in above example. +/// Please note that the format may vary between the each map provider. You can +/// check the exact URL format needed for the providers in their official +/// websites. /// /// Regarding the tile rendering, at the lowest zoom level (Level 0), the map is /// 256 x 256 pixels and the whole world map renders as a single tile. At each @@ -216,11 +231,12 @@ Future _fetchResponse(String url) { /// (These details are just for your information and all these calculation are /// done internally.) /// -/// However, based on the size of the [SfMaps] widget, [initialFocalLatLng] and -/// [initialZoomLevel], number of initial tiles needed in the view port alone -/// will be rendered. While zooming and panning, new tiles will be requested and -/// rendered on demand based on the current zoom level and focal point. -/// The current zoom level and focal point can be obtained from the +/// However, based on the size of the [SfMaps] widget, +/// [MapTileLayer.initialFocalLatLng] and [MapTileLayer.initialZoomLevel], +/// number of initial tiles needed in the view port alone will be rendered. +/// While zooming and panning, new tiles will be requested and rendered on +/// demand based on the current zoom level and focal point. The current zoom +/// level and focal point can be obtained from the /// [MapZoomPanBehavior.zoomLevel] and [MapZoomPanBehavior.focalLatLng] /// respectively. Once the particular tile is rendered, it will be stored in the /// cache and it will be used from it for the next time for better performance. @@ -247,8 +263,8 @@ Future _fetchResponse(String url) { /// bubbles, legends, selection etc. /// * [MapShapeSource], for providing the data for the elements like data /// labels, tooltip, bubbles, legends etc. -/// * For enabling zooming and panning, set [MapShapeLayer.zoomPanBehavior] or -/// [MapTileLayer.zoomPanBehavior] with the instance of [MapZoomPanBehavior]. +/// * For enabling zooming and panning, set [MapLayer.zoomPanBehavior] with the +/// instance of [MapZoomPanBehavior]. class SfMaps extends StatefulWidget { /// Creates a [SfMaps]. const SfMaps({ @@ -353,24 +369,24 @@ class _RenderMaps extends RenderProxyBox { /// [MapShapeSublayer.source]. The source can be set as the .json file from an /// asset bundle, network or from [Uint8List] as bytes. /// -/// The [MapShapeSublayer.shapeDataField] property is used to +/// The [MapShapeSource.shapeDataField] property is used to /// refer the unique field name in the .json file to identify each shapes and /// map with the respective data in the data source. /// /// By default, the value specified for the -/// [MapShapeSublayer.shapeDataField] in the GeoJSON source will be used in +/// [MapShapeSource.shapeDataField] in the GeoJSON source will be used in /// the elements like data labels, and tooltip for their respective /// shapes. /// /// However, it is possible to keep a data source and customize these elements /// based on the requirement. The value of the -/// [MapShapeSublayer.shapeDataField] will be used to map with the -/// respective data returned in [MapShapeSublayer.primaryValueMapper] +/// [MapShapeSource.shapeDataField] will be used to map with the +/// respective data returned in [MapShapeSource.primaryValueMapper] /// from the data source. /// /// Once the above mapping is done, you can customize the elements using the -/// APIs like [MapShapeSublayer.dataLabelMapper], -/// [MapShapeSublayer.shapeColorMappers], etc. +/// APIs like [MapShapeSource.dataLabelMapper], +/// [MapShapeSource.shapeColorMappers], etc. /// /// The snippet below shows how to render the basic world map using the data /// from .json file. @@ -1003,6 +1019,7 @@ class MapShapeLayer extends MapLayer { required this.source, this.loadingBuilder, this.controller, + MapLatLngBounds? initialLatLngBounds, List? sublayers, int initialMarkersCount = 0, MapMarkerBuilder? markerBuilder, @@ -1025,6 +1042,7 @@ class MapShapeLayer extends MapLayer { WillPanCallback? onWillPan, }) : super( key: key, + initialLatLngBounds: initialLatLngBounds, sublayers: sublayers, initialMarkersCount: initialMarkersCount, markerBuilder: markerBuilder, @@ -1432,7 +1450,7 @@ class MapShapeLayer extends MapLayer { /// Customizes the appearance of the bubbles. /// /// See also: - /// * [MapShapeLayer.bubbleSizeMapper], to show the bubbles. + /// * [MapShapeSource.bubbleSizeMapper], to show the bubbles. final MapBubbleSettings bubbleSettings; /// Shows legend for the bubbles or shapes. @@ -1599,6 +1617,7 @@ class MapShapeLayer extends MapLayer { source: source, loadingBuilder: loadingBuilder, controller: controller, + initialLatLngBounds: initialLatLngBounds, sublayers: sublayers, initialMarkersCount: initialMarkersCount, markerBuilder: markerBuilder, @@ -1628,6 +1647,10 @@ class MapShapeLayer extends MapLayer { properties.add(source.toDiagnosticsNode(name: 'source')); properties.add(ObjectFlagProperty.has( 'loadingBuilder', loadingBuilder)); + if (initialLatLngBounds != null) { + properties.add(DiagnosticsProperty( + 'initialLatLngBounds', initialLatLngBounds)); + } if (controller != null) { properties.add(IntProperty('markersCount', controller!.markersCount)); } else { @@ -1688,6 +1711,7 @@ class _ShapeLayer extends StatefulWidget { required this.source, required this.loadingBuilder, required this.controller, + required this.initialLatLngBounds, required this.sublayers, required this.initialMarkersCount, required this.markerBuilder, @@ -1713,6 +1737,7 @@ class _ShapeLayer extends StatefulWidget { final MapShapeSource source; final MapLoadingBuilder? loadingBuilder; final MapShapeLayerController? controller; + final MapLatLngBounds? initialLatLngBounds; final List? sublayers; final int initialMarkersCount; final MapMarkerBuilder? markerBuilder; @@ -1758,6 +1783,7 @@ class _ShapeLayerState extends State<_ShapeLayer> { source: widget.source, loadingBuilder: widget.loadingBuilder, controller: widget.controller, + initialLatLngBounds: widget.initialLatLngBounds, sublayers: widget.sublayers, initialMarkersCount: widget.initialMarkersCount, markerBuilder: widget.markerBuilder, @@ -1821,8 +1847,7 @@ class _ShapeLayerState extends State<_ShapeLayer> { /// respectively. Once the particular tile is rendered, it will be stored in the /// cache and it will be used from it next time for better performance. /// -/// Regarding the cache and clearing it, please check the APIs in [imageCache] -/// (https://api.flutter.dev/flutter/painting/imageCache.html). +/// Regarding the cache and clearing it, please check the APIs in [imageCache]. /// /// ```dart /// @override @@ -1849,6 +1874,7 @@ class MapTileLayer extends MapLayer { required this.urlTemplate, this.initialFocalLatLng = const MapLatLng(0.0, 0.0), this.initialZoomLevel = 1, + MapLatLngBounds? initialLatLngBounds, this.controller, List? sublayers, int initialMarkersCount = 0, @@ -1863,6 +1889,7 @@ class MapTileLayer extends MapLayer { initialMarkersCount != 0 && markerBuilder != null), super( key: key, + initialLatLngBounds: initialLatLngBounds, sublayers: sublayers, initialMarkersCount: initialMarkersCount, markerBuilder: markerBuilder, @@ -2081,6 +2108,7 @@ class MapTileLayer extends MapLayer { urlTemplate: urlTemplate, initialFocalLatLng: initialFocalLatLng, initialZoomLevel: initialZoomLevel, + initialLatLngBounds: initialLatLngBounds, sublayers: sublayers, initialMarkersCount: initialMarkersCount, markerBuilder: markerBuilder, @@ -2102,6 +2130,10 @@ class MapTileLayer extends MapLayer { properties.add(DiagnosticsProperty( 'initialFocalLatLng', initialFocalLatLng)); properties.add(IntProperty('initialZoomLevel', initialZoomLevel)); + if (initialLatLngBounds != null) { + properties.add(DiagnosticsProperty( + 'initialLatLngBounds', initialLatLngBounds)); + } if (sublayers != null && sublayers!.isNotEmpty) { final DebugSublayerTree pointerTreeNode = DebugSublayerTree(sublayers!); properties.add(pointerTreeNode.toDiagnosticsNode()); @@ -2129,6 +2161,7 @@ class _TileLayer extends StatefulWidget { required this.urlTemplate, required this.initialFocalLatLng, required this.initialZoomLevel, + required this.initialLatLngBounds, required this.zoomPanBehavior, required this.sublayers, required this.initialMarkersCount, @@ -2144,6 +2177,7 @@ class _TileLayer extends StatefulWidget { final String urlTemplate; final MapLatLng initialFocalLatLng; final int initialZoomLevel; + final MapLatLngBounds? initialLatLngBounds; final MapZoomPanBehavior? zoomPanBehavior; final List? sublayers; final int initialMarkersCount; @@ -2166,7 +2200,12 @@ class _TileLayerState extends State<_TileLayer> { void initState() { _controller = MapController() ..tooltipKey = GlobalKey() - ..layerType = LayerType.tile; + ..layerType = LayerType.tile + ..tileCurrentLevelDetails = TileZoomLevelDetails(); + if (widget.zoomPanBehavior != null && + widget.zoomPanBehavior!._zoomController == null) { + widget.zoomPanBehavior!._zoomController = ZoomableController(); + } super.initState(); } @@ -2174,11 +2213,13 @@ class _TileLayerState extends State<_TileLayer> { Widget build(BuildContext context) { return MapLayerInheritedWidget( controller: _controller, + zoomController: widget.zoomPanBehavior?._zoomController, sublayers: widget.sublayers, child: TileLayer( urlTemplate: widget.urlTemplate, initialFocalLatLng: widget.initialFocalLatLng, initialZoomLevel: widget.initialZoomLevel, + initialLatLngBounds: widget.initialLatLngBounds, sublayers: widget.sublayers, initialMarkersCount: widget.initialMarkersCount, markerBuilder: widget.markerBuilder, @@ -2292,10 +2333,11 @@ abstract class MapBehavior extends DiagnosticableTree { class MapZoomPanBehavior extends MapBehavior { /// Creates a new [MapZoomPanBehavior]. MapZoomPanBehavior({ - double zoomLevel = 1.0, + double zoomLevel = kDefaultMinZoomLevel, MapLatLng? focalLatLng, - double minZoomLevel = 1.0, - double maxZoomLevel = 15.0, + MapLatLngBounds? latLngBounds, + double minZoomLevel = kDefaultMinZoomLevel, + double maxZoomLevel = kDefaultMaxZoomLevel, bool enablePinching = true, bool enablePanning = true, bool enableDoubleTapZooming = false, @@ -2303,13 +2345,15 @@ class MapZoomPanBehavior extends MapBehavior { MapToolbarSettings toolbarSettings = const MapToolbarSettings(), }) : _zoomLevel = zoomLevel.clamp(minZoomLevel, maxZoomLevel), _focalLatLng = focalLatLng, + _latLngBounds = latLngBounds, _minZoomLevel = minZoomLevel, _maxZoomLevel = maxZoomLevel, _enablePinching = enablePinching, _enablePanning = enablePanning, _enableDoubleTapZooming = enableDoubleTapZooming, _showToolbar = showToolbar, - _toolbarSettings = toolbarSettings; + _toolbarSettings = toolbarSettings, + _zoomController = ZoomableController(); /// Current zoom level of the map layer. /// @@ -2325,13 +2369,26 @@ class MapZoomPanBehavior extends MapBehavior { double get zoomLevel => _zoomLevel; double _zoomLevel; set zoomLevel(double value) { - assert(value >= 1); + assert(value >= kDefaultMinZoomLevel); assert(value >= minZoomLevel && value <= maxZoomLevel); if (_zoomLevel == value || value < minZoomLevel || value > maxZoomLevel) { return; } + _zoomLevel = value; - _controller?.onZoomLevelChange(zoomLevel); + if (_controller != null && _controller!.layerType == LayerType.shape) { + _controller!.onZoomLevelChange(value); + return; + } + + if (_zoomController!.parentRect != null) { + _zoomController!.zoomLevel = value; + // Intimating the zoom controller to update the actual rect based on the + // new zoom level. This gets works only when there is simultaneous + // zoom pan property changes has been performed. + _updateZoomControllerActualRect(); + } + return; } /// The [focalLatLng] is the focal point of the map layer based on which @@ -2347,8 +2404,39 @@ class MapZoomPanBehavior extends MapBehavior { if (_focalLatLng == value) { return; } + _focalLatLng = value; - _controller?.onPanChange(value); + if (_controller != null && _controller!.layerType == LayerType.shape) { + _controller!.onPanChange(value); + return; + } + + if (_zoomController!.parentRect != null) { + _updateZoomControllerActualRect(); + } + } + + /// Specifies the current latlng bounds of the maps. The maps zoom level + /// and focalLatLng get updated based on the given bounds. + /// + /// For dynamic changes, the bounds value get updated with an animation.If + /// both [latLngBounds] and [MapTileLayer.initialLatLngBounds] or + /// [MapShapeLayer.initialLatLngBounds] are given, considered [latLngBounds] + /// values as load time value. + MapLatLngBounds? get latLngBounds => _latLngBounds; + MapLatLngBounds? _latLngBounds; + set latLngBounds(MapLatLngBounds? value) { + if (_latLngBounds == value) { + return; + } + + _latLngBounds = value; + if (_controller != null) { + zoomLevel = getZoomLevel(_latLngBounds!, _controller!.layerType!, + renderBox.size, _controller!.shapeLayerSizeFactor / zoomLevel) + .clamp(minZoomLevel, maxZoomLevel); + focalLatLng = getFocalLatLng(_latLngBounds!); + } } /// Minimum zoom level of the map layer. @@ -2357,7 +2445,7 @@ class MapZoomPanBehavior extends MapBehavior { double get minZoomLevel => _minZoomLevel; double _minZoomLevel; set minZoomLevel(double value) { - assert(value >= 1); + assert(value >= kDefaultMinZoomLevel); if (_minZoomLevel == value) { return; } @@ -2446,6 +2534,13 @@ class MapZoomPanBehavior extends MapBehavior { _toolbarSettings = value; } + /// The controller which stores the actual zoomLevel and actual rect for the + /// interaction or animation changes. + /// + /// It intimate its listeners, when changing the zoomLevel and actual rect + /// values. + ZoomableController? _zoomController; + /// Called whenever zooming is happening. /// /// Subclasses can override this method to do any custom operations based on @@ -2473,8 +2568,18 @@ class MapZoomPanBehavior extends MapBehavior { /// pointers in contact with the screen. void onZooming(MapZoomDetails details) { if (_controller != null) { - _controller!.notifyZoomingListeners(details); - _controller!.notifyListeners(); + _latLngBounds = details.newVisibleBounds; + if (_controller!.layerType == LayerType.shape) { + _controller!.notifyZoomingListeners(details); + _controller!.notifyListeners(); + } else { + _controller! + ..isInInteractive = true + ..gesture = Gesture.scale; + zoomLevel = details.newZoomLevel!; + focalLatLng = details.focalLatLng!; + _controller!.notifyListeners(); + } } } @@ -2501,8 +2606,19 @@ class MapZoomPanBehavior extends MapBehavior { /// pointers in contact with the screen. void onPanning(MapPanDetails details) { if (_controller != null) { - _controller!.notifyPanningListeners(details); - _controller!.notifyListeners(); + _latLngBounds = details.newVisibleBounds; + if (_controller!.layerType == LayerType.shape) { + _controller!.notifyPanningListeners(details); + _controller!.notifyListeners(); + } else { + _controller! + ..isInInteractive = true + ..gesture = Gesture.pan + ..localScale = 1.0 + ..panDistance += details.delta!; + focalLatLng = details.focalLatLng!; + _controller!.notifyListeners(); + } } } @@ -2510,8 +2626,27 @@ class MapZoomPanBehavior extends MapBehavior { /// [MapZoomPanBehavior.minZoomLevel]. void reset() { if (_controller != null) { - _controller!.notifyResetListeners(); - _controller!.notifyListeners(); + zoomLevel = minZoomLevel; + if (_controller!.layerType == LayerType.shape) { + _controller!.notifyListeners(); + } + } + } + + // Converts the current focalLatLng into a offset and validates it with + // zoom controller actual rect to perform animation. + void _updateZoomControllerActualRect() { + final Offset center = pixelFromLatLng(_focalLatLng!.latitude, + _focalLatLng!.longitude, Size.square(getTotalTileWidth(_zoomLevel))); + final Offset newOffset = Offset( + _zoomController!.parentRect!.width / 2 - center.dx, + _zoomController!.parentRect!.height / 2 - center.dy); + final Offset delta = newOffset - _zoomController!.actualRect.topLeft; + if (delta != Offset.zero) { + _zoomController!.actualRect = + (_zoomController!.actualRect.topLeft + delta) & + _zoomController!.actualRect.size; + return; } } @@ -2538,6 +2673,8 @@ class MapZoomPanBehavior extends MapBehavior { ifFalse: 'Double tap is disabled', showName: false)); properties.add(DiagnosticsProperty('focalLatLng', focalLatLng)); + properties.add( + DiagnosticsProperty('latLngBounds', latLngBounds)); properties.add(FlagProperty('showToolbar', value: showToolbar, ifTrue: 'Toolbar is enabled', @@ -2636,20 +2773,21 @@ class MapLatLngBounds { class MapZoomDetails { /// Creates a [MapZoomDetails]. MapZoomDetails({ - this.newVisibleBounds, this.localFocalPoint, this.globalFocalPoint, this.previousZoomLevel, this.newZoomLevel, + this.focalLatLng, this.previousVisibleBounds, + this.newVisibleBounds, }); - /// The global focal point of the pointers in contact with the screen. - final Offset? globalFocalPoint; - /// The local focal point of the pointers in contact with the screen. final Offset? localFocalPoint; + /// The global focal point of the pointers in contact with the screen. + final Offset? globalFocalPoint; + /// Provides the zoom level before the current zooming operation completes /// i.e. current zoom level. final double? previousZoomLevel; @@ -2660,6 +2798,9 @@ class MapZoomDetails { /// changes in the UI. final double? newZoomLevel; + /// Represents the new focal latitude and longitude position. + final MapLatLng? focalLatLng; + /// Provides the visible bounds before the current zooming operation completes /// i.e. current visible bounds. final MapLatLngBounds? previousVisibleBounds; @@ -2669,36 +2810,33 @@ class MapZoomDetails { /// Hence, if the `super.onZooming(details)` is not called, there will be no /// changes in the UI. final MapLatLngBounds? newVisibleBounds; - - /// Creates a copy of this class but with the given fields - /// replaced with the new values. - MapZoomDetails copyWith({double? newZoomLevel}) { - return MapZoomDetails( - localFocalPoint: localFocalPoint, - globalFocalPoint: globalFocalPoint, - previousZoomLevel: previousZoomLevel, - newZoomLevel: newZoomLevel ?? this.newZoomLevel, - previousVisibleBounds: previousVisibleBounds, - newVisibleBounds: newVisibleBounds, - ); - } } /// Contains details about the current pan position. class MapPanDetails { /// Creates a [MapPanDetails]. MapPanDetails({ - this.newVisibleBounds, + this.localFocalPoint, + this.globalFocalPoint, this.zoomLevel, + this.focalLatLng, this.delta, this.previousVisibleBounds, - this.globalFocalPoint, - this.localFocalPoint, + this.newVisibleBounds, }); + /// The local focal point of the pointers in contact with the screen. + final Offset? localFocalPoint; + + /// The global focal point of the pointers in contact with the screen. + final Offset? globalFocalPoint; + /// Provides the current zoom level. final double? zoomLevel; + /// Provides the current focal latLng. + final MapLatLng? focalLatLng; + /// The difference in pixels between touch start and current touch position. final Offset? delta; @@ -2711,12 +2849,258 @@ class MapPanDetails { /// Hence, if the `super.onPanning(details)` is not called, there will be no /// changes in the UI. final MapLatLngBounds? newVisibleBounds; +} - /// The global focal point of the pointers in contact with the screen. - final Offset? globalFocalPoint; +/// Widget for [MapZoomPanBehavior]. +class BehaviorView extends StatefulWidget { + /// Creates [BehaviorView]. + const BehaviorView({ + Key? key, + required this.zoomLevel, + required this.focalLatLng, + required this.controller, + required this.behavior, + required this.onWillZoom, + required this.onWillPan, + }) : super(key: key); - /// The local focal point of the pointers in contact with the screen. - final Offset? localFocalPoint; + /// Denotes the zoom level of the zoomable widget. + final double zoomLevel; + + /// Denotes the focalLatLng of the zoomable widget. + final MapLatLng focalLatLng; + + /// Used to coordinate with [MapShapeLayer] and its elements. + final MapController controller; + + /// Enables zooming and panning in [MapShapeLayer] and [MapTileLayer]. + final MapZoomPanBehavior behavior; + + /// Called whenever zooming is happening. + final WillZoomCallback? onWillZoom; + + /// Called whenever panning is happening. + final WillPanCallback? onWillPan; + + @override + _BehaviorViewState createState() => _BehaviorViewState(); +} + +class _BehaviorViewState extends State { + Size? _size; + late double _currentZoomLevel; + late MapLatLng _currentFocalLatLng; + Rect? _initialRect; + + Offset _pixelFromLatLng(MapLatLng latLng, double scale) { + final double latitude = + latLng.latitude.clamp(minimumLatitude, maximumLatitude); + final double longitude = + latLng.longitude.clamp(minimumLongitude, maximumLongitude); + return pixelFromLatLng( + latitude, longitude, Size.square(getTotalTileWidth(scale))); + } + + MapLatLng _pixelToLatLng(Offset point, double scale) { + return pixelToLatLng(point, Size.square(getTotalTileWidth(scale))); + } + + void _invokeOnZooming({ + required Offset localFocalPoint, + required Offset globalFocalPoint, + required double newZoomLevel, + required MapLatLng newFocalLatLng, + required double scale, + required Offset? pinchCenter, + }) { + newZoomLevel = newZoomLevel.clamp( + widget.behavior.minZoomLevel, widget.behavior.maxZoomLevel); + final Rect previousVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng( + widget.behavior.focalLatLng!, widget.behavior.zoomLevel), + width: _size!.width, + height: _size!.height, + ); + final MapLatLngBounds previousVisibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(previousVisibleBounds.topRight, widget.behavior.zoomLevel), + _pixelToLatLng( + previousVisibleBounds.bottomLeft, widget.behavior.zoomLevel), + ); + widget.controller + ..localScale = scale + ..pinchCenter = pinchCenter ?? Offset.zero; + + final Rect newVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), + width: _size!.width, + height: _size!.height, + ); + final MapLatLngBounds newVisibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, newZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, newZoomLevel), + ); + + final MapZoomDetails zoomDetails = MapZoomDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: widget.behavior.zoomLevel, + newZoomLevel: newZoomLevel, + previousVisibleBounds: previousVisibleLatLngBounds, + newVisibleBounds: newVisibleLatLngBounds, + focalLatLng: newFocalLatLng, + ); + if (widget.onWillZoom == null || widget.onWillZoom!(zoomDetails)) { + widget.behavior.onZooming(zoomDetails); + } + } + + void _invokeOnPanning({ + required Offset localFocalPoint, + required Offset globalFocalPoint, + required double newZoomLevel, + required MapLatLng newFocalLatLng, + required Offset delta, + }) { + final Rect previousVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng( + widget.behavior.focalLatLng!, widget.behavior.zoomLevel), + width: _size!.width, + height: _size!.height, + ); + final MapLatLngBounds previousVisibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(previousVisibleBounds.topRight, widget.behavior.zoomLevel), + _pixelToLatLng( + previousVisibleBounds.bottomLeft, widget.behavior.zoomLevel), + ); + + final Rect newVisibleBounds = Rect.fromCenter( + center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), + width: _size!.width, + height: _size!.height, + ); + final MapLatLngBounds newVisibleLatLngBounds = MapLatLngBounds( + _pixelToLatLng(newVisibleBounds.topRight, newZoomLevel), + _pixelToLatLng(newVisibleBounds.bottomLeft, newZoomLevel), + ); + + final MapPanDetails panDetails = MapPanDetails( + globalFocalPoint: globalFocalPoint, + localFocalPoint: localFocalPoint, + zoomLevel: widget.behavior.zoomLevel, + delta: delta, + previousVisibleBounds: previousVisibleLatLngBounds, + newVisibleBounds: newVisibleLatLngBounds, + focalLatLng: newFocalLatLng, + ); + widget.controller.visibleLatLngBounds = panDetails.newVisibleBounds; + if (widget.onWillPan == null || widget.onWillPan!(panDetails)) { + widget.behavior.onPanning(panDetails); + } + } + + void _handleZoomableChange(ZoomPanDetails details) { + final Offset focalPoint = Offset( + (_size!.width / 2) - details.actualRect.left, + (_size!.height / 2) - details.actualRect.top); + final MapLatLng newFocalLatLng = pixelToLatLng( + focalPoint, Size.square(getTotalTileWidth(details.newZoomLevel))); + _currentZoomLevel = details.newZoomLevel; + _currentFocalLatLng = newFocalLatLng; + + if (widget.behavior._zoomController!.actionType == ActionType.pinch || + widget.behavior._zoomController!.actionType == ActionType.tap) { + _invokeOnZooming( + localFocalPoint: details.localFocalPoint, + globalFocalPoint: details.globalFocalPoint, + newZoomLevel: _currentZoomLevel, + newFocalLatLng: newFocalLatLng, + scale: details.scale, + pinchCenter: details.pinchCenter, + ); + } else if (widget.behavior._zoomController!.actionType == ActionType.pan) { + _invokeOnPanning( + localFocalPoint: details.localFocalPoint, + globalFocalPoint: details.globalFocalPoint, + newZoomLevel: _currentZoomLevel, + newFocalLatLng: newFocalLatLng, + delta: details.actualRect.topLeft - details.previousRect.topLeft, + ); + } + } + + void _handleZoomableEnd(ZoomPanDetails details) { + widget.controller + ..isInInteractive = false + ..normalize = Offset.zero + ..panDistance = Offset.zero + ..gesture = null + ..localScale = 1.0 + ..notifyRefreshListeners(); + } + + Rect _getInitialRect() { + widget.behavior._zoomController!.parentRect = Offset.zero & _size!; + final double tileSize = getTotalTileWidth(_currentZoomLevel); + final Offset center = + _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); + final Rect initialRect = + Offset(_size!.width / 2 - center.dx, _size!.height / 2 - center.dy) & + Size.square(tileSize); + return initialRect; + } + + @override + void initState() { + _currentFocalLatLng = widget.focalLatLng; + _currentZoomLevel = widget.zoomLevel; + widget.behavior._zoomLevel = _currentZoomLevel; + widget.behavior._focalLatLng = _currentFocalLatLng; + super.initState(); + } + + @override + void dispose() { + widget.behavior + .._zoomController?.dispose() + .._zoomController = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size newSize = getBoxSize(constraints); + if (_size == null || _size != newSize) { + _size = newSize; + _currentFocalLatLng = widget.focalLatLng; + _currentZoomLevel = widget.zoomLevel; + widget.behavior + .._zoomLevel = _currentZoomLevel + .._focalLatLng = _currentFocalLatLng; + _initialRect = _getInitialRect(); + } + + return Zoomable( + initialZoomLevel: widget.behavior.zoomLevel, + initialRect: _initialRect!, + minZoomLevel: widget.behavior.minZoomLevel, + maxZoomLevel: widget.behavior.maxZoomLevel, + zoomController: widget.behavior._zoomController!, + enablePinching: widget.behavior.enablePinching, + enablePanning: widget.behavior.enablePanning, + enableDoubleTapZooming: widget.behavior.enableDoubleTapZooming, + onUpdate: _handleZoomableChange, + onComplete: _handleZoomableEnd, + frictionCoefficient: 0.009, + child: BehaviorViewRenderObjectWidget( + controller: widget.controller, + zoomPanBehavior: widget.behavior, + ), + ); + }, + ); + } } /// Render object widget of the internal widget which handles @@ -2752,7 +3136,7 @@ class BehaviorViewRenderObjectWidget extends LeafRenderObjectWidget { } } -class _RenderBehaviorView extends RenderBox { +class _RenderBehaviorView extends RenderBox implements MouseTrackerAnnotation { _RenderBehaviorView({ required MapController listener, required MapZoomPanBehavior zoomPanBehavior, @@ -2763,6 +3147,7 @@ class _RenderBehaviorView extends RenderBox { } MapController controller; + late bool _validForMouseTracker; MapZoomPanBehavior get zoomPanBehavior => _zoomPanBehavior; MapZoomPanBehavior _zoomPanBehavior; @@ -2779,23 +3164,57 @@ class _RenderBehaviorView extends RenderBox { markNeedsPaint(); } + void _handleEnter(PointerEnterEvent event) { + zoomPanBehavior.handleEvent(event); + } + + void _handleExit(PointerExitEvent event) { + zoomPanBehavior.handleEvent(event); + } + @override void attach(PipelineOwner owner) { super.attach(owner); + _validForMouseTracker = true; controller.addListener(_handleBehaviorChange); } @override void detach() { + _validForMouseTracker = false; controller.removeListener(_handleBehaviorChange); super.detach(); } + @override + MouseCursor get cursor => controller.gesture == Gesture.pan + ? SystemMouseCursors.grabbing + : SystemMouseCursors.basic; + + @override + PointerEnterEventListener? get onEnter => _handleEnter; + + @override + PointerExitEventListener? get onExit => _handleExit; + + @override + bool get validForMouseTracker => _validForMouseTracker; + @override bool get isRepaintBoundary => true; @override - bool hitTestSelf(Offset position) => false; + bool hitTest(BoxHitTestResult result, {required Offset position}) { + if (size.contains(position)) { + result.add(BoxHitTestEntry(this, position)); + } + return false; + } + + @override + void handleEvent(PointerEvent event, HitTestEntry entry) { + zoomPanBehavior.handleEvent(event); + } @override void performLayout() { diff --git a/packages/syncfusion_flutter_maps/lib/src/common.dart b/packages/syncfusion_flutter_maps/lib/src/common.dart index 68f728d30..ecf0ed771 100644 --- a/packages/syncfusion_flutter_maps/lib/src/common.dart +++ b/packages/syncfusion_flutter_maps/lib/src/common.dart @@ -7,6 +7,7 @@ import '../maps.dart'; import 'controller/map_controller.dart'; import 'enum.dart'; import 'layer/layer_base.dart'; +import 'layer/zoomable.dart'; import 'utils.dart'; // ignore_for_file: public_member_api_docs @@ -51,10 +52,10 @@ class TileZoomLevelDetails { late double zoomLevel; /// Provides the distance moved from the origin when doing pinch zooming. - late Offset translatePoint; + Offset translatePoint = Offset.zero; /// Represents the fractional zoom value. - late double scale; + double scale = 1.0; } class MapModel { @@ -167,12 +168,18 @@ class MapModel { class MapLayerInheritedWidget extends InheritedWidget { /// Creates [MapLayerInheritedWidget]. const MapLayerInheritedWidget( - {required Widget child, required this.controller, this.sublayers}) + {required Widget child, + required this.controller, + this.zoomController, + this.sublayers}) : super(child: child); /// Creates [MapController]. final MapController controller; + /// Creates [ZoomableController]. + final ZoomableController? zoomController; + /// Collection of [MapShapeSublayer], [MapLineLayer], [MapPolylineLayer], /// [MapPolygonLayer], [MapCircleLayer], and [MapArcLayer]. final List? sublayers; diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart index 593827f5d..1881de1f5 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/layer_controller.dart @@ -1,5 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import '../common.dart'; diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart b/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart index a38a1b68f..ed9e69989 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/map_controller.dart @@ -30,6 +30,7 @@ class MapController { ObserverList? _refreshListeners = ObserverList(); ObserverList? _resetListeners = ObserverList(); ObserverList? _toggledListeners = ObserverList(); + ObserverList? _zoomPanListeners = ObserverList(); double shapeLayerSizeFactor = 1.0; double localScale = 1.0; @@ -86,6 +87,20 @@ class MapController { } } + void addZoomPanListener(VoidCallback listener) { + _zoomPanListeners?.add(listener); + } + + void removeZoomPanListener(VoidCallback listener) { + _zoomPanListeners?.remove(listener); + } + + void notifyZoomPanListeners() { + for (final VoidCallback listener in _zoomPanListeners!) { + listener(); + } + } + bool wasToggled(MapModel model) { return toggledIndices.contains(model.legendMapperIndex); } @@ -256,6 +271,7 @@ class MapController { void dispose() { _listeners = null; _toggledListeners = null; + _zoomPanListeners = null; _zoomingListeners = null; _panningListeners = null; _resetListeners = null; diff --git a/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart b/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart index b8652dae6..76fbaf428 100644 --- a/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart +++ b/packages/syncfusion_flutter_maps/lib/src/controller/map_provider.dart @@ -1,11 +1,10 @@ import 'dart:convert' show utf8; import 'dart:typed_data' show Uint8List; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:http/http.dart' as http; -import 'package:flutter/material.dart'; - /// Converts the given source file to future string based on source type. abstract class MapProvider { /// Abstract const constructor. This constructor enables subclasses to provide diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart index b4c7411d8..20e78c2a0 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/bubble.dart @@ -237,6 +237,13 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { markNeedsPaint(); } + void _handleZoomPanChange() { + if (_currentHoverItem != null) { + onExit(); + } + markNeedsPaint(); + } + void _handleRefresh() { markNeedsPaint(); } @@ -290,15 +297,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { Color _getHoverFillColor(double opacity, Color defaultColor, MapModel model) { final Color bubbleColor = model.bubbleColor ?? defaultColor; - final bool canAdjustHoverOpacity = (model.bubbleColor != null && - double.parse(model.bubbleColor!.opacity.toStringAsFixed(2)) != - hoverColorOpacity) || - _bubbleSettings.color!.opacity != hoverColorOpacity; return _themeData.bubbleHoverColor != null && _themeData.bubbleHoverColor != Colors.transparent ? _themeData.bubbleHoverColor! - : bubbleColor.withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + : getSaturatedColor(bubbleColor); } @override @@ -316,6 +318,7 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { hoverBubbleAnimationController.addListener(markNeedsPaint); if (controller != null) { controller! + ..addZoomPanListener(_handleZoomPanChange) ..addToggleListener(_handleToggleChange) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) @@ -383,14 +386,10 @@ class RenderMapBubble extends ShapeLayerChildRenderBoxBase { Color _getHoverStrokeColor() { final Color bubbleStrokeColor = _bubbleSettings.strokeColor!; - final bool canAdjustHoverOpacity = - double.parse(bubbleStrokeColor.opacity.toStringAsFixed(2)) != - hoverColorOpacity; - return _themeData.bubbleHoverStrokeColor != null && - _themeData.bubbleHoverStrokeColor != Colors.transparent + return (_themeData.bubbleHoverStrokeColor != null && + _themeData.bubbleHoverStrokeColor != Colors.transparent) ? _themeData.bubbleHoverStrokeColor! - : bubbleStrokeColor.withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + : getSaturatedColor(bubbleStrokeColor); } void _updateToggledBubbleTweenColor() { diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart b/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart index 9e0168a92..64f1cc674 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/data_label.dart @@ -162,6 +162,7 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { dataLabelAnimationController.addListener(markNeedsPaint); if (controller != null) { controller! + ..addZoomPanListener(_handleZoomPanChange) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addRefreshListener(_handleRefresh) @@ -191,6 +192,10 @@ class _RenderMapDataLabel extends ShapeLayerChildRenderBoxBase { markNeedsPaint(); } + void _handleZoomPanChange() { + markNeedsPaint(); + } + void _handleRefresh() { markNeedsPaint(); } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart index 18a813cff..3fc4fc518 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/legend.dart @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:syncfusion_flutter_core/core.dart'; import 'package:syncfusion_flutter_core/legend_internal.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_maps/maps.dart'; import 'package:syncfusion_flutter_maps/src/controller/map_controller.dart'; import '../common.dart'; @@ -542,8 +543,8 @@ class MapLegend extends DiagnosticableTree { /// icon will have the default color. /// /// See also: - /// * [legend], to enable the legend toggle interaction and customize - /// the appearance of the legend items. + /// * [MapShapeLayer.legend], to enable the legend toggle interaction and + /// customize the appearance of the legend items. final MapElement source; /// Sets a title for the legend. @@ -1888,7 +1889,7 @@ class MapLegend extends DiagnosticableTree { /// Returns a widget for the given value. /// - /// The pointer is used to indicate the exact colour of the hovering + /// The pointer is used to indicate the exact color of the hovering /// shape or bubble on the segment. /// /// The [pointerBuilder] will be called when the user interacts with the diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart index 4b1f9150f..f95bd139d 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/marker.dart @@ -4,9 +4,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -// ignore: unused_import import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_core/core.dart'; +import 'package:syncfusion_flutter_core/core.dart' as core; import 'package:syncfusion_flutter_core/theme.dart'; import '../../maps.dart'; @@ -74,6 +73,10 @@ class _RenderMarkerContainer extends RenderStack { markNeedsLayout(); } + void _handleZoomPanChange() { + markNeedsLayout(); + } + void _handleReset() { markNeedsLayout(); } @@ -86,6 +89,7 @@ class _RenderMarkerContainer extends RenderStack { void attach(PipelineOwner owner) { super.attach(owner); controller + ..addZoomPanListener(_handleZoomPanChange) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(_handleReset) @@ -95,6 +99,7 @@ class _RenderMarkerContainer extends RenderStack { @override void detach() { controller + ..removeZoomPanListener(_handleZoomPanChange) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(_handleReset) @@ -130,6 +135,18 @@ class _RenderMarkerContainer extends RenderStack { } childParentData.offset -= Offset(marker.size.width / 2, marker.size.height / 2); + if (marker.alignment != Alignment.center) { + final Alignment effectiveAlignment = + marker.alignment.resolve(textDirection); + childParentData.offset -= Offset( + effectiveAlignment.x * marker.size.width / 2, + effectiveAlignment.y * marker.size.height / 2); + } + + if (marker.offset != Offset.zero) { + childParentData.offset += Offset(marker.offset.dx, marker.offset.dy); + } + child = childParentData.nextSibling; } } @@ -232,6 +249,8 @@ class MapMarker extends SingleChildRenderObjectWidget { required this.latitude, required this.longitude, this.size, + this.alignment = Alignment.center, + this.offset = Offset.zero, this.iconColor, this.iconStrokeColor, this.iconStrokeWidth, @@ -443,6 +462,144 @@ class MapMarker extends SingleChildRenderObjectWidget { /// updating the markers. final Size? size; + /// Sets the alignment for the marker on the map. + /// + /// Defaults to [Alignment.center]. + /// + /// ```dart + /// late List data; + /// late MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// data = const [ + /// Model('Brazil', -14.235004, -51.92528), + /// Model('Germany', 51.16569, 10.451526), + /// Model('Australia', -25.274398, 133.775136), + /// Model('India', 20.593684, 78.96288), + /// Model('Russia', 61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// initialMarkersCount: 5, + /// markerBuilder: (BuildContext context, int index) { + /// return MapMarker( + /// latitude: data[index].latitude, + /// longitude: data[index].longitude, + /// alignment: Alignment.topLeft, + /// ); + /// }, + /// ), + /// ], + /// ), + /// ), + /// ) + /// ), + /// ); + /// } + /// + /// class Model { + /// const Model(this.country, this.latitude, this.longitude); + /// + /// final String country; + /// final double latitude; + /// final double longitude; + /// } + /// ``` + /// See also: + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. + final AlignmentGeometry alignment; + + /// Places the marker position in additional to the given offset. + /// + /// Defaults to Offset.zero. + /// + /// ```dart + /// late List data; + /// late MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// data = const [ + /// Model('Brazil', -14.235004, -51.92528), + /// Model('Germany', 51.16569, 10.451526), + /// Model('Australia', -25.274398, 133.775136), + /// Model('India', 20.593684, 78.96288), + /// Model('Russia', 61.52401, 105.318756) + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/world_map.json', + /// shapeDataField: 'name', + /// dataCount: data.length, + /// primaryValueMapper: (int index) => data[index].country, + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: Center( + /// child: Container( + /// height: 350, + /// child: Padding( + /// padding: EdgeInsets.only(left: 15, right: 15), + /// child: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// initialMarkersCount: 5, + /// markerBuilder: (BuildContext context, int index) { + /// return MapMarker( + /// latitude: data[index].latitude, + /// longitude: data[index].longitude, + /// offset: Offset(20.0, 10.0), + /// ); + /// }, + /// ), + /// ], + /// ), + /// ), + /// ) + /// ), + /// ); + /// } + /// + /// class Model { + /// const Model(this.country, this.latitude, this.longitude); + /// + /// final String country; + /// final double latitude; + /// final double longitude; + /// } + /// ``` + /// See also: + /// * [MapShapeLayerController], [MapTileLayerController] for dynamically + /// updating the markers. + final Offset offset; + /// Sets the icon color for the marker. /// /// ```dart @@ -721,6 +878,8 @@ class MapMarker extends SingleChildRenderObjectWidget { longitude: longitude, latitude: latitude, markerSize: size, + alignment: alignment, + offset: offset, iconColor: iconColor, iconStrokeColor: iconStrokeColor, iconStrokeWidth: iconStrokeWidth, @@ -736,6 +895,8 @@ class MapMarker extends SingleChildRenderObjectWidget { ..longitude = longitude ..latitude = latitude ..markerSize = size + ..alignment = alignment + ..offset = offset ..iconColor = iconColor ..iconStrokeColor = iconStrokeColor ..iconStrokeWidth = iconStrokeWidth @@ -751,6 +912,8 @@ class _RenderMapMarker extends RenderProxyBox required double longitude, required double latitude, required Size? markerSize, + required AlignmentGeometry alignment, + required Offset offset, required Color? iconColor, required Color? iconStrokeColor, required double? iconStrokeWidth, @@ -760,6 +923,8 @@ class _RenderMapMarker extends RenderProxyBox }) : _longitude = longitude, _latitude = latitude, _markerSize = markerSize, + _alignment = alignment, + _offset = offset, _iconColor = iconColor, _iconStrokeColor = iconStrokeColor, _iconStrokeWidth = iconStrokeWidth, @@ -803,6 +968,26 @@ class _RenderMapMarker extends RenderProxyBox markNeedsLayout(); } + AlignmentGeometry get alignment => _alignment; + AlignmentGeometry _alignment; + set alignment(AlignmentGeometry value) { + if (_alignment == value) { + return; + } + _alignment = value; + markNeedsLayout(); + } + + Offset get offset => _offset; + Offset _offset; + set offset(Offset value) { + if (_offset == value) { + return; + } + _offset = value; + markNeedsLayout(); + } + Color? get iconColor => _iconColor; Color? _iconColor; set iconColor(Color? value) { @@ -960,16 +1145,16 @@ class _RenderMapMarker extends RenderProxyBox } } - ShapeMarkerType _getShapeType() { + core.ShapeMarkerType _getEffectiveShapeType() { switch (_iconType) { case MapIconType.circle: - return ShapeMarkerType.circle; + return core.ShapeMarkerType.circle; case MapIconType.diamond: - return ShapeMarkerType.diamond; + return core.ShapeMarkerType.diamond; case MapIconType.rectangle: - return ShapeMarkerType.rectangle; + return core.ShapeMarkerType.rectangle; case MapIconType.triangle: - return ShapeMarkerType.triangle; + return core.ShapeMarkerType.triangle; } } @@ -987,10 +1172,10 @@ class _RenderMapMarker extends RenderProxyBox @override void paint(PaintingContext context, Offset offset) { if (child == null) { - ShapePainter.paint( + core.paint( canvas: context.canvas, rect: paintBounds, - shapeType: _getShapeType(), + shapeType: _getEffectiveShapeType(), paint: Paint()..color = _iconColor ?? _themeData.markerIconColor, borderPaint: _getBorderPaint()); } else { diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart index ea8583dac..b2e5e34b8 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/toolbar.dart @@ -119,6 +119,10 @@ class _ToolbarItemState extends State<_ToolbarItem> { _updateToolbarItemState(); } + void _handleZoomPanChange() { + _updateToolbarItemState(); + } + void _handleReset() { _updateToolbarItemState(); } @@ -155,6 +159,7 @@ class _ToolbarItemState extends State<_ToolbarItem> { void initState() { if (widget.controller != null) { widget.controller! + ..addZoomPanListener(_handleZoomPanChange) ..addZoomingListener(_handleZooming) ..addResetListener(_handleReset) ..addRefreshListener(_handleRefresh); @@ -272,12 +277,6 @@ class _ToolbarItemState extends State<_ToolbarItem> { newZoomLevel = newZoomLevel.clamp(widget.zoomPanBehavior.minZoomLevel, widget.zoomPanBehavior.maxZoomLevel); - final MapZoomDetails details = MapZoomDetails( - previousZoomLevel: widget.zoomPanBehavior.zoomLevel, - newZoomLevel: newZoomLevel, - ); - if (widget.onWillZoom == null || widget.onWillZoom!(details)) { - widget.zoomPanBehavior.onZooming(details); - } + widget.zoomPanBehavior.zoomLevel = newZoomLevel; } } diff --git a/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart index 1cb52a46d..54c080e7e 100644 --- a/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart +++ b/packages/syncfusion_flutter_maps/lib/src/elements/tooltip.dart @@ -198,7 +198,6 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { static const double tooltipTriangleHeight = 7; final _MapTooltipState _state; final _TooltipShape _tooltipShape = const _TooltipShape(); - final Duration _waitDuration = const Duration(seconds: 3); final Duration _hideDeferDuration = const Duration(milliseconds: 500); late Animation _scaleAnimation; Timer? _showTimer; @@ -287,9 +286,13 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { } void _startShowTimer() { + if (_tooltipSettings.hideDelay == double.infinity) { + return; + } _showTimer?.cancel(); if (_pointerKind == PointerKind.touch) { - _showTimer = Timer(_waitDuration, hideTooltip); + _showTimer = Timer( + Duration(seconds: _tooltipSettings.hideDelay.toInt()), hideTooltip); } } @@ -375,6 +378,8 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _scaleAnimation.addListener(markNeedsPaint); if (_state.widget.controller != null) { _state.widget.controller! + ..addZoomPanListener(_handleReset) + ..addRefreshListener(_handleReset) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(_handleReset); @@ -388,6 +393,8 @@ class _RenderMapTooltip extends ShapeLayerChildRenderBoxBase { _scaleAnimation.removeListener(markNeedsPaint); if (_state.widget.controller != null) { _state.widget.controller! + ..removeZoomPanListener(_handleReset) + ..removeRefreshListener(_handleReset) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(_handleReset); diff --git a/packages/syncfusion_flutter_maps/lib/src/enum.dart b/packages/syncfusion_flutter_maps/lib/src/enum.dart index f9e8c1c43..b0d4055c7 100644 --- a/packages/syncfusion_flutter_maps/lib/src/enum.dart +++ b/packages/syncfusion_flutter_maps/lib/src/enum.dart @@ -77,10 +77,10 @@ enum MapToolbarPosition { /// Option to place the labels either between the bars or on the bar in bar /// legend. enum MapLegendLabelsPlacement { - /// [MapLegendLabelPlacement.Item] places labels in the center of the bar. + /// [MapLegendLabelsPlacement.onItem] places labels in the center of the bar. onItem, - /// [MapLegendLabelPlacement.betweenItems] places labels in-between two bars. + /// [MapLegendLabelsPlacement.betweenItems] places labels in-between two bars. betweenItems } diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart b/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart index a7813347e..1c36b4e75 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/layer_base.dart @@ -1,5 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; import '../../maps.dart'; import '../layer/shape_layer.dart'; @@ -13,6 +14,7 @@ abstract class MapLayer extends StatelessWidget { /// Creates a [MapLayer]. const MapLayer({ Key? key, + this.initialLatLngBounds, this.sublayers, this.initialMarkersCount = 0, this.markerBuilder, @@ -23,6 +25,18 @@ abstract class MapLayer extends StatelessWidget { this.onWillPan, }) : super(key: key); + /// Option to set the LatLng bounds initially for the tile or shape layer. + /// + /// This property cannot be updated dynamically. + /// + /// The map will be rendered based on value of [initialLatLngBounds] + /// property in the visible bounds. + /// + /// See also: + /// * [MapZoomPanBehavior.latLngBounds], for dynamically updating the + /// LatLng bounds. + final MapLatLngBounds? initialLatLngBounds; + /// Collection of [MapShapeSublayer], [MapLineLayer], [MapPolylineLayer], /// [MapPolygonLayer], [MapCircleLayer], and [MapArcLayer]. /// 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 9beb3363e..335afa24a 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/shape_layer.dart @@ -4,7 +4,6 @@ import 'dart:math'; import 'dart:typed_data' show Uint8List; import 'dart:ui'; -import 'package:collection/collection.dart' show MapEquality; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -857,18 +856,16 @@ class MapShapeSource extends DiagnosticableTree { } class _ShapeBounds { - _ShapeBounds( - {this.minLongitude, - this.minLatitude, - this.maxLongitude, - this.maxLatitude}); + _ShapeBounds({ + this.minLongitude, + this.minLatitude, + this.maxLongitude, + this.maxLatitude, + }); num? minLongitude; - num? minLatitude; - num? maxLongitude; - num? maxLatitude; _ShapeBounds get empty => _ShapeBounds( @@ -880,11 +877,8 @@ class _ShapeBounds { class _ShapeFileData { Map? decodedJsonData; - late Map mapDataSource; - late _ShapeBounds bounds; - late MapModel initialSelectedModel; void reset() { @@ -898,9 +892,8 @@ Future<_ShapeFileData> _retrieveDataFromShapeFile( MapProvider provider, String? shapeDataField, _ShapeFileData shapeFileData, - bool isShapeFileDecoded, bool isSublayer) async { - if (isShapeFileDecoded) { + if (shapeFileData.mapDataSource.isNotEmpty) { return shapeFileData; } final String assertBundleData = await provider.loadString(); @@ -1144,6 +1137,7 @@ class GeoJSONLayer extends StatefulWidget { const GeoJSONLayer({ required this.source, required this.controller, + this.initialLatLngBounds, required this.initialMarkersCount, required this.markerBuilder, required this.shapeTooltipBuilder, @@ -1170,6 +1164,7 @@ class GeoJSONLayer extends StatefulWidget { final MapShapeSource source; final MapShapeLayerController? controller; + final MapLatLngBounds? initialLatLngBounds; final int initialMarkersCount; final MapMarkerBuilder? markerBuilder; final IndexedWidgetBuilder? shapeTooltipBuilder; @@ -1200,11 +1195,11 @@ class GeoJSONLayer extends StatefulWidget { class _GeoJSONLayerState extends State with TickerProviderStateMixin { late GlobalKey bubbleKey; - late _ShapeFileData shapeFileData; - late SfMapsThemeData _mapsThemeData; late MapLayerInheritedWidget ancestor; // Converts the given source file to future string based on source type. late MapProvider _provider; + late Future<_ShapeFileData> _computeDataSource; + late _ShapeFileData shapeFileData; late AnimationController toggleAnimationController; late AnimationController _hoverBubbleAnimationController; @@ -1221,146 +1216,116 @@ class _GeoJSONLayerState extends State double? minBubbleValue; double? maxBubbleValue; - bool _isShapeFileDecoded = false; bool _shouldUpdateMapDataSource = true; - bool isDesktop = false; bool _hasSublayer = false; bool isSublayer = false; MapController? _controller; - List get _geoJSONLayerChildren { - final List children = []; - if (_hasSublayer) { - children.add(_sublayerContainer); - } - if (_markers != null && _markers!.isNotEmpty) { - children.add(_markerContainer); - } - - return children; - } - - Widget get _shapeLayerRenderObjectWidget => _GeoJSONLayerRenderObjectWidget( - controller: _controller!, - mapDataSource: shapeFileData.mapDataSource, - mapSource: widget.source, - selectedIndex: widget.selectedIndex, - legend: widget.legend, - selectionSettings: widget.selectionSettings, - zoomPanBehavior: widget.zoomPanBehavior, - bubbleSettings: widget.bubbleSettings.copyWith( - color: _mapsThemeData.bubbleColor, - strokeColor: _mapsThemeData.bubbleStrokeColor, - strokeWidth: _mapsThemeData.bubbleStrokeWidth), - themeData: _mapsThemeData, - state: this, - children: _geoJSONLayerChildren, - ); - - Widget get _bubbleWidget => MapBubble( - key: bubbleKey, - controller: _controller, - source: widget.source, - mapDataSource: shapeFileData.mapDataSource, - bubbleSettings: widget.bubbleSettings.copyWith( - color: _mapsThemeData.bubbleColor, - strokeColor: _mapsThemeData.bubbleStrokeColor, - strokeWidth: _mapsThemeData.bubbleStrokeWidth), - legend: widget.legend, - showDataLabels: widget.showDataLabels, - themeData: _mapsThemeData, - bubbleAnimationController: bubbleAnimationController, - dataLabelAnimationController: dataLabelAnimationController, - toggleAnimationController: toggleAnimationController, - hoverBubbleAnimationController: _hoverBubbleAnimationController, - ); - - Widget get _dataLabelWidget => MapDataLabel( - controller: _controller, - source: widget.source, - mapDataSource: shapeFileData.mapDataSource, - settings: widget.dataLabelSettings, - effectiveTextStyle: Theme.of(context).textTheme.caption!.merge( - widget.dataLabelSettings.textStyle ?? - _mapsThemeData.dataLabelTextStyle), - themeData: _mapsThemeData, - dataLabelAnimationController: dataLabelAnimationController, - ); - - Widget get _sublayerContainer => - SublayerContainer(ancestor: ancestor, children: widget.sublayers!); - - Widget get _markerContainer => MarkerContainer( - controller: _controller!, - markerTooltipBuilder: widget.markerTooltipBuilder, - sublayer: widget.sublayerAncestor, - ancestor: ancestor, - children: _markers, - ); - - Widget get _behaviorViewRenderObjectWidget => BehaviorViewRenderObjectWidget( - controller: _controller!, zoomPanBehavior: widget.zoomPanBehavior!); - - Widget get _toolbarWidget => MapToolbar( - controller: _controller, - onWillZoom: widget.onWillZoom, - zoomPanBehavior: widget.zoomPanBehavior!, - ); - - Widget get _tooltipWidget => MapTooltip( - key: _controller!.tooltipKey, - controller: _controller, - mapSource: widget.source, - sublayers: widget.sublayers, - tooltipSettings: widget.tooltipSettings, - shapeTooltipBuilder: widget.shapeTooltipBuilder, - bubbleTooltipBuilder: widget.bubbleTooltipBuilder, - markerTooltipBuilder: widget.markerTooltipBuilder, - themeData: _mapsThemeData, - ); - - Widget get _shapeLayerWithElements { - final List children = []; - children.add(_shapeLayerRenderObjectWidget); - if (widget.source.bubbleSizeMapper != null) { - children.add(_bubbleWidget); - } - - if (widget.showDataLabels) { - children.add(_dataLabelWidget); - } - - if (!isSublayer) { - if (widget.zoomPanBehavior != null) { - children.add(_behaviorViewRenderObjectWidget); - if (widget.zoomPanBehavior!.showToolbar && isDesktop) { - children.add(_toolbarWidget); - } - } - - if (_hasTooltipBuilder()) { - children.add(_tooltipWidget); - } - } - - return ClipRect(child: Stack(children: children)); - } + Widget _buildGeoJSONLayer(SfMapsThemeData themeData, bool isDesktop) { + Widget current = ClipRect( + child: Stack( + children: [ + _GeoJSONLayerRenderObjectWidget( + controller: _controller!, + initialLatLngBounds: widget.initialLatLngBounds, + mapDataSource: shapeFileData.mapDataSource, + mapSource: widget.source, + selectedIndex: widget.selectedIndex, + legend: widget.legend, + selectionSettings: widget.selectionSettings, + zoomPanBehavior: widget.zoomPanBehavior, + bubbleSettings: widget.bubbleSettings.copyWith( + color: themeData.bubbleColor, + strokeColor: themeData.bubbleStrokeColor, + strokeWidth: themeData.bubbleStrokeWidth), + themeData: themeData, + isDesktop: isDesktop, + state: this, + children: [ + if (_hasSublayer) + SublayerContainer( + ancestor: ancestor, children: widget.sublayers!), + if (_markers != null && _markers!.isNotEmpty) + MarkerContainer( + controller: _controller!, + markerTooltipBuilder: widget.markerTooltipBuilder, + sublayer: widget.sublayerAncestor, + ancestor: ancestor, + children: _markers) + ], + ), + if (widget.source.bubbleSizeMapper != null) + MapBubble( + key: bubbleKey, + controller: _controller, + source: widget.source, + mapDataSource: shapeFileData.mapDataSource, + bubbleSettings: widget.bubbleSettings.copyWith( + color: themeData.bubbleColor, + strokeColor: themeData.bubbleStrokeColor, + strokeWidth: themeData.bubbleStrokeWidth), + legend: widget.legend, + showDataLabels: widget.showDataLabels, + themeData: themeData, + bubbleAnimationController: bubbleAnimationController, + dataLabelAnimationController: dataLabelAnimationController, + toggleAnimationController: toggleAnimationController, + hoverBubbleAnimationController: _hoverBubbleAnimationController, + ), + if (widget.showDataLabels) + MapDataLabel( + controller: _controller, + source: widget.source, + mapDataSource: shapeFileData.mapDataSource, + settings: widget.dataLabelSettings, + effectiveTextStyle: Theme.of(context).textTheme.caption!.merge( + widget.dataLabelSettings.textStyle ?? + themeData.dataLabelTextStyle), + themeData: themeData, + dataLabelAnimationController: dataLabelAnimationController, + ), + if (widget.zoomPanBehavior != null) + BehaviorViewRenderObjectWidget( + controller: _controller!, + zoomPanBehavior: widget.zoomPanBehavior!, + ), + if (widget.zoomPanBehavior != null && + widget.zoomPanBehavior!.showToolbar && + isDesktop) + MapToolbar( + controller: _controller, + onWillZoom: widget.onWillZoom, + zoomPanBehavior: widget.zoomPanBehavior!), + if (_hasTooltipBuilder()) + MapTooltip( + key: _controller!.tooltipKey, + controller: _controller, + mapSource: widget.source, + sublayers: widget.sublayers, + tooltipSettings: widget.tooltipSettings, + shapeTooltipBuilder: widget.shapeTooltipBuilder, + bubbleTooltipBuilder: widget.bubbleTooltipBuilder, + markerTooltipBuilder: widget.markerTooltipBuilder, + themeData: themeData, + ), + ], + ), + ); - Widget get _shapeLayerWithLegend { if (widget.legend != null) { - return Legend( + current = Legend( colorMappers: _getLegendSource(), dataSource: shapeFileData.mapDataSource, legend: widget.legend!, pointerController: _pointerController, controller: _controller, - themeData: _mapsThemeData, - child: _shapeLayerWithElements, + themeData: themeData, + child: current, ); } - return _shapeLayerWithElements; + return current; } List? _getLegendSource() { @@ -1397,96 +1362,58 @@ class _GeoJSONLayerState extends State return false; } - void _updateThemeData(BuildContext context, ThemeData themeData) { - final bool isLightTheme = _mapsThemeData.brightness == Brightness.light; - _mapsThemeData = _mapsThemeData.copyWith( + SfMapsThemeData _updateThemeData(BuildContext context, ThemeData themeData, + SfMapsThemeData mapsThemeData) { + final bool isLightTheme = mapsThemeData.brightness == Brightness.light; + return mapsThemeData.copyWith( layerColor: widget.color ?? (isSublayer ? (isLightTheme ? const Color.fromRGBO(198, 198, 198, 1) : const Color.fromRGBO(71, 71, 71, 1)) - : _mapsThemeData.layerColor), + : mapsThemeData.layerColor), layerStrokeColor: widget.strokeColor ?? (isSublayer ? (isLightTheme ? const Color.fromRGBO(145, 145, 145, 1) : const Color.fromRGBO(133, 133, 133, 1)) - : _mapsThemeData.layerStrokeColor), + : mapsThemeData.layerStrokeColor), layerStrokeWidth: widget.strokeWidth ?? (isSublayer ? (isLightTheme ? 0.5 : 0.25) - : _mapsThemeData.layerStrokeWidth), - shapeHoverStrokeWidth: _mapsThemeData.shapeHoverStrokeWidth ?? - _mapsThemeData.layerStrokeWidth, + : mapsThemeData.layerStrokeWidth), + shapeHoverStrokeWidth: + mapsThemeData.shapeHoverStrokeWidth ?? mapsThemeData.layerStrokeWidth, legendTextStyle: themeData.textTheme.caption! .copyWith( color: themeData.textTheme.caption!.color!.withOpacity(0.87)) - .merge(widget.legend?.textStyle ?? _mapsThemeData.legendTextStyle), - bubbleColor: widget.bubbleSettings.color ?? _mapsThemeData.bubbleColor, + .merge(widget.legend?.textStyle ?? mapsThemeData.legendTextStyle), + bubbleColor: widget.bubbleSettings.color ?? mapsThemeData.bubbleColor, bubbleStrokeColor: - widget.bubbleSettings.strokeColor ?? _mapsThemeData.bubbleStrokeColor, + widget.bubbleSettings.strokeColor ?? mapsThemeData.bubbleStrokeColor, bubbleStrokeWidth: - widget.bubbleSettings.strokeWidth ?? _mapsThemeData.bubbleStrokeWidth, - bubbleHoverStrokeWidth: _mapsThemeData.bubbleHoverStrokeWidth ?? - _mapsThemeData.bubbleStrokeWidth, + widget.bubbleSettings.strokeWidth ?? mapsThemeData.bubbleStrokeWidth, + bubbleHoverStrokeWidth: mapsThemeData.bubbleHoverStrokeWidth ?? + mapsThemeData.bubbleStrokeWidth, selectionColor: - widget.selectionSettings.color ?? _mapsThemeData.selectionColor, + widget.selectionSettings.color ?? mapsThemeData.selectionColor, selectionStrokeColor: widget.selectionSettings.strokeColor ?? - _mapsThemeData.selectionStrokeColor, + mapsThemeData.selectionStrokeColor, selectionStrokeWidth: widget.selectionSettings.strokeWidth ?? - _mapsThemeData.selectionStrokeWidth, - tooltipColor: widget.tooltipSettings.color ?? _mapsThemeData.tooltipColor, + mapsThemeData.selectionStrokeWidth, + tooltipColor: widget.tooltipSettings.color ?? mapsThemeData.tooltipColor, tooltipStrokeColor: widget.tooltipSettings.strokeColor ?? - _mapsThemeData.tooltipStrokeColor, + mapsThemeData.tooltipStrokeColor, tooltipStrokeWidth: widget.tooltipSettings.strokeWidth ?? - _mapsThemeData.tooltipStrokeWidth, - tooltipBorderRadius: _mapsThemeData.tooltipBorderRadius - .resolve(Directionality.of(context)), + mapsThemeData.tooltipStrokeWidth, + tooltipBorderRadius: + mapsThemeData.tooltipBorderRadius.resolve(Directionality.of(context)), toggledItemColor: - widget.legend?.toggledItemColor ?? _mapsThemeData.toggledItemColor, + widget.legend?.toggledItemColor ?? mapsThemeData.toggledItemColor, toggledItemStrokeColor: widget.legend?.toggledItemStrokeColor ?? - _mapsThemeData.toggledItemStrokeColor, + mapsThemeData.toggledItemStrokeColor, toggledItemStrokeWidth: widget.legend?.toggledItemStrokeWidth ?? - _mapsThemeData.toggledItemStrokeWidth, - ); - } - - Widget _buildShapeLayer() { - return FutureBuilder<_ShapeFileData>( - future: _retrieveDataFromShapeFile( - _provider, - widget.source.shapeDataField, - shapeFileData, - _isShapeFileDecoded, - isSublayer), - builder: (BuildContext context, AsyncSnapshot<_ShapeFileData> snapshot) { - if (snapshot.hasData && _isShapeFileDecoded) { - shapeFileData = snapshot.data!; - if (_shouldUpdateMapDataSource) { - minBubbleValue = null; - maxBubbleValue = null; - for (final MapModel model in shapeFileData.mapDataSource.values) { - model.reset(); - } - _bindMapsSourceIntoDataSource(); - _shouldUpdateMapDataSource = false; - } - return _shapeLayerWithLegend; - } else { - _isShapeFileDecoded = true; - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - final Size size = getBoxSize(constraints); - return Container( - width: size.width, - height: size.height, - alignment: Alignment.center, - child: widget.loadingBuilder?.call(context), - ); - }, - ); - } - }, + mapsThemeData.toggledItemStrokeWidth, ); } @@ -1676,9 +1603,28 @@ class _GeoJSONLayerState extends State }); } + void _obtainDataSource() { + _computeDataSource = _obtainDataSourceAndBindDataSource() + .then((_ShapeFileData data) => data); + } + + Future<_ShapeFileData> _obtainDataSourceAndBindDataSource() async { + shapeFileData = await _retrieveDataFromShapeFile( + _provider, widget.source.shapeDataField, shapeFileData, isSublayer); + if (_shouldUpdateMapDataSource) { + minBubbleValue = null; + maxBubbleValue = null; + for (final MapModel model in shapeFileData.mapDataSource.values) { + model.reset(); + } + _bindMapsSourceIntoDataSource(); + _shouldUpdateMapDataSource = false; + } + return shapeFileData; + } + @override void initState() { - super.initState(); _pointerController = PointerController(); bubbleKey = GlobalKey(); shapeFileData = _ShapeFileData() @@ -1716,6 +1662,8 @@ class _GeoJSONLayerState extends State widget.controller?.addListener(refreshMarkers); isSublayer = widget.sublayerAncestor != null; _provider = getSourceProvider(widget.source._path, widget.source._type); + _obtainDataSource(); + super.initState(); } @override @@ -1738,10 +1686,8 @@ class _GeoJSONLayerState extends State getSourceProvider(widget.source._path, widget.source._type); if (_provider != currentProvider) { _provider = currentProvider; - _isShapeFileDecoded = false; shapeFileData.reset(); } - if (oldWidget.controller != widget.controller) { widget.controller!._parentBox = // ignore: avoid_as @@ -1752,6 +1698,7 @@ class _GeoJSONLayerState extends State _controller!.visibleFocalLatLng = null; } + _obtainDataSource(); super.didUpdateWidget(oldWidget); } @@ -1791,19 +1738,40 @@ class _GeoJSONLayerState extends State widget.source.shapeColorMappers!.isNotEmpty); final ThemeData themeData = Theme.of(context); - _mapsThemeData = SfMapsTheme.of(context)!; - isDesktop = kIsWeb || + final bool isDesktop = kIsWeb || themeData.platform == TargetPlatform.macOS || themeData.platform == TargetPlatform.windows || themeData.platform == TargetPlatform.linux; - _updateThemeData(context, themeData); - return _buildShapeLayer(); + final SfMapsThemeData mapsThemeData = + _updateThemeData(context, themeData, SfMapsTheme.of(context)!); + + return FutureBuilder<_ShapeFileData>( + future: _computeDataSource, + builder: (BuildContext context, AsyncSnapshot<_ShapeFileData> snapshot) { + if (snapshot.hasData) { + return _buildGeoJSONLayer(mapsThemeData, isDesktop); + } else { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size size = getBoxSize(constraints); + return Container( + width: size.width, + height: size.height, + alignment: Alignment.center, + child: widget.loadingBuilder?.call(context), + ); + }, + ); + } + }, + ); } } class _GeoJSONLayerRenderObjectWidget extends Stack { _GeoJSONLayerRenderObjectWidget({ required this.controller, + required this.initialLatLngBounds, required this.mapDataSource, required this.mapSource, required this.selectedIndex, @@ -1812,25 +1780,30 @@ class _GeoJSONLayerRenderObjectWidget extends Stack { required this.selectionSettings, required this.zoomPanBehavior, required this.themeData, + required this.isDesktop, required this.state, List? children, }) : super(children: children ?? []); final MapController controller; + final MapLatLngBounds? initialLatLngBounds; final Map mapDataSource; final MapShapeSource mapSource; final int selectedIndex; + final MapLegend? legend; final MapBubbleSettings bubbleSettings; final MapSelectionSettings selectionSettings; final SfMapsThemeData themeData; + final bool isDesktop; final _GeoJSONLayerState state; - final MapLegend? legend; final MapZoomPanBehavior? zoomPanBehavior; @override RenderStack createRenderObject(BuildContext context) { return _RenderGeoJSONLayer( controller: controller, + initialZoomLevel: zoomPanBehavior?.zoomLevel ?? 1.0, + initialLatLngBounds: initialLatLngBounds, mapDataSource: mapDataSource, mapSource: mapSource, selectedIndex: selectedIndex, @@ -1839,6 +1812,7 @@ class _GeoJSONLayerRenderObjectWidget extends Stack { selectionSettings: selectionSettings, zoomPanBehavior: zoomPanBehavior, themeData: themeData, + isDesktop: isDesktop, context: context, state: state, ); @@ -1856,6 +1830,7 @@ class _GeoJSONLayerRenderObjectWidget extends Stack { ..selectionSettings = selectionSettings ..zoomPanBehavior = zoomPanBehavior ..themeData = themeData + ..isDesktop = isDesktop ..context = context; } } @@ -1864,6 +1839,8 @@ class _RenderGeoJSONLayer extends RenderStack implements MouseTrackerAnnotation { _RenderGeoJSONLayer({ required MapController controller, + required double initialZoomLevel, + required MapLatLngBounds? initialLatLngBounds, required Map mapDataSource, required MapShapeSource mapSource, required int selectedIndex, @@ -1872,9 +1849,12 @@ class _RenderGeoJSONLayer extends RenderStack required MapSelectionSettings selectionSettings, required MapZoomPanBehavior? zoomPanBehavior, required SfMapsThemeData themeData, + required this.isDesktop, required this.context, required _GeoJSONLayerState state, }) : _controller = controller, + _initialLatLngBounds = initialLatLngBounds, + _currentZoomLevel = initialZoomLevel, _mapDataSource = mapDataSource, _mapSource = mapSource, _selectedIndex = selectedIndex, @@ -1938,8 +1918,8 @@ class _RenderGeoJSONLayer extends RenderStack final _GeoJSONLayerState _state; final int _minPanDistance = 5; final MapController _controller; + final MapLatLngBounds? _initialLatLngBounds; double _actualFactor = 1.0; - double _currentZoomLevel = 1.0; double _maximumReachedScaleOnInteraction = 1.0; int _pointerCount = 0; Offset _panDistanceBeforeFlinging = Offset.zero; @@ -1947,7 +1927,9 @@ class _RenderGeoJSONLayer extends RenderStack bool _avoidPanUpdate = false; bool _isFlingAnimationActive = false; bool _doubleTapEnabled = false; + bool _isAnimationOnQueue = false; late bool _validForMouseTracker; + late double _currentZoomLevel; late Size _size; late Size _actualShapeSize; late ScaleGestureRecognizer _scaleGestureRecognizer; @@ -1989,6 +1971,7 @@ class _RenderGeoJSONLayer extends RenderStack ColorTween? _forwardSelectionColorTween; CurvedAnimation? _zoomLevelCurvedAnimation; + bool isDesktop; BuildContext context; bool get canZoom => @@ -2001,7 +1984,7 @@ class _RenderGeoJSONLayer extends RenderStack _state.widget.shapeTooltipBuilder != null || _state.widget.bubbleTooltipBuilder != null || _state.widget.onSelectionChanged != null || - (_state.isDesktop && (hasBubbleHoverColor || hasShapeHoverColor)); + (isDesktop && (hasBubbleHoverColor || hasShapeHoverColor)); bool get hasBubbleHoverColor => _themeData.bubbleHoverColor != Colors.transparent || @@ -2016,13 +1999,18 @@ class _RenderGeoJSONLayer extends RenderStack Map get mapDataSource => _mapDataSource; Map _mapDataSource; set mapDataSource(Map value) { - if (const MapEquality().equals(_mapDataSource, value)) { - return; + if (value.isNotEmpty && value.values.first.shapePath == null && hasSize) { + _mapDataSource = value; + _currentSelectedItem = null; + _prevSelectedItem = null; + _previousHoverItem = null; + _state.dataLabelAnimationController.value = 0.0; + _state.bubbleAnimationController.value = 0.0; + _refresh(); + markNeedsPaint(); + SchedulerBinding.instance + ?.addPostFrameCallback(_initiateInitialAnimations); } - - _mapDataSource = value; - _refresh(); - markNeedsPaint(); } MapShapeSource? get mapSource => _mapSource; @@ -2032,23 +2020,7 @@ class _RenderGeoJSONLayer extends RenderStack return; } - if (_mapSource != null && - value != null && - getSourceProvider(_mapSource!._path, _mapSource!._type) != - getSourceProvider(value._path, value._type)) { - _mapSource = value; - return; - } - _mapSource = value; - _currentSelectedItem = null; - _prevSelectedItem = null; - _previousHoverItem = null; - _refresh(); - markNeedsPaint(); - _state.dataLabelAnimationController.value = 0.0; - _state.bubbleAnimationController.value = 0.0; - SchedulerBinding.instance?.addPostFrameCallback(_initiateInitialAnimations); } MapBubbleSettings get bubbleSettings => _bubbleSettings; @@ -2074,12 +2046,11 @@ class _RenderGeoJSONLayer extends RenderStack set legend(MapLegend? value) { // Update [MapsShapeLayer.legend] value only when // [MapsShapeLayer.legend] property is set to shape. - if (_legend != null && _legend!.source != MapElement.shape || - _legend == value) { + if (_legend == value) { return; } _legend = value; - if (_legend!.enableToggleInteraction) { + if (_legend != null && _legend!.enableToggleInteraction) { _initializeToggledShapeTweenColors(); } markNeedsPaint(); @@ -2208,8 +2179,10 @@ class _RenderGeoJSONLayer extends RenderStack void _updateCurrentSelectedItemTween() { if (_currentSelectedItem != null && (_state.isSublayer || !_controller.wasToggled(_currentSelectedItem!))) { - _forwardSelectionColorTween!.begin = - getActualShapeColor(_currentSelectedItem!); + _forwardSelectionColorTween!.begin = (_currentHoverItem != null && + _currentInteractedElement == MapLayerElement.shape) + ? _forwardHoverColorTween.end + : getActualShapeColor(_currentSelectedItem!); } if (_prevSelectedItem != null) { @@ -2261,25 +2234,41 @@ class _RenderGeoJSONLayer extends RenderStack } Color _getHoverFillColor(MapModel model) { - final bool canAdjustHoverOpacity = - double.parse(getActualShapeColor(model).opacity.toStringAsFixed(2)) != - hoverColorOpacity; return _themeData.shapeHoverColor != null && _themeData.shapeHoverColor != Colors.transparent ? _themeData.shapeHoverColor! - : getActualShapeColor(model).withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + : getSaturatedColor(getActualShapeColor(model)); } Color _getHoverStrokeColor() { - final bool canAdjustHoverOpacity = - double.parse(_themeData.layerStrokeColor.opacity.toStringAsFixed(2)) != - hoverColorOpacity; - return _themeData.shapeHoverStrokeColor != null && - _themeData.shapeHoverStrokeColor != Colors.transparent + return (_themeData.shapeHoverStrokeColor != null && + _themeData.shapeHoverStrokeColor != Colors.transparent) ? _themeData.shapeHoverStrokeColor! - : _themeData.layerStrokeColor.withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + : getSaturatedColor(_themeData.layerStrokeColor); + } + + MapLatLngBounds _getMaxVisibleBounds(MapLatLngBounds initialBounds) { + final _ShapeBounds maxBounds = _state.shapeFileData.bounds; + double lat = initialBounds.northeast.latitude; + double lng = initialBounds.northeast.longitude; + if (initialBounds.northeast.latitude > maxBounds.maxLatitude!) { + lat = maxBounds.maxLatitude! as double; + } + if (initialBounds.northeast.longitude > maxBounds.maxLongitude!) { + lng = maxBounds.maxLongitude! as double; + } + final MapLatLng northEast = MapLatLng(lat, lng); + lat = initialBounds.southwest.latitude; + lng = initialBounds.southwest.longitude; + if (initialBounds.southwest.latitude < maxBounds.minLatitude!) { + lat = maxBounds.minLatitude! as double; + } + if (initialBounds.southwest.longitude < maxBounds.minLongitude!) { + lng = maxBounds.minLongitude! as double; + } + final MapLatLng southWest = MapLatLng(lat, lng); + + return MapLatLngBounds(northEast, southWest); } void _refresh([MapLatLng? latlng]) { @@ -2298,17 +2287,32 @@ class _RenderGeoJSONLayer extends RenderStack } } _computeActualFactor(); - _controller.shapeLayerSizeFactor = _actualFactor; + MapLatLngBounds? initialBounds = + _zoomPanBehavior?.latLngBounds ?? _initialLatLngBounds; + double zoomLevel = _currentZoomLevel; + if (latlng == null && initialBounds != null) { + initialBounds = _getMaxVisibleBounds(initialBounds); + zoomLevel = getZoomLevel( + initialBounds, _controller.layerType!, _size, _actualFactor); + latlng = getFocalLatLng(initialBounds); + } + if (_zoomPanBehavior != null) { - _controller.shapeLayerSizeFactor *= _zoomPanBehavior!.zoomLevel; + _currentZoomLevel = zoomLevel.clamp( + _zoomPanBehavior!.minZoomLevel, _zoomPanBehavior!.maxZoomLevel); + _zoomPanBehavior!.zoomLevel = _currentZoomLevel; final double inflateWidth = - _size.width * _zoomPanBehavior!.zoomLevel / 2 - _size.width / 2; + _size.width * _currentZoomLevel / 2 - _size.width / 2; final double inflateHeight = - _size.height * _zoomPanBehavior!.zoomLevel / 2 - _size.height / 2; + _size.height * _currentZoomLevel / 2 - _size.height / 2; _controller.shapeLayerOrigin = Offset( paintBounds.left - inflateWidth, paintBounds.top - inflateHeight); + } else { + _currentZoomLevel = + zoomLevel.clamp(kDefaultMinZoomLevel, kDefaultMaxZoomLevel); } + _controller.shapeLayerSizeFactor = _actualFactor * _currentZoomLevel; _controller.shapeLayerOffset = _getTranslationPoint(_controller.shapeLayerSizeFactor); final Offset offsetBeforeAdjust = _controller.shapeLayerOffset; @@ -2600,7 +2604,9 @@ class _RenderGeoJSONLayer extends RenderStack if (_zoomPanBehavior!.enablePinching && !_state.zoomLevelAnimationController.isAnimating && !_state.focalLatLngAnimationController.isAnimating) { - _invokeOnZooming(scale, _downLocalPoint, _downGlobalPoint); + _invokeOnZooming(scale, + localFocalPoint: _downLocalPoint, + globalFocalPoint: _downGlobalPoint); } return; case Gesture.pan: @@ -2731,7 +2737,8 @@ class _RenderGeoJSONLayer extends RenderStack scale = _actualFactor / _controller.shapeLayerSizeFactor; } - _invokeOnZooming(scale, _downLocalPoint, _downGlobalPoint); + _invokeOnZooming(scale, + localFocalPoint: _downLocalPoint, globalFocalPoint: _downGlobalPoint); // When the user didn't scrolled or scaled for certain time period, // we will refresh the map to the corresponding zoom level. _zoomingDelayTimer?.cancel(); @@ -2742,13 +2749,22 @@ class _RenderGeoJSONLayer extends RenderStack } void _invokeOnZooming(double scale, - [Offset? localFocalPoint, Offset? globalFocalPoint]) { + {MapLatLng? focalLatLng, + Offset? localFocalPoint, + Offset? globalFocalPoint}) { final double newZoomLevel = _getZoomLevel(scale); final double newShapeLayerSizeFactor = _getScale(newZoomLevel); final Offset newShapeLayerOffset = _controller.getZoomingTranslation(origin: localFocalPoint); final Rect newVisibleBounds = _controller.getVisibleBounds( newShapeLayerOffset, newShapeLayerSizeFactor); + final MapLatLngBounds newVisibleLatLngBounds = + _controller.getVisibleLatLngBounds( + newVisibleBounds.topRight, + newVisibleBounds.bottomLeft, + newShapeLayerOffset, + newShapeLayerSizeFactor, + ); _zoomDetails = MapZoomDetails( localFocalPoint: localFocalPoint, globalFocalPoint: globalFocalPoint, @@ -2757,12 +2773,8 @@ class _RenderGeoJSONLayer extends RenderStack previousVisibleBounds: _zoomDetails != null ? _zoomDetails!.newVisibleBounds : _controller.visibleLatLngBounds, - newVisibleBounds: _controller.getVisibleLatLngBounds( - newVisibleBounds.topRight, - newVisibleBounds.bottomLeft, - newShapeLayerOffset, - newShapeLayerSizeFactor, - ), + newVisibleBounds: newVisibleLatLngBounds, + focalLatLng: focalLatLng ?? getFocalLatLng(newVisibleLatLngBounds), ); if (_state.widget.onWillZoom == null || _state.widget.onWillZoom!(_zoomDetails!)) { @@ -2790,7 +2802,9 @@ class _RenderGeoJSONLayer extends RenderStack _downGlobalPoint = null; _isZoomedUsingToolbar = true; } - _zoomPanBehavior!.zoomLevel = details.newZoomLevel!; + _zoomPanBehavior! + ..zoomLevel = details.newZoomLevel! + ..focalLatLng = details.focalLatLng; } void _handleZoomLevelChange(double zoomLevel) { @@ -2800,6 +2814,10 @@ class _RenderGeoJSONLayer extends RenderStack _currentZoomLevel = zoomLevel; markNeedsPaint(); } else if (_zoomPanBehavior!.zoomLevel != _currentZoomLevel) { + if (_state.focalLatLngAnimationController.isAnimating) { + _isAnimationOnQueue = true; + return; + } if (!_isFlingAnimationActive && !_doubleTapEnabled) { _state.zoomLevelAnimationController.duration = const Duration(milliseconds: 650); @@ -2838,15 +2856,21 @@ class _RenderGeoJSONLayer extends RenderStack void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { if (status == AnimationStatus.completed && _zoomLevelTween.end != null) { - _handleZoomingAnimationEnd(); + final MapLatLng focalLatLng = + _zoomPanBehavior!.focalLatLng ?? _controller.visibleFocalLatLng!; + _handleZoomingAnimationEnd(focalLatLng); + if (_isAnimationOnQueue) { + _isAnimationOnQueue = false; + _handleFocalLatLngChange(focalLatLng); + } } } - void _handleZoomingAnimationEnd() { + void _handleZoomingAnimationEnd([MapLatLng? latLng]) { _isFlingAnimationActive = false; _zoomEnd(); if (!_isZoomedUsingToolbar && !_doubleTapEnabled) { - _invokeOnZooming(_getScale(_currentZoomLevel)); + _invokeOnZooming(_getScale(_currentZoomLevel), focalLatLng: latLng); } _isZoomedUsingToolbar = false; _doubleTapEnabled = false; @@ -2888,6 +2912,12 @@ class _RenderGeoJSONLayer extends RenderStack final Rect visibleBounds = _controller.getVisibleBounds( _controller.shapeLayerOffset + (canAvoidPanUpdate ? Offset.zero : delta)); + final MapLatLngBounds newVisibleLatLngBounds = + _controller.getVisibleLatLngBounds( + visibleBounds.topRight, + visibleBounds.bottomLeft, + _controller.shapeLayerOffset + (canAvoidPanUpdate ? Offset.zero : delta), + ); _panDetails = MapPanDetails( globalFocalPoint: focalPoint, localFocalPoint: localFocalPoint, @@ -2896,11 +2926,8 @@ class _RenderGeoJSONLayer extends RenderStack previousVisibleBounds: _panDetails != null ? _panDetails!.newVisibleBounds : _controller.visibleLatLngBounds, - newVisibleBounds: _controller.getVisibleLatLngBounds( - visibleBounds.topRight, - visibleBounds.bottomLeft, - _controller.shapeLayerOffset + - (canAvoidPanUpdate ? Offset.zero : delta)), + newVisibleBounds: newVisibleLatLngBounds, + focalLatLng: getFocalLatLng(newVisibleLatLngBounds), ); if (_state.widget.onWillPan == null || _state.widget.onWillPan!(_panDetails!)) { @@ -2929,8 +2956,17 @@ class _RenderGeoJSONLayer extends RenderStack } void _handleFocalLatLngChange(MapLatLng? latlng) { - if (!_controller.isInInteractive || + if (latlng != null && + _controller.isInInteractive && + !_state.focalLatLngAnimationController.isAnimating && + !_state.zoomLevelAnimationController.isAnimating) { + return; + } else if (!_controller.isInInteractive || _controller.visibleFocalLatLng != _zoomPanBehavior!.focalLatLng) { + if (_state.zoomLevelAnimationController.isAnimating) { + _isAnimationOnQueue = true; + return; + } if (!_isFlingAnimationActive) { _state.focalLatLngAnimationController.duration = const Duration(milliseconds: 650); @@ -2980,6 +3016,10 @@ class _RenderGeoJSONLayer extends RenderStack void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { _handleFocalLatLngAnimationEnd(); + if (_isAnimationOnQueue) { + _isAnimationOnQueue = false; + _handleZoomLevelChange(_zoomPanBehavior!.zoomLevel); + } } } @@ -3029,13 +3069,29 @@ class _RenderGeoJSONLayer extends RenderStack void _handleFlingAnimations() { if (_state.zoomLevelAnimationController.isAnimating && !_doubleTapEnabled) { - _state.zoomLevelAnimationController.stop(); - _isZoomedUsingToolbar = false; - _handleZoomingAnimationEnd(); + _cancelZoomingAnimation(); } if (_state.focalLatLngAnimationController.isAnimating) { - _state.focalLatLngAnimationController.stop(); - _handleFocalLatLngAnimationEnd(); + _cancelFocalLatLngAnimation(); + } + } + + void _cancelZoomingAnimation() { + _state.zoomLevelAnimationController.stop(); + _isZoomedUsingToolbar = false; + _handleZoomingAnimationEnd(); + if (_isAnimationOnQueue) { + _isAnimationOnQueue = false; + _zoomPanBehavior!.focalLatLng = _controller.visibleFocalLatLng; + } + } + + void _cancelFocalLatLngAnimation() { + _state.focalLatLngAnimationController.stop(); + _handleFocalLatLngAnimationEnd(); + if (_isAnimationOnQueue) { + _isAnimationOnQueue = false; + _zoomPanBehavior!.zoomLevel = _currentZoomLevel; } } @@ -3045,6 +3101,17 @@ class _RenderGeoJSONLayer extends RenderStack } } + void _handleZoomPanChange() { + if (_state.isSublayer) { + if (_state._controller!.localScale == 1.0 && + _state._controller!.panDistance == Offset.zero) { + _refresh(); + } else { + markNeedsPaint(); + } + } + } + void _handleReset() { _zoomPanBehavior!.zoomLevel = _zoomPanBehavior!.minZoomLevel; } @@ -3086,8 +3153,10 @@ class _RenderGeoJSONLayer extends RenderStack // _handleZooming(). To avoid this at double tap zooming, we have reset // the isInInteractive. _controller.isInInteractive = false; - _invokeOnZooming( - _getScale(newZoomLevel), _downLocalPoint, _downGlobalPoint); + _invokeOnZooming(_getScale(newZoomLevel), + localFocalPoint: _downLocalPoint, + globalFocalPoint: _downGlobalPoint, + focalLatLng: _controller.visibleFocalLatLng); } } @@ -3263,7 +3332,7 @@ class _RenderGeoJSONLayer extends RenderStack } } else if (_isShapeContains( position, mapModel, _currentInteractedElement) && - !(wasToggled && _state.widget.legend!.source == MapElement.shape)) { + !(wasToggled && _legend!.source == MapElement.shape)) { _currentInteractedItem = mapModel; _currentInteractedElement = MapLayerElement.shape; if (!(_state.widget.bubbleTooltipBuilder != null || @@ -3391,8 +3460,7 @@ class _RenderGeoJSONLayer extends RenderStack void _handleToggleChange() { _previousHoverItem = null; - if (_state.widget.legend != null && - _state.widget.legend!.source == MapElement.shape) { + if (_legend != null && _legend!.source == MapElement.shape) { late MapModel model; if (_state.widget.source.shapeColorMappers == null) { model = @@ -3431,7 +3499,9 @@ class _RenderGeoJSONLayer extends RenderStack ..addPanningListener(_handlePanning) ..addResetListener(_handleReset); if (_state.isSublayer) { - _controller.addRefreshListener(_handleRefresh); + _controller + ..addRefreshListener(_handleRefresh) + ..addZoomPanListener(_handleZoomPanChange); } else { _controller.addToggleListener(_handleToggleChange); @@ -3459,7 +3529,9 @@ class _RenderGeoJSONLayer extends RenderStack ..removePanningListener(_handlePanning) ..removeResetListener(_handleReset); if (_state.isSublayer) { - _controller.removeRefreshListener(_handleRefresh); + _controller + ..removeRefreshListener(_handleRefresh) + ..removeZoomPanListener(_handleZoomPanChange); } else { _controller.removeToggleListener(_handleToggleChange); @@ -3483,7 +3555,6 @@ class _RenderGeoJSONLayer extends RenderStack @override void handleEvent(PointerEvent event, HitTestEntry entry) { - _zoomPanBehavior?.handleEvent(event); if (event is PointerDownEvent && event.down && (isInteractive || canZoom)) { if (isInteractive && !_state.zoomLevelAnimationController.isAnimating && @@ -3514,7 +3585,7 @@ class _RenderGeoJSONLayer extends RenderStack _handleTap(event.localPosition, event.kind); } else if (event is PointerScrollEvent) { _handleScrollEvent(event); - } else if (_state.isDesktop && event is PointerHoverEvent) { + } else if (isDesktop && event is PointerHoverEvent) { // PointerHoverEvent is applicable only for web platform. _handleHover(event); } @@ -3580,7 +3651,8 @@ class _RenderGeoJSONLayer extends RenderStack !_controller.wasToggled(_previousHoverItem!) && _previousHoverItem != _currentHoverItem) { fillPaint.color = _themeData.shapeHoverColor != Colors.transparent - ? _reverseHoverColorTween.evaluate(_hoverColorAnimation)! + ? (_reverseHoverColorTween.evaluate(_hoverColorAnimation) ?? + getActualShapeColor(model)) : getActualShapeColor(model); if (_themeData.shapeHoverStrokeWidth! > 0.0 && @@ -3620,8 +3692,7 @@ class _RenderGeoJSONLayer extends RenderStack void _updateFillColor( MapModel model, Paint fillPaint, bool hasToggledIndices) { fillPaint.style = PaintingStyle.fill; - if (_state.widget.legend != null && - _state.widget.legend!.source == MapElement.shape) { + if (_legend != null && _legend!.source == MapElement.shape) { if (_controller.currentToggledItemIndex == model.legendMapperIndex) { final Color? shapeColor = _controller.wasToggled(model) ? _forwardToggledShapeColorTween.evaluate(_toggleShapeAnimation) @@ -3647,8 +3718,7 @@ class _RenderGeoJSONLayer extends RenderStack // the [legendController.toggledIndices] collection. void _updateStrokePaint( MapModel model, Paint strokePaint, bool hasToggledIndices) { - if (_state.widget.legend != null && - _state.widget.legend!.source == MapElement.shape) { + if (_legend != null && _legend!.source == MapElement.shape) { if (_controller.currentToggledItemIndex == model.legendMapperIndex) { final Color? shapeStrokeColor = _controller.wasToggled(model) ? _forwardToggledShapeStrokeColorTween 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 298622a62..66bb1ba6f 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/tile_layer.dart @@ -1,12 +1,11 @@ -import 'dart:async'; import 'dart:math'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/physics.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import '../../maps.dart'; @@ -17,6 +16,7 @@ import '../elements/toolbar.dart'; import '../elements/tooltip.dart'; import '../layer/layer_base.dart'; import '../layer/vector_layers.dart'; +import '../layer/zoomable.dart'; import '../settings.dart'; import '../utils.dart'; @@ -135,6 +135,7 @@ class TileLayer extends StatefulWidget { required this.urlTemplate, required this.initialFocalLatLng, required this.initialZoomLevel, + this.initialLatLngBounds, required this.zoomPanBehavior, required this.sublayers, required this.initialMarkersCount, @@ -150,6 +151,7 @@ class TileLayer extends StatefulWidget { final String urlTemplate; final MapLatLng initialFocalLatLng; final int initialZoomLevel; + final MapLatLngBounds? initialLatLngBounds; final MapZoomPanBehavior? zoomPanBehavior; final List? sublayers; final int initialMarkersCount; @@ -168,7 +170,6 @@ class TileLayer extends StatefulWidget { class _TileLayerState extends State with TickerProviderStateMixin { // Both width and height of each tile is 256. static const double tileSize = 256; - static const double _frictionCoefficient = 0.05; // The [globalTileStart] represents the tile start factor value based on // the zoom level. static const Offset _globalTileStart = Offset.zero; @@ -179,44 +180,16 @@ class _TileLayerState extends State with TickerProviderStateMixin { late double _currentZoomLevel; late int _nextZoomLevel; - late double _touchStartZoomLevel; - late MapLatLng _touchStartLatLng; late MapLatLng _currentFocalLatLng; - late Offset _touchStartLocalPoint; - late Offset _touchStartGlobalPoint; late bool _isDesktop; - late double _maximumReachedScaleOnInteraction; - late MapLatLng _newFocalLatLng; late SfMapsThemeData _mapsThemeData; late MapLayerInheritedWidget _ancestor; - late AnimationController _zoomLevelAnimationController; - late AnimationController _focalLatLngAnimationController; - late CurvedAnimation _flingZoomLevelCurvedAnimation; - late CurvedAnimation _flingFocalLatLngCurvedAnimation; - late CurvedAnimation _zoomLevelCurvedAnimation; - late CurvedAnimation _focalLatLngCurvedAnimation; - late MapLatLngTween _focalLatLngTween; - late Tween _zoomLevelTween; Size? _size; MapController? _controller; - int _pointerCount = 0; - Gesture? _gestureType; - Timer? _doubleTapTimer; - bool _isSizeChanged = false; bool _hasSublayer = false; - bool _isZoomedUsingToolbar = false; - bool _isFlingAnimationActive = false; - bool _doubleTapEnabled = false; List? _markers; - MapZoomDetails? _zoomDetails; - MapPanDetails? _panDetails; - Timer? _zoomingDelayTimer; - MapLatLng? _mouseCenterLatLng; - double? _mouseStartZoomLevel; - Offset? _mouseStartLocalPoint; - Offset? _mouseStartGlobalPoint; - Offset? _touchStartOffset; + ZoomableController? _zoomController; bool _hasTooltipBuilder() { if (widget.markerTooltipBuilder != null) { @@ -370,28 +343,22 @@ class _TileLayerState extends State with TickerProviderStateMixin { TileZoomLevelDetails? level = _levels[zoom]; if (level == null) { + final double levelIndex = zoom.toDouble(); // The [_levels] collection contains each integer zoom level origin, // scale and translationPoint. The scale and translationPoint are // calculated in every pinch zoom level for scaling the tiles. - level = _levels[zoom.toDouble()] = TileZoomLevelDetails(); - level.origin = _getLevelOrigin(_currentFocalLatLng, zoom.toDouble()); - level.zoomLevel = zoom.toDouble(); + level = _levels[levelIndex] = TileZoomLevelDetails(); + level.origin = _getLevelOrigin(_currentFocalLatLng, levelIndex); + level.zoomLevel = levelIndex; - if (_gestureType != null && _gestureType == Gesture.scale) { + if (levelsCount == 0 || + (_zoomController != null && + _zoomController!.actionType == ActionType.pinch)) { _updateZoomLevelTransform( level, _currentFocalLatLng, _currentZoomLevel); - } else { - level.scale = 1.0; - level.translatePoint = Offset.zero; } } - // Recalculate tiles bounds origin for all existing zoom level - // when size changed. - if (_isSizeChanged) { - _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); - } - final double totalTileWidth = getTotalTileWidth(level.zoomLevel); _controller! ..totalTileSize = Size(totalTileWidth, totalTileWidth) @@ -540,597 +507,6 @@ class _TileLayerState extends State with TickerProviderStateMixin { ); } - void _handlePointerDown(PointerDownEvent event) { - if (_zoomLevelAnimationController.isAnimating && !_doubleTapEnabled) { - _zoomLevelAnimationController.stop(); - _isZoomedUsingToolbar = false; - _handleZoomingAnimationEnd(); - } - - if (_focalLatLngAnimationController.isAnimating) { - _focalLatLngAnimationController.stop(); - _handleFocalLatLngAnimationEnd(); - } - - if (widget.zoomPanBehavior != null && - widget.zoomPanBehavior!.enableDoubleTapZooming) { - _doubleTapTimer ??= Timer(kDoubleTapTimeout, _resetDoubleTapTimer); - } - - widget.zoomPanBehavior?.handleEvent(event); - } - - void _handlePointerMove(PointerMoveEvent event) { - widget.zoomPanBehavior?.handleEvent(event); - } - - void _handlePointerUp(PointerUpEvent event) { - if (_doubleTapTimer != null && _doubleTapTimer!.isActive) { - _pointerCount++; - } - - if (_pointerCount == 2) { - _touchStartLocalPoint = event.localPosition; - // ignore: avoid_as - final RenderBox renderBox = context.findRenderObject()! as RenderBox; - _touchStartGlobalPoint = renderBox.localToGlobal(_touchStartLocalPoint); - _resetDoubleTapTimer(); - _handleDoubleTap(); - } - - widget.zoomPanBehavior?.handleEvent(event); - } - - void _resetDoubleTapTimer() { - _pointerCount = 0; - if (_doubleTapTimer != null) { - _doubleTapTimer!.cancel(); - _doubleTapTimer = null; - } - } - - // This method called when start pinch zooming or panning action. - void _handleScaleStart(ScaleStartDetails details) { - if (widget.zoomPanBehavior != null && - !_zoomLevelAnimationController.isAnimating && - !_focalLatLngAnimationController.isAnimating) { - if (widget.zoomPanBehavior!.enablePinching || - widget.zoomPanBehavior!.enablePanning) { - _gestureType = null; - _touchStartOffset = - _getTouchStartOffset(details.localFocalPoint, details.focalPoint); - } - } - } - - Offset _getTouchStartOffset(Offset localFocalPoint, Offset globalFocalPoint) { - _maximumReachedScaleOnInteraction = 1.0; - _touchStartLocalPoint = localFocalPoint; - _touchStartGlobalPoint = globalFocalPoint; - _touchStartZoomLevel = _currentZoomLevel; - _calculateLatLngFromTappedPoint(); - final MapLatLng touchStartFocalLatLng = - _calculateVisibleLatLng(_touchStartLocalPoint, _touchStartZoomLevel); - return _pixelFromLatLng(touchStartFocalLatLng, _touchStartZoomLevel); - } - - // This method called when doing pinch zooming or panning action. - void _handleScaleUpdate(ScaleUpdateDetails details) { - if (widget.zoomPanBehavior != null && - !_doubleTapEnabled && - (widget.zoomPanBehavior!.enablePinching || - widget.zoomPanBehavior!.enablePanning)) { - final double zoomLevel = _touchStartZoomLevel + log(details.scale) / ln2; - if (zoomLevel > widget.zoomPanBehavior!.maxZoomLevel || - zoomLevel < widget.zoomPanBehavior!.minZoomLevel) { - _resetDoubleTapTimer(); - return; - } - - _gestureType ??= _getGestureType(details.scale, details.localFocalPoint); - if (_gestureType == null) { - return; - } - - if (_controller!.localScale < details.scale) { - _maximumReachedScaleOnInteraction = details.scale; - } - - _resetDoubleTapTimer(); - _controller!.localScale = details.scale; - final double newZoomLevel = - _getZoomLevel(_touchStartZoomLevel + log(details.scale) / ln2); - switch (_gestureType!) { - case Gesture.scale: - if (widget.zoomPanBehavior!.enablePinching && - !_focalLatLngAnimationController.isAnimating) { - final MapLatLng newFocalLatLng = - _calculateVisibleLatLng(_touchStartLocalPoint, newZoomLevel); - _controller! - ..isInInteractive = true - ..gesture = _gestureType - ..localScale = details.scale - ..pinchCenter = _touchStartLocalPoint; - _invokeOnZooming(newZoomLevel, _touchStartLocalPoint, - _touchStartGlobalPoint, newFocalLatLng); - } - return; - case Gesture.pan: - if (widget.zoomPanBehavior!.enablePanning && - !_zoomLevelAnimationController.isAnimating) { - _touchStartOffset ??= _getTouchStartOffset( - details.localFocalPoint, details.focalPoint); - final MapLatLng newFocalLatLng = - _calculateVisibleLatLng(details.localFocalPoint, newZoomLevel); - final Offset newFocalOffset = - _pixelFromLatLng(newFocalLatLng, newZoomLevel); - - _controller! - ..isInInteractive = true - ..gesture = _gestureType - ..localScale = 1.0 - ..panDistance = _touchStartOffset! - newFocalOffset; - _invokeOnPanning(newZoomLevel, - localFocalPoint: details.localFocalPoint, - globalFocalPoint: details.focalPoint, - touchStartLocalPoint: _touchStartLocalPoint, - newFocalLatLng: newFocalLatLng); - } - return; - } - } - } - - // This method is called when the pinching or panning action ends. - void _handleScaleEnd(ScaleEndDetails details) { - if (widget.zoomPanBehavior != null && - details.velocity.pixelsPerSecond.distance >= kMinFlingVelocity) { - _resetDoubleTapTimer(); - if (_gestureType == Gesture.pan && - widget.zoomPanBehavior!.enablePanning && - !_zoomLevelAnimationController.isAnimating) { - _startFlingAnimationForPanning(details); - } else if (_gestureType == Gesture.scale && - widget.zoomPanBehavior!.enablePinching && - !_focalLatLngAnimationController.isAnimating) { - _startFlingAnimationForPinching(details); - } - } - - _controller!.localScale = 1.0; - _touchStartOffset = null; - _gestureType = null; - _panDetails = null; - _zoomDetails = null; - _invalidateSublayer(); - } - - void _handleDoubleTap() { - if (_gestureType == null && widget.zoomPanBehavior != null) { - _zoomLevelAnimationController.duration = - const Duration(milliseconds: 200); - _touchStartZoomLevel = _currentZoomLevel; - _calculateLatLngFromTappedPoint(); - // When double tapping, we had incremented zoom level by one from the - // currentZoomLevel. - double newZoomLevel = _currentZoomLevel + 1; - newZoomLevel = newZoomLevel.clamp(widget.zoomPanBehavior!.minZoomLevel, - widget.zoomPanBehavior!.maxZoomLevel); - if (newZoomLevel == _currentZoomLevel) { - return; - } - _doubleTapEnabled = true; - final MapLatLng newFocalLatLng = - _calculateVisibleLatLng(_touchStartLocalPoint, newZoomLevel); - _invokeOnZooming(newZoomLevel, _touchStartLocalPoint, - _touchStartGlobalPoint, newFocalLatLng); - } - } - - // This methods performs fling animation for panning. - void _startFlingAnimationForPanning(ScaleEndDetails details) { - _isFlingAnimationActive = true; - final Offset currentFocalOffset = - _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); - final MapLatLng newFocalLatLng = _pixelToLatLng( - Offset( - FrictionSimulation( - _frictionCoefficient, - currentFocalOffset.dx, - -details.velocity.pixelsPerSecond.dx, - ).finalX, - FrictionSimulation( - _frictionCoefficient, - currentFocalOffset.dy, - -details.velocity.pixelsPerSecond.dy, - ).finalX), - _currentZoomLevel); - _gestureType = null; - _focalLatLngAnimationController.duration = _getFlingAnimationDuration( - details.velocity.pixelsPerSecond.distance, _frictionCoefficient); - widget.zoomPanBehavior!.focalLatLng = newFocalLatLng; - } - - // This methods performs fling animation for pinching. - void _startFlingAnimationForPinching(ScaleEndDetails details) { - _isFlingAnimationActive = true; - final int direction = - _controller!.localScale >= _maximumReachedScaleOnInteraction ? 1 : -1; - double newZoomLevel = _currentZoomLevel + - (direction * - (details.velocity.pixelsPerSecond.distance / kMaxFlingVelocity) * - widget.zoomPanBehavior!.maxZoomLevel); - newZoomLevel = newZoomLevel.clamp(widget.zoomPanBehavior!.minZoomLevel, - widget.zoomPanBehavior!.maxZoomLevel); - _gestureType = null; - _zoomLevelAnimationController.duration = _getFlingAnimationDuration( - details.velocity.pixelsPerSecond.distance, _frictionCoefficient); - widget.zoomPanBehavior!.zoomLevel = newZoomLevel; - } - - // Returns the animation duration for the given distance and - // friction co-efficient. - Duration _getFlingAnimationDuration( - double distance, double frictionCoefficient) { - final int duration = - (log(10.0 / distance) / log(frictionCoefficient / 100)).round(); - final int durationInMs = (duration * 1000).round(); - return Duration(milliseconds: durationInMs < 350 ? 350 : durationInMs); - } - - // This method called when doing mouse wheel action in web. - void _handleMouseWheelZooming(PointerSignalEvent event) { - if (widget.zoomPanBehavior != null && - widget.zoomPanBehavior!.enablePinching) { - if (event is PointerScrollEvent) { - widget.zoomPanBehavior!.handleEvent(event); - _gestureType = Gesture.scale; - _mouseStartZoomLevel ??= _currentZoomLevel; - _mouseCenterLatLng ??= _currentFocalLatLng; - _mouseStartLocalPoint ??= event.localPosition; - _mouseStartGlobalPoint ??= event.position; - - final MapZoomPanBehavior zoomPanBehavior = widget.zoomPanBehavior!; - final Offset localPointCenterDiff = Offset( - (_size!.width / 2) - _mouseStartLocalPoint!.dx, - (_size!.height / 2) - _mouseStartLocalPoint!.dy); - final Offset actualCenterPixelPosition = - _pixelFromLatLng(_mouseCenterLatLng!, _mouseStartZoomLevel!); - final Offset point = actualCenterPixelPosition - localPointCenterDiff; - _touchStartLatLng = _pixelToLatLng(point, _mouseStartZoomLevel!); - double scale = _controller!.tileLayerLocalScale - - (event.scrollDelta.dy / _size!.height); - _controller!.tileLayerLocalScale = scale; - final double newZoomLevel = - _getZoomLevel(_mouseStartZoomLevel! + log(scale) / ln2); - // If the scale * _mouseStartZoomLevel value goes beyond - // minZoomLevel, set the min scale value. - if (scale * _mouseStartZoomLevel! < zoomPanBehavior.minZoomLevel) { - scale = zoomPanBehavior.minZoomLevel / _mouseStartZoomLevel!; - } - // If the scale * _mouseStartZoomLevel value goes beyond - // maxZoomLevel, set the max scale value. - else if (scale * _mouseStartZoomLevel! > zoomPanBehavior.maxZoomLevel) { - scale = zoomPanBehavior.maxZoomLevel / _mouseStartZoomLevel!; - } - - _controller! - ..isInInteractive = true - ..gesture = _gestureType - ..localScale = scale - ..pinchCenter = event.localPosition; - final MapLatLng newFocalLatLng = - _calculateVisibleLatLng(event.localPosition, newZoomLevel); - _invokeOnZooming(newZoomLevel, _mouseStartLocalPoint!, - _mouseStartGlobalPoint!, newFocalLatLng); - - _zoomingDelayTimer?.cancel(); - _zoomingDelayTimer = Timer(const Duration(milliseconds: 300), () { - _invalidateSublayer(); - _controller!.tileLayerLocalScale = 1.0; - _mouseStartZoomLevel = null; - _mouseStartLocalPoint = null; - _mouseStartGlobalPoint = null; - _gestureType = null; - _mouseCenterLatLng = null; - }); - } - } - } - - // Calculate the actual latLng value in the place of tapped location. - void _calculateLatLngFromTappedPoint() { - final Offset localPointCenterDiff = Offset( - (_size!.width / 2) - _touchStartLocalPoint.dx, - (_size!.height / 2) - _touchStartLocalPoint.dy); - final Offset actualCenterPixelPosition = - _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel); - final Offset point = actualCenterPixelPosition - localPointCenterDiff; - _touchStartLatLng = _pixelToLatLng(point, _touchStartZoomLevel); - - final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(_currentFocalLatLng, _touchStartZoomLevel), - width: _size!.width, - height: _size!.height); - final MapLatLngBounds newVisibleLatLng = MapLatLngBounds( - _pixelToLatLng(newVisibleBounds.topRight, _touchStartZoomLevel), - _pixelToLatLng(newVisibleBounds.bottomLeft, _touchStartZoomLevel)); - _zoomDetails = MapZoomDetails(newVisibleBounds: newVisibleLatLng); - _panDetails = MapPanDetails(newVisibleBounds: newVisibleLatLng); - _newFocalLatLng = _currentFocalLatLng; - } - - // Check whether gesture type is scale or pan. - Gesture? _getGestureType(double scale, Offset focalPoint) { - // The minimum distance required to start scale or pan gesture. - const int minScaleDistance = 3; - final Offset distance = focalPoint - _touchStartLocalPoint; - if (scale == 1) { - return distance.dx.abs() > minScaleDistance || - distance.dy.abs() > minScaleDistance - ? Gesture.pan - : null; - } - - return (distance.dx.abs() > minScaleDistance || - distance.dy.abs() > minScaleDistance) - ? Gesture.scale - : null; - } - - // Calculate new focal coordinate value while scaling, panning or mouse wheel - // actions. - MapLatLng _calculateVisibleLatLng( - Offset localFocalPoint, double newZoomLevel) { - final Offset focalStartPoint = - _pixelFromLatLng(_touchStartLatLng, newZoomLevel); - final Offset newCenterPoint = focalStartPoint - - localFocalPoint + - Offset(_size!.width / 2, _size!.height / 2); - return _pixelToLatLng(newCenterPoint, newZoomLevel); - } - - // Restricting new zoom level value either to - // [widget.zoomPanBehavior.minZoomLevel] or - // [widget.zoomPanBehavior.maxZoomLevel] if the new zoom level value is not - // in zoom level range. - double _getZoomLevel(double zoomLevel) { - return zoomLevel.isNaN - ? widget.zoomPanBehavior!.minZoomLevel - : zoomLevel.clamp( - widget.zoomPanBehavior!.minZoomLevel, - widget.zoomPanBehavior!.maxZoomLevel, - ); - } - - // This method called for both pinch zooming action and mouse wheel zooming - // action for passing [MapZoomDetails] parameters to notify user about the - // zooming details. - void _invokeOnZooming(double newZoomLevel, - [Offset? localFocalPoint, - Offset? globalFocalPoint, - MapLatLng? newFocalLatLng]) { - final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(newFocalLatLng!, newZoomLevel), - width: _size!.width, - height: _size!.height); - - _zoomDetails = MapZoomDetails( - localFocalPoint: localFocalPoint, - globalFocalPoint: globalFocalPoint, - previousZoomLevel: widget.zoomPanBehavior!.zoomLevel, - newZoomLevel: newZoomLevel, - previousVisibleBounds: _zoomDetails != null - ? _zoomDetails!.newVisibleBounds - : _controller!.visibleLatLngBounds, - newVisibleBounds: MapLatLngBounds( - _pixelToLatLng(newVisibleBounds.topRight, newZoomLevel), - _pixelToLatLng(newVisibleBounds.bottomLeft, newZoomLevel))); - _newFocalLatLng = newFocalLatLng; - if (widget.onWillZoom == null || widget.onWillZoom!(_zoomDetails!)) { - widget.zoomPanBehavior?.onZooming(_zoomDetails!); - } - } - - // This method invoked when user override the [onZooming] method in - // [ZoomPanBehavior] and called [super.onZooming(details)]. - void _handleZooming(MapZoomDetails details) { - if (_gestureType != null) { - // Updating map while pinching and scrolling. - _currentZoomLevel = details.newZoomLevel!; - _controller!.tileZoomLevel = _currentZoomLevel; - _currentFocalLatLng = _newFocalLatLng; - _controller!.visibleFocalLatLng = _currentFocalLatLng; - _controller!.visibleLatLngBounds = details.newVisibleBounds; - widget.zoomPanBehavior!.focalLatLng = _currentFocalLatLng; - setState(() { - _handleTransform(); - }); - } else { - // Updating map via toolbar or double tap or fling animation end. - final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(_currentFocalLatLng, details.newZoomLevel!), - width: _size!.width, - height: _size!.height); - _controller!.visibleLatLngBounds = MapLatLngBounds( - _pixelToLatLng(newVisibleBounds.topRight, details.newZoomLevel!), - _pixelToLatLng(newVisibleBounds.bottomLeft, details.newZoomLevel!)); - _isZoomedUsingToolbar = _currentZoomLevel != details.newZoomLevel!; - } - - widget.zoomPanBehavior!.zoomLevel = details.newZoomLevel!; - } - - // This method called when dynamically changing the [zoomLevel] property of - // ZoomPanBehavior. - void _handleZoomLevelChange(double zoomLevel) { - if (_gestureType == null && - _currentZoomLevel != widget.zoomPanBehavior!.zoomLevel) { - if (!_isFlingAnimationActive && !_doubleTapEnabled) { - _zoomLevelAnimationController.duration = - const Duration(milliseconds: 650); - } - - _zoomLevelTween.begin = _currentZoomLevel; - _zoomLevelTween.end = widget.zoomPanBehavior!.zoomLevel; - _zoomLevelAnimationController.forward(from: 0.0); - } - } - - void _handleZoomLevelAnimation() { - if (_zoomLevelTween.end != null) { - _currentZoomLevel = _zoomLevelTween.evaluate(_isFlingAnimationActive - ? _flingZoomLevelCurvedAnimation - : _zoomLevelCurvedAnimation); - } - - if (_isFlingAnimationActive || _doubleTapEnabled) { - _currentFocalLatLng = - _calculateVisibleLatLng(_touchStartLocalPoint, _currentZoomLevel); - } - - _handleZoomPanAnimation(); - } - - void _handleZoomPanAnimation() { - setState(() { - _handleTransform(); - if (_hasSublayer) { - _controller!.visibleFocalLatLng = _currentFocalLatLng; - _controller!.tileZoomLevel = _currentZoomLevel; - _invalidateSublayer(); - } else { - _controller!.notifyRefreshListeners(); - } - }); - } - - void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.completed) { - _handleZoomingAnimationEnd(); - } - } - - void _handleZoomingAnimationEnd() { - _isFlingAnimationActive = false; - if (_zoomLevelTween.end != null && - !_isZoomedUsingToolbar && - !_doubleTapEnabled) { - _touchStartLocalPoint = - _pixelFromLatLng(_currentFocalLatLng, _zoomLevelTween.begin!); - // ignore: avoid_as - final RenderBox renderBox = context.findRenderObject()! as RenderBox; - _invokeOnZooming(_currentZoomLevel, _touchStartLocalPoint, - renderBox.localToGlobal(_touchStartLocalPoint), _currentFocalLatLng); - } else { - _isZoomedUsingToolbar = false; - _doubleTapEnabled = false; - } - } - - void _invokeOnPanning(double newZoomLevel, - {required Offset localFocalPoint, - required Offset globalFocalPoint, - required Offset touchStartLocalPoint, - required MapLatLng newFocalLatLng}) { - final Rect newVisibleBounds = Rect.fromCenter( - center: _pixelFromLatLng(newFocalLatLng, newZoomLevel), - width: _size!.width, - height: _size!.height); - - _panDetails = MapPanDetails( - globalFocalPoint: globalFocalPoint, - localFocalPoint: localFocalPoint, - zoomLevel: widget.zoomPanBehavior!.zoomLevel, - delta: localFocalPoint - touchStartLocalPoint, - previousVisibleBounds: _panDetails != null - ? _panDetails!.newVisibleBounds - : _controller!.visibleLatLngBounds, - newVisibleBounds: MapLatLngBounds( - _pixelToLatLng(newVisibleBounds.topRight, newZoomLevel), - _pixelToLatLng(newVisibleBounds.bottomLeft, newZoomLevel))); - _newFocalLatLng = newFocalLatLng; - if (widget.onWillPan == null || widget.onWillPan!(_panDetails!)) { - widget.zoomPanBehavior?.onPanning(_panDetails!); - } - } - - // This method invoked when user override the [onPanning] method in - // [ZoomPanBehavior] and called [super.onPanning(details)]. - void _handlePanning(MapPanDetails details) { - setState(() { - _currentFocalLatLng = _newFocalLatLng; - widget.zoomPanBehavior!.focalLatLng = _currentFocalLatLng; - _controller!.visibleFocalLatLng = _currentFocalLatLng; - _controller!.visibleLatLngBounds = details.newVisibleBounds; - _handleTransform(); - }); - } - - // This method called when dynamically changing the [focalLatLng] property of - // ZoomPanBehavior. - void _handleFocalLatLngChange(MapLatLng? latlng) { - if (_gestureType == null && - _currentFocalLatLng != widget.zoomPanBehavior!.focalLatLng) { - if (!_isFlingAnimationActive) { - _focalLatLngAnimationController.duration = - const Duration(milliseconds: 650); - } - - _focalLatLngTween.begin = _currentFocalLatLng; - _focalLatLngTween.end = widget.zoomPanBehavior!.focalLatLng; - _focalLatLngAnimationController.forward(from: 0.0); - } - } - - void _handleFocalLatLngAnimation() { - if (_focalLatLngTween.end != null) { - _currentFocalLatLng = _focalLatLngTween.evaluate(_isFlingAnimationActive - ? _flingFocalLatLngCurvedAnimation - : _focalLatLngCurvedAnimation); - } - - _handleZoomPanAnimation(); - } - - void _handleFocalLatLngAnimationStatusChange(AnimationStatus status) { - if (status == AnimationStatus.completed && _focalLatLngTween.end != null) { - _handleFocalLatLngAnimationEnd(); - } - } - - void _handleFocalLatLngAnimationEnd() { - _isFlingAnimationActive = false; - final Offset previousFocalPoint = - _pixelFromLatLng(_focalLatLngTween.begin!, _currentZoomLevel); - final Offset currentFocalPoint = - _pixelFromLatLng(_currentFocalLatLng, _currentZoomLevel); - // ignore: avoid_as - final RenderBox renderBox = context.findRenderObject()! as RenderBox; - _invokeOnPanning(_currentZoomLevel, - localFocalPoint: currentFocalPoint, - globalFocalPoint: renderBox.localToGlobal(currentFocalPoint), - touchStartLocalPoint: previousFocalPoint, - newFocalLatLng: _currentFocalLatLng); - } - - // This method invoked when user called the [reset] method in - // [ZoomPanBehavior]. - void _handleReset() { - widget.zoomPanBehavior!.zoomLevel = widget.zoomPanBehavior!.minZoomLevel; - } - - void _invalidateSublayer() { - _controller! - ..isInInteractive = false - ..normalize = Offset.zero - ..gesture = null - ..localScale = 1.0; - if (_hasSublayer) { - _controller!.notifyRefreshListeners(); - } - } - // Scale and transform the existing level tiles if the current zoom level // value is in-between the zoom levels i.e.,fractional value. Request // new tiles if the current zoom level value reached next zoom level. @@ -1201,34 +577,27 @@ class _TileLayerState extends State with TickerProviderStateMixin { final List children = []; Widget current; - current = Listener( - onPointerDown: _handlePointerDown, - onPointerMove: _handlePointerMove, - onPointerUp: _handlePointerUp, - onPointerSignal: _handleMouseWheelZooming, - child: GestureDetector( - onScaleStart: _handleScaleStart, - onScaleUpdate: _handleScaleUpdate, - onScaleEnd: _handleScaleEnd, - child: Stack(children: [ - _buildTiles(), - if (_hasSublayer) - SublayerContainer(ancestor: _ancestor, children: widget.sublayers!), - if (_markers != null && _markers!.isNotEmpty) - MarkerContainer( - markerTooltipBuilder: widget.markerTooltipBuilder, - controller: _controller!, - children: _markers, - ), - ]), - ), - ); + current = Stack(children: [ + _buildTiles(), + if (_hasSublayer) + SublayerContainer(ancestor: _ancestor, children: widget.sublayers!), + if (_markers != null && _markers!.isNotEmpty) + MarkerContainer( + markerTooltipBuilder: widget.markerTooltipBuilder, + controller: _controller!, + children: _markers, + ), + ]); children.add(current); if (widget.zoomPanBehavior != null) { - children.add(BehaviorViewRenderObjectWidget( + children.add(BehaviorView( + zoomLevel: _currentZoomLevel, + focalLatLng: _currentFocalLatLng, controller: _controller!, - zoomPanBehavior: widget.zoomPanBehavior!, + behavior: widget.zoomPanBehavior!, + onWillZoom: widget.onWillZoom, + onWillPan: widget.onWillPan, )); if (_isDesktop && widget.zoomPanBehavior!.showToolbar) { children.add(MapToolbar( @@ -1257,24 +626,81 @@ class _TileLayerState extends State with TickerProviderStateMixin { ); } - void _initializeMapController() { - if (_controller == null) { - _ancestor = context - .dependOnInheritedWidgetOfExactType()!; - _controller = _ancestor.controller - ..addZoomingListener(_handleZooming) - ..addPanningListener(_handlePanning) - ..addResetListener(_handleReset) + void _handledZoomPanChange() { + if (SchedulerBinding.instance!.schedulerPhase == + SchedulerPhase.persistentCallbacks) { + SchedulerBinding.instance!.addPostFrameCallback((Duration duration) { + _handledZoomPanChange(); + }); + return; + } + + setState(() { + _currentZoomLevel = _zoomController!.zoomLevel; + final Offset point = Offset( + (_size!.width / 2) - _zoomController!.actualRect.left, + (_size!.height / 2) - _zoomController!.actualRect.top); + final MapLatLng newFocalLatLng = pixelToLatLng( + point, Size.square(getTotalTileWidth(_currentZoomLevel))); + _currentFocalLatLng = newFocalLatLng; + _handleTransform(); + if (_hasSublayer) { + _controller! + ..visibleFocalLatLng = _currentFocalLatLng + ..tileZoomLevel = _currentZoomLevel; + if (_zoomController!.actionType == ActionType.fling) { + _invalidateSublayer(); + } + } + _controller!.notifyZoomPanListeners(); + }); + } + + void _invalidateSublayer() { + _controller! + ..isInInteractive = false + ..normalize = Offset.zero + ..panDistance = Offset.zero + ..gesture = null + ..localScale = 1.0; + } + + void _initializeController() { + _ancestor = + context.dependOnInheritedWidgetOfExactType()!; + _controller ??= _ancestor.controller + ..tileZoomLevel = _currentZoomLevel + ..visibleFocalLatLng = _currentFocalLatLng; + if (_ancestor.zoomController != null) { + _zoomController ??= _ancestor.zoomController! + ..addListener(_handledZoomPanChange); + } + } + + void _validateInitialBounds() { + final MapLatLngBounds? bounds = + widget.zoomPanBehavior?.latLngBounds ?? widget.initialLatLngBounds; + if (bounds != null) { + final double zoomLevel = + getZoomLevel(bounds, _controller!.layerType!, _size!); + _currentFocalLatLng = getFocalLatLng(bounds); + if (widget.zoomPanBehavior != null) { + _currentZoomLevel = zoomLevel.clamp( + widget.zoomPanBehavior!.minZoomLevel, + widget.zoomPanBehavior!.maxZoomLevel); + } else { + _currentZoomLevel = + zoomLevel.clamp(kDefaultMinZoomLevel, kDefaultMaxZoomLevel); + } + _nextZoomLevel = _currentZoomLevel.floor(); + _controller! ..tileZoomLevel = _currentZoomLevel - ..visibleFocalLatLng = _currentFocalLatLng - ..onZoomLevelChange = _handleZoomLevelChange - ..onPanChange = _handleFocalLatLngChange; + ..visibleFocalLatLng = _currentFocalLatLng; } } @override void initState() { - super.initState(); _currentFocalLatLng = widget.zoomPanBehavior?.focalLatLng ?? widget.initialFocalLatLng; _currentZoomLevel = (widget.zoomPanBehavior != null && @@ -1293,33 +719,14 @@ class _TileLayerState extends State with TickerProviderStateMixin { final MapMarker marker = widget.markerBuilder!(context, i); _markers!.add(marker); } - - _zoomLevelAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)) - ..addListener(_handleZoomLevelAnimation) - ..addStatusListener(_handleZoomLevelAnimationStatusChange); - _focalLatLngAnimationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 650)) - ..addListener(_handleFocalLatLngAnimation) - ..addStatusListener(_handleFocalLatLngAnimationStatusChange); - _flingZoomLevelCurvedAnimation = CurvedAnimation( - parent: _zoomLevelAnimationController, curve: Curves.decelerate); - _flingFocalLatLngCurvedAnimation = CurvedAnimation( - parent: _focalLatLngAnimationController, curve: Curves.decelerate); - _zoomLevelCurvedAnimation = CurvedAnimation( - parent: _zoomLevelAnimationController, curve: Curves.easeInOut); - _focalLatLngCurvedAnimation = CurvedAnimation( - parent: _focalLatLngAnimationController, curve: Curves.easeInOut); - - _focalLatLngTween = MapLatLngTween(); - _zoomLevelTween = Tween(); _hasSublayer = widget.sublayers != null && widget.sublayers!.isNotEmpty; widget.controller?.addListener(refreshMarkers); + super.initState(); } @override void didChangeDependencies() { - _initializeMapController(); + _initializeController(); super.didChangeDependencies(); } @@ -1331,24 +738,9 @@ class _TileLayerState extends State with TickerProviderStateMixin { @override void dispose() { - if (_controller != null) { - _controller! - ..removeZoomingListener(_handleZooming) - ..removePanningListener(_handlePanning) - ..removeResetListener(_handleReset); - _controller = null; + if (_zoomController != null) { + _zoomController!.removeListener(_handledZoomPanChange); } - - _zoomLevelAnimationController - ..removeListener(_handleZoomLevelAnimation) - ..removeStatusListener(_handleZoomLevelAnimationStatusChange) - ..dispose(); - - _focalLatLngAnimationController - ..removeListener(_handleFocalLatLngAnimation) - ..removeStatusListener(_handleFocalLatLngAnimationStatusChange) - ..dispose(); - widget.controller?.removeListener(refreshMarkers); _controller?.dispose(); _markers!.clear(); @@ -1377,12 +769,16 @@ class _TileLayerState extends State with TickerProviderStateMixin { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - if (_size != null) { - _isSizeChanged = _size!.width != constraints.maxWidth || - _size!.height != constraints.maxHeight; + final Size newSize = getBoxSize(constraints); + if (_size == null || _size != newSize) { + _size = newSize; + _controller!.tileLayerBoxSize = _size; + _validateInitialBounds(); + // Recalculate tiles bounds origin for all existing zoom level + // when size changed. + _updateZoomLevelTransforms(_currentFocalLatLng, _currentZoomLevel); } - _size = getBoxSize(constraints); - _controller!.tileLayerBoxSize = _size; + return _buildTileLayer(); }, ); 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 0a1413861..c6cb2167b 100644 --- a/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart +++ b/packages/syncfusion_flutter_maps/lib/src/layer/vector_layers.dart @@ -41,6 +41,50 @@ Offset _getScaledOffset(Offset offset, MapController controller) { return offset; } +// To calculate a point along a line with a radius away from another point. +Offset _getPointAlongLineWithCap( + Offset start, Offset end, double capRadius, bool canUpdateStartPoint) { + // Calculate distance between two points. + // i.e, d = sqrt[(x1 - x2)^2 + (y1 - y2)^2]. + // Here, x1 = end point's dx value, x2 = start point's dx value, + // y1 = end point's dy value and y2 = start point's dy value. + final double lineLength = sqrt((end.dx - start.dx) * (end.dx - start.dx) + + (end.dy - start.dy) * (end.dy - start.dy)); + if (canUpdateStartPoint) { + // Calculated the center point, if the distance between the two points + // is less than the cap diameter. + if (lineLength < (capRadius * 2)) { + return start + ((end - start) / 2); + } + // Calculate ratio of distances from start point towards end point + // i.e, d = r / l. Here, r = capRadius and l = lineLength. + final double startPointDifference = capRadius / lineLength; + // Then the required point is ((1 − d)x1 + dx2, (1 − d)y1 + dy2). + // Here, x1 = start point's dx value, x2 = end point's dx value, + // y1 = start point's dy value, y2 = end point's dy value + // and d = startPointDifference. + return Offset( + (1 - startPointDifference) * start.dx + startPointDifference * end.dx, + (1 - startPointDifference) * start.dy + startPointDifference * end.dy); + } else { + // Returned the start point, if the distance between the two points + // is less than the cap radius. + if (lineLength < capRadius) { + return start; + } + // Calculate ratio of distances from end point towards start point + // i.e, d = (l - r) / l. Here, r = capRadius and l = lineLength. + final double endPointDifference = (lineLength - capRadius) / lineLength; + // Then the required point is ((1 − d)x1 + dx2, (1 − d)y1 + dy2). + // Here, x1 = start point's dx value, x2 = end point's dx value, + // y1 = start point's dy value, y2 = end point's dy value + // and d = endPointDifference. + return Offset( + (1 - endPointDifference) * start.dx + endPointDifference * end.dx, + (1 - endPointDifference) * start.dy + endPointDifference * end.dy); + } +} + void _drawInvertedPath( PaintingContext context, Path path, @@ -75,13 +119,10 @@ void _drawInvertedPath( Color _getHoverColor( Color? elementColor, Color layerColor, SfMapsThemeData themeData) { final Color color = elementColor ?? layerColor; - final bool canAdjustHoverOpacity = - double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity; return themeData.shapeHoverColor != null && themeData.shapeHoverColor != Colors.transparent ? themeData.shapeHoverColor! - : color.withOpacity( - canAdjustHoverOpacity ? hoverColorOpacity : minHoverOpacity); + : getSaturatedColor(color); } /// Base class for all vector layers. @@ -171,6 +212,7 @@ class MapLineLayer extends MapVectorLayer { this.animation, this.color, this.width = 2, + this.strokeCap = StrokeCap.butt, this.dashArray = const [0, 0], IndexedWidgetBuilder? tooltipBuilder, }) : super(key: key, tooltipBuilder: tooltipBuilder); @@ -476,6 +518,72 @@ class MapLineLayer extends MapVectorLayer { /// [color], to set the color. final double width; + /// Applies stroke cap to the start and end of all [lines]. You can set + /// [StrokeCap.round] to get a semi-circle or [StrokeCap.square] to get + /// a semi-square at the edges of the line. + /// + /// The default value is [StrokeCap.butt] which doesn't apply any cap + /// at the ends. + /// + /// ```dart + /// late List _lines; + /// late MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// ); + /// }, + /// ).toSet(), + /// strokeCap: StrokeCap.round, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; + /// } + /// ``` + /// See also: + /// * [MapLine.strokeCap] for setting stroke cap for each [MapLine]. + final StrokeCap strokeCap; + /// Apply same dash pattern for all the [MapLine] in the [lines] collection. /// /// A sequence of dash and gap will be rendered based on the values in this @@ -558,6 +666,7 @@ class MapLineLayer extends MapVectorLayer { animation: animation, color: color, width: width, + strokeCap: strokeCap, dashArray: dashArray, tooltipBuilder: tooltipBuilder, lineLayer: this, @@ -582,6 +691,7 @@ class MapLineLayer extends MapVectorLayer { properties.add(ColorProperty('color', color)); } properties.add(DoubleProperty('width', width)); + properties.add(DiagnosticsProperty('strokeCap', strokeCap)); } } @@ -591,6 +701,7 @@ class _MapLineLayer extends StatefulWidget { required this.animation, required this.color, required this.width, + required this.strokeCap, required this.dashArray, required this.tooltipBuilder, required this.lineLayer, @@ -600,6 +711,7 @@ class _MapLineLayer extends StatefulWidget { final Animation? animation; final Color? color; final double width; + final StrokeCap strokeCap; final List dashArray; final IndexedWidgetBuilder? tooltipBuilder; final MapLineLayer lineLayer; @@ -656,6 +768,7 @@ class _MapLineLayerState extends State<_MapLineLayer> ? const Color.fromRGBO(140, 140, 140, 1) : const Color.fromRGBO(208, 208, 208, 1)), width: widget.width, + strokeCap: widget.strokeCap, dashArray: widget.dashArray, tooltipBuilder: widget.tooltipBuilder, lineLayer: widget.lineLayer, @@ -674,6 +787,7 @@ class _MapLineLayerRenderObject extends LeafRenderObjectWidget { required this.animation, required this.color, required this.width, + required this.strokeCap, required this.dashArray, required this.tooltipBuilder, required this.lineLayer, @@ -688,6 +802,7 @@ class _MapLineLayerRenderObject extends LeafRenderObjectWidget { final Animation? animation; final Color color; final double width; + final StrokeCap strokeCap; final List dashArray; final IndexedWidgetBuilder? tooltipBuilder; final MapLineLayer lineLayer; @@ -704,6 +819,7 @@ class _MapLineLayerRenderObject extends LeafRenderObjectWidget { animation: animation, color: color, width: width, + strokeCap: strokeCap, dashArray: dashArray, tooltipBuilder: tooltipBuilder, context: context, @@ -722,6 +838,7 @@ class _MapLineLayerRenderObject extends LeafRenderObjectWidget { ..animation = animation ..color = color ..width = width + ..strokeCap = strokeCap ..dashArray = dashArray ..tooltipBuilder = tooltipBuilder ..context = context @@ -737,6 +854,7 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { required Animation? animation, required Color color, required double width, + required StrokeCap strokeCap, required List dashArray, required IndexedWidgetBuilder? tooltipBuilder, required this.context, @@ -750,6 +868,7 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { _animation = animation, _color = color, _width = width, + _strokeCap = strokeCap, _dashArray = dashArray, _tooltipBuilder = tooltipBuilder, _themeData = themeData { @@ -838,6 +957,16 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } + StrokeCap get strokeCap => _strokeCap; + StrokeCap _strokeCap; + set strokeCap(StrokeCap value) { + if (_strokeCap == value) { + return; + } + _strokeCap = value; + markNeedsPaint(); + } + List get dashArray => _dashArray; List _dashArray; set dashArray(List value) { @@ -1030,6 +1159,7 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { super.attach(owner); if (_controller != null) { _controller! + ..addZoomPanListener(markNeedsPaint) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(markNeedsPaint) @@ -1043,6 +1173,7 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { void detach() { if (_controller != null) { _controller! + ..removeZoomPanListener(markNeedsPaint) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(markNeedsPaint) @@ -1096,6 +1227,16 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { translationOffset, _controller!.shapeLayerSizeFactor, ); + final double strokeWidth = + _getCurrentWidth(line.width ?? _width, _controller!); + final StrokeCap strokeCap = line.strokeCap ?? _strokeCap; + final bool hasCap = strokeCap != StrokeCap.butt; + if (hasCap) { + startPoint = _getPointAlongLineWithCap( + startPoint, endPoint, strokeWidth / 2, true); + endPoint = _getPointAlongLineWithCap( + startPoint, endPoint, strokeWidth / 2, false); + } if (_previousHoverItem != null && _previousHoverItem == line && @@ -1109,7 +1250,9 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { paint.color = line.color ?? _color; } - paint.strokeWidth = _getCurrentWidth(line.width ?? _width, _controller!); + paint + ..strokeWidth = strokeWidth + ..strokeCap = strokeCap; path ..reset() ..moveTo(startPoint.dx, startPoint.dy) @@ -1120,7 +1263,8 @@ class _RenderMapLine extends RenderBox implements MouseTrackerAnnotation { if (line.dashArray != null) { assert(line.dashArray!.length >= 2 && line.dashArray!.length.isEven); - _drawDashedLine(context.canvas, line.dashArray!, paint, path); + _drawDashedLine(context.canvas, line.dashArray!, paint, path, + hasCap ? strokeWidth / 2 : 0); } else { _drawDashedLine(context.canvas, _dashArray, paint, path); } @@ -2045,6 +2189,7 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { super.attach(owner); if (_controller != null) { _controller! + ..addZoomPanListener(markNeedsPaint) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(markNeedsPaint) @@ -2058,6 +2203,7 @@ class _RenderMapArc extends RenderBox implements MouseTrackerAnnotation { void detach() { if (_controller != null) { _controller! + ..removeZoomPanListener(markNeedsPaint) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(markNeedsPaint) @@ -2247,6 +2393,7 @@ class MapPolylineLayer extends MapVectorLayer { this.animation, this.color, this.width = 2, + this.strokeCap = StrokeCap.butt, this.dashArray = const [0, 0], IndexedWidgetBuilder? tooltipBuilder, }) : super(key: key, tooltipBuilder: tooltipBuilder); @@ -2524,6 +2671,71 @@ class MapPolylineLayer extends MapVectorLayer { /// [color], for setting the color. final double width; + /// Applies stroke cap to the start and end of all [polylines]. You can set + /// [StrokeCap.round] to get a semi-circle or [StrokeCap.square] to get a + /// semi-square at the edges of the polyline. + /// + /// The default value is [StrokeCap.butt] which doesn't apply any cap + /// at the ends. + /// + /// See also: + /// * [MapPolyline.strokeCap] for setting stroke cap for each [MapPolyline]. + /// + /// ```dart + /// late List _polyLines; + /// late MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// _polyLines.length, + /// (int index) { + /// return MapPolyline( + /// points: _polyLines, + /// ); + /// }, + /// ).toSet(), + /// strokeCap: StrokeCap.round, + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + /// See also: + /// * [MapPolyline.strokeCap] for setting stroke cap for each [MapPolyline]. + final StrokeCap strokeCap; + /// Apply same dash pattern for all the [MapPolyline] in the [polylines] /// collection. /// @@ -2595,6 +2807,7 @@ class MapPolylineLayer extends MapVectorLayer { animation: animation, color: color, width: width, + strokeCap: strokeCap, dashArray: dashArray, tooltipBuilder: tooltipBuilder, polylineLayer: this, @@ -2618,6 +2831,7 @@ class MapPolylineLayer extends MapVectorLayer { properties.add(ColorProperty('color', color)); } properties.add(DoubleProperty('width', width)); + properties.add(DiagnosticsProperty('strokeCap', strokeCap)); } } @@ -2627,6 +2841,7 @@ class _MapPolylineLayer extends StatefulWidget { required this.animation, required this.color, required this.width, + required this.strokeCap, required this.dashArray, required this.tooltipBuilder, required this.polylineLayer, @@ -2636,6 +2851,7 @@ class _MapPolylineLayer extends StatefulWidget { final Animation? animation; final Color? color; final double width; + final StrokeCap strokeCap; final List dashArray; final IndexedWidgetBuilder? tooltipBuilder; final MapPolylineLayer polylineLayer; @@ -2692,6 +2908,7 @@ class _MapPolylineLayerState extends State<_MapPolylineLayer> ? const Color.fromRGBO(140, 140, 140, 1) : const Color.fromRGBO(208, 208, 208, 1)), width: widget.width, + strokeCap: widget.strokeCap, dashArray: widget.dashArray, tooltipBuilder: widget.tooltipBuilder, polylineLayer: widget.polylineLayer, @@ -2710,6 +2927,7 @@ class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { required this.animation, required this.color, required this.width, + required this.strokeCap, required this.dashArray, required this.tooltipBuilder, required this.polylineLayer, @@ -2724,6 +2942,7 @@ class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { final Animation? animation; final Color color; final double width; + final StrokeCap strokeCap; final List dashArray; final IndexedWidgetBuilder? tooltipBuilder; final MapPolylineLayer polylineLayer; @@ -2740,6 +2959,7 @@ class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { animation: animation, color: color, width: width, + strokeCap: strokeCap, dashArray: dashArray, tooltipBuilder: tooltipBuilder, context: context, @@ -2759,6 +2979,7 @@ class _MapPolylineLayerRenderObject extends LeafRenderObjectWidget { ..animation = animation ..color = color ..width = width + ..strokeCap = strokeCap ..dashArray = dashArray ..tooltipBuilder = tooltipBuilder ..context = context @@ -2774,6 +2995,7 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { required Animation? animation, required Color color, required double width, + required StrokeCap strokeCap, required List dashArray, required IndexedWidgetBuilder? tooltipBuilder, required this.context, @@ -2786,6 +3008,7 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { _polylines = polylines, _color = color, _width = width, + _strokeCap = strokeCap, _dashArray = dashArray, _animation = animation, _tooltipBuilder = tooltipBuilder, @@ -2872,6 +3095,16 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } + StrokeCap get strokeCap => _strokeCap; + StrokeCap _strokeCap; + set strokeCap(StrokeCap value) { + if (_strokeCap == value) { + return; + } + _strokeCap = value; + markNeedsPaint(); + } + List get dashArray => _dashArray; List _dashArray; set dashArray(List value) { @@ -3042,6 +3275,7 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { super.attach(owner); if (_controller != null) { _controller! + ..addZoomPanListener(markNeedsPaint) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(markNeedsPaint) @@ -3055,6 +3289,7 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { void detach() { if (_controller != null) { _controller! + ..removeZoomPanListener(markNeedsPaint) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(markNeedsPaint) @@ -3109,18 +3344,65 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { final Offset translationOffset = _getTranslation(_controller!); _controller!.applyTransform(context, offset, true); for (final MapPolyline polyline in polylines) { + final int polylinePointsLength = polyline.points.length; final MapLatLng startCoordinate = polyline.points[0]; - final Offset startPoint = pixelFromLatLng( + final MapLatLng endCoordinate = polyline.points[polylinePointsLength - 1]; + final double strokeWidth = + _getCurrentWidth(polyline.width ?? _width, _controller!); + final StrokeCap strokeCap = polyline.strokeCap ?? _strokeCap; + final bool hasCap = strokeCap != StrokeCap.butt; + Offset startPoint = pixelFromLatLng( startCoordinate.latitude, startCoordinate.longitude, boxSize, translationOffset, _controller!.shapeLayerSizeFactor); + Offset endPoint = pixelFromLatLng( + endCoordinate.latitude, + endCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor); + + if (hasCap) { + final MapLatLng? secondCoordinate = + polylinePointsLength > 1 ? polyline.points[1] : null; + final MapLatLng? beforeEndCoordinate = polylinePointsLength > 1 + ? polyline.points[polylinePointsLength - 2] + : null; + if (secondCoordinate != null) { + final Offset secondPoint = pixelFromLatLng( + secondCoordinate.latitude, + secondCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor); + startPoint = _getPointAlongLineWithCap( + startPoint, secondPoint, strokeWidth / 2, true); + } + + if (beforeEndCoordinate != null) { + final Offset beforeEndPoint = pixelFromLatLng( + beforeEndCoordinate.latitude, + beforeEndCoordinate.longitude, + boxSize, + translationOffset, + _controller!.shapeLayerSizeFactor); + endPoint = _getPointAlongLineWithCap( + beforeEndPoint, endPoint, strokeWidth / 2, false); + } + } + + paint + ..strokeWidth = strokeWidth + ..strokeCap = strokeCap + ..strokeJoin = + strokeCap == StrokeCap.round ? StrokeJoin.round : StrokeJoin.miter; path ..reset() ..moveTo(startPoint.dx, startPoint.dy); - for (int j = 1; j < polyline.points.length; j++) { + for (int j = 1; j < polylinePointsLength; j++) { final MapLatLng nextCoordinate = polyline.points[j]; final Offset nextPoint = pixelFromLatLng( nextCoordinate.latitude, @@ -3128,7 +3410,11 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { boxSize, translationOffset, _controller!.shapeLayerSizeFactor); - path.lineTo(nextPoint.dx, nextPoint.dy); + if (j < polylinePointsLength - 1) { + path.lineTo(nextPoint.dx, nextPoint.dy); + } else { + path.lineTo(endPoint.dx, endPoint.dy); + } } if (_previousHoverItem != null && @@ -3143,8 +3429,6 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { paint.color = polyline.color ?? _color; } - paint.strokeWidth = - _getCurrentWidth(polyline.width ?? _width, _controller!); if (_animation != null) { path = _getAnimatedPath(path, _animation!); } @@ -3152,7 +3436,8 @@ class _RenderMapPolyline extends RenderBox implements MouseTrackerAnnotation { if (polyline.dashArray != null) { assert(polyline.dashArray!.length >= 2 && polyline.dashArray!.length.isEven); - _drawDashedLine(context.canvas, polyline.dashArray!, paint, path); + _drawDashedLine(context.canvas, polyline.dashArray!, paint, path, + hasCap ? strokeWidth / 2 : 0); } else { _drawDashedLine(context.canvas, _dashArray, paint, path); } @@ -3619,8 +3904,8 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> ErrorSummary('Incorrect MapPolygon arguments.'), ErrorDescription( 'Inverted polygons cannot be customized individually.'), - ErrorHint( - '''To customize all the polygon's color, use MapPolygonLayer.color''') + ErrorHint('To customize all the polygon\'s color,' + ' use MapPolygonLayer.color') ])); assert( polygon.strokeColor == null, @@ -3628,8 +3913,8 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> ErrorSummary('Incorrect MapPolygon arguments.'), ErrorDescription( 'Inverted polygons cannot be customized individually.'), - ErrorHint( - '''To customize all the polygon's stroke color, use MapPolygonLayer.strokeColor''') + ErrorHint('To customize all the polygon\'s stroke color,' + ' use MapPolygonLayer.strokeColor') ])); assert( polygon.strokeWidth == null, @@ -3637,8 +3922,8 @@ class _MapPolygonLayerState extends State<_MapPolygonLayer> ErrorSummary('Incorrect MapPolygon arguments.'), ErrorDescription( 'Inverted polygons cannot be customized individually.'), - ErrorHint( - '''To customize all the polygon's stroke width, use MapPolygonLayer.strokeWidth''') + ErrorHint('To customize all the polygon\'s stroke width,' + ' use MapPolygonLayer.strokeWidth') ])); } } @@ -3909,10 +4194,7 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { return _themeData.shapeHoverColor!; } final Color color = polygon.color ?? _color; - return color.withOpacity( - (double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity) - ? hoverColorOpacity - : minHoverOpacity); + return getSaturatedColor(color); } Color _getHoverStrokeColor(MapPolygon polygon) { @@ -3920,11 +4202,7 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { return _themeData.shapeHoverStrokeColor!; } final Color strokeColor = polygon.strokeColor ?? _strokeColor; - return strokeColor.withOpacity( - (double.parse(strokeColor.opacity.toStringAsFixed(2)) != - hoverColorOpacity) - ? hoverColorOpacity - : minHoverOpacity); + return getSaturatedColor(strokeColor); } void _handlePointerExit(PointerExitEvent event) { @@ -3999,7 +4277,7 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { : size; final Offset translationOffset = getTranslationOffset(_controller!); for (final MapPolygon polygon in _polygonsInList!.reversed) { - if (polygon.onTap != null || _tooltipBuilder != null || canHover) { + if (polygon.onTap != null || _tooltipBuilder != null || isDesktop) { final Path path = Path(); final MapLatLng startCoordinate = polygon.points[0]; @@ -4059,6 +4337,7 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { super.attach(owner); if (_controller != null) { _controller! + ..addZoomPanListener(markNeedsPaint) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(markNeedsPaint) @@ -4071,6 +4350,7 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { void detach() { if (_controller != null) { _controller! + ..removeZoomPanListener(markNeedsPaint) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(markNeedsPaint) @@ -4142,7 +4422,9 @@ class _RenderMapPolygon extends RenderBox implements MouseTrackerAnnotation { if (!isInverted) { _updateFillColor(polygon, fillPaint); _updateStroke(polygon, strokePaint); - context.canvas..drawPath(path, fillPaint)..drawPath(path, strokePaint); + context.canvas + ..drawPath(path, fillPaint) + ..drawPath(path, strokePaint); } } @@ -4291,7 +4573,7 @@ class MapCircleLayer extends MapVectorLayer { /// to the outer region of the highlighted circles. /// /// Only one [MapCircleLayer.inverted] can be added and must be positioned at - /// the top of all sublayers added under the [sublayers] property. + /// the top of all sublayers added under the [MapLayer.sublayers] property. /// /// ```dart /// late List _circles; @@ -4792,8 +5074,8 @@ class _MapCircleLayerState extends State<_MapCircleLayer> ErrorSummary('Incorrect MapCircle arguments.'), ErrorDescription( 'Inverted circles cannot be customized individually.'), - ErrorHint( - '''To customize all the circle's color, use MapCircleLayer.color''') + ErrorHint('To customize all the circle\'s color,' + ' use MapCircleLayer.color') ])); assert( circle.strokeColor == null, @@ -4801,8 +5083,8 @@ class _MapCircleLayerState extends State<_MapCircleLayer> ErrorSummary('Incorrect MapCircle arguments.'), ErrorDescription( 'Inverted circles cannot be customized individually.'), - ErrorHint( - '''To customize all the circle's stroke color, use MapCircleLayer.strokeColor''') + ErrorHint('To customize all the circle\'s stroke color,' + ' use MapCircleLayer.strokeColor') ])); assert( circle.strokeWidth == null, @@ -4810,8 +5092,8 @@ class _MapCircleLayerState extends State<_MapCircleLayer> ErrorSummary('Incorrect MapCircle arguments.'), ErrorDescription( 'Inverted circles cannot be customized individually.'), - ErrorHint( - '''To customize all the circle's stroke width, use MapCircleLayer.strokeWidth''') + ErrorHint('To customize all the circle\'s stroke width,' + ' use MapCircleLayer.strokeWidth') ])); } } @@ -5097,10 +5379,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { return _themeData.shapeHoverColor!; } final Color color = circle.color ?? _color; - return color.withOpacity( - (double.parse(color.opacity.toStringAsFixed(2)) != hoverColorOpacity) - ? hoverColorOpacity - : minHoverOpacity); + return getSaturatedColor(color); } Color _getHoverStrokeColor(MapCircle circle) { @@ -5108,11 +5387,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { return _themeData.shapeHoverStrokeColor!; } final Color strokeColor = circle.strokeColor ?? _strokeColor; - return strokeColor.withOpacity( - (double.parse(strokeColor.opacity.toStringAsFixed(2)) != - hoverColorOpacity) - ? hoverColorOpacity - : minHoverOpacity); + return getSaturatedColor(strokeColor); } void _handlePointerExit(PointerExitEvent event) { @@ -5216,7 +5491,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { final Offset translationOffset = getTranslationOffset(_controller!); for (final MapCircle circle in _circlesInList!.reversed) { - if (circle.onTap != null || _tooltipBuilder != null || canHover) { + if (circle.onTap != null || _tooltipBuilder != null || isDesktop) { final Path path = Path(); Offset center = pixelFromLatLng( circle.center.latitude, @@ -5247,6 +5522,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { super.attach(owner); if (_controller != null) { _controller! + ..addZoomPanListener(markNeedsPaint) ..addZoomingListener(_handleZooming) ..addPanningListener(_handlePanning) ..addResetListener(markNeedsPaint) @@ -5260,6 +5536,7 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { void detach() { if (_controller != null) { _controller! + ..removeZoomPanListener(markNeedsPaint) ..removeZoomingListener(_handleZooming) ..removePanningListener(_handlePanning) ..removeResetListener(markNeedsPaint) @@ -5344,7 +5621,9 @@ class _RenderMapCircle extends RenderBox implements MouseTrackerAnnotation { if (!isInverted) { _updateFillColor(circle, fillPaint); _updateStroke(circle, strokePaint); - context.canvas..drawPath(path, fillPaint)..drawPath(path, strokePaint); + context.canvas + ..drawPath(path, fillPaint) + ..drawPath(path, strokePaint); } } @@ -5487,6 +5766,7 @@ class MapLine extends DiagnosticableTree { this.dashArray, this.color, this.width, + this.strokeCap, this.onTap, }); @@ -5825,6 +6105,70 @@ class MapLine extends DiagnosticableTree { ///``` final double? width; + /// Applies stroke cap to the start and end of the line. You can set + /// [StrokeCap.round] to get a semi-circle or [StrokeCap.square] to get + /// a semi-square at the edges of the line. + /// + /// The default value is [StrokeCap.butt] which doesn't apply any cap + /// at the ends. + /// + /// ```dart + /// late List _lines; + /// late MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// ); + /// + /// _lines = [ + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(44.9778, -93.2650)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(33.4484, -112.0740)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(29.7604, -95.3698)), + /// Model(MapLatLng(40.7128, -74.0060), MapLatLng(39.7392, -104.9903)), + /// ]; + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapLineLayer( + /// lines: List.generate( + /// _lines.length, + /// (int index) { + /// return MapLine( + /// from: _lines[index].from, + /// to: _lines[index].to, + /// strokeCap: StrokeCap.round, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class Model { + /// Model(this.from, this.to); + /// + /// MapLatLng from; + /// MapLatLng to; + /// } + ///``` + final StrokeCap? strokeCap; + /// Callback to receive tap event for this line. /// /// You can customize the appearance of the tapped line based on the index @@ -5917,6 +6261,10 @@ class MapLine extends DiagnosticableTree { properties.add(DoubleProperty('width', width)); } + if (strokeCap != null) { + properties.add(DiagnosticsProperty('strokeCap', strokeCap)); + } + properties.add(ObjectFlagProperty.has('onTap', onTap)); } } @@ -5986,6 +6334,7 @@ class MapPolyline extends DiagnosticableTree { this.dashArray, this.color, this.width, + this.strokeCap, this.onTap, }); @@ -6238,6 +6587,65 @@ class MapPolyline extends DiagnosticableTree { /// ``` final double? width; + /// Applies stroke cap to the start and end of the [MapPolyline]. You can set + /// [StrokeCap.round] to get a semi-circle or [StrokeCap.square] to get a + /// semi-square at the edges of the polyline. + /// + /// By default, the [StrokeCap.butt] which doesn't apply any cap at endings. + /// + /// ```dart + /// late List _polyLines; + /// late MapShapeSource _mapSource; + /// + /// @override + /// void initState() { + /// _polyLines = [ + /// MapLatLng(13.0827, 80.2707), + /// MapLatLng(14.4673, 78.8242), + /// MapLatLng(14.9091, 78.0092), + /// MapLatLng(16.2160, 77.3566), + /// MapLatLng(17.1557, 76.8697), + /// MapLatLng(18.0975, 75.4249), + /// MapLatLng(18.5204, 73.8567), + /// MapLatLng(19.0760, 72.8777), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// 'assets/india.json', + /// shapeDataField: 'name', + /// ); + /// + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// sublayers: [ + /// MapPolylineLayer( + /// polylines: List.generate( + /// _polyLines.length, + /// (int index) { + /// return MapPolyline( + /// points: _polyLines, + /// strokeCap: StrokeCap.round, + /// ); + /// }, + /// ).toSet(), + /// ), + /// ], + /// ), + /// ], + /// ), + /// ); + /// } + /// ``` + final StrokeCap? strokeCap; + /// Callback to receive tap event for this polyline. /// /// You can customize the appearance of the tapped polyline based on the @@ -6324,6 +6732,10 @@ class MapPolyline extends DiagnosticableTree { properties.add(DoubleProperty('width', width)); } + if (strokeCap != null) { + properties.add(DiagnosticsProperty('strokeCap', strokeCap)); + } + properties.add(ObjectFlagProperty.has('onTap', onTap)); } } @@ -7781,6 +8193,7 @@ class MapArc extends DiagnosticableTree { Path? _dashPath( Path? source, { required _IntervalList dashArray, + double capRadius = 0, }) { if (source == null) { return null; @@ -7793,8 +8206,17 @@ Path? _dashPath( while (distance < matric.length) { final double length = dashArray.next; if (canDraw) { - path.addPath( - matric.extractPath(distance, distance + length), Offset.zero); + double startPoint = distance; + double endPoint = distance + length; + if (capRadius != 0) { + startPoint = _getPointAlongLineWithCap( + Offset(startPoint, 0), Offset(endPoint, 0), capRadius, true) + .dx; + endPoint = _getPointAlongLineWithCap( + Offset(startPoint, 0), Offset(endPoint, 0), capRadius, false) + .dx; + } + path.addPath(matric.extractPath(startPoint, endPoint), Offset.zero); } distance += length; canDraw = !canDraw; @@ -7804,7 +8226,8 @@ Path? _dashPath( } void _drawDashedLine( - Canvas canvas, List dashArray, Paint paint, Path path) { + Canvas canvas, List dashArray, Paint paint, Path path, + [double capRadius = 0]) { bool even = false; for (int i = 1; i < dashArray.length; i = i + 2) { if (dashArray[i] == 0) { @@ -7817,6 +8240,7 @@ void _drawDashedLine( _dashPath( path, dashArray: _IntervalList(dashArray), + capRadius: capRadius, )!, paint); } else { diff --git a/packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart b/packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart new file mode 100644 index 000000000..f78105674 --- /dev/null +++ b/packages/syncfusion_flutter_maps/lib/src/layer/zoomable.dart @@ -0,0 +1,862 @@ +import 'dart:async' show Timer; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/physics.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:vector_math/vector_math_64.dart' show Vector3, Matrix4; + +/// Signature for when the zoom level or actual rect value changes get +/// indicated. +typedef ZoomableUpdateCallback = void Function(ZoomPanDetails); + +/// Signature for when the zooming and panning action has been completed. +typedef ZoomableCompleteCallback = void Function(ZoomPanDetails); + +/// Signature for when zoom level change callback. +typedef ZoomCallback = void Function(double); + +/// Signature for when actual rect change callback. +typedef RectCallback = void Function(Rect); + +/// Shows the current action performing in the zoomable widget. +enum ActionType { + /// Denotes the current action as pinching. + pinch, + + /// Denotes the current action as panning. + pan, + + /// Denotes the current action as double tap. + tap, + + /// Denotes the current action as flinging. + fling, + + /// Denotes there is no action currently. + none +} + +/// Contains details about the current zooming and panning action. +class ZoomPanDetails { + /// Creates a [ZoomPanDetails]. + ZoomPanDetails({ + required this.localFocalPoint, + required this.globalFocalPoint, + required this.previousZoomLevel, + required this.newZoomLevel, + required this.previousRect, + required this.actualRect, + required this.scale, + required this.pinchCenter, + }); + + /// The local focal point of the pointers in contact with the screen. + final Offset localFocalPoint; + + /// The global focal point of the pointers in contact with the screen. + final Offset globalFocalPoint; + + /// Provides the last zoom level which has been in zoomable. + final double previousZoomLevel; + + /// Provides the new zoom level for the current zooming. + final double newZoomLevel; + + /// Specifies the previous rect of the zoomable. + final Rect previousRect; + + /// Specifies the new rect for the current zooming. + final Rect actualRect; + + /// Specifies the current scale based on interaction. + final double scale; + + /// Specified the center point which we have pinching currently. + final Offset? pinchCenter; +} + +/// A widget which handles the zooming and panning with considering its bounds +/// based on the zoomLevel and child size. +class Zoomable extends StatefulWidget { + /// Creates a [Zoomable]. + const Zoomable({ + Key? key, + required this.initialZoomLevel, + required this.initialRect, + required this.minZoomLevel, + required this.maxZoomLevel, + required this.zoomController, + this.enablePinching = true, + this.enablePanning = true, + this.enableDoubleTapZooming = false, + this.animationDuration = const Duration(milliseconds: 600), + this.frictionCoefficient = 0.005, + required this.onUpdate, + required this.onComplete, + this.child, + }) : assert(minZoomLevel >= 1 && minZoomLevel <= maxZoomLevel), + assert(initialZoomLevel >= minZoomLevel && + initialZoomLevel <= maxZoomLevel), + assert(frictionCoefficient > 0.0), + super(key: key); + + /// Specifies the initial zoomLevel of the widget. + /// + /// It can't be null and must between the [minZoomLevel] and [maxZoomLevel]. + final double initialZoomLevel; + + /// A rect which is used to translate the controller matrix initially. + final Rect initialRect; + + /// Specifies the minimum zoomLevel of the widget. + /// + /// It can't be null and must be greater than or equal to `1` and lesser than + /// [maxZoomLevel]. + final double minZoomLevel; + + /// Specifies the maximum zoomLevel of the widget. + /// + /// It can't be null and must be greater than or equal to `1` and + /// [minZoomLevel]. + final double maxZoomLevel; + + /// Holds the details of the current zoom level and actual rect. These details + /// can be used in multiple widget which used the same zoomable widget. + final ZoomableController zoomController; + + /// An animation duration which is used to animate the zoom level and actual + /// rect property change using the given duration. + /// + /// By default it will be `600` milliseconds. + final Duration animationDuration; + + /// In the inertial translation animation, this value is used as the + /// coefficient of friction. + /// + /// By default it will be `0.005`. + final double frictionCoefficient; + + /// Option to enable pinch zooming support. + /// + /// By default it will be `true`. + final bool enablePinching; + + /// Option to enable panning support. + /// + /// By default it will be `true`. + final bool enablePanning; + + /// Option to enable double tap zooming support. + /// + /// By default it will be `true`. + final bool enableDoubleTapZooming; + + /// Called when there is a change in the zoom level or actual rect of the + /// zoomable widget. + final ZoomableUpdateCallback onUpdate; + + /// Called when the zoom level or actual rect updating has been completed. + final ZoomableCompleteCallback onComplete; + + /// Specifies the child of the zoomable widget. It didn't get updated based on + /// the zoomable widget interaction or changes. + /// + /// By default it will be `null`. + final Widget? child; + + @override + _ZoomableState createState() => _ZoomableState(); +} + +class _ZoomableState extends State with TickerProviderStateMixin { + late AnimationController _zoomLevelAnimationController; + late AnimationController _actualRectAnimationController; + late CurvedAnimation _zoomLevelAnimation; + late CurvedAnimation _actualRectAnimation; + late Tween _zoomLevelTween; + late Tween _actualRectTween; + late Matrix4 _newMatrix; + late Offset _startLocalPoint; + + Rect? _boundaryRect; + Timer? _doubleTapTimer; + Size? _size; + Offset? _matrixStartPoint; + + double _maximumReachedScaleOnInteraction = 1.0; + double _lastScaleValueOnInteraction = 1.0; + double? _scaleStart; + int _pointerCount = 0; + + bool _isFlingAnimationActive = false; + bool _doubleTapEnabled = false; + + // The boundary rect is the actual rect in which the edge restriction has + // been handled using this rect only. + Rect _getBoundaryRect() { + final double width = + widget.initialRect.size.width / _getScale(widget.initialZoomLevel); + final double height = + widget.initialRect.size.height / _getScale(widget.initialZoomLevel); + return Offset.zero & Size(width, height); + } + + // Returns a new matrix based on the given translation offset. + Matrix4 _translateMatrix(Matrix4 matrix, Offset translation) { + if (translation == Offset.zero) { + return matrix.clone(); + } + + return matrix.clone()..translate(translation.dx, translation.dy); + } + + // Scales the given matrix with considering the min and max zoomLevel. + Matrix4 _matrixScale(Matrix4 matrix, double scale) { + if (scale == 1.0) { + return matrix.clone(); + } + assert(scale != 0.0); + + final double currentScale = matrix.getMaxScaleOnAxis(); + final double clampedTotalScale = (currentScale * scale) + .clamp(_getScale(widget.minZoomLevel), _getScale(widget.maxZoomLevel)); + final double clampedScale = clampedTotalScale / currentScale; + return matrix.clone()..scale(clampedScale); + } + + ActionType _getActionTypes( + double scale, Offset focalPoint, Offset startFocalPoint) { + // The minimum distance required to start scale or pan gesture. + const int minScaleDistance = 3; + final Offset distance = focalPoint - startFocalPoint; + if (scale == 1) { + return widget.enablePanning && + (distance.dx.abs() > minScaleDistance || + distance.dy.abs() > minScaleDistance) + ? ActionType.pan + : ActionType.none; + } + + return widget.enablePinching && + (distance.dx.abs() > minScaleDistance || + distance.dy.abs() > minScaleDistance) + ? ActionType.pinch + : ActionType.none; + } + + void _handleScaleStart(ScaleStartDetails details) { + if (!_zoomLevelAnimationController.isAnimating && + !_actualRectAnimationController.isAnimating) { + widget.zoomController.actionType = ActionType.none; + _maximumReachedScaleOnInteraction = 1.0; + _lastScaleValueOnInteraction = 1.0; + + _startLocalPoint = details.localFocalPoint; + _newMatrix = widget.zoomController.controllerMatrix.clone(); + _scaleStart = _newMatrix.getMaxScaleOnAxis(); + _matrixStartPoint = _getActualPointInMatrix(_newMatrix, _startLocalPoint); + } + } + + void _handleScaleUpdate(ScaleUpdateDetails details) { + if (!_zoomLevelAnimationController.isAnimating && + !_actualRectAnimationController.isAnimating) { + final double scale = _newMatrix.getMaxScaleOnAxis(); + final Offset matrixFocalPoint = + _getActualPointInMatrix(_newMatrix, details.localFocalPoint); + + if (widget.zoomController.actionType == ActionType.none) { + widget.zoomController.actionType = _getActionTypes( + details.scale, details.localFocalPoint, _startLocalPoint); + } + + if (widget.zoomController.actionType == ActionType.none) { + return; + } + + _resetDoubleTapTimer(); + switch (widget.zoomController.actionType) { + case ActionType.pinch: + _scaleStart ??= _newMatrix.getMaxScaleOnAxis(); + + // At scale end, to perform flinging we need to know the direction + // of the zooming. For that, we have stored the max scale value + // reached during the scale update. + if (_lastScaleValueOnInteraction < details.scale) { + _maximumReachedScaleOnInteraction = details.scale; + } + + _lastScaleValueOnInteraction = details.scale; + final double desiredScale = _scaleStart! * details.scale; + final double newScale = desiredScale / scale; + _newMatrix = _matrixScale(_newMatrix, newScale); + + final Offset matrixFocalPointScaled = + _getActualPointInMatrix(_newMatrix, _startLocalPoint); + + _newMatrix = _translateMatrix( + _newMatrix, matrixFocalPointScaled - _matrixStartPoint!); + + _invokeZoomableUpdate(_newMatrix, details.localFocalPoint, + details.focalPoint, details.scale, _startLocalPoint); + break; + + case ActionType.pan: + assert(_matrixStartPoint != null); + + if (details.scale != 1.0) { + return; + } + + final Offset newTranslation = matrixFocalPoint - _matrixStartPoint!; + _newMatrix = _translateMatrix(_newMatrix, newTranslation); + _matrixStartPoint = + _getActualPointInMatrix(_newMatrix, details.localFocalPoint); + _invokeZoomableUpdate( + _newMatrix, details.localFocalPoint, details.focalPoint); + break; + case ActionType.tap: + case ActionType.fling: + case ActionType.none: + break; + } + } + } + + void _handleScaleEnd(ScaleEndDetails details) { + // If the scale ended with enough velocity, we have handled the last gesture + // with fling animation. + if (widget.zoomController.actionType != ActionType.none && + details.velocity.pixelsPerSecond.distance >= kMinFlingVelocity) { + _resetDoubleTapTimer(); + if (widget.zoomController.actionType == ActionType.pinch && + !_actualRectAnimationController.isAnimating) { + _startFlingAnimationForPinching(details); + } else if (widget.zoomController.actionType == ActionType.pan && + !_zoomLevelAnimationController.isAnimating) { + _startFlingAnimationForPanning(details); + } + } else if (!_doubleTapEnabled) { + _scaleStart = null; + widget.zoomController.actionType = ActionType.none; + _invokeZoomableComplete(_newMatrix); + } + } + + // This methods performs fling animation for pinching. + void _startFlingAnimationForPinching(ScaleEndDetails details) { + _isFlingAnimationActive = true; + _newMatrix = widget.zoomController.controllerMatrix.clone(); + final double zoomLevel = _getZoomLevel(_newMatrix.getMaxScaleOnAxis()); + final int direction = + _lastScaleValueOnInteraction >= _maximumReachedScaleOnInteraction + ? 1 + : -1; + double newZoomLevel = zoomLevel + + (direction * + (details.velocity.pixelsPerSecond.distance / kMaxFlingVelocity) * + widget.maxZoomLevel); + newZoomLevel = newZoomLevel.clamp(widget.minZoomLevel, widget.maxZoomLevel); + _zoomLevelTween + ..begin = _newMatrix.getMaxScaleOnAxis() + ..end = _getScale(newZoomLevel); + widget.zoomController.actionType = ActionType.fling; + _zoomLevelAnimationController.duration = + _getFlingAnimationDuration(details.velocity.pixelsPerSecond.distance); + _zoomLevelAnimationController.forward(from: 0.0); + } + + // This methods performs fling animation for panning. + void _startFlingAnimationForPanning(ScaleEndDetails details) { + _isFlingAnimationActive = true; + _newMatrix = widget.zoomController.controllerMatrix.clone(); + final Vector3 translationVector = _newMatrix.getTranslation(); + final Offset translation = Offset(translationVector.x, translationVector.y); + final FrictionSimulation frictionSimulationX = FrictionSimulation( + widget.frictionCoefficient, + translation.dx, + details.velocity.pixelsPerSecond.dx, + ); + final FrictionSimulation frictionSimulationY = FrictionSimulation( + widget.frictionCoefficient, + translation.dy, + details.velocity.pixelsPerSecond.dy, + ); + _actualRectTween.begin = translation; + _actualRectTween.end = + Offset(frictionSimulationX.finalX, frictionSimulationY.finalX); + widget.zoomController.actionType = ActionType.fling; + _actualRectAnimationController.duration = + _getFlingAnimationDuration(details.velocity.pixelsPerSecond.distance); + _actualRectAnimationController.forward(from: 0.0); + } + + // Returns the animation duration for the given distance and + // friction co-efficient. + Duration _getFlingAnimationDuration(double distance) { + final int duration = + (log(10.0 / distance) / log(widget.frictionCoefficient / 100)).round(); + final int durationInMs = (duration * 1000).round(); + return Duration(milliseconds: durationInMs < 350 ? 350 : durationInMs); + } + + void _handleZoomLevelAnimation() { + _zoomLevelAnimation.curve = + _isFlingAnimationActive ? Curves.decelerate : Curves.easeInOut; + final double scale = _newMatrix.getMaxScaleOnAxis(); + final double desiredScale = _zoomLevelTween.evaluate(_zoomLevelAnimation); + final double scaleChange = desiredScale / scale; + _newMatrix = _matrixScale(_newMatrix, scaleChange); + final Offset matrixFocalPointScaled = + _getActualPointInMatrix(_newMatrix, _startLocalPoint); + _newMatrix = _translateMatrix( + _newMatrix, matrixFocalPointScaled - _matrixStartPoint!); + + if (widget.zoomController.actionType == ActionType.none || + widget.zoomController.actionType == ActionType.fling) { + final double zoomLevel = _getZoomLevel(_newMatrix.getMaxScaleOnAxis()); + + final Offset translatedPoint = _getTranslationOffset(_newMatrix); + final Rect actualRect = translatedPoint & + Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + widget.zoomController._internalSetValues(zoomLevel, actualRect); + } else { + _invokeZoomableUpdate(_newMatrix, _startLocalPoint); + } + } + + void _handleActualRectAnimation() { + _actualRectAnimation.curve = + _isFlingAnimationActive ? Curves.decelerate : Curves.easeInOut; + final Vector3 translationVector = _newMatrix.getTranslation(); + final Offset translation = Offset(translationVector.x, translationVector.y); + final Offset matrixActualPoint = + _getActualPointInMatrix(_newMatrix, translation); + final Offset newTranslation = + _actualRectTween.evaluate(_actualRectAnimation); + final Offset animationMatrixPoint = + _getActualPointInMatrix(_newMatrix, newTranslation); + final Offset matrixTranslationChange = + animationMatrixPoint - matrixActualPoint; + _newMatrix = _translateMatrix(_newMatrix, matrixTranslationChange); + + if (widget.zoomController.actionType == ActionType.none || + widget.zoomController.actionType == ActionType.fling) { + final double zoomLevel = _getZoomLevel(_newMatrix.getMaxScaleOnAxis()); + final Offset translatedPoint = _getTranslationOffset(_newMatrix); + final Rect actualRect = translatedPoint & + Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + widget.zoomController._internalSetValues(zoomLevel, actualRect); + } else { + _invokeZoomableUpdate(_newMatrix); + } + } + + void _handleZoomLevelChange(double zoomLevel) { + if (widget.enablePinching && zoomLevel != widget.zoomController.zoomLevel) { + _startLocalPoint = widget.zoomController.parentRect!.center; + _newMatrix = widget.zoomController.controllerMatrix.clone(); + _matrixStartPoint = _getActualPointInMatrix(_newMatrix, _startLocalPoint); + zoomLevel = zoomLevel.clamp(widget.minZoomLevel, widget.maxZoomLevel); + _zoomLevelTween + ..begin = _newMatrix.getMaxScaleOnAxis() + ..end = _getScale(zoomLevel); + _zoomLevelAnimationController.duration = widget.animationDuration; + _zoomLevelAnimationController.forward(from: 0.0); + } + } + + void _handleActualRectChange(Rect actualRect) { + if (widget.enablePanning && + actualRect != widget.zoomController.actualRect) { + _startLocalPoint = widget.zoomController.parentRect!.center; + _newMatrix = widget.zoomController.controllerMatrix.clone(); + final Vector3 translationVector = _newMatrix.getTranslation(); + final Offset translation = + Offset(translationVector.x, translationVector.y); + _actualRectTween.begin = translation; + _actualRectTween.end = actualRect.topLeft; + _actualRectAnimationController.duration = widget.animationDuration; + _actualRectAnimationController.forward(from: 0.0); + } + } + + void _handlePointerDown(PointerDownEvent event) { + if (widget.enableDoubleTapZooming) { + _doubleTapTimer ??= Timer(kDoubleTapTimeout, _resetDoubleTapTimer); + } + + if (_zoomLevelAnimationController.isAnimating && + widget.zoomController.actionType == ActionType.fling) { + _zoomLevelAnimationController.stop(); + _handleZoomLevelAnimationEnd(); + } + if (_actualRectAnimationController.isAnimating && + widget.zoomController.actionType == ActionType.fling) { + _actualRectAnimationController.stop(); + _handleActualRectAnimationEnd(); + } + } + + void _handlePointerUp(PointerUpEvent event) { + if (_doubleTapTimer != null && _doubleTapTimer!.isActive) { + _pointerCount++; + } + + // We have performed the double tap event in the pointer up because in the + // gesture detector when there is both double tap and scale events, it + // recognize the specific event with a delay. So to avoid that delay, we + // have used the pointer events. + if (_pointerCount == 2) { + _resetDoubleTapTimer(); + // By default, we have increased the zoom level by 1 while double tapping. + final double lastZoomLevel = _getZoomLevel( + widget.zoomController.controllerMatrix.getMaxScaleOnAxis()); + double newZoomLevel = lastZoomLevel + 1; + newZoomLevel = + newZoomLevel.clamp(widget.minZoomLevel, widget.maxZoomLevel); + if (newZoomLevel != lastZoomLevel) { + _handleDoubleTap( + event.localPosition, pow(2, newZoomLevel - 1).toDouble()); + } + } + } + + void _handlePointerCancel(PointerCancelEvent event) { + _resetDoubleTapTimer(); + } + + void _resetDoubleTapTimer() { + _pointerCount = 0; + if (_doubleTapTimer != null) { + _doubleTapTimer!.cancel(); + _doubleTapTimer = null; + } + } + + void _handleDoubleTap(Offset position, double scale) { + _doubleTapEnabled = true; + widget.zoomController.actionType = ActionType.tap; + _newMatrix = widget.zoomController.controllerMatrix.clone(); + _matrixStartPoint = _getActualPointInMatrix(_newMatrix, position); + _startLocalPoint = position; + _zoomLevelTween + ..begin = _newMatrix.getMaxScaleOnAxis() + ..end = scale; + _zoomLevelAnimationController.duration = const Duration(milliseconds: 200); + _zoomLevelAnimationController.forward(from: 0.0); + } + + void _handleActualRectAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed) { + _handleActualRectAnimationEnd(); + } + } + + void _handleZoomLevelAnimationStatusChange(AnimationStatus status) { + if (status == AnimationStatus.completed) { + _handleZoomLevelAnimationEnd(); + } + } + + void _handleZoomLevelAnimationEnd() { + _isFlingAnimationActive = false; + _doubleTapEnabled = false; + widget.zoomController.actionType = ActionType.pinch; + _invokeZoomableUpdate( + widget.zoomController.controllerMatrix, _startLocalPoint); + widget.zoomController.actionType = ActionType.none; + _invokeZoomableComplete(widget.zoomController.controllerMatrix); + } + + void _handleActualRectAnimationEnd() { + _isFlingAnimationActive = false; + widget.zoomController.actionType = ActionType.pan; + _invokeZoomableUpdate( + widget.zoomController.controllerMatrix, _startLocalPoint); + widget.zoomController.actionType = ActionType.none; + _invokeZoomableComplete(widget.zoomController.controllerMatrix); + } + + void _handleMouseWheelZooming(PointerSignalEvent event) { + if (event is PointerScrollEvent) { + if (event.scrollDelta.dy == 0.0) { + return; + } else if (_actualRectAnimationController.isAnimating) { + _actualRectAnimationController.stop(); + _handleActualRectAnimationEnd(); + } + + final double scaleChange = exp(-event.scrollDelta.dy / 200); + _newMatrix = widget.zoomController.controllerMatrix.clone(); + final Offset matrixFocalPoint = + _getActualPointInMatrix(_newMatrix, event.localPosition); + + _newMatrix = _matrixScale(_newMatrix, scaleChange); + + final Offset matrixFocalPointScaled = + _getActualPointInMatrix(_newMatrix, event.localPosition); + _newMatrix = _translateMatrix( + _newMatrix, matrixFocalPointScaled - matrixFocalPoint); + widget.zoomController.actionType = ActionType.pinch; + _invokeZoomableUpdate(_newMatrix, event.localPosition, event.position); + widget.zoomController.actionType = ActionType.none; + _invokeZoomableComplete(widget.zoomController.controllerMatrix); + } + } + + void _invokeZoomableUpdate(Matrix4 matrix, + [Offset? localFocalPoint, + Offset? globalFocalPoint, + double scale = 1.0, + Offset? pinchCenter]) { + final double zoomLevel = _getZoomLevel(matrix.getMaxScaleOnAxis()); + localFocalPoint ??= widget.zoomController.parentRect!.center; + if (globalFocalPoint == null) { + final RenderBox renderBox = context.findRenderObject()! as RenderBox; + globalFocalPoint = renderBox.localToGlobal(localFocalPoint); + } + final Offset translatedPoint = _getTranslationOffset(matrix); + final Rect actualRect = translatedPoint & + Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + final ZoomPanDetails details = ZoomPanDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: widget.zoomController.zoomLevel, + newZoomLevel: zoomLevel, + previousRect: widget.zoomController.actualRect, + actualRect: actualRect, + scale: scale, + pinchCenter: pinchCenter, + ); + widget.onUpdate(details); + } + + void _invokeZoomableComplete(Matrix4 matrix, + [Offset? localFocalPoint, + Offset? globalFocalPoint, + double scale = 1.0, + Offset? pinchCenter]) { + final double zoomLevel = _getZoomLevel(matrix.getMaxScaleOnAxis()); + localFocalPoint ??= widget.zoomController.parentRect!.center; + if (globalFocalPoint == null) { + final RenderBox renderBox = context.findRenderObject()! as RenderBox; + globalFocalPoint = renderBox.localToGlobal(localFocalPoint); + } + final Offset translatedPoint = _getTranslationOffset(matrix); + final Rect actualRect = translatedPoint & + Size(_getTotalChildSize(zoomLevel, _boundaryRect!.width / 2), + _getTotalChildSize(zoomLevel, _boundaryRect!.height / 2)); + final ZoomPanDetails details = ZoomPanDetails( + localFocalPoint: localFocalPoint, + globalFocalPoint: globalFocalPoint, + previousZoomLevel: widget.zoomController.zoomLevel, + newZoomLevel: zoomLevel, + previousRect: widget.zoomController.actualRect, + actualRect: actualRect, + scale: scale, + pinchCenter: pinchCenter, + ); + widget.onComplete(details); + } + + void _initializeAnimations() { + _zoomLevelAnimationController = + AnimationController(vsync: this, duration: widget.animationDuration) + ..addListener(_handleZoomLevelAnimation) + ..addStatusListener(_handleZoomLevelAnimationStatusChange); + _actualRectAnimationController = + AnimationController(vsync: this, duration: widget.animationDuration) + ..addListener(_handleActualRectAnimation) + ..addStatusListener(_handleActualRectAnimationStatusChange); + _zoomLevelAnimation = CurvedAnimation( + parent: _zoomLevelAnimationController, curve: Curves.easeInOut); + _actualRectAnimation = CurvedAnimation( + parent: _actualRectAnimationController, curve: Curves.easeInOut); + _actualRectTween = Tween(); + _zoomLevelTween = Tween(); + } + + @override + void initState() { + widget.zoomController + .._onZoomLevelChange = _handleZoomLevelChange + .._onActualRectChange = _handleActualRectChange; + _initializeAnimations(); + super.initState(); + } + + @override + void dispose() { + _zoomLevelAnimationController + ..removeListener(_handleZoomLevelAnimation) + ..removeStatusListener(_handleZoomLevelAnimationStatusChange) + ..dispose(); + _actualRectAnimationController + ..removeListener(_handleActualRectAnimation) + ..removeStatusListener(_handleActualRectAnimationStatusChange) + ..dispose(); + // Zoom Controller dispose should be handled by the user. + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size newSize = Size(constraints.maxWidth, constraints.maxHeight); + if (_size == null || _size != newSize) { + _size = newSize; + widget.zoomController + ..parentRect = Offset.zero & _size! + .._actualRect = widget.initialRect + .._zoomLevel = widget.initialZoomLevel; + _boundaryRect = _getBoundaryRect(); + widget.zoomController.controllerMatrix = Matrix4.identity() + ..scale(_getScale(widget.initialZoomLevel)) + ..setTranslation( + Vector3(widget.initialRect.left, widget.initialRect.top, 0.0)); + } + + return Listener( + onPointerDown: _handlePointerDown, + onPointerUp: _handlePointerUp, + onPointerCancel: _handlePointerCancel, + onPointerSignal: _handleMouseWheelZooming, + behavior: HitTestBehavior.translucent, + child: GestureDetector( + onScaleStart: _handleScaleStart, + onScaleUpdate: _handleScaleUpdate, + onScaleEnd: _handleScaleEnd, + behavior: HitTestBehavior.translucent, + child: widget.child, + ), + ); + }); + } +} + +// Return the translation from the given Matrix4 as an Offset. +Offset _getTranslationOffset(Matrix4 matrix) { + final Vector3 nextTranslation = matrix.getTranslation(); + return Offset(nextTranslation.x, nextTranslation.y); +} + +double _getTotalChildSize(double zoom, double size) { + return size * pow(2, zoom).toDouble(); +} + +double _getZoomLevel(double scale) { + // The minimum zoom level was set to 1.0. The scale value, however, will be + // greater or equal to 0.0. We've added 1 to the return value. + return (log(scale) / log(2)) + 1; +} + +double _getScale(double zoomLevel) { + // To derive the scale value from 0.0, we subtracted 1 from the obtained + // zoom level. + return pow(2, zoomLevel - 1).toDouble(); +} + +/// Return the exact pixel point of the given matrix. +Offset _getActualPointInMatrix(Matrix4 matrix, Offset localPoint) { + final Matrix4 inverseMatrix = Matrix4.inverted(matrix); + final Vector3 untransformed = + inverseMatrix.transform3(Vector3(localPoint.dx, localPoint.dy, 0)); + return Offset(untransformed.x, untransformed.y); +} + +/// A controller which handles the zoomLevel, and actual rect +/// commonly across multiple widgets. +class ZoomableController { + ObserverList? _listeners = ObserverList(); + late ZoomCallback _onZoomLevelChange; + late RectCallback _onActualRectChange; + + /// By defaults it will be a identity matrix, which corresponds to no + /// transformation. + /// + /// Holds the zoomable widget matrix4 value which will be same in + /// multiple widgets. + Matrix4 controllerMatrix = Matrix4.identity(); + + /// Holds the current action type of the widget. + ActionType actionType = ActionType.none; + + /// Holds the rect of the parent. + Rect? parentRect; + + /// Holds the current zoomLevel of the zoomable widget. + double get zoomLevel => _zoomLevel; + late double _zoomLevel; + set zoomLevel(double value) { + assert(value >= 1); + if (_zoomLevel == value) { + return; + } + + if (actionType != ActionType.none) { + _zoomLevel = value; + return; + } + _onZoomLevelChange(value); + } + + /// Holds the actualRect of the zoomable widget. + Rect get actualRect => _actualRect; + late Rect _actualRect; + set actualRect(Rect value) { + if (_actualRect == value) { + return; + } + if (actionType != ActionType.none) { + _actualRect = value; + final double newScale = pow(2, _zoomLevel - 1).toDouble(); + controllerMatrix = Matrix4.identity() + ..scale(newScale) + ..setTranslation(Vector3(_actualRect.left, _actualRect.top, 0.0)); + notifyListeners(); + return; + } + _onActualRectChange(value); + } + + void _internalSetValues(double zoomLevel, Rect actualRect) { + _zoomLevel = zoomLevel; + _actualRect = actualRect; + final double newScale = pow(2, _zoomLevel - 1).toDouble(); + controllerMatrix = Matrix4.identity() + ..scale(newScale) + ..setTranslation(Vector3(_actualRect.left, _actualRect.top, 0.0)); + notifyListeners(); + } + + /// Adds the listeners to the corresponding widget. + void addListener(VoidCallback listener) { + _listeners?.add(listener); + } + + /// Removes the listeners from the corresponding widget. + void removeListener(VoidCallback listener) { + _listeners?.remove(listener); + } + + /// Notifies the active listeners as values has been updated. + void notifyListeners() { + for (final VoidCallback listener in _listeners!) { + listener(); + } + } + + /// Disposes the active listeners. + void dispose() { + _listeners = null; + } +} diff --git a/packages/syncfusion_flutter_maps/lib/src/settings.dart b/packages/syncfusion_flutter_maps/lib/src/settings.dart index 971515311..af5615561 100644 --- a/packages/syncfusion_flutter_maps/lib/src/settings.dart +++ b/packages/syncfusion_flutter_maps/lib/src/settings.dart @@ -1731,11 +1731,79 @@ class MapSelectionSettings extends DiagnosticableTree { class MapTooltipSettings extends DiagnosticableTree { /// Creates a [MapTooltipSettings]. const MapTooltipSettings({ + this.hideDelay = 3.0, this.color, this.strokeWidth, this.strokeColor, }); + /// Specifies the duration of the tooltip visibility. + /// + /// The default value of the [hideDelay] property is 3. + /// + /// By default, the tooltip will disappear after 3 seconds of inactivity. You + /// can always show the tooltip without hiding it by setting double.infinity + /// to the [hideDelay] property. + /// + /// When you perform a zoom/pan operation, a window resize, or a change of + /// orientation, the tooltip will disappear. + /// + /// ```dart + /// late MapShapeSource _mapSource; + /// late List _data; + /// + /// @override + /// void initState() { + /// _data = [ + /// DataModel('Asia', '44,579,000 sq. km.'), + /// DataModel('Africa', '30,370,000 sq. km.'), + /// DataModel('Europe', '10,180,000 sq. km.'), + /// DataModel('North America', '24,709,000 sq. km.'), + /// DataModel('South America', '17,840,000 sq. km.'), + /// DataModel('Australia', '8,600,000 sq. km.'), + /// ]; + /// + /// _mapSource = MapShapeSource.asset( + /// "assets/world_map.json", + /// shapeDataField: "continent", + /// dataCount: _data.length, + /// primaryValueMapper: (int index) => _data[index].continent, + /// ); + /// super.initState(); + /// } + /// + /// @override + /// Widget build(BuildContext context) { + /// return Scaffold( + /// body: SfMaps( + /// layers: [ + /// MapShapeLayer( + /// source: _mapSource, + /// tooltipSettings: MapTooltipSettings( + /// hideDelay: double.infinity, + /// ), + /// shapeTooltipBuilder: (BuildContext context, int index) { + /// if (index == 0) { + /// return Icon(Icons.airplanemode_inactive); + /// } else { + /// return Icon(Icons.airplanemode_active); + /// } + /// }, + /// ), + /// ], + /// ), + /// ); + /// } + /// + /// class DataModel { + /// const DataModel(this.continent, this.area); + /// + /// final String continent; + /// final String area; + /// } + /// ``` + final double hideDelay; + /// Fills the tooltip by this color. /// /// This snippet shows how to set the tooltip color in [SfMaps]. @@ -1804,7 +1872,8 @@ class MapTooltipSettings extends DiagnosticableTree { /// } /// ``` /// See also: - /// * [textStyle], for customizing the style of the tooltip text. + /// * [strokeColor], for customizing the stroke color of the tooltip. + /// * [strokeWidth] for customizing the stroke width of the tooltip. final Color? color; /// Specifies the stroke width applies to the tooltip. @@ -1965,18 +2034,19 @@ class MapTooltipSettings extends DiagnosticableTree { return false; } return other is MapTooltipSettings && + other.hideDelay == hideDelay && other.color == color && other.strokeWidth == strokeWidth && other.strokeColor == strokeColor; } @override - int get hashCode => hashValues(color, strokeWidth, strokeColor); + int get hashCode => hashValues(hideDelay, color, strokeWidth, strokeColor); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); - + properties.add(DoubleProperty('hideDelay', hideDelay)); if (color != null) { properties.add(ColorProperty('color', color)); } diff --git a/packages/syncfusion_flutter_maps/lib/src/utils.dart b/packages/syncfusion_flutter_maps/lib/src/utils.dart index 724cff80e..5ba4d5525 100644 --- a/packages/syncfusion_flutter_maps/lib/src/utils.dart +++ b/packages/syncfusion_flutter_maps/lib/src/utils.dart @@ -13,14 +13,6 @@ import 'controller/map_provider.dart'; // ignore_for_file: public_member_api_docs -// Default hover color opacity value. -const double hoverColorOpacity = 0.7; - -// If shape color opacity is same hover color opacity, -// hover is not visible in UI. -// So need to decrease hover opacity -const double minHoverOpacity = 0.5; - // Using this factor, the tooltip position of the bubble and marker is // determined from the total height. const double tooltipHeightFactor = 0.25; @@ -30,6 +22,22 @@ const double maximumLatitude = 85.05112878; const double minimumLongitude = -180; const double maximumLongitude = 180; +const double kDefaultMinZoomLevel = 1.0; +const double kDefaultMaxZoomLevel = 15.0; + +// Combines the two color values and returns a new saturated color. We have +// used black color as default mix color. +Color getSaturatedColor(Color color, [Color mix = Colors.black]) { + const double factor = 0.2; + return color == Colors.transparent + ? color + : Color.fromRGBO( + ((1 - factor) * color.red + factor * mix.red).toInt(), + ((1 - factor) * color.green + factor * mix.green).toInt(), + ((1 - factor) * color.blue + factor * mix.blue).toInt(), + 1); +} + Size getBoxSize(BoxConstraints constraints) { final double width = constraints.hasBoundedWidth ? constraints.maxWidth : 300; final double height = @@ -44,8 +52,8 @@ Offset pixelFromLatLng(num latitude, num longitude, Size size, final double y = 0.5 - log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * pi); final double mapSize = size.longestSide * scale; - final double dx = offset.dx + ((x * mapSize + 0.5).clamp(0.0, mapSize - 1)); - final double dy = offset.dy + ((y * mapSize + 0.5).clamp(0.0, mapSize - 1)); + final double dx = offset.dx + ((x * mapSize).clamp(0.0, mapSize)); + final double dy = offset.dy + ((y * mapSize).clamp(0.0, mapSize)); return Offset(dx, dy); } @@ -66,6 +74,43 @@ MapLatLng pixelToLatLng(Offset offset, Size size, return MapLatLng(latitude, longitude); } +MapLatLng getFocalLatLng(MapLatLngBounds bounds) { + final double latitude = + (bounds.southwest.latitude + bounds.northeast.latitude) / 2; + final double longitude = + (bounds.southwest.longitude + bounds.northeast.longitude) / 2; + return MapLatLng(latitude, longitude); +} + +double getZoomLevel(MapLatLngBounds bounds, LayerType layerType, Size size, + [double actualShapeSizeFactor = 1.0]) { + switch (layerType) { + case LayerType.shape: + final Offset northEast = pixelFromLatLng( + bounds.northeast.latitude, bounds.northeast.longitude, size); + final Offset southWest = pixelFromLatLng( + bounds.southwest.latitude, bounds.southwest.longitude, size); + final Rect boundsRect = Rect.fromPoints(northEast, southWest); + final double latZoom = size.height / boundsRect.height; + final double lngZoom = size.width / boundsRect.width; + return min(latZoom.abs(), lngZoom.abs()) / actualShapeSizeFactor; + case LayerType.tile: + // Calculating the scale value for the given bounds using the + // default tile layer size with default minimum zoom level. + final Size tileLayerSize = + Size.square(getTotalTileWidth(kDefaultMinZoomLevel)); + final Offset northEast = pixelFromLatLng( + bounds.northeast.latitude, bounds.northeast.longitude, tileLayerSize); + final Offset southWest = pixelFromLatLng( + bounds.southwest.latitude, bounds.southwest.longitude, tileLayerSize); + final Rect boundsRect = Rect.fromPoints(northEast, southWest); + // Converting scale into zoom level. + final double latZoomLevel = log(boundsRect.height / size.height) / log(2); + final double lngZoomLevel = log(boundsRect.width / size.width) / log(2); + return min(latZoomLevel.abs(), lngZoomLevel.abs()) + 1; + } +} + String getTrimText(String text, TextStyle style, double maxWidth, TextPainter painter, double width, [double? nextTextHalfWidth, bool isInsideLastLabel = false]) { diff --git a/packages/syncfusion_flutter_maps/pubspec.yaml b/packages/syncfusion_flutter_maps/pubspec.yaml index 99c2292cf..6581ff1f2 100644 --- a/packages/syncfusion_flutter_maps/pubspec.yaml +++ b/packages/syncfusion_flutter_maps/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: syncfusion_flutter_core: path: ../syncfusion_flutter_core - collection: ">=1.9.0 <=1.15.0" http: ">=0.12.0 <=0.13.3" + vector_math: ">=2.1.0 <=3.0.0" flutter: \ No newline at end of file diff --git a/packages/syncfusion_officechart/CHANGELOG.md b/packages/syncfusion_flutter_officechart/CHANGELOG.md similarity index 100% rename from packages/syncfusion_officechart/CHANGELOG.md rename to packages/syncfusion_flutter_officechart/CHANGELOG.md diff --git a/packages/syncfusion_officechart/LICENSE b/packages/syncfusion_flutter_officechart/LICENSE similarity index 100% rename from packages/syncfusion_officechart/LICENSE rename to packages/syncfusion_flutter_officechart/LICENSE diff --git a/packages/syncfusion_officechart/README.md b/packages/syncfusion_flutter_officechart/README.md similarity index 98% rename from packages/syncfusion_officechart/README.md rename to packages/syncfusion_flutter_officechart/README.md index 7a4e6723d..0ff296ed0 100644 --- a/packages/syncfusion_officechart/README.md +++ b/packages/syncfusion_flutter_officechart/README.md @@ -486,7 +486,7 @@ workbook.dispose(); 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_officechart/analysis_options.yaml b/packages/syncfusion_flutter_officechart/analysis_options.yaml similarity index 100% rename from packages/syncfusion_officechart/analysis_options.yaml rename to packages/syncfusion_flutter_officechart/analysis_options.yaml diff --git a/packages/syncfusion_officechart/example/README.md b/packages/syncfusion_flutter_officechart/example/README.md similarity index 100% rename from packages/syncfusion_officechart/example/README.md rename to packages/syncfusion_flutter_officechart/example/README.md diff --git a/packages/syncfusion_officechart/example/analysis_options.yaml b/packages/syncfusion_flutter_officechart/example/analysis_options.yaml similarity index 100% rename from packages/syncfusion_officechart/example/analysis_options.yaml rename to packages/syncfusion_flutter_officechart/example/analysis_options.yaml diff --git a/packages/syncfusion_officechart/example/android/.gitignore b/packages/syncfusion_flutter_officechart/example/android/.gitignore similarity index 100% rename from packages/syncfusion_officechart/example/android/.gitignore rename to packages/syncfusion_flutter_officechart/example/android/.gitignore diff --git a/packages/syncfusion_officechart/example/android/app/build.gradle b/packages/syncfusion_flutter_officechart/example/android/app/build.gradle similarity index 100% rename from packages/syncfusion_officechart/example/android/app/build.gradle rename to packages/syncfusion_flutter_officechart/example/android/app/build.gradle diff --git a/packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/debug/AndroidManifest.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/debug/AndroidManifest.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/AndroidManifest.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt b/packages/syncfusion_flutter_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/kotlin/com/example/officechart_example/MainActivity.kt diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/main/res/values/styles.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/main/res/values/styles.xml diff --git a/packages/syncfusion_officechart/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_officechart/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officechart/example/android/app/src/profile/AndroidManifest.xml rename to packages/syncfusion_flutter_officechart/example/android/app/src/profile/AndroidManifest.xml diff --git a/packages/syncfusion_officechart/example/android/build.gradle b/packages/syncfusion_flutter_officechart/example/android/build.gradle similarity index 100% rename from packages/syncfusion_officechart/example/android/build.gradle rename to packages/syncfusion_flutter_officechart/example/android/build.gradle diff --git a/packages/syncfusion_officechart/example/android/gradle.properties b/packages/syncfusion_flutter_officechart/example/android/gradle.properties similarity index 100% rename from packages/syncfusion_officechart/example/android/gradle.properties rename to packages/syncfusion_flutter_officechart/example/android/gradle.properties diff --git a/packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_officechart/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/syncfusion_officechart/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/syncfusion_flutter_officechart/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/syncfusion_officechart/example/android/officechart_example_android.iml b/packages/syncfusion_flutter_officechart/example/android/officechart_example_android.iml similarity index 100% rename from packages/syncfusion_officechart/example/android/officechart_example_android.iml rename to packages/syncfusion_flutter_officechart/example/android/officechart_example_android.iml diff --git a/packages/syncfusion_officechart/example/android/settings.gradle b/packages/syncfusion_flutter_officechart/example/android/settings.gradle similarity index 100% rename from packages/syncfusion_officechart/example/android/settings.gradle rename to packages/syncfusion_flutter_officechart/example/android/settings.gradle diff --git a/packages/syncfusion_officechart/example/ios/.gitignore b/packages/syncfusion_flutter_officechart/example/ios/.gitignore similarity index 100% rename from packages/syncfusion_officechart/example/ios/.gitignore rename to packages/syncfusion_flutter_officechart/example/ios/.gitignore diff --git a/packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_officechart/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/syncfusion_flutter_officechart/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_officechart/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/ios/Flutter/Debug.xcconfig rename to packages/syncfusion_flutter_officechart/example/ios/Flutter/Debug.xcconfig diff --git a/packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_officechart/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/ios/Flutter/Release.xcconfig rename to packages/syncfusion_flutter_officechart/example/ios/Flutter/Release.xcconfig diff --git a/packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/syncfusion_officechart/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officechart/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to packages/syncfusion_flutter_officechart/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/packages/syncfusion_officecore/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_officechart/example/ios/Runner/AppDelegate.swift similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/AppDelegate.swift rename to packages/syncfusion_flutter_officechart/example/ios/Runner/AppDelegate.swift diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/syncfusion_officechart/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_officechart/example/ios/Runner/Info.plist similarity index 100% rename from packages/syncfusion_officechart/example/ios/Runner/Info.plist rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Info.plist diff --git a/packages/syncfusion_officecore/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_officechart/example/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Runner-Bridging-Header.h rename to packages/syncfusion_flutter_officechart/example/ios/Runner/Runner-Bridging-Header.h diff --git a/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart new file mode 100644 index 000000000..12863e435 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_mobile.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:open_file/open_file.dart' as open_file; +import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + //Get the storage folder location using path_provider package. + String? path; + if (Platform.isAndroid || + Platform.isIOS || + Platform.isLinux || + Platform.isWindows) { + final Directory directory = + await path_provider.getApplicationSupportDirectory(); + path = directory.path; + } else { + path = await PathProviderPlatform.instance.getApplicationSupportPath(); + } + final File file = + File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + await file.writeAsBytes(bytes, flush: true); + if (Platform.isAndroid || Platform.isIOS) { + //Launch the file (used open_file package) + await open_file.OpenFile.open('$path/$fileName'); + } else if (Platform.isWindows) { + await Process.run('start', ['$path\\$fileName'], runInShell: true); + } else if (Platform.isMacOS) { + await Process.run('open', ['$path/$fileName'], runInShell: true); + } else if (Platform.isLinux) { + await Process.run('xdg-open', ['$path/$fileName'], + runInShell: true); + } +} diff --git a/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart new file mode 100644 index 000000000..a455ce682 --- /dev/null +++ b/packages/syncfusion_flutter_officechart/example/lib/helper/save_file_web.dart @@ -0,0 +1,15 @@ +///Dart imports +import 'dart:async'; +import 'dart:convert'; +// ignore: avoid_web_libraries_in_flutter +import 'dart:html'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + AnchorElement( + href: + 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') + ..setAttribute('download', fileName) + ..click(); +} diff --git a/packages/syncfusion_officechart/example/lib/main.dart b/packages/syncfusion_flutter_officechart/example/lib/main.dart similarity index 98% rename from packages/syncfusion_officechart/example/lib/main.dart rename to packages/syncfusion_flutter_officechart/example/lib/main.dart index a4fe51e63..7de7f45f9 100644 --- a/packages/syncfusion_officechart/example/lib/main.dart +++ b/packages/syncfusion_flutter_officechart/example/lib/main.dart @@ -172,6 +172,6 @@ class _CreateOfficeChartState extends State { final List bytes = workbook.saveAsStream(); workbook.dispose(); - await FileSaveHelper.saveAndLaunchFile(bytes, 'ExpenseReport.xlsx'); + await saveAndLaunchFile(bytes, 'ExpenseReport.xlsx'); } } diff --git a/packages/syncfusion_officecore/example/linux/.gitignore b/packages/syncfusion_flutter_officechart/example/linux/.gitignore similarity index 100% rename from packages/syncfusion_officecore/example/linux/.gitignore rename to packages/syncfusion_flutter_officechart/example/linux/.gitignore diff --git a/packages/syncfusion_officecore/example/linux/CMakeLists.txt b/packages/syncfusion_flutter_officechart/example/linux/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officecore/example/linux/CMakeLists.txt rename to packages/syncfusion_flutter_officechart/example/linux/CMakeLists.txt diff --git a/packages/syncfusion_officechart/example/linux/flutter/CMakeLists.txt b/packages/syncfusion_flutter_officechart/example/linux/flutter/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officechart/example/linux/flutter/CMakeLists.txt rename to packages/syncfusion_flutter_officechart/example/linux/flutter/CMakeLists.txt diff --git a/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugin_registrant.cc similarity index 86% rename from packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.cc rename to packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugin_registrant.h similarity index 93% rename from packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.h rename to packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_officecore/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_officecore/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugins.cmake similarity index 100% rename from packages/syncfusion_officecore/example/linux/flutter/generated_plugins.cmake rename to packages/syncfusion_flutter_officechart/example/linux/flutter/generated_plugins.cmake diff --git a/packages/syncfusion_officecore/example/linux/main.cc b/packages/syncfusion_flutter_officechart/example/linux/main.cc similarity index 100% rename from packages/syncfusion_officecore/example/linux/main.cc rename to packages/syncfusion_flutter_officechart/example/linux/main.cc diff --git a/packages/syncfusion_officecore/example/linux/my_application.cc b/packages/syncfusion_flutter_officechart/example/linux/my_application.cc similarity index 100% rename from packages/syncfusion_officecore/example/linux/my_application.cc rename to packages/syncfusion_flutter_officechart/example/linux/my_application.cc diff --git a/packages/syncfusion_officecore/example/linux/my_application.h b/packages/syncfusion_flutter_officechart/example/linux/my_application.h similarity index 100% rename from packages/syncfusion_officecore/example/linux/my_application.h rename to packages/syncfusion_flutter_officechart/example/linux/my_application.h diff --git a/packages/syncfusion_officecore/example/macos/.gitignore b/packages/syncfusion_flutter_officechart/example/macos/.gitignore similarity index 100% rename from packages/syncfusion_officecore/example/macos/.gitignore rename to packages/syncfusion_flutter_officechart/example/macos/.gitignore diff --git a/packages/syncfusion_officecore/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/syncfusion_flutter_officechart/example/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/macos/Flutter/Flutter-Debug.xcconfig rename to packages/syncfusion_flutter_officechart/example/macos/Flutter/Flutter-Debug.xcconfig diff --git a/packages/syncfusion_officecore/example/macos/Flutter/Flutter-Release.xcconfig b/packages/syncfusion_flutter_officechart/example/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/macos/Flutter/Flutter-Release.xcconfig rename to packages/syncfusion_flutter_officechart/example/macos/Flutter/Flutter-Release.xcconfig diff --git a/packages/syncfusion_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from packages/syncfusion_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift rename to packages/syncfusion_flutter_officechart/example/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_officechart/example/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.pbxproj rename to packages/syncfusion_flutter_officechart/example/macos/Runner.xcodeproj/project.pbxproj diff --git a/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officechart/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officechart/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/syncfusion_flutter_officechart/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officechart/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officechart/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_officecore/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officechart/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to packages/syncfusion_flutter_officechart/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/packages/syncfusion_officecore/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_officechart/example/macos/Runner/AppDelegate.swift similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/AppDelegate.swift rename to packages/syncfusion_flutter_officechart/example/macos/Runner/AppDelegate.swift diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/packages/syncfusion_officecore/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/syncfusion_flutter_officechart/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Base.lproj/MainMenu.xib rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/packages/syncfusion_officecore/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Configs/AppInfo.xcconfig rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/AppInfo.xcconfig diff --git a/packages/syncfusion_officecore/example/macos/Runner/Configs/Debug.xcconfig b/packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Configs/Debug.xcconfig rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/Debug.xcconfig diff --git a/packages/syncfusion_officecore/example/macos/Runner/Configs/Release.xcconfig b/packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Configs/Release.xcconfig rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/Release.xcconfig diff --git a/packages/syncfusion_officecore/example/macos/Runner/Configs/Warnings.xcconfig b/packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Configs/Warnings.xcconfig rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/packages/syncfusion_officecore/example/macos/Runner/DebugProfile.entitlements b/packages/syncfusion_flutter_officechart/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/DebugProfile.entitlements rename to packages/syncfusion_flutter_officechart/example/macos/Runner/DebugProfile.entitlements diff --git a/packages/syncfusion_officecore/example/macos/Runner/Info.plist b/packages/syncfusion_flutter_officechart/example/macos/Runner/Info.plist similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Info.plist rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Info.plist diff --git a/packages/syncfusion_officecore/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_officechart/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/MainFlutterWindow.swift rename to packages/syncfusion_flutter_officechart/example/macos/Runner/MainFlutterWindow.swift diff --git a/packages/syncfusion_officecore/example/macos/Runner/Release.entitlements b/packages/syncfusion_flutter_officechart/example/macos/Runner/Release.entitlements similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner/Release.entitlements rename to packages/syncfusion_flutter_officechart/example/macos/Runner/Release.entitlements diff --git a/packages/syncfusion_officecore/example/officecore_example.iml b/packages/syncfusion_flutter_officechart/example/officechart_example.iml similarity index 100% rename from packages/syncfusion_officecore/example/officecore_example.iml rename to packages/syncfusion_flutter_officechart/example/officechart_example.iml diff --git a/packages/syncfusion_officechart/example/pubspec.yaml b/packages/syncfusion_flutter_officechart/example/pubspec.yaml similarity index 100% rename from packages/syncfusion_officechart/example/pubspec.yaml rename to packages/syncfusion_flutter_officechart/example/pubspec.yaml diff --git a/packages/syncfusion_officecore/example/web/favicon.png b/packages/syncfusion_flutter_officechart/example/web/favicon.png similarity index 100% rename from packages/syncfusion_officecore/example/web/favicon.png rename to packages/syncfusion_flutter_officechart/example/web/favicon.png diff --git a/packages/syncfusion_officecore/example/web/icons/Icon-192.png b/packages/syncfusion_flutter_officechart/example/web/icons/Icon-192.png similarity index 100% rename from packages/syncfusion_officecore/example/web/icons/Icon-192.png rename to packages/syncfusion_flutter_officechart/example/web/icons/Icon-192.png diff --git a/packages/syncfusion_officecore/example/web/icons/Icon-512.png b/packages/syncfusion_flutter_officechart/example/web/icons/Icon-512.png similarity index 100% rename from packages/syncfusion_officecore/example/web/icons/Icon-512.png rename to packages/syncfusion_flutter_officechart/example/web/icons/Icon-512.png diff --git a/packages/syncfusion_officecore/example/web/index.html b/packages/syncfusion_flutter_officechart/example/web/index.html similarity index 100% rename from packages/syncfusion_officecore/example/web/index.html rename to packages/syncfusion_flutter_officechart/example/web/index.html diff --git a/packages/syncfusion_officecore/example/web/manifest.json b/packages/syncfusion_flutter_officechart/example/web/manifest.json similarity index 100% rename from packages/syncfusion_officecore/example/web/manifest.json rename to packages/syncfusion_flutter_officechart/example/web/manifest.json diff --git a/packages/syncfusion_officecore/example/windows/.gitignore b/packages/syncfusion_flutter_officechart/example/windows/.gitignore similarity index 100% rename from packages/syncfusion_officecore/example/windows/.gitignore rename to packages/syncfusion_flutter_officechart/example/windows/.gitignore diff --git a/packages/syncfusion_officecore/example/windows/CMakeLists.txt b/packages/syncfusion_flutter_officechart/example/windows/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officecore/example/windows/CMakeLists.txt rename to packages/syncfusion_flutter_officechart/example/windows/CMakeLists.txt diff --git a/packages/syncfusion_officecore/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_flutter_officechart/example/windows/flutter/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officecore/example/windows/flutter/CMakeLists.txt rename to packages/syncfusion_flutter_officechart/example/windows/flutter/CMakeLists.txt diff --git a/packages/syncfusion_officecore/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugin_registrant.cc similarity index 87% rename from packages/syncfusion_officecore/example/windows/flutter/generated_plugin_registrant.cc rename to packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugin_registrant.cc index 4bfa0f3a3..8b6d4680a 100644 --- a/packages/syncfusion_officecore/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_officecore/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugin_registrant.h similarity index 93% rename from packages/syncfusion_officecore/example/windows/flutter/generated_plugin_registrant.h rename to packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugin_registrant.h index 9846246b4..dc139d85a 100644 --- a/packages/syncfusion_officecore/example/windows/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_officecore/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugins.cmake similarity index 100% rename from packages/syncfusion_officecore/example/windows/flutter/generated_plugins.cmake rename to packages/syncfusion_flutter_officechart/example/windows/flutter/generated_plugins.cmake diff --git a/packages/syncfusion_officecore/example/windows/runner/CMakeLists.txt b/packages/syncfusion_flutter_officechart/example/windows/runner/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/CMakeLists.txt rename to packages/syncfusion_flutter_officechart/example/windows/runner/CMakeLists.txt diff --git a/packages/syncfusion_officecore/example/windows/runner/Runner.rc b/packages/syncfusion_flutter_officechart/example/windows/runner/Runner.rc similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/Runner.rc rename to packages/syncfusion_flutter_officechart/example/windows/runner/Runner.rc diff --git a/packages/syncfusion_officecore/example/windows/runner/flutter_window.cpp b/packages/syncfusion_flutter_officechart/example/windows/runner/flutter_window.cpp similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/flutter_window.cpp rename to packages/syncfusion_flutter_officechart/example/windows/runner/flutter_window.cpp diff --git a/packages/syncfusion_officecore/example/windows/runner/flutter_window.h b/packages/syncfusion_flutter_officechart/example/windows/runner/flutter_window.h similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/flutter_window.h rename to packages/syncfusion_flutter_officechart/example/windows/runner/flutter_window.h diff --git a/packages/syncfusion_officecore/example/windows/runner/main.cpp b/packages/syncfusion_flutter_officechart/example/windows/runner/main.cpp similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/main.cpp rename to packages/syncfusion_flutter_officechart/example/windows/runner/main.cpp diff --git a/packages/syncfusion_officecore/example/windows/runner/resource.h b/packages/syncfusion_flutter_officechart/example/windows/runner/resource.h similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/resource.h rename to packages/syncfusion_flutter_officechart/example/windows/runner/resource.h diff --git a/packages/syncfusion_officecore/example/windows/runner/resources/app_icon.ico b/packages/syncfusion_flutter_officechart/example/windows/runner/resources/app_icon.ico similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/resources/app_icon.ico rename to packages/syncfusion_flutter_officechart/example/windows/runner/resources/app_icon.ico diff --git a/packages/syncfusion_officecore/example/windows/runner/run_loop.cpp b/packages/syncfusion_flutter_officechart/example/windows/runner/run_loop.cpp similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/run_loop.cpp rename to packages/syncfusion_flutter_officechart/example/windows/runner/run_loop.cpp diff --git a/packages/syncfusion_officecore/example/windows/runner/run_loop.h b/packages/syncfusion_flutter_officechart/example/windows/runner/run_loop.h similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/run_loop.h rename to packages/syncfusion_flutter_officechart/example/windows/runner/run_loop.h diff --git a/packages/syncfusion_officecore/example/windows/runner/runner.exe.manifest b/packages/syncfusion_flutter_officechart/example/windows/runner/runner.exe.manifest similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/runner.exe.manifest rename to packages/syncfusion_flutter_officechart/example/windows/runner/runner.exe.manifest diff --git a/packages/syncfusion_officecore/example/windows/runner/utils.cpp b/packages/syncfusion_flutter_officechart/example/windows/runner/utils.cpp similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/utils.cpp rename to packages/syncfusion_flutter_officechart/example/windows/runner/utils.cpp diff --git a/packages/syncfusion_officecore/example/windows/runner/utils.h b/packages/syncfusion_flutter_officechart/example/windows/runner/utils.h similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/utils.h rename to packages/syncfusion_flutter_officechart/example/windows/runner/utils.h diff --git a/packages/syncfusion_officecore/example/windows/runner/win32_window.cpp b/packages/syncfusion_flutter_officechart/example/windows/runner/win32_window.cpp similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/win32_window.cpp rename to packages/syncfusion_flutter_officechart/example/windows/runner/win32_window.cpp diff --git a/packages/syncfusion_officecore/example/windows/runner/win32_window.h b/packages/syncfusion_flutter_officechart/example/windows/runner/win32_window.h similarity index 100% rename from packages/syncfusion_officecore/example/windows/runner/win32_window.h rename to packages/syncfusion_flutter_officechart/example/windows/runner/win32_window.h diff --git a/packages/syncfusion_officechart/lib/officechart.dart b/packages/syncfusion_flutter_officechart/lib/officechart.dart similarity index 95% rename from packages/syncfusion_officechart/lib/officechart.dart rename to packages/syncfusion_flutter_officechart/lib/officechart.dart index 373eceb63..b889b615a 100644 --- a/packages/syncfusion_officechart/lib/officechart.dart +++ b/packages/syncfusion_flutter_officechart/lib/officechart.dart @@ -3,21 +3,21 @@ library officechart; import 'dart:convert'; import 'dart:core'; -import 'package:syncfusion_flutter_xlsio/xlsio.dart'; import 'package:archive/archive.dart'; +import 'package:syncfusion_flutter_xlsio/xlsio.dart'; import 'package:xml/xml.dart'; -//Src -part 'src/chart/chart_impl.dart'; -part 'src/chart/chart_plotarea.dart'; +part 'src/chart/chart_axis.dart'; part 'src/chart/chart_category_axis.dart'; part 'src/chart/chart_collection.dart'; -part 'src/chart/chart_axis.dart'; -part 'src/chart/chart_value_axis.dart'; +part 'src/chart/chart_datalabel.dart'; +part 'src/chart/chart_enum.dart'; +//Src +part 'src/chart/chart_impl.dart'; part 'src/chart/chart_legend.dart'; -part 'src/chart/chart_series_collection.dart'; +part 'src/chart/chart_plotarea.dart'; +part 'src/chart/chart_serialization.dart'; part 'src/chart/chart_serie.dart'; +part 'src/chart/chart_series_collection.dart'; part 'src/chart/chart_text_area.dart'; -part 'src/chart/chart_datalabel.dart'; -part 'src/chart/chart_serialization.dart'; -part 'src/chart/chart_enum.dart'; \ No newline at end of file +part 'src/chart/chart_value_axis.dart'; diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_axis.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_axis.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_category_axis.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_category_axis.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_collection.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_collection.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_collection.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_datalabel.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_datalabel.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_enum.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_enum.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_enum.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_impl.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_impl.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_impl.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_legend.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_legend.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_legend.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_plotarea.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_plotarea.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_serialization.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_serialization.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_serie.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_serie.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_serie.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_series_collection.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_series_collection.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_text_area.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_text_area.dart diff --git a/packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart b/packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart similarity index 100% rename from packages/syncfusion_officechart/lib/src/chart/chart_value_axis.dart rename to packages/syncfusion_flutter_officechart/lib/src/chart/chart_value_axis.dart diff --git a/packages/syncfusion_officechart/pubspec.yaml b/packages/syncfusion_flutter_officechart/pubspec.yaml similarity index 91% rename from packages/syncfusion_officechart/pubspec.yaml rename to packages/syncfusion_flutter_officechart/pubspec.yaml index 218f66521..5d89f5621 100644 --- a/packages/syncfusion_officechart/pubspec.yaml +++ b/packages/syncfusion_flutter_officechart/pubspec.yaml @@ -12,4 +12,4 @@ dependencies: xml: ^5.0.2 archive: ^3.1.2 syncfusion_flutter_xlsio: - path: ../syncfusion_flutter_xlsio \ No newline at end of file + path: ../syncfusion_flutter_xlsio \ No newline at end of file diff --git a/packages/syncfusion_officechart/syncfusion_officechart.iml b/packages/syncfusion_flutter_officechart/syncfusion_officechart.iml similarity index 100% rename from packages/syncfusion_officechart/syncfusion_officechart.iml rename to packages/syncfusion_flutter_officechart/syncfusion_officechart.iml diff --git a/packages/syncfusion_officecore/CHANGELOG.md b/packages/syncfusion_flutter_officecore/CHANGELOG.md similarity index 100% rename from packages/syncfusion_officecore/CHANGELOG.md rename to packages/syncfusion_flutter_officecore/CHANGELOG.md diff --git a/packages/syncfusion_officecore/LICENSE b/packages/syncfusion_flutter_officecore/LICENSE similarity index 100% rename from packages/syncfusion_officecore/LICENSE rename to packages/syncfusion_flutter_officecore/LICENSE diff --git a/packages/syncfusion_officecore/README.md b/packages/syncfusion_flutter_officecore/README.md similarity index 92% rename from packages/syncfusion_officecore/README.md rename to packages/syncfusion_flutter_officecore/README.md index 075b3cba3..fc3ad0923 100644 --- a/packages/syncfusion_officecore/README.md +++ b/packages/syncfusion_flutter_officecore/README.md @@ -48,4 +48,4 @@ Take a look at the following to learn more about Syncfusion Flutter widgets: 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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_officecore/analysis_options.yaml b/packages/syncfusion_flutter_officecore/analysis_options.yaml similarity index 100% rename from packages/syncfusion_officecore/analysis_options.yaml rename to packages/syncfusion_flutter_officecore/analysis_options.yaml diff --git a/packages/syncfusion_officecore/example/README.md b/packages/syncfusion_flutter_officecore/example/README.md similarity index 100% rename from packages/syncfusion_officecore/example/README.md rename to packages/syncfusion_flutter_officecore/example/README.md diff --git a/packages/syncfusion_officecore/example/analysis_options.yaml b/packages/syncfusion_flutter_officecore/example/analysis_options.yaml similarity index 100% rename from packages/syncfusion_officecore/example/analysis_options.yaml rename to packages/syncfusion_flutter_officecore/example/analysis_options.yaml diff --git a/packages/syncfusion_officecore/example/android/.gitignore b/packages/syncfusion_flutter_officecore/example/android/.gitignore similarity index 100% rename from packages/syncfusion_officecore/example/android/.gitignore rename to packages/syncfusion_flutter_officecore/example/android/.gitignore diff --git a/packages/syncfusion_officecore/example/android/app/build.gradle b/packages/syncfusion_flutter_officecore/example/android/app/build.gradle similarity index 100% rename from packages/syncfusion_officecore/example/android/app/build.gradle rename to packages/syncfusion_flutter_officecore/example/android/app/build.gradle diff --git a/packages/syncfusion_officecore/example/android/app/src/debug/AndroidManifest.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/debug/AndroidManifest.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/debug/AndroidManifest.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/main/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/AndroidManifest.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/AndroidManifest.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt b/packages/syncfusion_flutter_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/kotlin/com/example/officecore_example/MainActivity.kt diff --git a/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/drawable/launch_background.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000..304732f88 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/main/res/values/styles.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/main/res/values/styles.xml diff --git a/packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml b/packages/syncfusion_flutter_officecore/example/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from packages/syncfusion_officecore/example/android/app/src/profile/AndroidManifest.xml rename to packages/syncfusion_flutter_officecore/example/android/app/src/profile/AndroidManifest.xml diff --git a/packages/syncfusion_officecore/example/android/build.gradle b/packages/syncfusion_flutter_officecore/example/android/build.gradle similarity index 100% rename from packages/syncfusion_officecore/example/android/build.gradle rename to packages/syncfusion_flutter_officecore/example/android/build.gradle diff --git a/packages/syncfusion_officecore/example/android/gradle.properties b/packages/syncfusion_flutter_officecore/example/android/gradle.properties similarity index 100% rename from packages/syncfusion_officecore/example/android/gradle.properties rename to packages/syncfusion_flutter_officecore/example/android/gradle.properties diff --git a/packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/syncfusion_flutter_officecore/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/syncfusion_officecore/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/syncfusion_flutter_officecore/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/syncfusion_officecore/example/android/officecore_example_android.iml b/packages/syncfusion_flutter_officecore/example/android/officecore_example_android.iml similarity index 100% rename from packages/syncfusion_officecore/example/android/officecore_example_android.iml rename to packages/syncfusion_flutter_officecore/example/android/officecore_example_android.iml diff --git a/packages/syncfusion_officecore/example/android/settings.gradle b/packages/syncfusion_flutter_officecore/example/android/settings.gradle similarity index 100% rename from packages/syncfusion_officecore/example/android/settings.gradle rename to packages/syncfusion_flutter_officecore/example/android/settings.gradle diff --git a/packages/syncfusion_officecore/example/ios/.gitignore b/packages/syncfusion_flutter_officecore/example/ios/.gitignore similarity index 100% rename from packages/syncfusion_officecore/example/ios/.gitignore rename to packages/syncfusion_flutter_officecore/example/ios/.gitignore diff --git a/packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist b/packages/syncfusion_flutter_officecore/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/syncfusion_flutter_officecore/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/syncfusion_flutter_officecore/example/ios/Flutter/Debug.xcconfig b/packages/syncfusion_flutter_officecore/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_flutter_officecore/example/ios/Flutter/Release.xcconfig b/packages/syncfusion_flutter_officecore/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000..592ceee85 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/syncfusion_officecore/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/syncfusion_officecore/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..a28140cfd --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000..f9b0d7c5e --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/AppDelegate.swift b/packages/syncfusion_flutter_officecore/example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..d36b1fab2 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 zcmds$`#;kQ7{|XelZftyR5~xW7?MLxS4^|Hw3&P7^y)@A9Fj{Xm1~_CIV^XZ%SLBn zA;!r`GqGHg=7>xrB{?psZQs88ZaedDoagm^KF{a*>G|dJWRSe^I$DNW008I^+;Kjt z>9p3GNR^I;v>5_`+91i(*G;u5|L+Bu6M=(afLjtkya#yZ175|z$pU~>2#^Z_pCZ7o z1c6UNcv2B3?; zX%qdxCXQpdKRz=#b*q0P%b&o)5ZrNZt7$fiETSK_VaY=mb4GK`#~0K#~9^ zcY!`#Af+4h?UMR-gMKOmpuYeN5P*RKF!(tb`)oe0j2BH1l?=>y#S5pMqkx6i{*=V9JF%>N8`ewGhRE(|WohnD59R^$_36{4>S zDFlPC5|k?;SPsDo87!B{6*7eqmMdU|QZ84>6)Kd9wNfh90=y=TFQay-0__>=<4pk& zYDjgIhL-jQ9o>z32K)BgAH+HxamL{ZL~ozu)Qqe@a`FpH=oQRA8=L-m-1dam(Ix2V z?du;LdMO+ooBelr^_y4{|44tmgH^2hSzPFd;U^!1p>6d|o)(-01z{i&Kj@)z-yfWQ)V#3Uo!_U}q3u`(fOs`_f^ueFii1xBNUB z6MecwJN$CqV&vhc+)b(p4NzGGEgwWNs z@*lUV6LaduZH)4_g!cE<2G6#+hJrWd5(|p1Z;YJ7ifVHv+n49btR}dq?HHDjl{m$T z!jLZcGkb&XS2OG~u%&R$(X+Z`CWec%QKt>NGYvd5g20)PU(dOn^7%@6kQb}C(%=vr z{?RP(z~C9DPnL{q^@pVw@|Vx~@3v!9dCaBtbh2EdtoNHm4kGxp>i#ct)7p|$QJs+U z-a3qtcPvhihub?wnJqEt>zC@)2suY?%-96cYCm$Q8R%-8$PZYsx3~QOLMDf(piXMm zB=<63yQk1AdOz#-qsEDX>>c)EES%$owHKue;?B3)8aRd}m~_)>SL3h2(9X;|+2#7X z+#2)NpD%qJvCQ0a-uzZLmz*ms+l*N}w)3LRQ*6>|Ub-fyptY(keUxw+)jfwF5K{L9 z|Cl_w=`!l_o><384d&?)$6Nh(GAm=4p_;{qVn#hI8lqewW7~wUlyBM-4Z|)cZr?Rh z=xZ&Ol>4(CU85ea(CZ^aO@2N18K>ftl8>2MqetAR53_JA>Fal`^)1Y--Am~UDa4th zKfCYpcXky$XSFDWBMIl(q=Mxj$iMBX=|j9P)^fDmF(5(5$|?Cx}DKEJa&XZP%OyE`*GvvYQ4PV&!g2|L^Q z?YG}tx;sY@GzMmsY`7r$P+F_YLz)(e}% zyakqFB<6|x9R#TdoP{R$>o7y(-`$$p0NxJ6?2B8tH)4^yF(WhqGZlM3=9Ibs$%U1w zWzcss*_c0=v_+^bfb`kBFsI`d;ElwiU%frgRB%qBjn@!0U2zZehBn|{%uNIKBA7n= zzE`nnwTP85{g;8AkYxA68>#muXa!G>xH22D1I*SiD~7C?7Za+9y7j1SHiuSkKK*^O zsZ==KO(Ua#?YUpXl{ViynyT#Hzk=}5X$e04O@fsMQjb}EMuPWFO0e&8(2N(29$@Vd zn1h8Yd>6z(*p^E{c(L0Lg=wVdupg!z@WG;E0k|4a%s7Up5C0c)55XVK*|x9RQeZ1J@1v9MX;>n34(i>=YE@Iur`0Vah(inE3VUFZNqf~tSz{1fz3Fsn_x4F>o(Yo;kpqvBe-sbwH(*Y zu$JOl0b83zu$JMvy<#oH^Wl>aWL*?aDwnS0iEAwC?DK@aT)GHRLhnz2WCvf3Ba;o=aY7 z2{Asu5MEjGOY4O#Ggz@@J;q*0`kd2n8I3BeNuMmYZf{}pg=jTdTCrIIYuW~luKecn z+E-pHY%ohj@uS0%^ z&(OxwPFPD$+#~`H?fMvi9geVLci(`K?Kj|w{rZ9JgthFHV+=6vMbK~0)Ea<&WY-NC zy-PnZft_k2tfeQ*SuC=nUj4H%SQ&Y$gbH4#2sT0cU0SdFs=*W*4hKGpuR1{)mV;Qf5pw4? zfiQgy0w3fC*w&Bj#{&=7033qFR*<*61B4f9K%CQvxEn&bsWJ{&winp;FP!KBj=(P6 z4Z_n4L7cS;ao2)ax?Tm|I1pH|uLpDSRVghkA_UtFFuZ0b2#>!8;>-_0ELjQSD-DRd z4im;599VHDZYtnWZGAB25W-e(2VrzEh|etsv2YoP#VbIZ{aFkwPrzJ#JvCvA*mXS& z`}Q^v9(W4GiSs}#s7BaN!WA2bniM$0J(#;MR>uIJ^uvgD3GS^%*ikdW6-!VFUU?JV zZc2)4cMsX@j z5HQ^e3BUzOdm}yC-xA%SY``k$rbfk z;CHqifhU*jfGM@DkYCecD9vl*qr58l6x<8URB=&%{!Cu3RO*MrKZ4VO}V6R0a zZw3Eg^0iKWM1dcTYZ0>N899=r6?+adUiBKPciJw}L$=1f4cs^bio&cr9baLF>6#BM z(F}EXe-`F=f_@`A7+Q&|QaZ??Txp_dB#lg!NH=t3$G8&06MFhwR=Iu*Im0s_b2B@| znW>X}sy~m#EW)&6E&!*0%}8UAS)wjt+A(io#wGI@Z2S+Ms1Cxl%YVE800007ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 zcmVjJBgitF5mAp-i>4+KS_oR{|13AP->1TD4=w)g|)JHOx|a2Wk1Va z!k)vP$UcQ#mdj%wNQoaJ!w>jv_6&JPyutpQps?s5dmDQ>`%?Bvj>o<%kYG!YW6H-z zu`g$@mp`;qDR!51QaS}|ZToSuAGcJ7$2HF0z`ln4t!#Yg46>;vGG9N9{V@9z#}6v* zfP?}r6b{*-C*)(S>NECI_E~{QYzN5SXRmVnP<=gzP+_Sp(Aza_hKlZ{C1D&l*(7IKXxQC1Z9#6wx}YrGcn~g%;icdw>T0Rf^w0{ z$_wn1J+C0@!jCV<%Go5LA45e{5gY9PvZp8uM$=1}XDI+9m7!A95L>q>>oe0$nC->i zeexUIvq%Uk<-$>DiDb?!In)lAmtuMWxvWlk`2>4lNuhSsjAf2*2tjT`y;@d}($o)S zn(+W&hJ1p0xy@oxP%AM15->wPLp{H!k)BdBD$toBpJh+crWdsNV)qsHaqLg2_s|Ih z`8E9z{E3sA!}5aKu?T!#enD(wLw?IT?k-yWVHZ8Akz4k5(TZJN^zZgm&zM28sfTD2BYJ|Fde3Xzh;;S` z=GXTnY4Xc)8nYoz6&vF;P7{xRF-{|2Xs5>a5)@BrnQ}I(_x7Cgpx#5&Td^4Q9_FnQ zX5so*;#8-J8#c$OlA&JyPp$LKUhC~-e~Ij!L%uSMu!-VZG7Hx-L{m2DVR2i=GR(_% zCVD!4N`I)&Q5S`?P&fQZ=4#Dgt_v2-DzkT}K(9gF0L(owe-Id$Rc2qZVLqI_M_DyO z9@LC#U28_LU{;wGZ&))}0R2P4MhajKCd^K#D+JJ&JIXZ_p#@+7J9A&P<0kdRujtQ_ zOy>3=C$kgi6$0pW06KaLz!21oOryKM3ZUOWqppndxfH}QpgjEJ`j7Tzn5bk6K&@RA?vl##y z$?V~1E(!wB5rH`>3nc&@)|#<1dN2cMzzm=PGhQ|Yppne(C-Vlt450IXc`J4R0W@I7 zd1e5uW6juvO%ni(WX7BsKx3MLngO7rHO;^R5I~0^nE^9^E_eYLgiR9&KnJ)pBbfno zSVnW$0R+&6jOOsZ82}nJ126+c|%svPo;TeUku<2G7%?$oft zyaO;tVo}(W)VsTUhq^XmFi#2z%-W9a{7mXn{uzivYQ_d6b7VJG{77naW(vHt-uhnY zVN#d!JTqVh(7r-lhtXVU6o})aZbDt_;&wJVGl2FKYFBFpU-#9U)z#(A%=IVnqytR$SY-sO( z($oNE09{D^@OuYPz&w~?9>Fl5`g9u&ecFGhqX=^#fmR=we0CJw+5xna*@oHnkahk+ z9aWeE3v|An+O5%?4fA&$Fgu~H_YmqR!yIU!bFCk4!#pAj%(lI(A5n)n@Id#M)O9Yx zJU9oKy{sRAIV3=5>(s8n{8ryJ!;ho}%pn6hZKTKbqk=&m=f*UnK$zW3YQP*)pw$O* zIfLA^!-bmBl6%d_n$#tP8Zd_(XdA*z*WH|E_yILwjtI~;jK#v-6jMl^?<%Y%`gvpwv&cFb$||^v4D&V=aNy?NGo620jL3VZnA%s zH~I|qPzB~e(;p;b^gJr7Ure#7?8%F0m4vzzPy^^(q4q1OdthF}Fi*RmVZN1OwTsAP zn9CZP`FazX3^kG(KodIZ=Kty8DLTy--UKfa1$6XugS zk%6v$Kmxt6U!YMx0JQ)0qX*{CXwZZk$vEROidEc7=J-1;peNat!vS<3P-FT5po>iE z!l3R+<`#x|+_hw!HjQGV=8!q|76y8L7N8gP3$%0kfush|u0uU^?dKBaeRSBUpOZ0c z62;D&Mdn2}N}xHRFTRI?zRv=>=AjHgH}`2k4WK=#AHB)UFrR-J87GgX*x5fL^W2#d z=(%K8-oZfMO=i{aWRDg=FX}UubM4eotRDcn;OR#{3q=*?3mE3_oJ-~prjhxh%PgQT zyn)Qozaq0@o&|LEgS{Ind4Swsr;b`u185hZPOBLL<`d2%^Yp1?oL)=jnLi;Zo0ZDliTtQ^b5SmfIMe{T==zZkbvn$KTQGlbG8w}s@M3TZnde;1Am46P3juKb zl9GU&3F=q`>j!`?SyH#r@O59%@aMX^rx}Nxe<>NqpUp5=lX1ojGDIR*-D^SDuvCKF z?3$xG(gVUsBERef_YjPFl^rU9EtD{pt z0CXwpN7BN3!8>hajGaTVk-wl=9rxmfWtIhC{mheHgStLi^+Nz12a?4r(fz)?3A%at zMlvQmL<2-R)-@G1wJ0^zQK%mR=r4d{Y3fHp){nWXUL#|CqXl(+v+qDh>FkF9`eWrW zfr^D%LNfOcTNvtx0JXR35J0~Jpi2#P3Q&80w+nqNfc}&G0A~*)lGHKv=^FE+b(37|)zL;KLF>oiGfb(?&1 zV3XRu!Sw>@quKiab%g6jun#oZ%!>V#A%+lNc?q>6+VvyAn=kf_6z^(TZUa4Eelh{{ zqFX-#dY(EV@7l$NE&kv9u9BR8&Ojd#ZGJ6l8_BW}^r?DIS_rU2(XaGOK z225E@kH5Opf+CgD^{y29jD4gHbGf{1MD6ggQ&%>UG4WyPh5q_tb`{@_34B?xfSO*| zZv8!)q;^o-bz`MuxXk*G^}(6)ACb@=Lfs`Hxoh>`Y0NE8QRQ!*p|SH@{r8=%RKd4p z+#Ty^-0kb=-H-O`nAA3_6>2z(D=~Tbs(n8LHxD0`R0_ATFqp-SdY3(bZ3;VUM?J=O zKCNsxsgt@|&nKMC=*+ZqmLHhX1KHbAJs{nGVMs6~TiF%Q)P@>!koa$%oS zjXa=!5>P`vC-a}ln!uH1ooeI&v?=?v7?1n~P(wZ~0>xWxd_Aw;+}9#eULM7M8&E?Y zC-ZLhi3RoM92SXUb-5i-Lmt5_rfjE{6y^+24`y$1lywLyHO!)Boa7438K4#iLe?rh z2O~YGSgFUBH?og*6=r9rme=peP~ah`(8Zt7V)j5!V0KPFf_mebo3z95U8(up$-+EA^9dTRLq>Yl)YMBuch9%=e5B`Vnb>o zt03=kq;k2TgGe4|lGne&zJa~h(UGutjP_zr?a7~#b)@15XNA>Dj(m=gg2Q5V4-$)D|Q9}R#002ovPDHLkV1o7DH3k3x literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 zcmV-m2cP(fP)x~L`~4d)Rspd&<9kFh{hn*KP1LP0~$;u(LfAu zp%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9uQR(l*ST}s+uPgQ-MeFwZ#GS?b332? z&Tk$&_miXn3IGq)AmQ)3sisq{raD4(k*bHvpCe-TdWq^NRTEVM)i9xbgQ&ccnUVx* zEY%vS%gDcSg=!tuIK8$Th2_((_h^+7;R|G{n06&O2#6%LK`a}n?h_fL18btz<@lFG za}xS}u?#DBMB> zw^b($1Z)`9G?eP95EKi&$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD? zmr-s{0wRmxUnbDrYfRvnZ@d z6johZ2sMX{YkGSKWd}m|@V7`Degt-43=2M?+jR%8{(H$&MLLmS;-|JxnX2pnz;el1jsvqQz}pGSF<`mqEXRQ5sC4#BbwnB_4` zc5bFE-Gb#JV3tox9fp-vVEN{(tOCpRse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{) zC@{O6MKY8tfZ5@!yy=p5Y|#+myRL=^{tc(6YgAnkg3I(Cd!r5l;|;l-MQ8B`;*SCE z{u)uP^C$lOPM z5d~UhKhRRmvv{LIa^|oavk1$QiEApSrP@~Jjbg`<*dW4TO?4qG%a%sTPUFz(QtW5( zM)lA+5)0TvH~aBaOAs|}?u2FO;yc-CZ1gNM1dAxJ?%m?YsGR`}-xk2*dxC}r5j$d* zE!#Vtbo69h>V4V`BL%_&$} z+oJAo@jQ^Tk`;%xw-4G>hhb&)B?##U+(6Fi7nno`C<|#PVA%$Y{}N-?(Gc$1%tr4Pc}}hm~yY#fTOe!@v9s-ik$dX~|ygArPhByaXn8 zpI^FUjNWMsTFKTP3X7m?UK)3m zp6rI^_zxRYrx6_QmhoWoDR`fp4R7gu6;gdO)!KexaoO2D88F9x#TM1(9Bn7g;|?|o z)~$n&Lh#hCP6_LOPD>a)NmhW})LADx2kq=X7}7wYRj-0?dXr&bHaRWCfSqvzFa=sn z-8^gSyn-RmH=BZ{AJZ~!8n5621GbUJV7Qvs%JNv&$%Q17s_X%s-41vAPfIR>;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3 z<6XEBh^S7>AbwGm@XP{RkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ) z#((dn=A#i_&EQn|hd)N$#A*fjBFuiHcYvo?@y1 z5|fV=a^a~d!c-%ZbMNqkMKiSzM{Yq=7_c&1H!mXk60Uv32dV;vMg&-kQ)Q{+PFtwc zj|-uQ;b^gts??J*9VxxOro}W~Q9j4Em|zSRv)(WSO9$F$s=Ydu%Q+5DOid~lwk&we zY%W(Z@ofdwPHncEZzZgmqS|!gTj3wQq9rxQy+^eNYKr1mj&?tm@wkO*9@UtnRMG>c aR{jt9+;fr}hV%pg00001^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&6$jw%VRuvdN2+38CZWny1cRtlsl+0_KtW)EU14Ei(F!UtWuj4IK+3{sK@>rh zs1Z;=(DD&U6+tlyL?UnHVN^&g6QhFi2#HS+*qz;(>63G(`|jRtW|nz$Pv7qTovP!^ zP_jES{mr@O-02w%!^a?^1ZP!_KmQiz0L~jZ=W@Qt`8wzOoclQsAS<5YdH;a(4bGLE zk8s}1If(PSIgVi!XE!5kA?~z*sobvNyohr;=Q_@h2@$6Flyej3J)D-6YfheRGl`HEcPk|~huT_2-U?PfL=4BPV)f1o!%rQ!NMt_MYw-5bUSwQ9Z&zC>u zOrl~UJglJNa%f50Ok}?WB{on`Ci`p^Y!xBA?m@rcJXLxtrE0FhRF3d*ir>yzO|BD$ z3V}HpFcCh6bTzY}Nt_(W%QYd3NG)jJ4<`F<1Od) zfQblTdC&h2lCz`>y?>|9o2CdvC8qZeIZt%jN;B7Hdn2l*k4M4MFEtq`q_#5?}c$b$pf_3y{Y!cRDafZBEj-*OD|gz#PBDeu3QoueOesLzB+O zxjf2wvf6Wwz>@AiOo2mO4=TkAV+g~%_n&R;)l#!cBxjuoD$aS-`IIJv7cdX%2{WT7 zOm%5rs(wqyPE^k5SIpUZ!&Lq4<~%{*>_Hu$2|~Xa;iX*tz8~G6O3uFOS?+)tWtdi| zV2b#;zRN!m@H&jd=!$7YY6_}|=!IU@=SjvGDFtL;aCtw06U;-v^0%k0FOyESt z1Wv$={b_H&8FiRV?MrzoHWd>%v6KTRU;-v^Miiz+@q`(BoT!+<37CKhoKb)|8!+RG z6BQFU^@fRW;s8!mOf2QViKQGk0TVER6EG1`#;Nm39Do^PoT!+<37AD!%oJe86(=et zZ~|sLzU>V-qYiU6V8$0GmU7_K8|Fd0B?+9Un1BhKAz#V~Fk^`mJtlCX#{^8^M8!me z8Yg;8-~>!e<-iG;h*0B1kBKm}hItVGY6WnjVpgnTTAC$rqQ^v)4KvOtpY|sIj@WYg zyw##ZZ5AC2IKNC;^hwg9BPk0wLStlmBr;E|$5GoAo$&Ui_;S9WY62n3)i49|T%C#i017z3J=$RF|KyZWnci*@lW4 z=AKhNN6+m`Q!V3Ye68|8y@%=am>YD0nG99M)NWc20%)gwO!96j7muR}Fr&54SxKP2 zP30S~lt=a*qDlbu3+Av57=9v&vr<6g0&`!8E2fq>I|EJGKs}t|{h7+KT@)LfIV-3K zK)r_fr2?}FFyn*MYoLC>oV-J~eavL2ho4a4^r{E-8m2hi>~hA?_vIG4a*KT;2eyl1 zh_hUvUJpNCFwBvRq5BI*srSle>c6%n`#VNsyC|MGa{(P&08p=C9+WUw9Hl<1o9T4M zdD=_C0F7#o8A_bRR?sFNmU0R6tW`ElnF8p53IdHo#S9(JoZCz}fHwJ6F<&?qrpVqE zte|m%89JQD+XwaPU#%#lVs-@-OL);|MdfINd6!XwP2h(eyafTUsoRkA%&@fe?9m@jw-v(yTTiV2(*fthQH9}SqmsRPVnwwbV$1E(_lkmo&S zF-truCU914_$jpqjr(>Ha4HkM4YMT>m~NosUu&UZ>zirfHo%N6PPs9^_o$WqPA0#5 z%tG>qFCL+b*0s?sZ;Sht0nE7Kl>OVXy=gjWxxK;OJ3yGd7-pZf7JYNcZo2*1SF`u6 zHJyRRxGw9mDlOiXqVMsNe#WX`fC`vrtjSQ%KmLcl(lC>ZOQzG^%iql2w-f_K@r?OE zwCICifM#L-HJyc7Gm>Ern?+Sk3&|Khmu4(~3qa$(m6Ub^U0E5RHq49za|XklN#?kP zl;EstdW?(_4D>kwjWy2f!LM)y?F94kyU3`W!6+AyId-89v}sXJpuic^NLL7GJItl~ zsiuB98AI-(#Mnm|=A-R6&2fwJ0JVSY#Q>&3$zFh|@;#%0qeF=j5Ajq@4i0tIIW z&}sk$&fGwoJpe&u-JeGLi^r?dO`m=y(QO{@h zQqAC7$rvz&5+mo3IqE?h=a~6m>%r5Quapvzq;{y~p zJpyXOBgD9VrW7@#p6l7O?o3feml(DtSL>D^R) zZUY%T2b0-vBAFN7VB;M88!~HuOXi4KcI6aRQ&h|XQ0A?m%j2=l1f0cGP}h(oVfJ`N zz#PpmFC*ieab)zJK<4?^k=g%OjPnkANzbAbmGZHoVRk*mTfm75s_cWVa`l*f$B@xu z5E*?&@seIo#*Y~1rBm!7sF9~~u6Wrj5oICUOuz}CS)jdNIznfzCA(stJ(7$c^e5wN z?lt>eYgbA!kvAR7zYSD&*r1$b|(@;9dcZ^67R0 zXAXJKa|5Sdmj!g578Nwt6d$sXuc&MWezA0Whd`94$h{{?1IwXP4)Tx4obDK%xoFZ_Z zjjHJ_P@R_e5blG@yEjnaJb`l;s%Lb2&=8$&Ct-fV`E^4CUs)=jTk!I}2d&n!f@)bm z@ z_4Dc86+3l2*p|~;o-Sb~oXb_RuLmoifDU^&Te$*FevycC0*nE3Xws8gsWp|Rj2>SM zns)qcYj?^2sd8?N!_w~4v+f-HCF|a$TNZDoNl$I1Uq87euoNgKb6&r26TNrfkUa@o zfdiFA@p{K&mH3b8i!lcoz)V{n8Q@g(vR4ns4r6w;K z>1~ecQR0-<^J|Ndg5fvVUM9g;lbu-){#ghGw(fg>L zh)T5Ljb%lWE;V9L!;Cqk>AV1(rULYF07ZBJbGb9qbSoLAd;in9{)95YqX$J43-dY7YU*k~vrM25 zxh5_IqO0LYZW%oxQ5HOzmk4x{atE*vipUk}sh88$b2tn?!ujEHn`tQLe&vo}nMb&{ zio`xzZ&GG6&ZyN3jnaQy#iVqXE9VT(3tWY$n-)uWDQ|tc{`?fq2F`oQ{;d3aWPg4Hp-(iE{ry>MIPWL> iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000..89c2725b7 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000..f2e259c7c --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/Main.storyboard b/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000..f3c28516f --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_officecore/example/ios/Runner/Info.plist b/packages/syncfusion_flutter_officecore/example/ios/Runner/Info.plist similarity index 100% rename from packages/syncfusion_officecore/example/ios/Runner/Info.plist rename to packages/syncfusion_flutter_officecore/example/ios/Runner/Info.plist diff --git a/packages/syncfusion_flutter_officecore/example/ios/Runner/Runner-Bridging-Header.h b/packages/syncfusion_flutter_officecore/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..308a2a560 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart new file mode 100644 index 000000000..12863e435 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_mobile.dart @@ -0,0 +1,36 @@ +import 'dart:io'; + +import 'package:open_file/open_file.dart' as open_file; +import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + //Get the storage folder location using path_provider package. + String? path; + if (Platform.isAndroid || + Platform.isIOS || + Platform.isLinux || + Platform.isWindows) { + final Directory directory = + await path_provider.getApplicationSupportDirectory(); + path = directory.path; + } else { + path = await PathProviderPlatform.instance.getApplicationSupportPath(); + } + final File file = + File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + await file.writeAsBytes(bytes, flush: true); + if (Platform.isAndroid || Platform.isIOS) { + //Launch the file (used open_file package) + await open_file.OpenFile.open('$path/$fileName'); + } else if (Platform.isWindows) { + await Process.run('start', ['$path\\$fileName'], runInShell: true); + } else if (Platform.isMacOS) { + await Process.run('open', ['$path/$fileName'], runInShell: true); + } else if (Platform.isLinux) { + await Process.run('xdg-open', ['$path/$fileName'], + runInShell: true); + } +} diff --git a/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart new file mode 100644 index 000000000..a455ce682 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/lib/helper/save_file_web.dart @@ -0,0 +1,15 @@ +///Dart imports +import 'dart:async'; +import 'dart:convert'; +// ignore: avoid_web_libraries_in_flutter +import 'dart:html'; + +///To save the Excel file in the device +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + AnchorElement( + href: + 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') + ..setAttribute('download', fileName) + ..click(); +} diff --git a/packages/syncfusion_officecore/example/lib/main.dart b/packages/syncfusion_flutter_officecore/example/lib/main.dart similarity index 99% rename from packages/syncfusion_officecore/example/lib/main.dart rename to packages/syncfusion_flutter_officecore/example/lib/main.dart index a018134f9..343a4d468 100644 --- a/packages/syncfusion_officecore/example/lib/main.dart +++ b/packages/syncfusion_flutter_officecore/example/lib/main.dart @@ -221,6 +221,6 @@ class _CreateExcelState extends State { //Dispose the document. workbook.dispose(); - await FileSaveHelper.saveAndLaunchFile(bytes, 'Invoice.xlsx'); + await saveAndLaunchFile(bytes, 'Invoice.xlsx'); } } diff --git a/packages/syncfusion_flutter_officecore/example/linux/.gitignore b/packages/syncfusion_flutter_officecore/example/linux/.gitignore new file mode 100644 index 000000000..d3896c984 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/packages/syncfusion_flutter_officecore/example/linux/CMakeLists.txt b/packages/syncfusion_flutter_officecore/example/linux/CMakeLists.txt new file mode 100644 index 000000000..a558bc458 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/CMakeLists.txt @@ -0,0 +1,116 @@ +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +set(BINARY_NAME "example") +set(APPLICATION_ID "com.example.example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Configure build options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Application build +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) +apply_standard_settings(${BINARY_NAME}) +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) +add_dependencies(${BINARY_NAME} flutter_assemble) +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/packages/syncfusion_officecore/example/linux/flutter/CMakeLists.txt b/packages/syncfusion_flutter_officecore/example/linux/flutter/CMakeLists.txt similarity index 100% rename from packages/syncfusion_officecore/example/linux/flutter/CMakeLists.txt rename to packages/syncfusion_flutter_officecore/example/linux/flutter/CMakeLists.txt diff --git a/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..e71a16d23 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..e0f0a47bc --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 000000000..51436ae8c --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,15 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/syncfusion_flutter_officecore/example/linux/main.cc b/packages/syncfusion_flutter_officecore/example/linux/main.cc new file mode 100644 index 000000000..e7c5c5437 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/packages/syncfusion_flutter_officecore/example/linux/my_application.cc b/packages/syncfusion_flutter_officecore/example/linux/my_application.cc new file mode 100644 index 000000000..634f4c519 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/my_application.cc @@ -0,0 +1,105 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen *screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } + else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject *object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/packages/syncfusion_flutter_officecore/example/linux/my_application.h b/packages/syncfusion_flutter_officecore/example/linux/my_application.h new file mode 100644 index 000000000..72271d5e4 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/packages/syncfusion_flutter_officecore/example/macos/.gitignore b/packages/syncfusion_flutter_officecore/example/macos/.gitignore new file mode 100644 index 000000000..d2fd37723 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/.gitignore @@ -0,0 +1,6 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/xcuserdata/ diff --git a/packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000..c2efd0b60 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Release.xcconfig b/packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000..c2efd0b60 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/syncfusion_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift b/packages/syncfusion_flutter_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from packages/syncfusion_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift rename to packages/syncfusion_flutter_officecore/example/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.pbxproj b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000..cc89c8782 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,572 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000..ae8ff59d9 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..1d526a16e --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/AppDelegate.swift b/packages/syncfusion_flutter_officecore/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000..d53ef6437 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000..a2ec33f19 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4935a7ca84f0976aca34b7f2895d65fb94d1ea GIT binary patch literal 46993 zcmZ5|3p`X?`~OCwR3s6~xD(})N~M}fiXn6%NvKp3QYhuNN0*apqmfHdR7#ShNQ99j zQi+P9nwlXbmnktZ_WnO>bl&&<{m*;O=RK!cd#$zCdM@AR`#jH%+2~+BeX7b-48x|= zZLBt9*d+MZNtpCx_&asa{+CselLUV<<&ceQ5QfRjLjQDSL-t4eq}5znmIXDtfA|D+VRV$*2jxU)JopC)!37FtD<6L^&{ia zgVf1p(e;c3|HY;%uD5<-oSFkC2JRh- z&2RTL)HBG`)j5di8ys|$z_9LSm^22*uH-%MmUJs|nHKLHxy4xTmG+)JoA`BN7#6IN zK-ylvs+~KN#4NWaH~o5Wuwd@W?H@diExdcTl0!JJq9ZOA24b|-TkkeG=Q(pJw7O;i z`@q+n|@eeW7@ z&*NP+)wOyu^5oNJ=yi4~s_+N)#M|@8nfw=2#^BpML$~dJ6yu}2JNuq!)!;Uwxic(z zM@Wa-v|U{v|GX4;P+s#=_1PD7h<%8ey$kxVsS1xt&%8M}eOF98&Rx7W<)gY(fCdmo{y*FPC{My!t`i=PS1cdV7DD=3S1J?b2<5BevW7!rWJ%6Q?D9UljULd*7SxX05PP^5AklWu^y` z-m9&Oq-XNSRjd|)hZ44DK?3>G%kFHSJ8|ZXbAcRb`gH~jk}Iwkl$@lqg!vu)ihSl= zjhBh%%Hq|`Vm>T7+SYyf4bI-MgiBq4mZlZmsKv+S>p$uAOoNxPT)R6owU%t*#aV}B z5@)X8nhtaBhH=={w;Du=-S*xvcPz26EI!gt{(hf;TllHrvku`^8wMj7-9=By>n{b= zHzQ?Wn|y=;)XM#St@o%#8idxfc`!oVz@Lv_=y(t-kUC`W)c0H2TX}Lop4121;RHE(PPHKfe_e_@DoHiPbVP%JzNudGc$|EnIv`qww1F5HwF#@l(=V zyM!JQO>Rt_PTRF1hI|u^2Uo#w*rdF*LXJky0?|fhl4-M%zN_2RP#HFhSATE3&{sos zIE_?MdIn!sUH*vjs(teJ$7^7#|M_7m`T>r>qHw>TQh?yhhc8=TJk2B;KNXw3HhnQs za(Uaz2VwP;82rTy(T3FJNKA86Y7;L(K=~BW_Q=jjRh=-k_=wh-$`nY+#au+v^C4VV z)U?X(v-_#i=3bAylP1S*pM_y*DB z2fR!imng6Dk$>dl*K@AIj<~zw_f$T!-xLO8r{OkE(l?W#W<={460Y02*K#)O4xp?W zAN+isO}!*|mN7B#jUt&!KNyFOpUxv&ybM>jmkfn8z^llBslztv!!`TBEPwu;#eR3d z@_VDa)|ByvXx1V=^Up4{;M8ji3FC7gm(C7Ty-#1gs+U<{Ouc(iV67{< zam#KwvR&s=k4W<13`}DxzJ9{TUa97N-cgWkCDc+C339)EEnC@^HQK6OvKDSCvNz(S zOFAF_6omgG!+zaPC8fBO3kH8YVBx9_AoM?->pv~@$saf(Myo|e@onD`a=;kO*Utem ze=eUH&;JB2I4}?Pm@=VnE+yb$PD~sA5+)|iH3bi|s?ExIePeoAMd(Z4Z%$mCu{t;B9(sgdG~Q}0ShAwe!l8nw0tJn zJ+m?ogrgty$3=T&6+JJa!1oS3AtQQ1gJ z3gR1<=hXU>{SB-zq!okl4c+V9N;vo4{fyGeqtgBIt%TPC1P&k!pR-GZ7O8b}9=%>3 zQrV%FQdB+CcCRKK)0}v>U25rbQk(1^9Ax|WcAo5?L(H&H@%zAoT2RH$iN6boyXpsYqME}WJZI6T%OMlkWXK>R`^7AHG&31 z&MIU}igQ7$;)7AEm#dXA+!I&6ymb7n6D;F7c$tO3Ql(`ht z1sFrzIk_q5#=!#D(e~#SdWz5K;tPF*R883Yu>*@jTeOGUjQekw zM+7HlfP{y8p}jA9bLfyKC_Ti8k#;AVp@RML^9MQp-E+Ns-Y zKA!aAZV-sfm<23fy#@TZZlQVQxH%R7rD}00LxHPUF!Yg3%OX ziDe4m<4fp{7ivBS?*AlJz$~vw5m)Ei8`|+~xOSqJ$waA0+Yys$z$9iN9TIXu8 zaYacjd09uRAsU|)g|03w`F|b1Xg#K~*Mp2X^K^)r3P^juoc}-me&YhkW3#G|H<~jK zoKD?lE@jOw7>4cpKkh!8qU!bF(i~Oa8a!EGy-j46eZYbKUvF=^^nq`EtWFK}gwrsB zeu<6~?mk+;+$whP)8ud8vjqh+NofU+Nu`~|pb&CN1y_idxxf6cGbT=fBZR_hl&G)GgnW$*oDrN-zz;cKs18n+dAn95w z)Y>l6!5eYpebJGw7it~Q5m}8$7@%p&KS=VtydFj4HPJ{xqUVS_Ih}c(^4nUdwG|0% zw8Fnm{IT`8MqoL(1BNtu_#7alS@3WSUUOFT@U*`V!zrPIeCbbO=pE%|g92$EU|lw; z^;^AqMVWVf-R5^OI79TzIyYf}HX%0Y)=aYH;EKo}?=R~ZM&s&F;W>u%hFUfNafb;- z8OkmkK3k||J#3`xdLuMJAhj9oPI?Cjt}cDN7hw26n7irWS0hsy`fs&Y?Y&(QF*Nu! z!p`NggHXaBU6$P42LkqnKsPG@363DHYGXg{!|z6VMAQt??>FK1B4x4{j;iY8A+7o% z*!0qt&w+w#Ob@pQp;q)u0;v^9FlY=AK>2!qku)!%TO<^lNBr!6R8X)iXgXi^1p`T8 z6sU@Y_Fsp6E89E1*jz~Tm2kF=mjYz_q99r^v0h-l7SP6azzL%woM6!7>IFWyizrNwAqoia3nN0q343q zFztMPh0)?ugQg5Izbk{5$EGcMzt*|=S8ZFK%O&^YV@V;ZRL>f!iG?s5z{(*Xq20c^ z(hkk~PljBo%U`$q>mz!ir7chKlE-oHA2&0i@hn4O5scsI&nIWsM>sYg;Ph5IO~VpT z%c-3_{^N>4kECzk?2~Z@V|jWio&a&no;boiNxqXOpS;ph)gEDFJ6E=zPJ$>y5w`U0 z;h9_6ncIEY?#j1+IDUuixRg&(hw+QSSEmFi%_$ua$^K%(*jUynGU@FlvsyThxqMRw z7_ALpqTj~jOSu2_(@wc_Z?>X&(5jezB6w-@0X_34f&cZ=cA-t%#}>L7Q3QRx1$qyh zG>NF=Ts>)wA)fZIlk-kz%Xa;)SE(PLu(oEC8>9GUBgd$(^_(G6Y((Hi{fsV; zt*!IBWx_$5D4D&ezICAdtEU!WS3`YmC_?+o&1RDSfTbuOx<*v`G<2SP;5Q4TqFV&q zJL=90Lcm^TL7a9xck}XPMRnQ`l0%w-fi@bRI&c*VDj!W4nj=qaQd$2U?^9RTT{*qS_)Q9OL>s}2P3&da^Pf(*?> z#&2bt;Q7N2`P{{KH@>)Tf5&za?crRmQ%8xZi<9f=EV3={K zwMet=oA0-@`8F;u`8j-!8G~0TiH5yKemY+HU@Zw3``1nT>D ziK465-m?Nm^~@G@RW2xH&*C#PrvCWU)#M4jQ`I*>_^BZB_c!z5Wn9W&eCBE(oc1pw zmMr)iu74Xl5>pf&D7Ml>%uhpFGJGyj6Mx=t#`}Mt3tDZQDn~K`gp0d)P>>4{FGiP$sPK*ExVs!1)aGgAX z6eA;-9@@Muti3xYv$8U{?*NxlHxs?)(6%!Iw&&l79K86h+Z8;)m9+(zzX?cS zH*~)yk)X^H1?AfL!xctY-8T0G0Vh~kcP=8%Wg*zZxm*;eb)TEh&lGuNkqJib_}i;l z*35qQ@}I#v;EwCGM2phE1{=^T4gT63m`;UEf5x2Get-WSWmt6%T6NJM`|tk-~4<#HHwCXuduB4+vW!BywlH8murH@|32CNxx7} zAoF?Gu02vpSl|q1IFO0tNEvKwyH5V^3ZtEO(su1sIYOr{t@Tr-Ot@&N*enq;Je38} zOY+C1bZ?P~1=Qb%oStI-HcO#|WHrpgIDR0GY|t)QhhTg*pMA|%C~>;R4t_~H1J3!i zyvQeDi&|930wZlA$`Wa9)m(cB!lPKD>+Ag$5v-}9%87`|7mxoNbq7r^U!%%ctxiNS zM6pV6?m~jCQEKtF3vLnpag``|bx+eJ8h=(8b;R+8rzueQvXgFhAW*9y$!DgSJgJj% zWIm~}9(R6LdlXEg{Y3g_i7dP^98=-3qa z$*j&xC_$5btF!80{D&2*mp(`rNLAM$JhkB@3al3s=1k^Ud6HHontlcZw&y?`uPT#a za8$RD%e8!ph8Ow7kqI@_vd7lgRhkMvpzp@4XJ`9dA@+Xk1wYf`0Dk!hIrBxhnRR(_ z%jd(~x^oqA>r>`~!TEyhSyrwNA(i}={W+feUD^8XtX^7^Z#c7att{ot#q6B;;t~oq zct7WAa?UK0rj0yhRuY$7RPVoO29JV$o1Z|sJzG5<%;7pCu%L-deUon-X_wAtzY@_d z6S}&5xXBtsf8TZ13chR&vOMYs0F1?SJcvPn>SFe#+P3r=6=VIqcCU7<6-vxR*BZUm zO^DkE{(r8!e56)2U;+8jH4tuD2c(ptk0R{@wWK?%Wz?fJckr9vpIU27^UN*Q$}VyHWx)reWgmEls}t+2#Zm z_I5?+htcQl)}OTqF<`wht89>W*2f6e)-ewk^XU5!sW2A2VtaI=lggR&I z;Rw{xd)WMqw`VUPbhrx!!1Eg_*O0Si6t@ny)~X^Gu8wZZDockr)5)6tm+<=z+rYu? zCof+;!nq6r9MAfh zp4|^2w^-3vFK~{JFX|F5BIWecBJkkEuE%iP8AZ z^&e|C+VEH&i(4Y|oWPCa#C3T$129o5xaJa=y8f(!k&q+x=M|rq{?Zw_n?1X-bt&bP zD{*>Io`F4(i+5eE2oEo6iF}jNAZ52VN&Cp>LD{MyB=mCeiwP+v#gRvr%W)}?JBTMY z_hc2r8*SksC%(pp$KGmWSa|fx;r^9c;~Q(Jqw1%;$#azZf}#Fca9NZOh{*YxV9(1ivVA^2Wz>!A&Xvmm-~{y8n!^Jdl8c>`J#=2~!P{ zC1g_5Ye3={{fB`R%Q|%9<1p1;XmPo5lH5PHvX$bCIYzQhGqj7hZ?@P4M0^mkejD|H zVzARm7LRy|8`jSG^GpxRIs=aD>Y{Cb>^IwGEKCMd5LAoI;b{Q<-G}x*e>86R8dNAV z<@jb1q%@QQanW1S72kOQ$9_E#O?o}l{mHd=%Dl{WQcPio$baXZN!j{2m)TH1hfAp{ zM`EQ=4J`fMj4c&T+xKT!I0CfT^UpcgJK22vC962ulgV7FrUrII5!rx1;{@FMg(dIf zAC}stNqooiVol%%TegMuWnOkWKKA}hg6c)ssp~EnTUVUI98;a}_8UeTgT|<%G3J=n zKL;GzAhIQ_@$rDqqc1PljwpfUwiB)w!#cLAkgR_af;>}(BhnC9N zqL|q8-?jsO&Srv54TxVuJ=rfcX=C7{JNV zSmW@s0;$(#!hNuU0|YyXLs{9$_y2^fRmM&g#toh}!K8P}tlJvYyrs6yjTtHU>TB0} zNy9~t5F47ocE_+%V1(D!mKNBQc{bnrAbfPC2KO?qdnCv8DJzEBeDbW}gd!g2pyRyK`H6TVU^~K# z488@^*&{foHKthLu?AF6l-wEE&g1CTKV|hN7nP+KJnkd0sagHm&k{^SE-woW9^fYD z7y?g*jh+ELt;$OgP>Se3o#~w9qS}!%#vBvB?|I-;GM63oYrJ}HFRW6D+{54v@PN8K z2kG8`!VVc+DHl^8y#cevo4VCnTaPTzCB%*)sr&+=p{Hh#(MwaJbeuvvd!5fd67J_W za`oKxTR=mtM7P}i2qHG8=A(39l)_rHHKduDVA@^_Ueb7bq1A5#zHAi**|^H@fD`_W z#URdSG86hhQ#&S-Vf_8b`TIAmM55XhaHX7}Ci-^(ZDs*yb-WrWV&(oAQu3vMv%u$5 zc;!ADkeNBN_@47r!;%G3iFzo;?k)xTS-;1D-YeS5QXN7`p2PzGK~e6ib;8COBa5)p zfMn}dA--&A12~zr&GVk?qnBGfIEo`5yir;-Q;ZLn{Fimdrk;e!)q`sAkYh^~^>4Q@ zN5RT>s38+`V{|6@k&vZW!W0*BEqV&~34d+Ev8h)ObYL7Bd_hgbUzjdJaXP=S@Dp6X z)i013q3K4Gr5d%2YIp>218pYK!xwH;k)j?uUrT-yVKLg*L3y~=a+qd!RWGTL`z>29 z-Zb4Y{%pT%`R-iA#?T58c-i@?jf-Ckol9O>HAZPUxN%Z=<4ad9BL7n`_kH0i#E(m& zaNb039+z~ONUCLsf_a|x*&ptU?`=R*n}rm-tOdCDrS!@>>xBg)B3Sy8?x^e=U=i8< zy7H-^BPfM}$hf*d_`Qhk_V$dRYZw<)_mbC~gPPxf0$EeXhl-!(ZH3rkDnf`Nrf4$+ zh?jsRS+?Zc9Cx7Vzg?q53ffpp43po22^8i1Obih&$oBufMR;cT2bHlSZ#fDMZZr~u zXIfM5SRjBj4N1}#0Ez|lHjSPQoL&QiT4mZn=SxHJg~R`ZjP!+hJ?&~tf$N!spvKPi zfY;x~laI9X`&#i#Z}RJ`0+MO_j^3#3TQJu2r;A-maLD8xfI+2Y*iDf4LsQ$9xiu?~ z?^wHEf^qlgtjdj(u_(W5sbGx1;maVPDHvI-76u2uUywf;>()=e>0le;bO0LIvs)iy z*lJTO+7gyf^)2uS-PhS_O-+RToQmc6VT>ej^y^stNkwIxUg?E|YMAAwQ}U!dC&cXL ziXKU?zT~xbh6C};rICGbdX~;8Z%L~Jdg|`senVEJo-CiDsX47Kc`;EiXWO<9o)(`4 zGj(9@c+Me=F~y(HUehcAy!tkoM&e1y#(qqCkE(0lik_U>wg8vOhGR(=gBGFSbR`mh zn-%j3VTD4 zwA1Kqw!OSgi_v0;6?=Bk4Z{l-7Fl4`ZT535OC{73{rBwpNHMPH>((4G`sh zZhr!v{zM@4Q$5?8)Jm;v$A2v$Yp9qFG7y`9j7O-zhzC+7wr3Cb8sS$O{yOFOODdL) zV2pU{=nHne51{?^kh%a$WEro~o(rKQmM!p?#>5Pt`;!{0$2jkmVzsl|Nr^UF^IHxG z8?HmZEVMY~ec%Ow6hjfg6!9hCC4xY?V;5Ipo-myV=3TmfT^@XkKME`+=_inm4h7ki z->K~a+20?)zic^zc&7h=0)T{Aa24FU_}(O|9DMW3Bf>MW=O%~8{unFxp4}B+>>_KN zU%rKs3Va&&27&OX4-o&y2ie|sN2p-=S^V<2wa2NUQ4)?0e|hgna*1R7(#R_ys3xmG zE#(ry+q=O~&t|RX@ZMD`-)0QmE*x%SBc(Yvq60JtCQ4RL(gdA(@=}0rYo5yKz36bW zkvLOosP6I?7qH!rce(}q@cH-{oM2ThKV2RZe+{{25hkc?T>=Tky12xHr0jmfH@SZi zLHPJ@^Oo^Zo%`gZk_hrbCzS+t|=O!Bt zWi|>M8mz~sD|Z>C1ZPf_Cs&R!S5E2qK+@j*UpP>;5_|+h+y{gb=zub7#QKSUabet# zFH2H0ul;zO+uc+V=W_W@_Ig-791T7J9&=5)wrBE?JEHS_A6P~VQ)u6s1)Pu|VxP(aYJV*(e<)(42R zm3AK>dr1QLbC1RMoQ|M5k+TWBjY9q+_vY=K-tUte35m4RWl51A<4O0ptqV3)KzL7U z0gpp-I1)|zvtA8V7-e-o9H)lB_Rx6;Bu7A2yE)6)SuDqWDs}~Ojfk?DFwI% z3E1(>LbbB7I(&E@B7nlulhvY=Wa1mGXD@ijD7WF^y@L1e55h)-hzoq}eWe!fh9m3V{)x^6F8?ed1z>+4;qW6A4hYYj zZCYP=c#I8+$pAIVyiY*#%!j3ySAnH`tp|=^lh{)#JimWaP_rXK40A0WcsEUj`G1}O zG?XQ~qK4F!lqauv6-BL_Up3+-l1=kVfD;D*C)yr>o9>W=%mIyATtn_OBLK+h@p)j5jRAb;m&Ok?TZH-5Q)~#UwdYFp~rEE{judWa9E)z zE>135C-xMdHYY&AZGR)tb`K}s0CK9 z1!))p^ZaUC*e50t`sL+)@`)#kJ}?C_cCMH@k{f4wh~0`OFnGQ2nzUuuu;=r4BYRcI z){G#a6Y$S(mIc6B#YS;jFcU{0`c)Raa$nG+hV(K|2|^ZWOI566zlF0N;t~$jD<_AX zjnD?HN-G>xRmHwtL3BcJX7)Q^YGfc?cS4Nj=yYl5MB(uBD?r@VTB|mIYs=au$e)e{ zLHWd!+EN*v2*(=y%G1JzyQdY&%|?~R5NPb)`S2dw1AJW8O;L=p?yVxJs=X?U#-l1O zk6xh8yyY;OTR7aF{P=kQ>y`*EFivnw%rQioA-I67WS+~hVamG4_sI)(Jo4vHS|@F@ zqrBHbxHd_Y8+?8Gfq=Z1O^Fs5moGayCHVUHY^8)^j)Aj*RB!S2-FA?4#-`puwBW`` zJ_6OQj(FGo8DotHYRKq;;$4xDn9=4rgw}5xvxhi)?n?W5{*%4%h9Tg)zlQl&fN~Z1)gL(Dn7X!P428I zwA+U-x5!cQ57g1N=2bLqAWF z!&cbvsD)dvYoqP5vaQz%rL@kv*J>0AMzWAKn~Mxi5g2GlI7qvVZo)Z5oj=#O!M&*O z`3O3)uvrjNTeremC}nW@(m%#E-sITB>j-!yBM#(=FN`~c#@XjL3e)SjR9&%QO%tUg zzGv=SLH()`ZIt?Ayym;9VG1Muq+a+7Zo+59?SuRu_`k>@S4!yS3roMnq+SDO?`C7V#2 z8vHf4&0k;{kLT)fa==7EILSu3e|ZnxtFO;1 zGqP-;Xo(>_QKcYUhsi-X72BqH#7Zb-TsiNIF>G9xOHT3XoA*qX^10+#XCU0)UO4_%A_s_vO=uDd3_Q%D{OsvLMW9wGvuuRnF52{2vH06D~7N672!bIMt@it_D}& zwjZ7gV!RzZ86*wbEB5cnMJRbEqMM{G!K)bfJjyPH^9nGnrOI9S{~!dm4~P#&b*~)h zCMwM8mR+y5i~E5*JAopwZ>F`=ORfA&IF%O8(aS<}^H6wcY1g^=lYLPtFpyvW9F z3;FCS-TGFYPr#Y$ue>}?rTYrmWr^VbUu>!eL$cEdh1e>5_UDnZ@Mu$l*KVo_NDEu^ zBn*!qVnzYv>t|<(>nt8%CoNPhN!qGP|sANRN^#+2YSSYHa>R1mss->c0f=#g@U58@? zA4sUbrA7)&KrTddS0M6pTSRaz)wqUgsT3&8-0eG|d;ULOUztdaiD3~>!10H`rRHWY z1iNu6=UaA8LUBoaH9G*;m`Mzm6d1d+A#I8sdkl*zfvbmV0}+u` zDMv=HJJm?IOwbP;f~yn|AI_J7`~+5&bPq6Iv?ILo2kk$%vIlGsI0%nf1z9Mth8cy! zWumMn=RL1O9^~bVEFJ}QVvss?tHIwci#ldC`~&KFS~DU5K5zzneq_Q91T~%-SVU4S zJ6nVI5jeqfh~*2{AY#b(R*Ny95RQBGIp^fxDK{I9nG0uHCqc-Ib;pUUh$t0-4wX*< z=RzW~;iR3xfRnW<>5Jr5O1MP)brA3+ei@H8Hjkt7yuYIpd7c-4j%U=8vn8HD#TPJo zSe+7~Db}4U3Y^4dl1)4XuKZ67f(ZP;?TYg9te>hbAr4R_0K$oq3y5m-gb?fR$UtF9 zS~S^=aDyFSE}9W2;Okj%uoG-Um^&Qo^bB#!W?|%=6+P>``bumeA2E7ti7Aj%Fr~qm z2gbOY{WTyX$!s5_0jPGPQQ0#&zQ0Zj0=_74X8|(#FMzl`&9G_zX*j$NMf?i3M;FCU z6EUr4vnUOnZd`*)Uw#6yI!hSIXr%OF5H z5QlF8$-|yjc^Y89Qfl!Er_H$@khM6&N*VKjIZ15?&DB?);muI`r;7r0{mI03v9#31 z#4O*vNqb=1b}TjLY`&ww@u^SE{4ZiO=jOP3!|6cKUV2*@kI9Aw0ASwn-OAV~0843$1_FGl7}eF6C57dJb3grW)*jtoUd zpqXvfJSCIv4G*_@XZE?> z4Lt=jTSc*hG3`qVq!PVMR2~G-1P{%amYoIg!8Odf4~nv6wnEVrBt-R5Au=g~4=X|n zHRJGVd|$>4@y#w;g!wz>+z%x?XM^xY%iw%QoqY@`vSqg0c>n_}g^lrV))+9n$zGOP zs%d&JWT2Jjxaz`_V%XtANP$#kLLlW=OG2?!Q%#ThY#Sj}*XzMsYis2HiU2OlfeC>d z8n8j-{Npr1ri$Jv2E_QqKsbc$6vedBiugD~S`_0QjTTtX(mS}j6)6e;xdh*sp5U0aMpuN}qTP=^_Qn zh~0padPWs&aXmf6b~}{7Raglc)$~p?G89N4)&a}`izf|bA)IUmFLQ8UM$T!6siQxr z=%)pPsWYXWCNdGMS3fK6cxVuhp7>mug|>DVtxGd~O8v@NFz<+l`8^#e^KS3})bovWb^ zILp4a_9#%Y*b6m$VH8#)2NL@6a9|q!@#XOXyU-oAe)RR$Auj6?p2LEp*lD!KP{%(- z@5}`S$R)Kxf@m68b}Tr7eUTO=dh2wBjlx;PuO~gbbS2~9KK1szxbz$R|Frl8NqGn= z2RDp@$u5Obk&sxp!<;h=C=ZKPZB+jk zBxrCc_gxabNnh6Gl;RR6>Yt8c$vkv>_o@KDMFW1bM-3krWm|>RG>U`VedjCz2lAB1 zg(qb_C@Z~^cR=_BmGB@f;-Is3Z=*>wR2?r({x}qymVe?YnczkKG%k?McZ2v3OVpT* z(O$vnv}*Tle9WVK_@X@%tR^Z!3?FT_3s@jb3KBVf#)4!p~AFGgmn%1fBbZe3T53$_+UX_A!@Kz63qSLeH@8(augJDJ;RA>6rNxQYkd6t(sqK=*zv4j;O#N(%*2cdD z3FjN6`owjbF%UFbCO=haP<;Y1KozVgUy(nnnoV7{_l5OYK>DKEgy%~)Rjb0meL49X z7Fg;d!~;Wh63AcY--x{1XWn^J%DQMg*;dLKxs$;db`_0so$qO!>~yPDNd-CrdN!ea zMgHt24mD%(w>*7*z-@bNFaTJlz;N0SU4@J(zDH*@!0V00y{QfFTt>Vx7y5o2Mv9*( z1J#J27gHPEI3{!^cbKr^;T8 z{knt%bS@nrExJq1{mz2x~tc$Dm+yw=~vZD|A3q>d534za^{X9e7qF29H5yu};J)vlJkKq}< zXObu*@ioXGp!F=WVG3eUtfIA$GGgv0N?d&3C47`Zo)ms*qO}A9BAEke!nh#AfQ0d_ z&_N)E>5BsoR0rPqZb)YN}b~6Ppjyev;MMis-HkWF!az%G? z#&it84hv!%_Q>bnwch!nZKxB05M=jgiFaB^M=e-sj1xR?dPYUzZ#jua`ggyCAcWY> z-L$r#a{=;JP5X}9(ZPC&PdG~h5>_8SueX($_)Qu(;()N3*ZQH(VGnkWq^C}0r)~G3_?a10y*LsFz zokU5AKsW9DUr-ylK61shLS#4@vPcteK-Ga9xvRnPq=xSD_zC=Q_%6IuM?GpL(9aDx z|8d_;^6_D4{IQ1ndMAcFz5ZaT+Ww0wWN`xP(U#^=POs(BpKm;(H(lmYp+XCb7Kaw0 z;LT945Ev3IkhP6$lQBiMgr+vAL}{8xO&IObqJBEP4Y^x&V?iGC=1lVIbH^Z!eXxr@ zz)D7Fon`z~N|Pq>Bsue&_T9d;G+d8#@k^cq~F^I8ETsZ*cGOf*gZ4ghlAzW|aZ;WA13^B!Tlr0sWA zosgXD-%zvO-*GLU@hVV(bbQ`s@f~Ux=4}(@7O)%o5EH((gYflccBC@jbLF3IgPozv zglX2IL}kL1rtn4mu~`J(MMY83Rz6gc1}cX4RB+tZO2~;3FI# z@dU(xa5J_KvL0)oSkvwz9|!QcEA$jKR@a-4^SU3O449TrO+x$1fkBU<<=E_IHnF6> zPmZ7I2E+9A_>j6og$>Nih~b2F_^@6ef|Hm-K2(>`6ag{Vpd`g35n`yW|Jme78-cSy z2Jz7V#5=~u#0eLSh3U4uM3Smk31>xEh^-Os%&5tK6hSAX83jJi%5l!MmL4E?=FerNG#3lj^;-F1VISY!4E)__J~gY zP{o~Xo!8DW{5lsBFKL~OJiQoH>yBZ+b^};UL&UUs!Hbu7Gsf<9sLAsOPD4?-3CP{Q zIDu8jLk6(U3VQPyTP{Esf)1-trW5Mi#zfpgoc-!H>F$J#8uDRwDwOaohB(_I%SuHg zGP)11((V9rRAG>80NrW}d`=G(Kh>nzPa1M?sP;UNfGQaOMG1@_D0EMIWhIn#$u2_$ zlG-ED(PU+v<1Dd?q-O#bsA)LwrwL>q#_&75H)_X4sJK{n%SGvVsWH7@1QZqq|LM`l zDhX8m%Pe5`p1qR{^wuQ&>A+{{KWhXs<4RD< z=qU6)+btESL>kZWH8w}Q%=>NJTj=b%SKV3q%jSW>r*Qv1j$bX>}sQ%KO7Il zm?7>4%Q6Nk!2^z})Kchu%6lv-7i=rS26q7)-02q?2$yNt7Y={z<^<+wy6ja-_X6P4 zoqZ1PW#`qSqD4qH&UR57+z0-hm1lRO2-*(xN-42|%wl2i^h8I{d8lS+b=v9_>2C2> zz(-(%#s*fpe18pFi+EIHHeQvxJT*^HFj2QyP0cHJw?Kg+hC?21K&4>=jmwcu-dOqEs{%c+yaQ z2z6rB>nPdwuUR*j{BvM-)_XMd^S1U|6kOQ$rR`lHO3z~*QZ71(y(42g`csRZ1M@K7 zGeZ27hWA%v`&zQExDnc@cm9?ZO?$?0mWaO7E(Js|3_MAlXFB$^4#Zpo;x~xOEbay( zq=N;ZD9RVV7`dZNzz+p@YqH@dW*ij8g053Cbd=Mo!Ad8*L<5m1c4Kk ziuca5CyQ05z7gOMecqu!vU=y93p+$+;m=;s-(45taf_P(2%vER<8q3}actBuhfk)( zf7nccmO{8zL?N5oynmJM4T?8E))e;;+HfHZHr` zdK}~!JG}R#5Bk%M5FlTSPv}Eb9qs1r0ZH{tSk@I{KB|$|16@&`0h3m7S+)$k*3QbQ zasW2`9>hwc)dVNgx46{Io zZ}aJHHNf1?!K|P;>g7(>TefcLJk%!vM`gH8V3!b= z>YS+)1nw9U(G&;7;PV4eIl{=6DT^Vw<2Elnox;u@xF5ad*9Fo|yKgq<>*?C$jaG2j z|29>K)fI^U!v?55+kQ*d2#3}*libC4>Dl4 zIo3Jvsk?)edMnpH<|*l<*0Pf{2#KedIt>~-QiB{4+KEpSjUAYOhGDpn3H_N9$lxaP ztZwagSRY~x@81bqe^3fb;|_A7{FmMBvwHN*Xu006qKo{1i!RbN__2q!Q*A;U*g-Mz zg)-3FZ`VJdognZ~WrWW^2J$ArQAr1&jl~kWhn+osG5wAlE5W&V%GI{8iMQ!5lmV~# zeb3SKZ@?7p;?7{uviY6`Oz16t0=B70`im=`D@xJa16j2eHoCtElU*~7={YUzN41sE z#Th>DvJq-#UwEpJGKx;;wfDhShgO0cM|e!Ej){RX#~>a?)c2|7Hjhh2d=)VUVJL<^Aq|>_df4DX>b9W2$_DM zTjF#j(9?Co`yor?pK<16@{h#F&F8~1PG|qQNZPX^b!L*L&?PH#W8za0c~v6I2W($Jderl%4gufl z#s;C*7APQJP46xHqw;mUyKp3}W^hjJ-Dj>h%`^XS7WAab^C^aRu1?*vh-k2df&y9E z=0p*sn0<83UL4w30FqnZ0EvXCBIMVSY9Zf?H1%IrwQybOvn~4*NKYubcyVkBZ4F$z zkqcP*S>k6!_MiTKIdGlG+pfw>o{ni`;Z7pup#g z4tDx3Kl$)-msHd1r(YpVz7`VW=fx9{ zP}U8rJ-IP)m}~5t&0Y$~Quyjflm!-eXC?_LMGCkZtNDZf0?w<{f^zp&@U@sQxcPOZ zBbfQTFDWL_>HytC*QQG_=K7ZRbL!`q{m8IjE0cz(t`V0Ee}v!C74^!Fy~-~?@}rdn zABORRmgOLz8{r!anhFgghZc>0l7EpqWKU|tG$`VM=141@!EQ$=@Zmjc zTs`)!A&yNGY6WfKa?)h>zHn!)=Jd73@T^(m_j|Z;f?avJ{EOr~O~Q2gox6dkyY@%M zBU+#=T?P8tvGG|D5JTR}XXwjgbH(uwnW%W?9<-OQU9|6H{09v#+jmnxwaQ-V;q{v% zA8srmJX7Fn@7mr*ZQ@)haPjWVN@e3K z_`+@X$k*ocx*uF^_mTqJpwpuhBX~CSu=zPE(Sy%fYz&lzZmz3xo4~-xBBvU0Ao?;I-81*Z%8Do+*}pqg>bt^{w-`V6Sj>{Znj+ z70GS2evXinf|S#9=NNoXoS;$BTW*G0!xuTSZUY45yPE+~*&a-XC+3_YPqhd*&aQ>f z$oMUq^jjA;x#?iJKrpAqa<2<21h*_lx9a}VMib;a6c$~=PJOj6XJXJ|+rc7O7PEN5uE7!4n9nllo@BI4$VW2Nf_jqnkz%cvU4O4umV z#n6oXGWOt3tuIjmX*b!!$t~94@a@QgybLpQo3icAyU`iNbY~XNAArFAn$nFJ()d-U zFaO#nxxVF-%J{UB**uRo0*+?S>=^il)1m7v-u`PDy*ln%|3E-{3U~R=QcE&zhiG_c zDnGMgf1}3h1gWz8IV0Oc7FmEt>6W?Eva;J`(!;IIny}PvD?vztz`F6su_tUO`M%K5 z%C#=nXbX})#uE!zcq2mB;hPUVU1!`9^2K303XfOIVS{mlnMqJyt}FV=$&fgoquO+N zU6!gWoL%3N1kyrhd^3!u>?l6|cIl*t4$Z$=ihyzD7FFY~U~{RaZmfyO4+$kC7+m zo+-*f-VwpUjTi_Idyl~efx)!$GpE!h+in4G1WQkoUr<#2BtxLNn*2A>a-2BL#z%QO@w0v^{s=`*I6=ew2nUj1=mvi%^U@2#Wf& zs1@q6l8WqrqGm!)Yr|*``||#A+4#du6`mR^_#?CymIr}O!8Zm?(XY$u-RGH;?HFMGIEYVuA1& z`3RlG_y0%Mo5w@-_W$E&#>g6j5|y1)2$hg(6k<{&NsACgQQ0c8&8Tdth-{@srKE*I zAW64%AvJJ+Z-|I~8`+eWv&+k8vhdJk5%jolc%e`^%_vul0~U8t)>=bU&^ z6qXW&GDP%~1{L1-nKK>IsFgDJrh>!wr3?Vu-cmi#wn`;F`$GNc_>D|>RSuC8Vh21N z|G;J1%1YxwLZDD400Ggw+FirsoXVWYtOwg-srm}6woBb!8@OIc`P$!?kH>E55zbMB z8rdpODYfVmf>cF`1;>9N>Fl(Rov!pm=okW>I(GNJoNZ6jfIunKna-h6zXZPoZ9E2PythpyYk3HRN%xhq2c?gT$?4}Ybl42kip$QiA+ab zf-!EqBXkT1OLW>C4;|irG4sMfh;hYVSD_t6!MISn-IW)w#8kgY0cI>A`yl?j@x)hc z=wMU^=%71lcELG|Q-og8R{RC9cZ%6f7a#815zaPmyWPN*LS3co#vcvJ%G+>a3sYE`9Xc&ucfU0bB}c_3*W#V7btcG|iC>LctSZUfMOK zlIUt>NBmx6Ed}w_WQARG+9fLiRjS1;g49srN1Xi&DRd|r+zz*OPLWOu>M?V>@!i49 zPLZ3Q(99%(t|l%5=+9=t$slX0Pq(K@S`^n|MKTZL_Sj+DUZY?GU8sG=*6xu)k5V3v zd-flrufs*;j-rU9;qM zyJMlz(uBh0IkV<(HkUxJ747~|gDR6xFu?QvXn`Kr|IWY-Y!UsDCEqsE#Jp*RQpnc# z8y3RX%c2lY9D*aL!VS`xgQ^u0rvl#61yjg03CBER7-#t7Z++5h_4pw{ZZ~j0n_S_g zR=eVrlZDiH4y2}EZMq2(0#uU|XHnU!+}(H*l~J&)BUDN~&$ju@&a=s$tH5L`_wLeB z944k;)JIH^T9GEFlXiNJ6JRymqtLGZc?#Mqk2XIWMuGIt#z#*kJtnk+uS;Gp}zp$(O%LOC|U4ibw%ce-6>id$j5^y?wv zp1At~Sp7Fp_z24oIbOREU!Mji-M;a|15$#ZnBpa^h+HS&4TCU-ul0{^n1aPzkSi1i zuGcMSC@(3Ac6tdQ&TkMI|5n7(6P4(qUTCr)vt5F&iIj9_%tlb|fQ{DyVu!X(gn<3c zCN6?RwFjgCJ2EfV&6mjcfgKQ^rpUedLTsEu8z7=q;WsYb>)E}8qeLhxjhj9K**-Ti z9Z2A=gg+}6%r9HXF!Z~du|jPz&{zgWHpcE+j@p0WhyHpkA6`@q{wXl6g6rL5Z|j~G zbBS~X7QXr3Pq0$@mUH1Snk^1WJ0Fx2nTyCGkWKok$bJZV0*W?kjT|mkUpK<)_!_K^OoTjMc+CWc^~{ZP8vgm`f&=ppzKtw}cxwV^gppu}^df1|va7Q?@=(076-( z4KJVmu?l(aQwmQ*y_mke>YLW^^Rsj@diLY$uUBHL3yGMwNwb7OR3VD%%4tDW(nC984jBWCd90yY(GEdE8s(j>(uPfknLwh!i6*LX}@vvrRCG`c?EdB8uYU zqgsI4=akCeC+&iMNpVu56Fj2xZQHs6SdWssIF#Q@u@f9kab0&y*PlG+PynjHy`}GT zg%aTjRs2+7CknhTQKI%YZhFq1quSM{u24Oy2As@4g(bpbi%y1i0^TwI)%1Whpa~qE zX4MD(PgFEK@jZBPXkFd437aL6#COs$WrNT#U=er-X1FX{{v9!0AS$HR{!_u;zldwY zKko!`w2u@($c&k_3uLFE0Z*2vms?uw1A{AqZw^jwg$|D7jAY20j`s*l##=4Ne_K5) zOtu6_kziEF@vPsS7+@UwqOW6>OUwF$j{r4=nOSf-{UC(rEKidie7IUn>5`UoNJ9k) zxJXXEBQifng+Pte3mPQ76pVlZ<`jnI##F1*YFA*)ZCEncvgF-%)0dUXV*pXTT^L`n zL=?A5Vty#{R9W4K)m$`me~*_(&a88M?Eon$P-YdVG}#Gq4=hh#w=`>8f`9}}zhv;~ za?I=Gb3v$Ln?-SDTBow0J5Tt&xPlw|%`*VTyVee1Oh<-&;mA|;$ zoPl;^f7Q~}km#_#HT2|!;LEqORn%~KJaM)r#x_{PstSGOiZ!zX2c}^!ea3+HSWrwE z=6SJ!7sNDPdbVr#vnUf}hr&g@7_Yj&=sY=q(v^BwLKQm|oSB}172GpPlj?a3GqX#B zJko4zRRttIY>Fv#2b#A<_DLx=T@eUj+f}!u?p)hmN)u4(Jp(`9j58ze{&~rV?WVbP z%A=|J96mQjtD037%>=yk3lkF5EOIYwcE;uQ5J6wRfI^P3{9U$(b>BlcJF$2O;>-{+a1l4;FSlb z_LRpoy$L%S<&ATf#SE z;L?-lQlUDX_s&jz;Q1Lr@5>p_RPPReGnBNxgpD!5R#3)#thAI3ufgc^L)u%Rr+Hlb zT(pLDt%wP7<%z(utq=l%1M78jveI@T$dF#su(&>JkE(#=f4;D54l*%(-^(nfbCUQe)FV9non9F%K+KZ(4_`uOciy82CO)OolxisUd0m^cqueIRnY< z;BgA4S1&XC3uUP?U$}4o&r|0VCC7fkuMZBa|2n4asR>*5`zBaOJPWT$bNn(W_CK%L$c2AsfSlwq?A8Q6 zhK&USSV=^-4vZ^5<}pnAOb&IKseHNxv_!|B{g@d^&w%{?x;i3iSo)+vt^VnMmS!v) zM)W)05vXqzH5^hOWWw~$#&7HoIw}}DD3bCQgc=I8Rv|G5fM8O^58?--_-*>%Nwk)j zIfvfok0n05!w%tZ=-dpffezI7(+}yX5XhwYk#0@KW%PkR;%#t|P6Ze_K*N6ns%jOt zNeW(bRsv0BK7ah~9U~UBAVA_L34F+;14x6-;I|o=%>?sS3@dpRv|GKxilsa#7N#@! z!RX~>&JX&r{A^^>S~n_hPKkPR_(~~g>SuPj5Kx6VI%8BOa(Iit&xSMU8B#EY-Wr?9 zOaRPw0PEbVSW@Wk{8kkVn34;D1pV2mUXnXWp{V-M9+d}|qfb6F`!a9JQO_-wlH?zf z4Sn0F4-q-tzkaJ?1fV0+cJBF$f0g6*DL6U3y`Tr`1wzCiwY#muw7Q-Ki)uN}{MoCWP%tQ@~J4}tyr1^_bV9PScNKQHK=BZFV!`0gRe?mVxhcA4hW5?p0B<5oK+?vG^NM%B%NDOvu0FMq#)u&zt_-g&2 z7?z%~p&32OAUSQV{<=pc_j2^<;)`8$zxCEomh=rvMiliShS?ahdYI1grE-M&+qkK_ zD=5Hexi<&8qb4hgtgj81OD(tfX3EJSqy9KFcxpeBerG`apI4!#93xpEFT??vLt>kf zac28;86CpMu=BWIe$NOT~+Es!y#+$ zvm2s*c`J9Gy*ERvLSI<9<=j*O=0xUG>7rYh^R4bGsvz;j-SBO|P^OQ1>G9_akF}D; zlRmB@k3c5!s|Vz3OMZ8M*n0AMTiSt5ZpRy+R1|ckna&w`UQjklt9f&0Z~=->XImVA zLXizO2h=<|wM~w>%}3q1!E{oSq7LBPwQ~93p-peDq-W?wCm8NOKgTSz-P)|cm}S5&HBsx#C@Ba5;hzi#Yw@y-kC~)@u4}Rf?KV0$lPjv}} zcFpNy=YJfsS||9&!-JFjw=@NU96ESzU^gme0_oNy?})II`>Sy>bUCHs_(m&)vn^&isCl+`F~qu8elAO z)-ZP7`gYE2H(1)5tKalz&NJbcutAU&&JFV~$Jrai31^j>vZ|HV1f}#C1<5>F8 zS1RWIzM%b{@2dAF^$+i4p>TC8-weiLAPN+Aa#(bxXo9%Vz2NEkgF&s#_>V?YPye^_ z`` z-h3Cv^m6K%28I$e2i=cFdhZN?JTWhqJC{Q9mg0Vg|FiPEWDl&K)_;Bz_K`jH7W7QX^d$WQF*iF@#4_P*D36w9&iJr2E{w?LRFapwZIIVHGH ziTp*5>T{=;(E}z{1VL4;_H`BAXA~&zpeWX!gN9m|AfcJ{`!XVz48O^&+0Gd|w;udP zzU|DbGTS|7qZoEoDZEH9Kb0%DZvCaWDzuJ=8jZz}pqPn+I!c_+*~>m>BQqN2560*< z$6sx_y8WRqj$SugYGip+et$;iJ!SQAx=HgVSh_3e)MOFHuXD@sg>Yi_p8Sh`{lP=5 zo?AFv1h;KqR`Yj!8Pjji3lr+qae2|a1GmlxE*su%_V)K0Xu0(#2LcO!*k11w*V12$ z;f~i{kI#9PzvFLZ3pz@d558HeK2BTvk*JvS^J8L^_?q4q z);;4Z!DsV!P*M>F>FiF*{|p_nUgy;pDh?J8vwO;emgOAAcxrgDXiSDS5ag?0l*jj< z(khZ3-)>eiwPwpb6T9meeL)!2C-K@z9fF`0j|t@;^f5+dx86R3ZM{bnx9Hm1O$s)N zk$OvZR0u2`Z^QP8V%{8sEhW~_xbZMad2jtz&0+ekxmp;9`ae;_f%-ltk5E%)VT*a6 zRbMnpCLPnalu+1TafJ4M0xNV8g}U4Mjk{le6MA|0y0rk)is}M%Z9tUU22SvIAh7`w zTysd{Pztfkk=jD^*!lA+rBcqb)Fx`A5iaU2tl&XdL1D)U@pLEXdu%#YB*ol1N?4ti zHBQcU#_%UqiQ1)J^u-ovU@-7l?`YzYFvA2#tM0mEh3?CpyEh_NUuVajD16t zyg$C*5du9R=K~6mCJ`W+dFI$9WZZauO)p2H)*SKpHVsIu2CxfJvi2>; zcit#57RP7DpSwMF-VBm|4V5d=tRgX7RM9%KQ0JRo6d<)RmiIPWe2zh6tmswP`fs^) zwy};#jk|NXMqCSfwIR3QZ#W2`(%sJ>qvk=53CYoLmQt9q|2Gm$sB;rEuBqGJA1OUM zoyl4Wy-HYn0J6L=cad8o)R!Ea^;`rSMg9hYo3?Fw6B9dUq75a-MSb56n8~AAsS(JP zZ!1khPu}!GRpsj+jvl`N1tDD8m1myJCI3c-c<9U-1Vg`xJO~}5_wvPXYh^=Boo^|V z3Tp}|lH!9m4Ipa_$p;b8fjUd=zc4iO7vr)M&Xs0_m$fgY@+hB9%K~4*9$p0d)m2bO ze5JH`W0fnIKdcW!oO#^g1YceSQ4u->{>u@>tLi!fky)o&$h(=he?Fe_6?}O~iSf(F zV&(P~*5h>BW{3e1H%8*7#_%L1#>W97b0@jHtliES^w6w5oldI7QL+?I(Pl$DaN>~d5nXx z;CO1E+S?3E2PLq~)-?ygkHAO1m&hOYmj7?;2XM!$D^f0l9K4P{n}mgb{CoYH6RJ8o ztydc6dNqA)`CG?=Gd~EIbi`UM)eyzGF^+i?&TOdyW~mFH_^Gye(D}clDVFQ@V2Tvy z7rQIaq8Xx`kC;AO-_{k%VI2e6X@bIy^mupEX%{u0=KDUGu~r6lS*7GOeppy{&I&Ly zjOTz=9~jC|qWXznRbrfjg!1`cE!Hzyjzw6l{%>X)TK(UEGi9Uy3f9D6bbn0gT-s`< z8%$Msh!^8WidX7S;)n2jh_n1-QCtSyOAKcPQc(Xlf0*Q|5CSBjo(I-u!R0GJgzTkL z|6QdQRrUMbUO|q0dQ%+d^4)*Mjbm$R}RUcz(7|E0Bq-bAYY@)OsM<+2>}CV zzPBgeD~kBHE(Y+@l2orJrdtV7XXq_V8IETas%7OCYo`oi)+h&v#YN!Qpp7drXFS>6 z?r-q7px+(rIy+bo1uU#I2A5s@ASe01FgGMbouFkhbkm-9yZ8Q2@Q1vuhDQ3D3L+zA z(uz8^rc24VmE5r0Gbd;yOrXnQKAEBfa3@T7fcF$#QYv^00)VZPYehpSc@?^8we}o{ zlX0~o_I<`xSfI8xF(WXO-DX1>wJ`XN?4rw@}_RLD*${$}UaXL=oM(=SDMIxZj1Ji#jAcrH7nYG`r z#ewodj>F5Bf9j(j`a;>)=*2j_ZN}vf!~Hq`2Eyt;9UH1_(yjq1OUO(1M0lI3FZ2j-fU9)L59v&OiQ>5$;d!jg?Fo{Svf5t5FCZbb?)* zJN=Q!?2BztV$7)CWtG0MO~Lr4E5>aoHD5N4(+@~gQEbZTc4s3HrIl_G23PCng4Y3f zbLZK1A-x9x!)WwuI=UBkQ5QyE^&Nrw?@fsRKK41G9-xq=#VyO%CEo`{_eioDj%M!3x=>I zfOPFiFX{1t-|+3E@?UuK=0miGN04hW0=JnJrEyWw{Bg-jMvAA}cg<5LN1c5BQdrIZ z#+bxj9Jbu`11@IUjU|RKfL(UzRlVB4XT ze|(WaxL$KiRqkgCr3^Al(19!_Y7b=E(4Xm7LCO$y5+k;Fu6B#=OSzW`-7p{zRv-_) zPr!|km?8aF}+3hm)QG92YaI+jctX&5IrvTUGf{Y$)TK6)s9v!SMhU=HIpEC~2 z4>o14mG$El2sTA(Ct?xS!l*x7^)oo}|3+BF8QNe;bBHcqdHVmb?#cbS*NqZ%mYS~z z`KLoq7B#KULt%9a#DE%VTEo4TV03T2nr`FK5jUTA$FP0JH6F9oD*|0z1Yf2b5?H0_ zD|K|_5Zk`uu?ZN0U! z_mL>>F;mnHU=@to!Vv*s4;TQr9y)L@1BXXz^a85NSifPTL4h6I>+m_S3~FkXB{N?E zS<3ue_(wqaIS5;4e9{HB`Okl9Y}iFiju+oTqb)BY)QT?~3Oag7nGu-NB5VCOFsiRs zs@m%Ruwl^FuJ1b}g^=*_R?=SYJQ@7o>c9j>)1HgB zyN9LI9ifwu{Shlb6QO2#MWhxq~IG!U^I!6%5}(sbi>=bq8!8@s;4Iaun#kvh7NPwX34Rjbp2f!D)cF&sNIO%9~;C`cs&ZY2=d@c3PpN$YZjUT}X7rY`dlWX$yc znw(7=fzWapI=KzQnJ(6!o0K_aDk!^dZ#)pSTif+jQtQXga$bPApM z=);jZ5c*?*GoeGMnV0=RrZucRRYBjx>tx`A3OuY)#tp2w7mh}&kj)SKoAvbbf;uO! z?+RItUow0xc*6StuO4D--+qY!o}Isy}s;ts5aM5X~eJUZoLOq@dGv=a4hHJD<* z5q{dZSN{bv_(Vj#pFm7Q<$C;MwL|Qizm~QCFx~xQyJoCOZ$`sYD}}q>PwRZjb<=E< zAeMP?qVfM>xu2}Il2xT6={KBdDIstxY-`5IWXN zUiWV&Oiy5R_=2X9Y$ug9Ee=ZSCaza!>dWBMYWrq7uqp>25`btLn^@ydwz?+v?-?2V z?yVwD=rAO!JEABUU1hQ|cY+_OZ14Hb-Ef`qemxp+ZSK?Z;r!gDkJ}&ayJBx+7>#~^ zTm<>LzxR^t-P;1x3$h;-xzQgveY$^C28?jNM6@8$uJiY81sCwNi~+F=78qJZ@bIsz1CO! zgtPM~p6kaCR~-M>zpRCpQI}kUfaiZS`ez6%P6%*!$YCfF=sn}dg!593GFRw>OV2nQ ztTF6uB&}1J`r>gJuBP(z%KW{I^Uz%(^r5#$SK~%w1agl)Gg9Zy9fSK0kyLE24Z(34 zYtihZMQO^*=eY=<5R6LztHaB1AcuIrXoFuQ=7&C}L{c?Z$rto$%n=!whqoqG>#vvC z2%J5LVkU%Ta8hoM($p1WqN}wurA!d@#mQGU5Nb>~#XC84EYH)Zf&DZR!uY+-;VqS< z@q?$ggdX#auS#%%%oS^EN)?JhSR4JYpSgGRQZD<9!YvvF+zp0>C#$!x*x}l8U|Bb& zv?v*im5Bq_(5Wi40b1^nKun$XTST(a8yOAcqQZmKTgGLo)Ig6JuEh5J9NnqJXin@Gxzz-k6xXWYJ&@=JZw=$+ zFPGde%HsR`gI+y`rtiPaMYwbtyp!sVb!pX~;c3zLoPO0eaZSV+O_z z%9H@UhqNowzBTPcMfL6kC>LRaFF6KVaSv1R@%4}rtleX!EMnL`rethYrhTLj1x$tj z;)H!fKo08&T(;i|FT&rPgZ*D0d=B2dXuO_(Uaoi9+vEhs4%{AD{Fl@4^|`X=PvH(s zI7$6bWJiWndP$;&!kSCIR1l57F2?yzmZm~lA5%JKVb;1rQwj*O=^WW~`+n*+fQkK0 zydInOU1Be2`jhA!rnk1iRWR=1SOZpzFoU5{OPpc&A#j6Oc?D&>fAw=>x@H7?SN;d^ z-o&}WR;E|OR`QKItu(y4mT)%Pgqju-3uyH?Y@5>oSLO2Y(0(P!?_xOL=@5+R7rWw# z3J8%Hb@%Pzf^`=J6fEJ_aG6+e7>OUnhaO1(R1<6>f}L z?d@Wnqw9?^;2?q(b@?Wd=T6r_8a@Z4)*_@Q7A`+ zW3w?j!HW0KbhxF%D`9d2HpvIrBxM!36W3Yh5=8_0qYfnHm*yiLB?Ay|V10N%F9XYq zanaDtDk$rS+|_H_r|a${C}C7b{E)Ii20-a?Grff$E?&|gWF<#Ern2GqhCiS0~Y%knIi8zY^lE4qLaR-3M;_Rkz(s;wu z9207W1PXIe#4h4Zw}dvdV&FYcnUlD5_C4hzJ@bPSBVBLpl$&52mi+wwH;svyVIzAB zoA+NQ;Hpqh?A}^Et~xhl>YQNQwh20!muW{ zq}|Pg3jHZWnDBN?r1KhiVG$%Sm-4+=Q2MZzlNr3{#Abqb9j}KK%sHZj{Vr2y4~GIQ zA3Mz1DjQ3q(CC~OyCaZn0M2!){)S!!L~t>-wA&%01?-*H5?nzW?LJB`{r&)vLB4!K zrSm({8SeZ0w(bL9%ZZAZ*^jf=8mAjK^ZR0q9004|3%73z#`-Npqx*X^Ozbja!C1MW z-M~84#=rU1r>p{+h9JU<#K_x$eWqJ+aP%e?7KTSK&1>dlxwhQmkr69uG~0iD@y|L- zlY0vSR2|IhZoS6PpfUai_AhKo2HfdD&mhv#k51CX;T z*sU)XbDyfKjxYC$*_^(U)2-c0>GJ(zVm$CihHKlFSw&1A$mq$vsRt-!$jJe3GTaZ6 z3GcVvmwZ0D>`U+f3i*pQ>${p1UeyF~G9g~g-n{ThVOuC#9=ok`Zgz@qKCSN!1&P`N z=pdlGNwal%9;)ujwWH*#K6CQG*fJDAQiKlO2vKJHeA1lj&WQC+VU^@ea8$#~UOX$*Q!V^8L- zL0$W5(Y3=??%&j_WUq6*x>=?BfmI*d8fmDF*-!XVvxL8p7$r+}Igd_(&`|D*;Z#GE zqm{tHx&aHBpXw&~l6>7-FlyiSPJtTJblAjLU5Ho$FeN0mDguFAq?r+6^~o6|b+rfE zGVcZ&O-X~tE3liGcdI~hHSCT+&F&uH8rr&f{6pr^1y5061`fu~=^_|Idrgti5+*U7 zQOb9G?Rz$j-G0Y}x+i{HB0!4ZmKzykB<0;Rbmo2)T4|VdcwujI_otLG@@8OOKg3kw zP|0ST0D4@zT?O=(0Pikp)Rpwxw_VsmW4!^j^sFd6r5l zw}SG_HQPs>ae%Bq{sye_SaBX%|F-}&^)Wz@Xi<)YNbO?lPs7z@3c;$b^Aw@>E%mOj zW^c%IdtC(Kk@s*}9NbKxEf8SZtP+32ZTxjnrNWS7;W&D~ft{QY?oqOmxlV7JP!kW!Yj`Ur{QbbM1h=0KMaIAmWiISb7TKd4=gMeo+Tcz2>e#NihnOV%iNdx` zeiuoOK^{}D+M+p(Y7EC=&-`$B0F< zQ=zHaM;&QQR4jM$sG=N&sqOvD_Bx*drQ6c@u0()g05cwl`Xm{!S_Nuaa2KlL*rmmk z51yPE)q?Bl$sNM474Y!=zZ zc{EVGpdJ!Su{Qq%llR5O6#zK8l(ld*UVl87@|iaH@C3+*;XBxjEg&fsQrzpMo3EEG zv*Tpms7a;7!|iz8WY7={0a$0ItO-(ajXl;wX_$$yzEF5k9nc>L3wv!p{8h2)G0W?h z{v6vH=7+>$Ho^+)9hDtCd+S_yh8pzS9$)hYev-=eDu?lGIR;-fgz+dr+wcmM-^dZp z9}`&kAf$~z1ovF)>Hgxc!Xe3cju-jQRluCm;c_1=PYQygb?Oxe z!QG0L3sT_k=WpfOPL#|EPlD^t;ENCC39O?tHd<(kfx7SOcxl+E#;ff19_+{vbkZSvbS$I{#>31KZj^$n%ayX0jj}EvsgnHg16P z_A6Y)pdp>kLW<;PtR*Vs#mVb%)ao7AXw{O&hBDmD;?mc3iMH;Ac@rZZ_BQa8CQ~|0 z&d1L{in-z--lBO|pxqc%bqy^~LAGv=E*eaVU~OeuVV{d`Vv#-_W7EYdTDzVraG9H+LC_dWcgZMn~KcP)XvKWbcr5&d+=a>{*(Ha6Y1$==bR z{O-?$7H;`2dt0B%Vm?6`_?ZOjJkyu9ZJsh^WH*+es&^@KDcR%Zej%3PJ*XovgyhTbaH(!H1H_OF~=*f55Jr8A%uW zz5IoAB~1e2-tDGp9}`MnavAMy?jgPM5F%y`%$}dFLrz_* zIrO=afT8+AkK5B1s3{ZDVP$g6y$-*U*=?-fh!cNyn3q6YhNhfRxW&GLIJ2#>9bYMD7-F%{|Iw%@a=DoAAU;3k9p$`V zImKm{5HU~wq|nQFwab)_7lNckW#1z2$|oW5x7vDbBURVjw8674P?L1ogMKpHoV>;# zO%*1OwI|($UOr#hL(*M~qsn3PF%_|15uc%Hy9@D>_~N|?<%lig6yKX0a#1s$o(^Laj8bF#5fGPOFMGmMiUaxSwE}Qf#SG_f79d2Iv=TFBXzTpr$^avJ?=|arh2<+ce}&248Kw0} zhlva`wD6X~s7|37la4FnFOgIHhBiFo`lw~?lSbk{>)P(3jyVhM4O)a=GX3(sW1vIC zz0mJ>;J{!eN5#nf2>$u=3Kq>`7u9QnChi8>CjONBN-b+W_UQIuN#{N$Q<$}IOvpQP zB&5ZrY{V&D=4)voh;6<1U`PFA>V%XUW73S9D^J>cQYfzIyIV5i35WNb5K9c^|M}=* zN_C3rnjCZP1^v{;EaGK7Tp5z~B#?f5NZaAsFUOLK)mI~bJTaL8DF_eRikE{%^J?y9-n_U32EKHPCkB^ZN2*zk{bC=GM%_I z61}nkr+Plg6S0V=mY>H_KQU&)P~=y3$#$*U8FunXkb_e1O-7t@m$5re%u!_G%^?_| zRIJzg+lX$}+ba|qx)Ec6c^ip;`_QfQrD~SPa4MoyRUOtX&~^XWcO^a}KBkXK9J{ZFOA~rovYa0!7btTC*=xNQrwJ)$Eu`TT$;%V&2@y@$ISdNn ztbM7|nO+U9r;ae{{;QiNEYpe4nrFq_x3 z4Tvf^b(I@_3odwhVe!aC0X&~inrYFu# zh)+eF__8ly&nLr4KlLWl%B_ZMo=zCH2QfO^$lJ zBvU*LQ#M(5HQ}2Z9_^y~i@C#h)1C*?N3v68pY+7DD09nxowdG#_AAM5z&*|-9NcB{ z_xKUY>Ya7>TO#Bat}yM}o(~8Ck^!QHnIj8N9}c*uyIs}IEqGn`xP;q3vhW6gsqUe>`m1 z)~ad@y1=?H`1SNl?ANCs5ZD`8tG&Hi=j|R%pP(%gB8pd)Q--E?hWU@)e?>SLV4s(- z!_I^oVC0x97@I(;cnEm$ttKBnI3gXE>>`K?vAq~SK?0YSBsx{@s1ZdiKfFb|zf}ju z7@rJb3mC{U`$R`YS(Z#KyxQx_*nU`kf;}QL%bw17%5~6!mMao^-{FFmX}|ItFuR~F zAAvTF%f4XKYo>2-PJ~ro@Ly#t@Sf69CrA+rmMRpihqH7V&SXX+$Sw`HZF`I*_3Vjz z%kPMyN0J3sl>X{-h12)j&XRhAAI;Aou%%z}gI>G+32z*qpZg{m`CezFrzg#&yc<1` z%j~}PN!F5Ddq(>R{+t0v{j6v^0XwWGu@5+`-$m`_>pCzM`r}wz*8Qv=$|P0R$%tJp z>D+N4GZ|Tg>XL<6XP9_wQRGDs^1icY*5GP4>*7mGMr;V zI%kT_^_SQml6$#uRE4Ps>}?ES)_XI8m-%GN{o^itb^S7e_bM$-wo_Ws)W? zx4_6#*X;T$n2N==N0#xzb~BQU#%^NF6|~898JGDbQxjK(ex;Q}_Qn@?Y>!kkUYUeY z&VclG1#eDPU78K@^p3tAUvZi1(nFfk6AAVHWt)Wbi7dPbjA4isOY~?*1&asp!wg#Q zSpSI6*!TGn3|-%vuJE<9V_1EKkz_0%z}Mb7;E!uz)+0^k;@x+<5tzj5 z!InbRtc`YwNCbCac{plY&Y}hWp#PC{o@5UsBj#tv3f^ns^`;$MVN?>q!pW+MYeC7= zkWr1kAX(0xVQ<{qny&CO*|g1{Mk_yE>1t}_YT<5#p8P7QXf;o|s>XQ#SoA&!ddE+8 zOM&VsxsRGS(Spli?P$^pK7Ty{v86RP_6h|MU^J z`J>vn0|BG3Vf!uR0zM|GwtiTPZNb;a@@1+V5+$P4GI_&$%6m!YRGL=lz5kh?z#5f55 z76COi1`R(5p69;ThuQnJ$R3w?I?jigai2arApagd=^tT~oMUWp^u|H_@zXBjpI)Dv zEFc^_`mVu5U*;ClT?x-t9{#fto_+92GF^dotz0sFWTDwZ`s40AY@mv+Qh5c-Ts8Zp z!(v7!zPvFhUZ-xkR!IvaW`{PqN|k)L4*anbtmK+UU&K*awl?DhxRalbtmDw`$#VzK zYFaG}?$F)1j`Qx7wbn|XzMJ&g@3Ai#u5M?%CLPghk;lD^)-|21{Sr+M(suBU4}6CMTMxc_tD;X;z<1-{FeHte=kh1B9O6Hl z!v2i$d1VFC&z&58zU0`G#7^K3Cs@9LYN16O%Vz)?-iQL!G6&sg6aaX>DBZmm@lFrRJpcL{K3(;+`$9GDFDw62Mud@LZjabzVC=w$dx>TQa}U z-{dhKYTYx*C=Fio`ez@wrzx+p%Fk3i&v?6ENXMb3p^?;_&huLLueDwr zpRqHbU%i;9TmexFxCS8F1rPo-ea3!}!ew7{(($76Rdnfa`~$9{8H@f7U&0&HjZ3TZ zuBc||%FljS_e&wNZ$1ezT$*})XAfm??$_cY_?13vM^tT0EKY2ptb+v5P10}a%aTk_ zh8@_T{ns2@jTFhv`)-Vxh}u(0DiL0MUi(We_eic$;gCoqj(T_S{jDo^PahnKJUp3@ zMOk+%weP*c%K6VFXR2icY`J~-&fVMYUg6fsFI->jlA|9`+07y~$Fsz}^;w;mNk$ms zu?y)VA@QH__tvYDudhEWuDD20H&uvrf_boY{($?5{s-SDjyRxSC%%2Xs5d2dpjdk$ zU*NURD#ovwIfd^H{fXR@UuaooJtQr7$d0+(K+1UEwtG9_T?sb$ExV$e-bpf}a@YUe zuzInI59w!x;<)>Be;a7ukLW>V=8~J6nKU<0@H+SQ!Be;1Za_pw#hiuW_PMPBo8W2G z*WDtiIAN<>HQOmh)DMi{s-0H^GmV3QMf4Zu(zXT!-c;2)uv4gUwt(-}-N*|KUOo$h z+Ak^R)h8yB5UD8 zsSjHgY}KguNi?xV=tdCWqJR!~dDpFQoRJOwxrWH^vfRq4%)v;sDfIjsLXF^)uy>!i z*S8Njd7yfa`+7(|8H9j73Rh|TwFpF(8H-p;RLLIU>k<*qI%A*SL{u$%<=X@Jm1QFe zVkQ(X8P4Tohl?_tSO__^aqaI?k$CC8uNLv2mp_zD@4oDaZfEN5;3#XY!L{8B!;Dtt zb~Zge@JF|#Gsk^5$-|(OPI73po|WZh<`UxaH#Y2!&p05Ph?H)d3Bc3J4sDi$f(6K`?&D&~eHVuE@_Prkt>_&8&aq=OzoN!ANkvho;qIX(g|d#EKQbJ@;-%_iARmgSF1fEK z@B4W@5mDME7AzfL**c&2#B7xO9>rA4x$rM{N=%0=goumK1kL{TF@CSk0yvqR2oo&m z)?nyiL$9~Jt(qnEuWt9Hc_duim%|zJQYiaF*~orVNDvJB;`%ZW_2x%Uu01LeX-JP& zD&fas6d3=igAgcfeki79{5!XPHHYR#nfLYRKv^wkv~cnEbLHMwQ8%yCZI^rK!D2qT zk40Vg;e!_!3d56&umIuidN?6MTZFzHot}AdqKzDh#w0s`)cV!2A74RSH1@lDXtC38 z+UhO4A9?oZEOV{bIgGd1{2qMR&xT+}q!=I8m)W23v!W2WPC?Tf!F!e%_(m^lQZtq* zYwi}gY(KZ*Y^OWRNj$Ph#uEEBM+wtN8QFQ@^`GDOln^ioNrmtvzNNi*qS5lPHxI96#sMil*teLVaa%$msF>@5p#SjT%q8|<4ZOUB#!-kG+|eFSED z!|3c8fXaym9qH`L;pmqTWcG}WE$(h1sZ3seM>)E3ptoP<;~h~qe6XA)lGVanf&->P zjZwi;_;Dt+bYdAeD_XSQ-DgXRXqLv`3Wcgl}myA-JlzBBIh zWq4Q*9#(zjAk_H8VS_AJ`?OS*^gB-rp|~qt;v(C5ef=SErv;~zL64hW`#g!UZQcvZ zF6Ra@S@YhVSkSWVAY=Z1w)w-hfJDRwKTUH0o-OG5TlW0HDH36hIjnP=?A+8u1)Qyy5U8Gi$! zt^!vy|f=YHfQ`ZRK?D zXXn*kItRg50vr2+_hV5kjOleg#s~z(J2p#`=1Tq4#JS`MC^e4p&s7Ir=3m(K$LW#` z=ULCoWtna!so+QQ*JHb~6Ps9_&Ag>9qsUskp0pKbi`n?(u3&@QT!?}N}rXn z>1eHi6(@LicU*AR1obe+nbzTCD#VTJ`PFLRT(nc$NWrhsgRwFni*D(#?W^x=J6?|b zENSc^D}s>Y55)PzFs2d_2;yh89E0ZIgs&>6JV=pL6k9g_(`$04EoY+Zjn}}8e#n83 zJ=zB>BU<253Erdo$wE4^+@QQJFZyAj#(InFlN;!UGg96R@{Y&%OlGG;dM)^X8=Ddw@&2Vx?zui$tO z-{zgaU7&F!xs=e`Mn}r+xrdIAmkraRN_7P1?qu1|TZ%1QR(Mn?k+pq`Xys2v9Gs=a z?r@g&;UKcM#?36r9k*eVD(}9qe8?irotsn0+eHH8*4 zPX@Lusr)$J%8jarx5ssEJ?twFyu4kAbrf`96_z{6at^&UkyDzFa69RXP>PeK+dAWqE5<5P+aHa zs<<*+OO_2ObTXau%y)Nn{(p5`XIPWlvi|asjYcui;E@)Ig{YKBXi}spqC!-P5owwL z3L*+9;0C0G!xoN;4KNfDaElv>1#DMDglI&MAVoK2+c2Pr8&sl*1dYj=^>NRS`{O&%YV25@5*eoOvpD_(xdKsnqb^`T}bm;n0BN9ben1Ynyi*OOf;qLpf^ z!T{}GzkXSszN_Xqzp>}S*Im)_Y8~2|B*ybw(U=Q)5_NcMkT;)1&52YQJB)Tn%kPK! z@3;^AI){B(&UOv<{v9KKJrInkdcXV0%O1%1=7vYV*j?v(Kp~arZio$#(A@$kYB3aM zRdm4!^Je15%66($EkCIWGhi@=kNAyLJ3ydlJnCpPuxH0+OA}J)+t8d7nT->##Nz4w-L=S7ExQt=Rx}S*mpT91(>t~qe7tM%e|O)TIO^dP zfo61GNS=cJbLutqUh84?7X#bq)bv57s&D_zm{+xNv7vHjb=_}j-Lrj-Ss*pcD@ts$ z)5Dol8Z_&*1@JdAQE7SL$*!TXI|YE7q=YGkIiUeLvT0)14Q-ivs|+cqeT6DTi9eQ)h?Pu9pqmH51B* zFMd|;l2@D4*56|EhMFlDxl2i<8qq=c+AhMYS3(A28#3DZ;_Ln>RA3q#IAdJq7M#N> zTZ8t=_>lq0=W&w|bdQ^sy&m^@KR)mNi3|1<6|OL(0KLtP#I6ix$2b{-Y9GP5I7 z8AJUSCnlia5vWawX%ZLWTC2UV$cn^sfv68W!6)QO;ZjnX=7#`$ZPRG~irfl)ZUJ^D z{lUk?(*SU7XIiS^H{Lpxn%542#PgxdeG)Ociej#(uvX)z;Z3)<16Yhd z-sv?qQ5D4a)ZYoYPRep2Zvom@U)HKq*54ZEwdaEq^FZG#(CyG!=Vw(0j8CCmP~`_z z=OR^i&WkDCf2cLvWm@d?)mEgme{hA(o#xAL023LZ3(82SGRg6jJF7$kZ4! z6*FTm4y6v~CP!3$+fxg{QeFo24<3iucgI!oyjV|9Dsx}r~4X@lt^VaH$u zD?87}1Jh=?G8OYg*ts2k;X9{f*Za?yu8IUUfyuQ**wbcWT+KncjD^qQ3h&w2+S(Mj zZM~?Ot%ggTIHwkBkL-4&jI5R=B+MCOR42bKzC2M>l?1%x2Iv7amIfQ1B#wwfD`z|m z+E?G+o(tde*Ws?;Wo4p#Yy>Nnf|*b<nj@-s(rZ)-U@ z(Xe(qZ1(_dH|J3yWu|bAPINK}DwF(kZ>FKx(?ZmU^KFC6*bh$;FKGh~pH1 zozA+kgcIk9@2aAwEJ=VYizT!sxDXX$N?XDiGKaaT-OU@Ib=~4DmgEk&{2D@IvyjF* zuF@sDcuuqx_FAgx;B@@8gqjMh!kQeEKA*y4+q+^4&uc0|>M;$Xb+ z@X%eUx1m%$WSP}Qchx68NQ?dO!h`6;Quq+A1(RORsQ-;6bZ90vj#^0(7>cLR+-_;9 zCd@b~B5V>$tpjkQU#BD%9^zu7-l>U8nzt+XuX5cYDCHYaX5t~~3?lpa;)Mr>q;5XW zu(Th;fr}-GkP`K)u97(#UB|L3f;H7Cd#Pox+auV`=m?a=mSv1v)(V!E=$%gkIJZ;` zZj{Lb@bhs%bRa znZw9cD$cDFVHPtpXwY1K)wys@LS~;!qdqkR>@&RtP>?M^>xe{4N#EtZy4zZ5Ar$ZF zV=X=(!xin-58MC<+b~;jk8Q|3B3THGIA$cM8Bg)Yd6ygP#i?4VrX3OvP_k5i{Cppw z-{$XwrJ-+X$ccJ(Q{|?T@U9=-?qlsfA43%8t247KZn?`+C4e`b-e^(df*iW66=Oc2 z3w9UhohfdY@pH1MZ}vc<1osV(2CGG)Ree$E-T;8>$zw*>x-505b&4(shMGIjbAfLS zEZ3ys(`SmCWc(75)^=aKer}>67qj^nGKtCK{35I|tA}wQa!uM!suX%Gb~ylORGGc( ze^|m|N!}G0#Ph|;wSXz`SByQM>lPM#8>mdSQs`7RxkXaSAADYA24u6xWqkIXY?o%z z%TEFL+wNW^&nrvaA1_#P%&Hbzrjl!*hIft>F0@g0IVydUU4MJgS3_3Js8{*>|G2jC z4%n#cOy9b2Xf&Pw=14;0Dtf00C^Z$I-v05OqtvN9>sAC&oV1Tk;;ku7VR`sQK4oFq zQ8)yoZNuTwV$t13|GCUIC{ID_r7M5&R*zhsxbrkg;EgMtL|9ne=^}BM!dxV!KDeXkWA^MfQTkQEt8~t>JznNh%ULvn@dbQ2cyf} z|C%ns#NJU}SHU(7Pg$<&8uDK>d5GZJ&`;CcfGP(~b-#UusXevc^q!km1X6_wVMqGk z^m&ZS6#42?p4c_t1TA$_+}h1L2c<<=$k%;v+D!<@j5hs|{>d18>~~v#oq4yGyS@QP zgTX2oJbEy@eJbo-f{ZQ>-nmB-#AqWcHbMQXFi*T)0n!(HIexz=pp<(O*DMh7CMupX z)ei1ZYuIW~E={-ND*nD;okiZdm!?^|LjLZhs*FHZvWld5TDj zcvWB)`-1Me9bu`*4M=CO6ye=pMgxlgYvsh2rV#5Z$hFKw0GX30%oufb=hJ0BFIJH` z+Fii4gQ+7!)8K^yc*PVEW^#f!|BW0Q5*`IewQ5YDFh?{x1L7tlaUAX@3Y+D>6FPVf zJzOGex~H34`8eq+TL$FsHm+27RS>3$CG;>0Jj4*1ukX$za})*b^S5p}I2jbFCHLsA zzYwAyftMz`uo2c8ieQcy-p&9iP3fMk(uRw+OlBPm`KCLei6g!|Vnk*-kjs>A25MTE z5GLDMV$70AC0j-tx*0sCruvKh{fSM)3X}13U>m|KeaOb`9^}v^44!$`06-JHf@L4EKyxV)M!8cL zi5p9kF97RiAT92!e?%9CP=qX3wyv^A8q!w%07d(9f-U))uDgsr4FDVL;|%r)fw}-@ zlB$F79X^EKYF%8J7mU?3VzJoYQ0<;NczW1jH4=4kEh_)q|^9wj zIsn-SsmRx0_EJ7(6WypwptIwZ)-T<__UgUu?BXt zoIf|a!5`?&JEb$w2PZSqhA>J;GIA^rJ-Cpz8MKX~bcqZNOUzPtu|NMvEP>+cO;V*W zNQ8YPENkr!)lN+tlxB79RUD20$)+_P6Jc`+4q@%Kno{F+#1qR*zrj%T>nTSceO?a5 zyqGDa59#G6k*RXu6+#=e=e!~i1Y&15!cHmE6sLh_K%Ppv$tFE-Le3RQs-nx5LB>gy z5A))kwkxWSy73{@I{%{DY8X+2o{CLJb~R$3r=oT^P~Xo$2lKz8?Z!3QLn$5l#L2k2 zb1=?UT&c<8!&9gW1M&jI!5%dhJbD3nQXpaeNJ>=zR+EL!4iY(nMBQI+|2J+Hw-WMr z08Mt9h8(PGbY?zKtk=cqw(yW}1A#htn* z8&}5Y>$uc>Lv!bSuWQ5UB&ct7*jiZAFpxz|%xO&5kg zzlf?6xy7H3G^*wvP5scW*Wf(<&eP!YIUf%&HT?K)RWmKg$G^=mSoi~;&9dU%{o}WV z#BX;9+q)fpVU`>Vdo~AtYK)`7z*H;dc-e|q6Qt;3J0APUL!~g&Q literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4cc16421680a50164ba74381b4b35ceaa0ccfc GIT binary patch literal 3276 zcmZ`*X*|?x8~)E?#xi3t91%vcMKbnsIy2_j%QE2ziLq8HEtbf{7%?Q-9a%z_Y^9`> zEHh*&vUG%uWkg7pKTS-`$veH@-Vg8ZdG7oAJ@<88AMX3Z{d}TU-4*=KI1-hF6u>DKF2moPt09c{` zfN3rO$X+gJI&oA$AbgKoTL8PiPI1eFOhHBDvW+$&oPl1s$+O5y3$30Jx9nC_?fg%8Om)@;^P;Ee~8ibejUNlSR{FL7-+ zCzU}3UT98m{kYI^@`mgCOJ))+D#erb#$UWt&((j-5*t1id2Zak{`aS^W*K5^gM02# zUAhZn-JAUK>i+SNuFbWWd*7n1^!}>7qZ1CqCl*T+WoAy&z9pm~0AUt1cCV24f z3M@&G~UKrjVHa zjcE@a`2;M>eV&ocly&W3h{`Kt`1Fpp?_h~9!Uj5>0eXw@$opV(@!pixIux}s5pvEqF5$OEMG0;c zAfMxC(-;nx_`}8!F?OqK19MeaswOomKeifCG-!9PiHSU$yamJhcjXiq)-}9`M<&Au|H!nKY(0`^x16f205i2i;E%(4!?0lLq0sH_%)Wzij)B{HZxYWRl3DLaN5`)L zx=x=|^RA?d*TRCwF%`zN6wn_1C4n;lZG(9kT;2Uhl&2jQYtC1TbwQlP^BZHY!MoHm zjQ9)uu_K)ObgvvPb}!SIXFCtN!-%sBQe{6NU=&AtZJS%}eE$i}FIll!r>~b$6gt)V z7x>OFE}YetHPc-tWeu!P@qIWb@Z$bd!*!*udxwO6&gJ)q24$RSU^2Mb%-_`dR2`nW z)}7_4=iR`Tp$TPfd+uieo)8B}Q9#?Szmy!`gcROB@NIehK|?!3`r^1>av?}e<$Qo` zo{Qn#X4ktRy<-+f#c@vILAm;*sfS}r(3rl+{op?Hx|~DU#qsDcQDTvP*!c>h*nXU6 zR=Un;i9D!LcnC(AQ$lTUv^pgv4Z`T@vRP3{&xb^drmjvOruIBJ%3rQAFLl7d9_S64 zN-Uv?R`EzkbYIo)af7_M=X$2p`!u?nr?XqQ_*F-@@(V zFbNeVEzbr;i2fefJ@Gir3-s`syC93he_krL1eb;r(}0yUkuEK34aYvC@(yGi`*oq? zw5g_abg=`5Fdh1Z+clSv*N*Jifmh&3Ghm0A=^s4be*z5N!i^FzLiShgkrkwsHfMjf z*7&-G@W>p6En#dk<^s@G?$7gi_l)y7k`ZY=?ThvvVKL~kM{ehG7-q6=#%Q8F&VsB* zeW^I zUq+tV(~D&Ii_=gn-2QbF3;Fx#%ajjgO05lfF8#kIllzHc=P}a3$S_XsuZI0?0__%O zjiL!@(C0$Nr+r$>bHk(_oc!BUz;)>Xm!s*C!32m1W<*z$^&xRwa+AaAG= z9t4X~7UJht1-z88yEKjJ68HSze5|nKKF9(Chw`{OoG{eG0mo`^93gaJmAP_i_jF8a z({|&fX70PXVE(#wb11j&g4f{_n>)wUYIY#vo>Rit(J=`A-NYYowTnl(N6&9XKIV(G z1aD!>hY!RCd^Sy#GL^0IgYF~)b-lczn+X}+eaa)%FFw41P#f8n2fm9=-4j7}ULi@Z zm=H8~9;)ShkOUAitb!1fvv%;2Q+o)<;_YA1O=??ie>JmIiTy6g+1B-1#A(NAr$JNL znVhfBc8=aoz&yqgrN|{VlpAniZVM?>0%bwB6>}S1n_OURps$}g1t%)YmCA6+5)W#B z=G^KX>C7x|X|$~;K;cc2x8RGO2{{zmjPFrfkr6AVEeW2$J9*~H-4~G&}~b+Pb}JJdODU|$n1<7GPa_>l>;{NmA^y_eXTiv z)T61teOA9Q$_5GEA_ox`1gjz>3lT2b?YY_0UJayin z64qq|Nb7^UhikaEz3M8BKhNDhLIf};)NMeS8(8?3U$ThSMIh0HG;;CW$lAp0db@s0 zu&jbmCCLGE*NktXVfP3NB;MQ>p?;*$-|htv>R`#4>OG<$_n)YvUN7bwzbWEsxAGF~ zn0Vfs?Dn4}Vd|Cf5T-#a52Knf0f*#2D4Lq>-Su4g`$q={+5L$Ta|N8yfZ}rgQm;&b z0A4?$Hg5UkzI)29=>XSzdH4wH8B@_KE{mSc>e3{yGbeiBY_+?^t_a#2^*x_AmN&J$ zf9@<5N15~ty+uwrz0g5k$sL9*mKQazK2h19UW~#H_X83ap-GAGf#8Q5b8n@B8N2HvTiZu&Mg+xhthyG3#0uIny33r?t&kzBuyI$igd`%RIcO8{s$$R3+Z zt{ENUO)pqm_&<(vPf*$q1FvC}W&G)HQOJd%x4PbxogX2a4eW-%KqA5+x#x`g)fN&@ zLjG8|!rCj3y0%N)NkbJVJgDu5tOdMWS|y|Tsb)Z04-oAVZ%Mb311P}}SG#!q_ffMV z@*L#25zW6Ho?-x~8pKw4u9X)qFI7TRC)LlEL6oQ9#!*0k{=p?Vf_^?4YR(M z`uD+8&I-M*`sz5af#gd$8rr|oRMVgeI~soPKB{Q{FwV-FW)>BlS?inI8girWs=mo5b18{#~CJz!miCgQYU>KtCPt()StN;x)c2P3bMVB$o(QUh z$cRQlo_?#k`7A{Tw z!~_YKSd(%1dBM+KE!5I2)ZZsGz|`+*fB*n}yxtKVyx14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>GbI`Jdw*pGcA%L+*Q#&*YQOJ$_%U#(BDn``;rKxi&&)LfRxIZ*98z8UWRslDo@Xu)QVh}rB>bKwe@Bjzwg%m$hd zG)gFMgHZlPxGcm3paLLb44yHI|Ag0wdp!_yD5R<|B29Ui~27`?vfy#ktk_KyHWMDA42{J=Uq-o}i z*%kZ@45mQ-Rw?0?K+z{&5KFc}xc5Q%1PFAbL_xCmpj?JNAm>L6SjrCMpiK}5LG0ZE zO>_%)r1c48n{Iv*t(u1=&kH zeO=ifbFy+6aSK)V_5t;NKhE#$Iz=+Oii|KDJ}W>g}0%`Svgra*tnS6TRU4iTH*e=dj~I` zym|EM*}I1?pT2#3`oZ(|3I-Y$DkeHMN=8~%YSR?;>=X?(Emci*ZIz9+t<|S1>hE8$ zVa1LmTh{DZv}x6@Wz!a}+qZDz%AHHMuHCzM^XlEpr!QPzf9QzkS_0!&1MPx*ICxe}RFdTH+c}l9E`G zYL#4+3Zxi}3=A!G4S>ir#L(2r)WFKnP}jiR%D`ZOPH`@ZhTQy=%(P0}8ZH)|z6jL7 N;OXk;vd$@?2>?>Ex^Vyi literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..bcbf36df2f2aaaa0a63c7dabc94e600184229d0d GIT binary patch literal 5933 zcmZ{Idpwix|Np(&m_yAF>K&UIn{t*2ZOdsShYs(MibU!|=pZCJq~7E>B$QJr)hC5| zmk?V?ES039lQ~RC!kjkl-TU4?|NZ{>J$CPLUH9vHy`Hbhhnc~SD_vpzBp6Xw4`$%jfmPw(;etLCccvfU-s)1A zLl8-RiSx!#?Kwzd0E&>h;Fc z^;S84cUH7gMe#2}MHYcDXgbkI+Qh^X4BV~6y<@s`gMSNX!4@g8?ojjj5hZj5X4g9D zavr_NoeZ=4vim%!Y`GnF-?2_Gb)g$xAo>#zCOLB-jPww8a%c|r&DC=eVdE;y+HwH@ zy`JK(oq+Yw^-hLvWO4B8orWwLiKT!hX!?xw`kz%INd5f)>k1PZ`ZfM&&Ngw)HiXA| ze=+%KkiLe1hd>h!ZO2O$45alH0O|E+>G2oCiJ|3y2c$;XedBozx93BprOr$#d{W5sb*hQQ~M@+v_m!8s?9+{Q0adM?ip3qQ*P5$R~dFvP+5KOH_^A+l-qu5flE*KLJp!rtjqTVqJsmpc1 zo>T>*ja-V&ma7)K?CE9RTsKQKk7lhx$L`9d6-Gq`_zKDa6*>csToQ{&0rWf$mD7x~S3{oA z1wUZl&^{qbX>y*T71~3NWd1Wfgjg)<~BnK96Ro#om&~8mU{}D!Fu# zTrKKSM8gY^*47b2Vr|ZZe&m9Y`n+Y8lHvtlBbIjNl3pGxU{!#Crl5RPIO~!L5Y({ym~8%Ox-9g>IW8 zSz2G6D#F|L^lcotrZx4cFdfw6f){tqITj6>HSW&ijlgTJTGbc7Q#=)*Be0-s0$fCk z^YaG;7Q1dfJq#p|EJ~YYmqjs`M0jPl=E`Id{+h%Lo*|8xp6K7yfgjqiH7{61$4x~A zNnH+65?QCtL;_w(|mDNJXybin=rOy-i7A@lXEu z&jY(5jhjlP{TsjMe$*b^2kp8LeAXu~*q&5;|3v|4w4Ij_4c{4GG8={;=K#lh{#C8v z&t9d7bf{@9aUaE94V~4wtQ|LMT*Ruuu0Ndjj*vh2pWW@|KeeXi(vt!YXi~I6?r5PG z$_{M*wrccE6x42nPaJUO#tBu$l#MInrZhej_Tqki{;BT0VZeb$Ba%;>L!##cvieb2 zwn(_+o!zhMk@l~$$}hivyebloEnNQmOy6biopy`GL?=hN&2)hsA0@fj=A^uEv~TFE z<|ZJIWplBEmufYI)<>IXMv(c+I^y6qBthESbAnk?0N(PI>4{ASayV1ErZ&dsM4Z@E-)F&V0>tIF+Oubl zin^4Qx@`Un4kRiPq+LX5{4*+twI#F~PE7g{FpJ`{)K()FH+VG^>)C-VgK>S=PH!m^ zE$+Cfz!Ja`s^Vo(fd&+U{W|K$e(|{YG;^9{D|UdadmUW;j;&V!rU)W_@kqQj*Frp~ z7=kRxk)d1$$38B03-E_|v=<*~p3>)2w*eXo(vk%HCXeT5lf_Z+D}(Uju=(WdZ4xa( zg>98lC^Z_`s-=ra9ZC^lAF?rIvQZpAMz8-#EgX;`lc6*53ckpxG}(pJp~0XBd9?RP zq!J-f`h0dC*nWxKUh~8YqN{SjiJ6vLBkMRo?;|eA(I!akhGm^}JXoL_sHYkGEQWWf zTR_u*Ga~Y!hUuqb`h|`DS-T)yCiF#s<KR}hC~F%m)?xjzj6w#Za%~XsXFS@P0E3t*qs)tR43%!OUxs(|FTR4Sjz(N zppN>{Ip2l3esk9rtB#+To92s~*WGK`G+ECt6D>Bvm|0`>Img`jUr$r@##&!1Ud{r| zgC@cPkNL_na`74%fIk)NaP-0UGq`|9gB}oHRoRU7U>Uqe!U61fY7*Nj(JiFa-B7Av z;VNDv7Xx&CTwh(C2ZT{ot`!E~1i1kK;VtIh?;a1iLWifv8121n6X!{C%kw|h-Z8_U z9Y8M38M2QG^=h+dW*$CJFmuVcrvD*0hbFOD=~wU?C5VqNiIgAs#4axofE*WFYd|K;Et18?xaI|v-0hN#D#7j z5I{XH)+v0)ZYF=-qloGQ>!)q_2S(Lg3<=UsLn%O)V-mhI-nc_cJZu(QWRY)*1il%n zOR5Kdi)zL-5w~lOixilSSF9YQ29*H+Br2*T2lJ?aSLKBwv7}*ZfICEb$t>z&A+O3C z^@_rpf0S7MO<3?73G5{LWrDWfhy-c7%M}E>0!Q(Iu71MYB(|gk$2`jH?!>ND0?xZu z1V|&*VsEG9U zm)!4#oTcgOO6Hqt3^vcHx>n}%pyf|NSNyTZX*f+TODT`F%IyvCpY?BGELP#s<|D{U z9lUTj%P6>^0Y$fvIdSj5*=&VVMy&nms=!=2y<5DP8x;Z13#YXf7}G)sc$_TQQ=4BD zQ1Le^y+BwHl7T6)`Q&9H&A2fJ@IPa;On5n!VNqWUiA*XXOnvoSjEIKW<$V~1?#zts>enlSTQaG2A|Ck4WkZWQoeOu(te znV;souKbA2W=)YWldqW@fV^$6EuB`lFmXYm%WqI}X?I1I7(mQ8U-pm+Ya* z|7o6wac&1>GuQfIvzU7YHIz_|V;J*CMLJolXMx^9CI;I+{Nph?sf2pX@%OKT;N@Uz9Y zzuNq11Ccdwtr(TDLx}N!>?weLLkv~i!xfI0HGWff*!12E*?7QzzZT%TX{5b7{8^*A z3ut^C4uxSDf=~t4wZ%L%gO_WS7SR4Ok7hJ;tvZ9QBfVE%2)6hE>xu9y*2%X5y%g$8 z*8&(XxwN?dO?2b4VSa@On~5A?zZZ{^s3rXm54Cfi-%4hBFSk|zY9u(3d1ButJuZ1@ zfOHtpSt)uJnL`zg9bBvUkjbPO0xNr{^{h0~$I$XQzel_OIEkgT5L!dW1uSnKsEMVp z9t^dfkxq=BneR9`%b#nWSdj)u1G=Ehv0$L@xe_eG$Ac%f7 zy`*X(p0r3FdCTa1AX^BtmPJNR4%S1nyu-AM-8)~t-KII9GEJU)W^ng7C@3%&3lj$2 z4niLa8)fJ2g>%`;;!re+Vh{3V^}9osx@pH8>b0#d8p`Dgm{I?y@dUJ4QcSB<+FAuT)O9gMlwrERIy z6)DFLaEhJkQ7S4^Qr!JA6*SYni$THFtE)0@%!vAw%X7y~!#k0?-|&6VIpFY9>5GhK zr;nM-Z`Omh>1>7;&?VC5JQoKi<`!BU_&GLzR%92V$kMohNpMDB=&NzMB&w-^SF~_# zNsTca>J{Y555+z|IT75yW;wi5A1Z zyzv|4l|xZ-Oy8r8_c8X)h%|a8#(oWcgS5P6gtuCA_vA!t=)IFTL{nnh8iW!B$i=Kd zj1ILrL;ht_4aRKF(l1%^dUyVxgK!2QsL)-{x$`q5wWjjN6B!Cj)jB=bii;9&Ee-;< zJfVk(8EOrbM&5mUciP49{Z43|TLoE#j(nQN_MaKt16dp#T6jF7z?^5*KwoT-Y`rs$ z?}8)#5Dg-Rx!PTa2R5; zx0zhW{BOpx_wKPlTu;4ev-0dUwp;g3qqIi|UMC@A?zEb3RXY`z_}gbwju zzlNht0WR%g@R5CVvg#+fb)o!I*Zpe?{_+oGq*wOmCWQ=(Ra-Q9mx#6SsqWAp*-Jzb zKvuPthpH(Fn_k>2XPu!=+C{vZsF8<9p!T}U+ICbNtO}IAqxa57*L&T>M6I0ogt&l> z^3k#b#S1--$byAaU&sZL$6(6mrf)OqZXpUPbVW%T|4T}20q9SQ&;3?oRz6rSDP4`b z(}J^?+mzbp>MQDD{ziSS0K(2^V4_anz9JV|Y_5{kF3spgW%EO6JpJ(rnnIN%;xkKf zn~;I&OGHKII3ZQ&?sHlEy)jqCyfeusjPMo7sLVr~??NAknqCbuDmo+7tp8vrKykMb z(y`R)pVp}ZgTErmi+z`UyQU*G5stQRsx*J^XW}LHi_af?(bJ8DPho0b)^PT|(`_A$ zFCYCCF={BknK&KYTAVaHE{lqJs4g6B@O&^5oTPLkmqAB#T#m!l9?wz!C}#a6w)Z~Z z6jx{dsXhI(|D)x%Yu49%ioD-~4}+hCA8Q;w_A$79%n+X84jbf?Nh?kRNRzyAi{_oV zU)LqH-yRdPxp;>vBAWqH4E z(WL)}-rb<_R^B~fI%ddj?Qxhp^5_~)6-aB`D~Nd$S`LY_O&&Fme>Id)+iI>%9V-68 z3crl=15^%0qA~}ksw@^dpZ`p;m=ury;-OV63*;zQyRs4?1?8lbUL!bR+C~2Zz1O+E@6ZQW!wvv z|NLqSP0^*J2Twq@yws%~V0^h05B8BMNHv_ZZT+=d%T#i{faiqN+ut5Bc`uQPM zgO+b1uj;)i!N94RJ>5RjTNXN{gAZel|L8S4r!NT{7)_=|`}D~ElU#2er}8~UE$Q>g zZryBhOd|J-U72{1q;Lb!^3mf+H$x6(hJHn$ZJRqCp^In_PD+>6KWnCnCXA35(}g!X z;3YI1luR&*1IvESL~*aF8(?4deU`9!cxB{8IO?PpZ{O5&uY<0DIERh2wEoAP@bayv z#$WTjR*$bN8^~AGZu+85uHo&AulFjmh*pupai?o?+>rZ7@@Xk4muI}ZqH`n&<@_Vn zvT!GF-_Ngd$B7kLge~&3qC;TE=tEid(nQB*qzXI0m46ma*2d(Sd*M%@Zc{kCFcs;1 zky%U)Pyg3wm_g12J`lS4n+Sg=L)-Y`bU705E5wk&zVEZw`eM#~AHHW96@D>bz#7?- zV`xlac^e`Zh_O+B5-kO=$04{<cKUG?R&#bnF}-?4(Jq+?Ph!9g zx@s~F)Uwub>Ratv&v85!6}3{n$bYb+p!w(l8Na6cSyEx#{r7>^YvIj8L?c*{mcB^x zqnv*lu-B1ORFtrmhfe}$I8~h*3!Ys%FNQv!P2tA^wjbH f$KZHO*s&vt|9^w-6P?|#0pRK8NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!ItFh?!xdN1Q+aGJ{c&& zS>O>_%)r1c48n{Iv*t(u1=&kHeO=ifbFy+6aSK)V_AxLppYn8Z42d|rc6w}vOsL55 z`t&mC&y2@JTEyg!eDiFX^k#CC!jq%>erB=yHqUP0XcDOTw6ko}L zX;EmMrq(fKk*eygEuA616;0)>@A{TK|55PV@70 z$OfzS*(VJxQev3J?yY?O=ul(v`fp}?u9z`JK3ugibK>)DyCwImZOF4d{xK%%Ks1*} zv$oa)9anR%lXIBUqYnhLmT>VOzHfNP?ZwJNZ!5$s9M08RynIvaXw>@G^T9@r9^KH1 zVy??F&uuk)bH9Y4pQY!hP58i_H6 znl-NcuCpLV6ZWU;4C zu@9exF&OZi`Bovq_m%T+WhU2kvkz@^_LpycBvqm3bMpLw8X-Or5sL>0AKE1$(k_L=_Zc=CUq#=x1-QZf)G7nHu@fmsQ1eN_N3+nTEz`4HI4Z6uVlE zJH+X&det8JU?tO?upcM4Z=cV!JV;yF>FfL5Q$M|W_2Z!P`S=}Wzp|_1^#d%e?_H`> zV@%vA$+bFVqhw9`U;TfP|5|PD{||OiYdor8P*i??|NJcb%kzT_73*7WE?Ua5hAnR2 z=7WE=PhTlJ#ZeRznjTUb;`E(wkMZrj4e|Hilz-mK>9cZHQY**5TUPw~u}k;u73KI}xAx!0m-)GVia|x^d3p~s_9gh83jA&Ra<8rM%`>U3x69t&NzbwWY}7Ar?)FK#IZ0z|d0H0EkRO w3{9;}4Xg|ebq&m|3=9_N6z8I7$jwj5OsmAL;bP(Gi$Dzwp00i_>zopr02+f8CIA2c literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/syncfusion_flutter_officecore/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..e71a726136a47ed24125c7efc79d68a4a01961b4 GIT binary patch literal 14800 zcmZ{Lc|26@`~R6Crm_qwyCLMMh!)vm)F@HWt|+6V6lE=CaHfcnn4;2x(VilEl9-V} zsce-cGK|WaF}4{T=lt&J`Fy_L-|vs#>v^7+XU=`!*L|PszSj43o%o$Dj`9mM7C;ar z@3hrnHw59q|KcHn4EQr~{_70*BYk4yj*SqM&s>NcnFoIBdT-sm1A@YrK@dF#f+SPu z{Sb8441xx|AjtYQ1gQq5z1g(^49Fba=I8)nl7BMGpQeB(^8>dY41u79Dw6+j(A_jO z@K83?X~$;S-ud$gYZfZg5|bdvlI`TMaqs!>e}3%9HXev<6;dZZT8Yx`&;pKnN*iCJ z&x_ycWo9{*O}Gc$JHU`%s*$C%@v73hd+Mf%%9ph_Y1juXamcTAHd9tkwoua7yBu?V zgROzw>LbxAw3^;bZU~ZGnnHW?=7r9ZAK#wxT;0O<*z~_>^uV+VCU9B@)|r z*z^v>$!oH7%WZYrwf)zjGU|(8I%9PoktcsH8`z^%$48u z(O_}1U25s@Q*9{-3O!+t?w*QHo;~P99;6-KTGO{Cb#ADDYWF!eATsx{xh-!YMBiuE z%bJc7j^^B$Sa|27XRxg(XTaxWoFI}VFfV>0py8mMM;b^vH}49j;kwCA+Lw=q8lptk z?Pe`{wHI39A&xYkltf5*y%;-DF>5v`-lm0vydYtmqo0sClh5ueHCLJ+6$0y67Z zO-_LCT|JXi3tN7fB-!0_Kn#I+=tyUj87uR5*0>|SZ zy3x2;aql87`{aPZ@UbBwY0;Z-a*lYL90YApOAMKur7YgOiqA~Cne6%b&{V-t>Am2c z{eyEuKl!GsA*jF2H_gvX?bP~v46%3ax$r~B$HnZQ;UiCmRl`ROK8v>;Zs~upH9}qu1ZA3kn-AY2k2@CaH=Qh7K6`nU z3ib(Bk%H*^_omL6N4_G5NpY20UXGi}a$!}#lf<&J4~nhRwRM5cCB3Zvv#6+N1$g@W zj9?qmQ`zz-G9HTpoNl~bCOaEQqlTVYi7G0WmB5E34;f{SGcLvFpOb`+Zm)C(wjqLA z2;+nmB6~QDXbxZGWKLt38I%X$Q!;h zup9S~byxKv=$x|^YEV;l0l67jH~E8BU45ft_7xomac-48oq4PZpSNJbw<7DTM4mmz z!$)z#04cy%b8w@cOvjmb36o;gwYIOLwy+{I#3dJj#W4QdOWwJQ2#20AL49`hSFUa7 zFNAN3OD==G3_kbr1d96>l`_cI`<=thKNh5>hgg7FV>5TfC6d#u)9BNXi@p1K*;2Is zz+x;l4GbSt#*%>1iq}jGIebXYJY5;PGG0y(^{>SSuZY89aL`sDghOM&&pyP6ABJ#w zYwK~4^1eUQD)4!GL>`zrWeHV z-W!6JZbW*Ngo;Edhp_cOysYr!uhKS}vIg_UC}x z=jXxQfV@4B3`5 z!u#byBVXV5GtrSx_8bnT@iKv=Uc6n)Zpa`<9N>+!J~Loxptl5$Z`!u<3a)-+P)say z#=jc7^mJzPMI2;yMhCmN7YN78E7-^S(t8E}FklC;z|4PL{bO|JieM#p1mBjwyZMEm zkX^A1RXPGeS2YqtPMX~~t^$~oeFfWAU#jVLi%Z@l2hle^3|e(q?(uS=BVauF?VF{j z(owKLJuze;_@5p1OtRyrT`EFXf)NfMYb-)E8RVVdr<@}M>4R&~P=;B`c1L%o|8YfB z-a(LB-i8jc5!&B5cowyI2~M^YID&@Xt(D9v{|DB z959W z*vEA77fh3*w*UJ`4Y(bxsoEy6hm7_Wc5gT0^cvso%Ow>9<&@9Q>mxb6-^pv)5yc>n zQ~^!qY(lPQ1EDGkr%_*y*D8T^YbCa52^MVqYpTLhgJ;N5PfCQ{SXk|plD#Sm+g4c- zFeL2Dih35W4{_qb75U`4Rb#S0FEo%F85dOhXSX0huPOxdAid{&p6P;+9}I)XU7^=3RZu9M(g0dLyz_7$8K{`AddBLOfU&B_QNHtmsnNXq`hy~% zvJ{vtz~Yt9X|o}5vXX)9ZCHaRq8iAb zUDj8%(MpzJN39LferYKvIc!)z^5T-eW@j3h9a6d%WZ!%@2^@4+6%Z9W1GHZbOj|sb z0cU$}*~G$fYvDC|XulSC_;m}?KC2jg5pxES$Bt!hA|@EX*2+O!UEb5sn_^d>z;>;r~ zmO3BivdXboPY*}amsO&`xk|e)S*u=`o67MC(1WTB;OwG+ua4UV7T5Wvy%?U{Pa5cO zMoLG>#@chO{Oc72XPyX8f3jC7P`$j4$)0wc(b50COaDP3_Cm}aPAglUa7kRXAqmo5 z0KDD7G>Gmnpons40WJNYn+pxko92GXy@PvSErKE-Ou3)3UiRr7!L4+0%+5}sD{bf)uj^ounQ-Yn2%%JoZ%FjUv%yjS?Ks4u_88Jh%tNliYW~817IV@fqd1T zi(?;Fv-s3rQEn=9G*E-QzSl%YS|^fe*yn}Aqh!&P<5%#oB?*{wZMa5$PYa*A{VA8! zbOfS1W!W}cTo%g~iP$>WhE_x7#O4?h$jq=>{M77>bTAK_ z6uU0tl6HARboGi}=4krr6WP`9`aAt&P5ON1v(+H{T?jZuJ}B{L-=z3VX)}mZwzrqH zpf?T!k&$?{&{0_p>b`kdJbSb(p~tFcuG4zh6}hfl@ues6CfJu<-P+!>FlYMlD_3!E z9$6VE==tlxNYe(s;@8@+4c4jQ$R2g8t0QwE>Et|)5)@kJj6^yaqFYY?0LEM2C!+7+ z+FN|UxR1GCy1KA`{T_%24U+Vserchr5h`;U7TZPr@43x#MMN{@vV?KSII}R@5k`7cVK}E;c)$f~_{ZLDOoL|-01p~oafxi4F zG$?Wha&a*rTnz-nTI-bAJ*SLb!5(L!#iRdvLEyo>7D_=H78-qZrm=6{hkUR{tR{H! z`ZTOV$Oi6^qX5=_{f}V9h}WJAO%h9)kEUF#*-JyYDbOGZ>Nfs%7L}4p zopIul&&Bbn!C9o83ypC6W4F$X=_|pex$V4!Whm#48Wfm3*oAW0Gc&#&b+oq<8>aZR z2BLpouQQwyf$aHpQUK3pMRj(mS^^t#s$IC3{j*m9&l7sQt@RU{o_}N-xI_lh`rND^ zX~-8$o(;p^wf3_5-WZ^qgW`e8T@37{`J)e2KJdSSCUpX6KZu0Ga&U*+u3*PDAs1uK zpl)40+fROA@Vo#vK?^@Pq%w8DO9HdfmH+~vNinZ$5GRz?sD|k246NepqZd`>81P^P z#x#3kUS-}x4k%&~iEUrsb&-X#_;;?y9oCP4crMkC`=q58#NxQ| z*NXNA;GR4X=GiGXwab5=&M3j04fQw%2UxM`S(aE)_PlgJttBX96$$lY@Q%0xV^IbcHqzw^Uk&E=vFB;EQ@kzVIeM8lDIW_Q_ zrfy)l6s2QBApF;J2xTD_@wuNMlwDfsdfMyzRq)<>qG{M)Yt}9F1{1HaI_X7=F=7>& zYB54VaKlxu0lIgS;Ac&25Aw(tcf@K~(cvPi8(OChzhlYp6}#<_MVhU95sD&)n0FtL zmxm4w$~s(S9jmHOgyovpG!x4uLfJsMsJn^QMraKAa1Ix?{zkV!a7{f%-!u2{NqZ&) zo+^XB`eFQ4 zk-(;_>T#pTKyvW${yL|XXbcv?CE2Tp<3(PjeXhu^Jrp6^Mj}lg_)jamK{g;C+q^Da ztb!gV!q5)B7G1%lVanA2b>Xs?%hzCgJ{Hc!ldr9dnz7k^xG#4pDpr|0ZmxxiUVl}j zbD_rg3yAFQ>nnc)0>71D==715jRj4XsRb2#_lJoSOwky&c4957V-|m)@>b^Nak1!8 z@DsIOS8>Oe^T>tgB)WX3Y^I^65Uae+2M;$RxX_C)Aoo0dltvoRRIVQkpnegWj;D#G z+TwFIRUN%bZW3(K{8yN8!(1i0O!X3YN?Zo08L5D~)_tWQA8&|CvuQb8Od?p_x=GMF z-B@v9iNLYS1lUsbb`!%f5+1ev8RFPk7xyx5*G;ybRw(PW*yEZ$unu2`wpH)7b@ZXEz4Jr{?KZKYl!+3^)Q z)~^g?KlPGtT!{yQU&(Z&^rVjPu>ueeZN86AnhRwc)m|;5NvM&W3xD%n`+Hjg5$e8M zKh1Ju82L~&^ z-IQ5bYhsjqJfr38iwi~8<{oeREh|3l)*Enj4&Q$+mM$15YqwXeufK9P^(O=pj=F-1 zD+&REgwY~!W#ZPccSEi(*jiKJ5)Q|zX;hP}S2T9j_);epH9JQs{n>RG}{Nak)vIbfa zFQm?H;D+tzrBN2)6{?Mo%fzN6;6d_h0Qyn61)+XT63=!T*WQyRUoB_x0_)Ir`$FtS zak07C(mOaWN5m%bk?F9X&@mEVKN%{R6obt(9qw&p>w&p;R*l2th9$D^*`pC}NmB+v z>bk;OJ(C8p$G;jNvRsBbt=a!!tKnjJ`9*yQFgjEN1HcC<&>u9aStT3>Oq=MOQV!#WOZ6{cv$YVmlJdovPRV}<=IZUPeBVh5DC z91-?kimq3JUr;UMQ@0?h52gupvG=~(5AVdP(2(%*sL8!#K1-L$9B7MrWGdt(h&whR@vz~0oEHF8u3U1Q zdGdaIytJj4x@eF*E+^zgi{nPCA8tkjN}UoR8WhDzM3-zLqx0z?2tTdDKyENM={fp8VC@3Dt`AiK$;K#H$K2{08mrHG%jgEOLX3MCsG>afZm_0mLPS4jmYUJp~Dm! z5AUe_vEaOAT3zWdwl#cLvqwd1^lwW?gt7(92wEsOE6c#<0}{szFV4(uO70?3>=((! zQr}1{J?Wx2ZmjxYL_8OB*m&mimfojzYn~PiJ2g8R&ZRx-i^yF#sdhEWXAUIZ@J?T$ zs3PgT2<&Ki>Bob_n(@S>kUIvE+nY~ti9~6j;O9VAG#{oZ!DZCW)}i6iA!Tgsyz+hC z1VVyvbQ_nwgdZSEP=U4d#U`2*`e~d4y8uM4Bcmm%!jidaee#4WqN!ZnlBmbYpuaO! z!rU3`Kl2 z0O7PD&fQ|_b)Ub!g9^s;C2e>1i*2&?1$6yEn?~Y zI)-WIN8N(5s9;grW+J@K@I%g#?G&hzmlgV=L}ZA{f>3YCMx^P{u@c5Z;U1qmdk#)L zvX6z1!sL>+@vxO8qVn#k3YxYi?8ggV){?Rn@j$+Fd4-QkuH1@)j#3-=f82GZ!nl~{ zzZ(?kO`ANttVeHSo%xmH!NmNZECh*{s!-8S>ALoe5xOPs>|P5BbUmP@rlV8`d(c=7 zypcpLaI*FM^;GM%@q`GAb8kO`$oE|R48yn)?p(c1t>5;Wwn5r6ck&uw4}TnT80jI`IS~J%q8CpaVgIze<8IykSpVBg8~E! zW_tGqB;GO47r_er05y+Kwrcn{VLxL*1;HMv@*sd}MB6DH4zaP~u4Y;>@Nw7?F8S?c zfVIY(^ntnGgWlD|idzGz$Y+Oh(Ra=&VIf4!K2W*a)(%5%78s}8qxOknAGtDAq+HMO zM+Nu;0OgQRn36 zA@~a8`uVQ~v9?d!BxnsVaB-z-djypO44BjQAmg7&eVoaew|~)wH$SgefJ2$7_RiY+ z_7ACGoFM6Lhvho+eUG@pU&0X(Uy(*j;9pr?ET?FHTXadlfXC|MReZoU5>AG`mTM<% zc~*I@E*u0|hwVTdFA~4^b2VT7_~}~tCueNY{de3og=ASFQ`)0dhC2~Ne<}}Rc?ptA zi}+bQE%N9o*hpSUMH)9xt%Zlz&^p&5=cW}{m#f85iVX64^{!(vhClT<I)+c)RuiyrZqIw4v`z%YK&;_Fh4_+0B?qAGxMfAM`LzG_bjD>ib4;KGT4_1I>sxvL&&qp40ajgQOqIE^9=Az4w#ymo)bW-Vg{T!n=l&|nR_ zw+wcH|FxUH63)~{M;goHepmD{Fe?W9sO|eJP9L$G<{e_7FxxuXQ+)(Z^@;X8I1=%k zTK$gbHA1^4W<`q~ubQ0M_C^CA5#Z&*nGc(T?4Y_2jLu&FJDQYpCSiRny->$+nC9Jl z?avTW`ZXYT51%SrEq!}dXNM&!pM6nmL^lce=%S7{_TS)ckN8;{p*LT~LMgmlE~dpL zEBQy-jDj%cSK6N3)|CCR0LQ$N6iDM~+-1Oz|LAdkip(VZcO`gqCuJ+(Mm{m6@P%_; zBtF|MMVMP;E`5NJ{&@4j^JE5j&}(Jq{lCGL(P^#uqvbD`2)FVyfNgy|pvT!XY;02Z zZWbgGsvi6#!*$Zxwd{Xk6_M{+^yV_K@%_SAW(x)Lg|*AuG-%g2#GQYk8F?W&8|2dU z;00ppzrQnnYXnT`(S%_qF2#QNz&@Y$zcq+O8p>Gto2&4z8(^#cY?DuQwBQP4Fe?qUK_-yh4xT{8O@gb`uh` z>Q%jrgPAnANn4_)->n;w{Mei#J)F+`12&+-MLKSRzF6bL3;4O~oy~v7 zL0K-=m?>>(^qDCgvFRLBI@`04EGdTxe5}xBg#7#Wb!aUED;?5BLDEvZ@tai4*Rh8& z4V)cOr}DJ0&(FjWH%50Y+&=WtB42^eEVsmaHG)Il#j265oK&Bot(+-IIn`6InmuE# z;)qXs+X{fSb8^rYb#46X5?KCzH9X0>ppBQi(aKS--;4yA%0N|D<#8RZlOS(8n26=u zv~y;KC>`ypW=aqj`&x9 z0Zm>NKp}hPJu1+QDo(_U(Gt0SZ`IJWnp%QK`pye>Bm!w{sG>;VU^2 z4lZhV1}tCE8(?zu#j99|l3-qRBcz3bG+DlyxPGB$^6B^ssc_qYQ6lG0q~EAI?1$?( zahfn%etVvuKwB7R=>JDQluP97nLDM6*5;b0Ox#b{4nIgZA*+?IvyDN{K9WGnlA=Ju z+)6hjr}{;GxQQIDr3*lf32lRp{nHP8uiz^Fa|K+dUc@wD4Kf5RPxVkUZFCdtZH{+=c$AC)G2T-Qn@BPbr zZigIhKhKrVYy`!Mlc#HVr=CURVrhUjExhI~gZ%a=WM9BwvnN?=z!_ZQ$(sP?X;2Jy zyI$}H^^SvH2tf6+Uk$pJww@ngzPp856-l9g6WtW+%Yf>N^A}->#1W2n=WJ%sZ0<){Z&#% z^Kzl$>Km)sIxKLFjtc;}bZeoaZSpL4>`jCmAeRM-NP9sQ&-mi@p0j7Iq>1n&z@8?M z%dM7K^SgE5z)@i5w#rLE4+8%|^J`a6wYr`3BlvdD>7xW?Dd>`0HC0o{w7r_ot~h*G z2gI7Y!AUZ6YN+z$=GNzns@Tu7BxgAb3MBha30-ZG7a%rckU5}y{df`lj@^+34kr5> z988PPbWYdHye~=?>uZ4N&MN@4RBLk_?9W*b$}jqt0j%>yO9QOV(*!#cX~=wRdVL&S zhPQ{${0CGU-rfdS&b@u|IK{hV2Z=(*B2d0?&jwWfT=?Gk`4T9TfMQ)CfNgpLQa#>Q z%6A$w#QNc&qOtrHAbqY>J782@!X{9Y@N(HMSr;PP^;0DlJNxfC`oMB%Ocg zC*hnEsF|p*=CVe^dT)>BTL0yff)uo!U<+_2o3p)CE8quU1JI(=6)9$KxVdJYD*S*~ zzNeSkzFIQyqK}578+qq6X8rrRdgX z4k&R=AGex~a)MoB0pK&|yA<(*J#P&tR?ImBVD)ZTA4VH5L5DxXe<-*s`Aox%H1{-^Qa`kG_DGXD%QX-;l1#&#IVQP6>kir ztO@~ZvJDPnTvKt>fc*(j$W^)JhWk{4kWwbpFIXzuPt2V%M4H19-i5Gn*6(D`4_c1+ zYoI1@yT^~9JF~t>2eVM6p=GP3b*;daJpQOhAMNO|LKnwE2B5n8y9mf;q=)-L_FfD0 z<}YIRBO{k)6AHAn8iG>pYT+3bJ7jvP9}LSMR1nZW$5HR%PD1rFz z{4XE^Vmi-QX#?|Farz=CYS_8!%$E#G%4j2+;Avz|9QBj|YIExYk?y-1(j}0h{$$MnC_*F0U2*ExSi1ZCb_S9aV zTgyGP0Cl=m`emxM4Qih1E{`J{4oJo8K}WnH`@js^pR7Z-vTBK5F5JIFCDN}7pU^_nV>NTz@2$|Kcc5o+L&^Db_AQ);F?)X5BF*QJRCdLI-a%gW z++DZM)x=6*fNrSaUA&hf&CUqC$F*y^CJC-MAm9gd*5#^mh;-dR1?a&<3-hp3@}XN! z&8dcwo6=MQua%0KFvYbi>O{j)RrbDQo3S*y!oEJ~2=}^-v%zn~@hnmKGOvX6JLr;>DNC3)={8OM9n5Zs*(DlS*|%JTniJX2Uav7sOFT0vdIiUOC5pEtY?EF)@Fh9pCfD%N zXskZ8b^ldI{HHj{-l?iWo@IW6Nr`hAS>f8S*8FGc*gmcK^f2JS+>I&r#Gcewy=-JM zv0*w<5qBa6UQB@`esOG*4*t@7c9AkrTpM`v=eY?cO#z17H9B%Xy4m!}LhW}*iZ27w1?HrevgB1SZ1q2X$mm@FK@Qt7o z!s~Lio^IRdwzyvQ80{5iYeTV@mAo=2o5>KepRH0d{*Szlg~n%w2)S5v2|K8}pj;c{ zoDRLvYJO1@?x-=mq+LVhD{l-1-Dw4`7M?3@+ z`fu7?1#9W++6Y46N=H0+bD|CJH~q*CdEBm8D##VS7`cXy4~+x=ZC17rJeBh zI~qW^&FU`+e!{AKO3(>z5Ghh14bUT$=4B>@DVm(cj* zSLA*j!?z!=SLuVvAPh_EFKx}JE8T8;Gx)LH^H136=#Jn3Bo*@?=S`5M{WJPY&~ODs z+^V57DhJ2kD^Z|&;H}eoN~sxS8~cN5u1eW{t&y{!ouH`%p4(yDZaqw$%dlm4A0f0| z8H}XZFDs?3QuqI^PEy}T;r!5+QpfKEt&V|D)Z*xoJ?XXZ+k!sU2X!rcTF4tg8vWPM zr-JE>iu9DZK`#R5gQO{nyGDALY!l@M&eZsc*j*H~l4lD)8S?R*nrdxn?ELUR4kxK? zH(t9IM~^mfPs9WxR>J{agadQg@N6%=tUQ8Bn++TC|Hbqn*q;WydeNIS@gt|3j!P`w zxCKoeKQ*WBlF%l4-apIhERKl(hXS1vVk$U?Wifi)&lL6vF@bmFXmQEe{=$iG)Zt*l z0df@_)B-P_^K2P7h=>OIQ6f0Q-E@|M?$Z5n^oN>2_sBCpN>q(LnqUoef{tm^5^L$# z{<SL zKmH78cHX`4cBKIY8u1x*lwrgP^fJ%E&&AmHrRY7^hH*=2OA9K?!+|~Aeia=nAA`5~ z#zI=h#I>@FXaGk(n)0uqelNY;A5I9obE~OjsuW!%^NxK*52CfBPWYuw--v<1v|B>h z8R=#$TS-Pt3?d@P+xqmYpL4oB8- z>w99}%xqy9W!A^ODfLq8iA@z}10u?o#nG#MXumSaybi(S{`wIM z&nE3n2gWWMu93EvtofWzvG2{v;$ysuw^8q?3n}y=pB1vUr5gi++PjiyBH3jzKBRny zSO~O++1ZLdy7v7VzS&$yY;^Z7*j_#BI`PK`dAzJa9G1{9ahPqPi1C}ti+L)WHii*= z+RZ^+at-tlatc4|akPa&9H;%gn9aS`X_kfb>n>#NTyUVM6m4NCIfLm(28>qaYv7}t zn`M;XcONtXoa3#u3{L-ytd_&g z2mO$8CnE?460w#eSm|smlnNwFHM;A&IxSKLzVkV7nNVqZ*A`)eI{Nbg6WxsarAFuc=FFf1z|%#eTvBgUhY}N zsCT>`_YO>14i^vFX0KXbARLItzT{TeD%N~=ovGtZ6j{>PxkuYlHNTe0!u>rgw#?td z{)n=QrGvgCDE6BUem$Rh(1y!$@(Bn!k3E0|>PQ(8O==zN`?yBhAqlWyq+c%+h?p^- zE&OtLind}^_=>pbhxOgOIC0q9{cLK6p6*eg_|S+p9$W~_u4wzx@N?$QmFg2S)m~^R znni$X{U*!lHgdS@fI;|Owl=9Gwi?dr0m#>yL<8<}bLW_Kpl| zSGesADX&n?qmHC`2GyIev^hi~ka}ISZ^Y4w-yUzyPxaJB0mm%ww^>if3<;P^U+L5=s+cifT-ct*;!dOOk#SOZNv@a^J|DrS3YtSn8EEAlabX1NV3RfHwZn_41Xa z4;$taa6JJR()-FQ<#0G~WlML<l5I+IPnqDpW(PP>hRcQ+S2zU?tbG^(y z1K_?1R){jF;OKGw0WYjnm>aPxnmr5?bP?^B-|Fv`TT4ecH3O`Z3`X_r;vgFn>t1tE zGE6W2PODPKUj+@a%3lB;lS?srE5lp(tZ;uvzrPb){f~n7v_^z! z=16!Vdm!Q0q#?jy0qY%#0d^J8D9o)A;Rj!~j%u>KPs-tB08{4s1ry9VS>gW~5o^L; z7vyjmfXDGRVFa@-mis2!a$GI@9kE*pe3y_C3-$iVGUTQzZE+%>vT0=r|2%xMDBC@>WlkGU4CjoWs@D(rZ zS1NB#e69fvI^O#5r$Hj;bhHPEE4)4q5*t5Gyjzyc{)o459VkEhJ$%hJUC&67k z7gdo`Q*Jm3R&?ueqBezPTa}OI9wqcc;FRTcfVXob^z|dNIB0hMkHV26$zA%YgR$sM zTKM61S}#wJ#u+0UDE3N+U*~Tz1nnV;W<8Akz&6M7-6mIF(Pq`wJ1A%loYL( zIS;&2((xbyL7zoyaY2Sa%BBYBxo6Aa*53`~e@|RA`MP+?iI4KZ+y4EU&I zS_|(#*&j2hxpELa3r0O7ok&5!ijRiRu9i-_3cdnydZU9Mp6Y);skv%!$~`i-J7e-g zj@EoHf+gtcrKf;tY5`4iLnWSHa)9brUM$XmEzG3T0BXTG_+0}p7uGLs^(uYh0j$;~ zT1&~S%_Y5VImvf1EkD7vP-@F%hRlBe{a@T!SW(4WEQd1!O47*Crf@u-TS==48iR5x z!*`Ul4AJI^vIVaN3u5UifXBX{fJ@z>4Q2#1?jpcdLocwymBgKrZ+^Cb@QuIxl58B* zD{t-W3;M;{MGHm_@&n(6A-AsD;JO#>J3o4ru{hy;k;8?=rkp0tadEEcHNECoTI(W31`El-CI0eWQ zWD4&2ehvACkLCjG`82T`L^cNNC4Oo2IH(T4e;C75IwkJ&`|ArqSKD}TX_-E*eeiU& ziUuAC)A?d>-;@9Jcmsdca>@q1`6vzo^3etEH%1Gco&gvC{;Y-qyJ$Re`#A!5Kd((5 z6sSiKnA20uPX0**Mu&6tNgTunUR1sodoNmDst1&wz8v7AG3=^huypTi`S7+GrO$D6 z)0Ja-y5r?QQ+&jVQBjitIZ`z2Ia}iXWf#=#>nU+ zL29$)Q>f#o<#4deo!Kuo@WX{G(`eLaf%(_Nc}E`q=BXHMS(Os{!g%(|&tTDIczE_# z5y%wjCp9S?&*8bS3imJi_9_COC)-_;6D9~8Om@?U2PGQpM^7LKG7Q~(AoSRgP#tZfVDF_zr;_U*!F9qsbVQ@un9O2>T4M5tr0B~~v_@a=w^8h510a#=L z;8+9zhV}57uajb+9DbZm1G`_NqOuKN`bQ2fw9A*v*Kdb_E-SA`?2 z)OFIY-%uD`JZUZg?D4lHtNegKgWr!1m%hOpu5`R+bZ2K#&)*R-7ElKYo0$0xYxIL8 zLg%u|4oZixz}ILB-@aS4=XOe)z!VL6@?dX{LW^YCPjKtyw44)xT=H;h(fmFr>R?p%r5*}W z7_bo0drVDRq9V9QL4_!dazughK6t}tVVvBq={T0+3(1zmb>f+|;{D%J?^xnZcqio5 z%H?@L+L-CIdO=x6QrALL9&PwvjrZi5NS)1e<*%V8ntw~S2PF}zH}B5f_DHyB=I3m@ z_;^TpN|sesCU}qxQ`~jIwF>#8wGvxg9kdMT$}us8BM&W>OzZ|ry2BB)+UY*_yH+&L zl_=Jy9BNzIZs}D~Yv_H%HPjVGNV=xT3xpIW!Np1F^G#9Y8X zl)c_V1(DhYu-v%H3-m&n%M_}}c{E5Wu+6*>R24gW_A7$(U=9D|H$r;;;@o zJ)c_CmVf9l*;4SyJ}E{+4)}^C>SIJ*_bul7OJ{v&0oO>jG(5xzYP0$I%*YH|Mwu#r zubNW5VZ9^X#Phw<;?=^G?Kg&C)^x1FVsKGZ*n+{C1znj~YHSP?6PS(k5e9qGvS4X* z=1kA_27(iV65a(i+Sicmd@Vzf^2@*Wed-`aYQ~em=-h%Pu`gHfz)&@$hpr<&mNO={ zl^kI0HP0wTbbh{d(>5a#;zT2_=ppef?;D4;2^}&kZjB^yl%LBJ;|> zkLc)JEg*5rpQ;_)w?PnKynWtv!@ z>}+am{@(g$KKM+e$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000..cf9be60ca --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Debug.xcconfig b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000..36b0fd946 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Release.xcconfig b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000..dff4f4956 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Warnings.xcconfig b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000..42bcbf478 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/DebugProfile.entitlements b/packages/syncfusion_flutter_officecore/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000..dddb8a30c --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Info.plist b/packages/syncfusion_flutter_officecore/example/macos/Runner/Info.plist new file mode 100644 index 000000000..4789daa6a --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/MainFlutterWindow.swift b/packages/syncfusion_flutter_officecore/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000..2722837ec --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/syncfusion_flutter_officecore/example/macos/Runner/Release.entitlements b/packages/syncfusion_flutter_officecore/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000..852fa1a47 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/syncfusion_flutter_officecore/example/officecore_example.iml b/packages/syncfusion_flutter_officecore/example/officecore_example.iml new file mode 100644 index 000000000..e5c837191 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/officecore_example.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/syncfusion_officecore/example/pubspec.yaml b/packages/syncfusion_flutter_officecore/example/pubspec.yaml similarity index 89% rename from packages/syncfusion_officecore/example/pubspec.yaml rename to packages/syncfusion_flutter_officecore/example/pubspec.yaml index 829637df3..c4754e915 100644 --- a/packages/syncfusion_officecore/example/pubspec.yaml +++ b/packages/syncfusion_flutter_officecore/example/pubspec.yaml @@ -10,7 +10,7 @@ dependencies: path_provider: ^2.0.1 open_file: ^3.0.1 syncfusion_flutter_xlsio: - path: ../../syncfusion_flutter_xlsio + path: ../../syncfusion_flutter_xlsio # The following section is specific to Flutter. diff --git a/packages/syncfusion_flutter_officecore/example/web/favicon.png b/packages/syncfusion_flutter_officecore/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/web/icons/Icon-192.png b/packages/syncfusion_flutter_officecore/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 zcmZ`-2T+sGz6~)*FVZ`aW+(v>MIm&M-g^@e2u-B-DoB?qO+b1Tq<5uCCv>ESfRum& zp%X;f!~1{tzL__3=gjVJ=j=J>+nMj%ncXj1Q(b|Ckbw{Y0FWpt%4y%$uD=Z*c-x~o zE;IoE;xa#7Ll5nj-e4CuXB&G*IM~D21rCP$*xLXAK8rIMCSHuSu%bL&S3)8YI~vyp@KBu9Ph7R_pvKQ@xv>NQ`dZp(u{Z8K3yOB zn7-AR+d2JkW)KiGx0hosml;+eCXp6+w%@STjFY*CJ?udJ64&{BCbuebcuH;}(($@@ znNlgBA@ZXB)mcl9nbX#F!f_5Z=W>0kh|UVWnf!At4V*LQP%*gPdCXd6P@J4Td;!Ur z<2ZLmwr(NG`u#gDEMP19UcSzRTL@HsK+PnIXbVBT@oHm53DZr?~V(0{rsalAfwgo zEh=GviaqkF;}F_5-yA!1u3!gxaR&Mj)hLuj5Q-N-@Lra{%<4ONja8pycD90&>yMB` zchhd>0CsH`^|&TstH-8+R`CfoWqmTTF_0?zDOY`E`b)cVi!$4xA@oO;SyOjJyP^_j zx^@Gdf+w|FW@DMdOi8=4+LJl$#@R&&=UM`)G!y%6ZzQLoSL%*KE8IO0~&5XYR9 z&N)?goEiWA(YoRfT{06&D6Yuu@Qt&XVbuW@COb;>SP9~aRc+z`m`80pB2o%`#{xD@ zI3RAlukL5L>px6b?QW1Ac_0>ew%NM!XB2(H+1Y3AJC?C?O`GGs`331Nd4ZvG~bMo{lh~GeL zSL|tT*fF-HXxXYtfu5z+T5Mx9OdP7J4g%@oeC2FaWO1D{=NvL|DNZ}GO?O3`+H*SI z=grGv=7dL{+oY0eJFGO!Qe(e2F?CHW(i!!XkGo2tUvsQ)I9ev`H&=;`N%Z{L zO?vV%rDv$y(@1Yj@xfr7Kzr<~0{^T8wM80xf7IGQF_S-2c0)0D6b0~yD7BsCy+(zL z#N~%&e4iAwi4F$&dI7x6cE|B{f@lY5epaDh=2-(4N05VO~A zQT3hanGy_&p+7Fb^I#ewGsjyCEUmSCaP6JDB*=_()FgQ(-pZ28-{qx~2foO4%pM9e z*_63RT8XjgiaWY|*xydf;8MKLd{HnfZ2kM%iq}fstImB-K6A79B~YoPVa@tYN@T_$ zea+9)<%?=Fl!kd(Y!G(-o}ko28hg2!MR-o5BEa_72uj7Mrc&{lRh3u2%Y=Xk9^-qa zBPWaD=2qcuJ&@Tf6ue&)4_V*45=zWk@Z}Q?f5)*z)-+E|-yC4fs5CE6L_PH3=zI8p z*Z3!it{1e5_^(sF*v=0{`U9C741&lub89gdhKp|Y8CeC{_{wYK-LSbp{h)b~9^j!s z7e?Y{Z3pZv0J)(VL=g>l;<}xk=T*O5YR|hg0eg4u98f2IrA-MY+StQIuK-(*J6TRR z|IM(%uI~?`wsfyO6Tgmsy1b3a)j6M&-jgUjVg+mP*oTKdHg?5E`!r`7AE_#?Fc)&a z08KCq>Gc=ne{PCbRvs6gVW|tKdcE1#7C4e`M|j$C5EYZ~Y=jUtc zj`+?p4ba3uy7><7wIokM79jPza``{Lx0)zGWg;FW1^NKY+GpEi=rHJ+fVRGfXO zPHV52k?jxei_!YYAw1HIz}y8ZMwdZqU%ESwMn7~t zdI5%B;U7RF=jzRz^NuY9nM)&<%M>x>0(e$GpU9th%rHiZsIT>_qp%V~ILlyt^V`=d z!1+DX@ah?RnB$X!0xpTA0}lN@9V-ePx>wQ?-xrJr^qDlw?#O(RsXeAvM%}rg0NT#t z!CsT;-vB=B87ShG`GwO;OEbeL;a}LIu=&@9cb~Rsx(ZPNQ!NT7H{@j0e(DiLea>QD zPmpe90gEKHEZ8oQ@6%E7k-Ptn#z)b9NbD@_GTxEhbS+}Bb74WUaRy{w;E|MgDAvHw zL)ycgM7mB?XVh^OzbC?LKFMotw3r@i&VdUV%^Efdib)3@soX%vWCbnOyt@Y4swW925@bt45y0HY3YI~BnnzZYrinFy;L?2D3BAL`UQ zEj))+f>H7~g8*VuWQ83EtGcx`hun$QvuurSMg3l4IP8Fe`#C|N6mbYJ=n;+}EQm;< z!!N=5j1aAr_uEnnzrEV%_E|JpTb#1p1*}5!Ce!R@d$EtMR~%9# zd;h8=QGT)KMW2IKu_fA_>p_und#-;Q)p%%l0XZOXQicfX8M~7?8}@U^ihu;mizj)t zgV7wk%n-UOb z#!P5q?Ex+*Kx@*p`o$q8FWL*E^$&1*!gpv?Za$YO~{BHeGY*5%4HXUKa_A~~^d z=E*gf6&+LFF^`j4$T~dR)%{I)T?>@Ma?D!gi9I^HqvjPc3-v~=qpX1Mne@*rzT&Xw zQ9DXsSV@PqpEJO-g4A&L{F&;K6W60D!_vs?Vx!?w27XbEuJJP&);)^+VF1nHqHBWu z^>kI$M9yfOY8~|hZ9WB!q-9u&mKhEcRjlf2nm_@s;0D#c|@ED7NZE% zzR;>P5B{o4fzlfsn3CkBK&`OSb-YNrqx@N#4CK!>bQ(V(D#9|l!e9(%sz~PYk@8zt zPN9oK78&-IL_F zhsk1$6p;GqFbtB^ZHHP+cjMvA0(LqlskbdYE_rda>gvQLTiqOQ1~*7lg%z*&p`Ry& zRcG^DbbPj_jOKHTr8uk^15Boj6>hA2S-QY(W-6!FIq8h$<>MI>PYYRenQDBamO#Fv zAH5&ImqKBDn0v5kb|8i0wFhUBJTpT!rB-`zK)^SNnRmLraZcPYK7b{I@+}wXVdW-{Ps17qdRA3JatEd?rPV z4@}(DAMf5EqXCr4-B+~H1P#;t@O}B)tIJ(W6$LrK&0plTmnPpb1TKn3?f?Kk``?D+ zQ!MFqOX7JbsXfQrz`-M@hq7xlfNz;_B{^wbpG8des56x(Q)H)5eLeDwCrVR}hzr~= zM{yXR6IM?kXxauLza#@#u?Y|o;904HCqF<8yT~~c-xyRc0-vxofnxG^(x%>bj5r}N zyFT+xnn-?B`ohA>{+ZZQem=*Xpqz{=j8i2TAC#x-m;;mo{{sLB_z(UoAqD=A#*juZ zCv=J~i*O8;F}A^Wf#+zx;~3B{57xtoxC&j^ie^?**T`WT2OPRtC`xj~+3Kprn=rVM zVJ|h5ux%S{dO}!mq93}P+h36mZ5aZg1-?vhL$ke1d52qIiXSE(llCr5i=QUS?LIjc zV$4q=-)aaR4wsrQv}^shL5u%6;`uiSEs<1nG^?$kl$^6DL z43CjY`M*p}ew}}3rXc7Xck@k41jx}c;NgEIhKZ*jsBRZUP-x2cm;F1<5$jefl|ppO zmZd%%?gMJ^g9=RZ^#8Mf5aWNVhjAS^|DQO+q$)oeob_&ZLFL(zur$)); zU19yRm)z<4&4-M}7!9+^Wl}Uk?`S$#V2%pQ*SIH5KI-mn%i;Z7-)m$mN9CnI$G7?# zo`zVrUwoSL&_dJ92YhX5TKqaRkfPgC4=Q&=K+;_aDs&OU0&{WFH}kKX6uNQC6%oUH z2DZa1s3%Vtk|bglbxep-w)PbFG!J17`<$g8lVhqD2w;Z0zGsh-r zxZ13G$G<48leNqR!DCVt9)@}(zMI5w6Wo=N zpP1*3DI;~h2WDWgcKn*f!+ORD)f$DZFwgKBafEZmeXQMAsq9sxP9A)7zOYnkHT9JU zRA`umgmP9d6=PHmFIgx=0$(sjb>+0CHG)K@cPG{IxaJ&Ueo8)0RWgV9+gO7+Bl1(F z7!BslJ2MP*PWJ;x)QXbR$6jEr5q3 z(3}F@YO_P1NyTdEXRLU6fp?9V2-S=E+YaeLL{Y)W%6`k7$(EW8EZSA*(+;e5@jgD^I zaJQ2|oCM1n!A&-8`;#RDcZyk*+RPkn_r8?Ak@agHiSp*qFNX)&i21HE?yuZ;-C<3C zwJGd1lx5UzViP7sZJ&|LqH*mryb}y|%AOw+v)yc`qM)03qyyrqhX?ub`Cjwx2PrR! z)_z>5*!*$x1=Qa-0uE7jy0z`>|Ni#X+uV|%_81F7)b+nf%iz=`fF4g5UfHS_?PHbr zB;0$bK@=di?f`dS(j{l3-tSCfp~zUuva+=EWxJcRfp(<$@vd(GigM&~vaYZ0c#BTs z3ijkxMl=vw5AS&DcXQ%eeKt!uKvh2l3W?&3=dBHU=Gz?O!40S&&~ei2vg**c$o;i89~6DVns zG>9a*`k5)NI9|?W!@9>rzJ;9EJ=YlJTx1r1BA?H`LWijk(rTax9(OAu;q4_wTj-yj z1%W4GW&K4T=uEGb+E!>W0SD_C0RR91 literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/web/icons/Icon-512.png b/packages/syncfusion_flutter_officecore/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 zcmd5=2T+s!lYZ%-(h(2@5fr2dC?F^$C=i-}R6$UX8af(!je;W5yC_|HmujSgN*6?W z3knF*TL1$|?oD*=zPbBVex*RUIKsL<(&Rj9%^UD2IK3W?2j>D?eWQgvS-HLymHo9%~|N2Q{~j za?*X-{b9JRowv_*Mh|;*-kPFn>PI;r<#kFaxFqbn?aq|PduQg=2Q;~Qc}#z)_T%x9 zE|0!a70`58wjREmAH38H1)#gof)U3g9FZ^ zF7&-0^Hy{4XHWLoC*hOG(dg~2g6&?-wqcpf{ z&3=o8vw7lMi22jCG9RQbv8H}`+}9^zSk`nlR8?Z&G2dlDy$4#+WOlg;VHqzuE=fM@ z?OI6HEJH4&tA?FVG}9>jAnq_^tlw8NbjNhfqk2rQr?h(F&WiKy03Sn=-;ZJRh~JrD zbt)zLbnabttEZ>zUiu`N*u4sfQaLE8-WDn@tHp50uD(^r-}UsUUu)`!Rl1PozAc!a z?uj|2QDQ%oV-jxUJmJycySBINSKdX{kDYRS=+`HgR2GO19fg&lZKyBFbbXhQV~v~L za^U944F1_GtuFXtvDdDNDvp<`fqy);>Vw=ncy!NB85Tw{&sT5&Ox%-p%8fTS;OzlRBwErvO+ROe?{%q-Zge=%Up|D4L#>4K@Ke=x%?*^_^P*KD zgXueMiS63!sEw@fNLB-i^F|@Oib+S4bcy{eu&e}Xvb^(mA!=U=Xr3||IpV~3K zQWzEsUeX_qBe6fky#M zzOJm5b+l;~>=sdp%i}}0h zO?B?i*W;Ndn02Y0GUUPxERG`3Bjtj!NroLoYtyVdLtl?SE*CYpf4|_${ku2s`*_)k zN=a}V8_2R5QANlxsq!1BkT6$4>9=-Ix4As@FSS;1q^#TXPrBsw>hJ}$jZ{kUHoP+H zvoYiR39gX}2OHIBYCa~6ERRPJ#V}RIIZakUmuIoLF*{sO8rAUEB9|+A#C|@kw5>u0 zBd=F!4I)Be8ycH*)X1-VPiZ+Ts8_GB;YW&ZFFUo|Sw|x~ZajLsp+_3gv((Q#N>?Jz zFBf`~p_#^${zhPIIJY~yo!7$-xi2LK%3&RkFg}Ax)3+dFCjGgKv^1;lUzQlPo^E{K zmCnrwJ)NuSaJEmueEPO@(_6h3f5mFffhkU9r8A8(JC5eOkux{gPmx_$Uv&|hyj)gN zd>JP8l2U&81@1Hc>#*su2xd{)T`Yw< zN$dSLUN}dfx)Fu`NcY}TuZ)SdviT{JHaiYgP4~@`x{&h*Hd>c3K_To9BnQi@;tuoL z%PYQo&{|IsM)_>BrF1oB~+`2_uZQ48z9!)mtUR zdfKE+b*w8cPu;F6RYJiYyV;PRBbThqHBEu_(U{(gGtjM}Zi$pL8Whx}<JwE3RM0F8x7%!!s)UJVq|TVd#hf1zVLya$;mYp(^oZQ2>=ZXU1c$}f zm|7kfk>=4KoQoQ!2&SOW5|JP1)%#55C$M(u4%SP~tHa&M+=;YsW=v(Old9L3(j)`u z2?#fK&1vtS?G6aOt@E`gZ9*qCmyvc>Ma@Q8^I4y~f3gs7*d=ATlP>1S zyF=k&6p2;7dn^8?+!wZO5r~B+;@KXFEn^&C=6ma1J7Au6y29iMIxd7#iW%=iUzq&C=$aPLa^Q zncia$@TIy6UT@69=nbty5epP>*fVW@5qbUcb2~Gg75dNd{COFLdiz3}kODn^U*=@E z0*$7u7Rl2u)=%fk4m8EK1ctR!6%Ve`e!O20L$0LkM#f+)n9h^dn{n`T*^~d+l*Qlx z$;JC0P9+en2Wlxjwq#z^a6pdnD6fJM!GV7_%8%c)kc5LZs_G^qvw)&J#6WSp< zmsd~1-(GrgjC56Pdf6#!dt^y8Rg}!#UXf)W%~PeU+kU`FeSZHk)%sFv++#Dujk-~m zFHvVJC}UBn2jN& zs!@nZ?e(iyZPNo`p1i#~wsv9l@#Z|ag3JR>0#u1iW9M1RK1iF6-RbJ4KYg?B`dET9 zyR~DjZ>%_vWYm*Z9_+^~hJ_|SNTzBKx=U0l9 z9x(J96b{`R)UVQ$I`wTJ@$_}`)_DyUNOso6=WOmQKI1e`oyYy1C&%AQU<0-`(ow)1 zT}gYdwWdm4wW6|K)LcfMe&psE0XGhMy&xS`@vLi|1#Za{D6l@#D!?nW87wcscUZgELT{Cz**^;Zb~7 z(~WFRO`~!WvyZAW-8v!6n&j*PLm9NlN}BuUN}@E^TX*4Or#dMMF?V9KBeLSiLO4?B zcE3WNIa-H{ThrlCoN=XjOGk1dT=xwwrmt<1a)mrRzg{35`@C!T?&_;Q4Ce=5=>z^*zE_c(0*vWo2_#TD<2)pLXV$FlwP}Ik74IdDQU@yhkCr5h zn5aa>B7PWy5NQ!vf7@p_qtC*{dZ8zLS;JetPkHi>IvPjtJ#ThGQD|Lq#@vE2xdl%`x4A8xOln}BiQ92Po zW;0%A?I5CQ_O`@Ad=`2BLPPbBuPUp@Hb%a_OOI}y{Rwa<#h z5^6M}s7VzE)2&I*33pA>e71d78QpF>sNK;?lj^Kl#wU7G++`N_oL4QPd-iPqBhhs| z(uVM}$ItF-onXuuXO}o$t)emBO3Hjfyil@*+GF;9j?`&67GBM;TGkLHi>@)rkS4Nj zAEk;u)`jc4C$qN6WV2dVd#q}2X6nKt&X*}I@jP%Srs%%DS92lpDY^K*Sx4`l;aql$ zt*-V{U&$DM>pdO?%jt$t=vg5|p+Rw?SPaLW zB6nvZ69$ne4Z(s$3=Rf&RX8L9PWMV*S0@R zuIk&ba#s6sxVZ51^4Kon46X^9`?DC9mEhWB3f+o4#2EXFqy0(UTc>GU| zGCJmI|Dn-dX#7|_6(fT)>&YQ0H&&JX3cTvAq(a@ydM4>5Njnuere{J8p;3?1az60* z$1E7Yyxt^ytULeokgDnRVKQw9vzHg1>X@@jM$n$HBlveIrKP5-GJq%iWH#odVwV6cF^kKX(@#%%uQVb>#T6L^mC@)%SMd4DF? zVky!~ge27>cpUP1Vi}Z32lbLV+CQy+T5Wdmva6Fg^lKb!zrg|HPU=5Qu}k;4GVH+x z%;&pN1LOce0w@9i1Mo-Y|7|z}fbch@BPp2{&R-5{GLoeu8@limQmFF zaJRR|^;kW_nw~0V^ zfTnR!Ni*;-%oSHG1yItARs~uxra|O?YJxBzLjpeE-=~TO3Dn`JL5Gz;F~O1u3|FE- zvK2Vve`ylc`a}G`gpHg58Cqc9fMoy1L}7x7T>%~b&irrNMo?np3`q;d3d;zTK>nrK zOjPS{@&74-fA7j)8uT9~*g23uGnxwIVj9HorzUX#s0pcp2?GH6i}~+kv9fWChtPa_ z@T3m+$0pbjdQw7jcnHn;Pi85hk_u2-1^}c)LNvjdam8K-XJ+KgKQ%!?2n_!#{$H|| zLO=%;hRo6EDmnOBKCL9Cg~ETU##@u^W_5joZ%Et%X_n##%JDOcsO=0VL|Lkk!VdRJ z^|~2pB@PUspT?NOeO?=0Vb+fAGc!j%Ufn-cB`s2A~W{Zj{`wqWq_-w0wr@6VrM zbzni@8c>WS!7c&|ZR$cQ;`niRw{4kG#e z70e!uX8VmP23SuJ*)#(&R=;SxGAvq|&>geL&!5Z7@0Z(No*W561n#u$Uc`f9pD70# z=sKOSK|bF~#khTTn)B28h^a1{;>EaRnHj~>i=Fnr3+Fa4 z`^+O5_itS#7kPd20rq66_wH`%?HNzWk@XFK0n;Z@Cx{kx==2L22zWH$Yg?7 zvDj|u{{+NR3JvUH({;b*$b(U5U z7(lF!1bz2%06+|-v(D?2KgwNw7( zJB#Tz+ZRi&U$i?f34m7>uTzO#+E5cbaiQ&L}UxyOQq~afbNB4EI{E04ZWg53w0A{O%qo=lF8d zf~ktGvIgf-a~zQoWf>loF7pOodrd0a2|BzwwPDV}ShauTK8*fmF6NRbO>Iw9zZU}u zw8Ya}?seBnEGQDmH#XpUUkj}N49tP<2jYwTFp!P+&Fd(%Z#yo80|5@zN(D{_pNow*&4%ql zW~&yp@scb-+Qj-EmErY+Tu=dUmf@*BoXY2&oKT8U?8?s1d}4a`Aq>7SV800m$FE~? zjmz(LY+Xx9sDX$;vU`xgw*jLw7dWOnWWCO8o|;}f>cu0Q&`0I{YudMn;P;L3R-uz# zfns_mZED_IakFBPP2r_S8XM$X)@O-xVKi4`7373Jkd5{2$M#%cRhWer3M(vr{S6>h zj{givZJ3(`yFL@``(afn&~iNx@B1|-qfYiZu?-_&Z8+R~v`d6R-}EX9IVXWO-!hL5 z*k6T#^2zAXdardU3Ao~I)4DGdAv2bx{4nOK`20rJo>rmk3S2ZDu}))8Z1m}CKigf0 z3L`3Y`{huj`xj9@`$xTZzZc3je?n^yG<8sw$`Y%}9mUsjUR%T!?k^(q)6FH6Af^b6 zlPg~IEwg0y;`t9y;#D+uz!oE4VP&Je!<#q*F?m5L5?J3i@!0J6q#eu z!RRU`-)HeqGi_UJZ(n~|PSNsv+Wgl{P-TvaUQ9j?ZCtvb^37U$sFpBrkT{7Jpd?HpIvj2!}RIq zH{9~+gErN2+}J`>Jvng2hwM`=PLNkc7pkjblKW|+Fk9rc)G1R>Ww>RC=r-|!m-u7( zc(a$9NG}w#PjWNMS~)o=i~WA&4L(YIW25@AL9+H9!?3Y}sv#MOdY{bb9j>p`{?O(P zIvb`n?_(gP2w3P#&91JX*md+bBEr%xUHMVqfB;(f?OPtMnAZ#rm5q5mh;a2f_si2_ z3oXWB?{NF(JtkAn6F(O{z@b76OIqMC$&oJ_&S|YbFJ*)3qVX_uNf5b8(!vGX19hsG z(OP>RmZp29KH9Ge2kKjKigUmOe^K_!UXP`von)PR8Qz$%=EmOB9xS(ZxE_tnyzo}7 z=6~$~9k0M~v}`w={AeqF?_)9q{m8K#6M{a&(;u;O41j)I$^T?lx5(zlebpY@NT&#N zR+1bB)-1-xj}R8uwqwf=iP1GbxBjneCC%UrSdSxK1vM^i9;bUkS#iRZw2H>rS<2<$ zNT3|sDH>{tXb=zq7XZi*K?#Zsa1h1{h5!Tq_YbKFm_*=A5-<~j63he;4`77!|LBlo zR^~tR3yxcU=gDFbshyF6>o0bdp$qmHS7D}m3;^QZq9kBBU|9$N-~oU?G5;jyFR7>z hN`IR97YZXIo@y!QgFWddJ3|0`sjFx!m))><{BI=FK%f8s literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/web/index.html b/packages/syncfusion_flutter_officecore/example/web/index.html new file mode 100644 index 000000000..0081e1894 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/web/index.html @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + example + + + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/web/manifest.json b/packages/syncfusion_flutter_officecore/example/web/manifest.json new file mode 100644 index 000000000..8c012917d --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/packages/syncfusion_flutter_officecore/example/windows/.gitignore b/packages/syncfusion_flutter_officecore/example/windows/.gitignore new file mode 100644 index 000000000..d492d0d98 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/packages/syncfusion_flutter_officecore/example/windows/CMakeLists.txt b/packages/syncfusion_flutter_officecore/example/windows/CMakeLists.txt new file mode 100644 index 000000000..abf90408e --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/CMakeLists.txt @@ -0,0 +1,95 @@ +cmake_minimum_required(VERSION 3.15) +project(example LANGUAGES CXX) + +set(BINARY_NAME "example") + +cmake_policy(SET CMP0063 NEW) + +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Configure build options. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() + +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") + +# Flutter library and tool build rules. +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/packages/syncfusion_flutter_officecore/example/windows/flutter/CMakeLists.txt b/packages/syncfusion_flutter_officecore/example/windows/flutter/CMakeLists.txt new file mode 100644 index 000000000..b02c5485c --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.15) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 000000000..8b6d4680a --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 000000000..dc139d85a --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugins.cmake b/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000..4d10c2518 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,15 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/CMakeLists.txt b/packages/syncfusion_flutter_officecore/example/windows/runner/CMakeLists.txt new file mode 100644 index 000000000..977e38b5d --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.15) +project(runner LANGUAGES CXX) + +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "run_loop.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) +apply_standard_settings(${BINARY_NAME}) +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/Runner.rc b/packages/syncfusion_flutter_officecore/example/windows/runner/Runner.rc new file mode 100644 index 000000000..51812dcd4 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "A new Flutter project." "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.cpp b/packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.cpp new file mode 100644 index 000000000..41bbc5e03 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.cpp @@ -0,0 +1,64 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(RunLoop* run_loop, + const flutter::DartProject& project) + : run_loop_(run_loop), project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.h b/packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.h new file mode 100644 index 000000000..b663ddd50 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/flutter_window.h @@ -0,0 +1,39 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "run_loop.h" +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow driven by the |run_loop|, hosting a + // Flutter view running |project|. + explicit FlutterWindow(RunLoop* run_loop, + const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The run loop driving events for this window. + RunLoop* run_loop_; + + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/main.cpp b/packages/syncfusion_flutter_officecore/example/windows/runner/main.cpp new file mode 100644 index 000000000..b637809bd --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/main.cpp @@ -0,0 +1,42 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "run_loop.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + RunLoop run_loop; + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(&run_loop, project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + run_loop.Run(); + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/resource.h b/packages/syncfusion_flutter_officecore/example/windows/runner/resource.h new file mode 100644 index 000000000..66a65d1e4 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/resources/app_icon.ico b/packages/syncfusion_flutter_officecore/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 zcmeHQc|26z|35SKE&G-*mXah&B~fFkXr)DEO&hIfqby^T&>|8^_Ub8Vp#`BLl3lbZ zvPO!8k!2X>cg~Elr=IVxo~J*a`+9wR=A83c-k-DFd(XM&UI1VKCqM@V;DDtJ09WB} zRaHKiW(GT00brH|0EeTeKVbpbGZg?nK6-j827q-+NFM34gXjqWxJ*a#{b_apGN<-L_m3#8Z26atkEn& ze87Bvv^6vVmM+p+cQ~{u%=NJF>#(d;8{7Q{^rWKWNtf14H}>#&y7$lqmY6xmZryI& z($uy?c5-+cPnt2%)R&(KIWEXww>Cnz{OUpT>W$CbO$h1= z#4BPMkFG1Y)x}Ui+WXr?Z!w!t_hjRq8qTaWpu}FH{MsHlU{>;08goVLm{V<&`itk~ zE_Ys=D(hjiy+5=?=$HGii=Y5)jMe9|wWoD_K07(}edAxh`~LBorOJ!Cf@f{_gNCC| z%{*04ViE!#>@hc1t5bb+NO>ncf@@Dv01K!NxH$3Eg1%)|wLyMDF8^d44lV!_Sr}iEWefOaL z8f?ud3Q%Sen39u|%00W<#!E=-RpGa+H8}{ulxVl4mwpjaU+%2pzmi{3HM)%8vb*~-M9rPUAfGCSos8GUXp02|o~0BTV2l#`>>aFV&_P$ejS;nGwSVP8 zMbOaG7<7eKD>c12VdGH;?2@q7535sa7MN*L@&!m?L`ASG%boY7(&L5imY#EQ$KrBB z4@_tfP5m50(T--qv1BJcD&aiH#b-QC>8#7Fx@3yXlonJI#aEIi=8&ChiVpc#N=5le zM*?rDIdcpawoc5kizv$GEjnveyrp3sY>+5_R5;>`>erS%JolimF=A^EIsAK zsPoVyyUHCgf0aYr&alx`<)eb6Be$m&`JYSuBu=p8j%QlNNp$-5C{b4#RubPb|CAIS zGE=9OFLP7?Hgc{?k45)84biT0k&-C6C%Q}aI~q<(7BL`C#<6HyxaR%!dFx7*o^laG z=!GBF^cwK$IA(sn9y6>60Rw{mYRYkp%$jH z*xQM~+bp)G$_RhtFPYx2HTsWk80+p(uqv9@I9)y{b$7NK53rYL$ezbmRjdXS?V}fj zWxX_feWoLFNm3MG7pMUuFPs$qrQWO9!l2B(SIuy2}S|lHNbHzoE+M2|Zxhjq9+Ws8c{*}x^VAib7SbxJ*Q3EnY5lgI9 z=U^f3IW6T=TWaVj+2N%K3<%Un;CF(wUp`TC&Y|ZjyFu6co^uqDDB#EP?DV5v_dw~E zIRK*BoY9y-G_ToU2V_XCX4nJ32~`czdjT!zwme zGgJ0nOk3U4@IE5JwtM}pwimLjk{ln^*4HMU%Fl4~n(cnsLB}Ja-jUM>xIB%aY;Nq8 z)Fp8dv1tkqKanv<68o@cN|%thj$+f;zGSO7H#b+eMAV8xH$hLggtt?O?;oYEgbq@= zV(u9bbd12^%;?nyk6&$GPI%|+<_mEpJGNfl*`!KV;VfmZWw{n{rnZ51?}FDh8we_L z8OI9nE31skDqJ5Oa_ybn7|5@ui>aC`s34p4ZEu6-s!%{uU45$Zd1=p$^^dZBh zu<*pDDPLW+c>iWO$&Z_*{VSQKg7=YEpS3PssPn1U!lSm6eZIho*{@&20e4Y_lRklKDTUCKI%o4Pc<|G^Xgu$J^Q|B87U;`c1zGwf^-zH*VQ^x+i^OUWE0yd z;{FJq)2w!%`x7yg@>uGFFf-XJl4H`YtUG%0slGKOlXV`q?RP>AEWg#x!b{0RicxGhS!3$p7 zij;{gm!_u@D4$Ox%>>bPtLJ> zwKtYz?T_DR1jN>DkkfGU^<#6sGz|~p*I{y`aZ>^Di#TC|Z!7j_O1=Wo8thuit?WxR zh9_S>kw^{V^|g}HRUF=dcq>?q(pHxw!8rx4dC6vbQVmIhmICF#zU!HkHpQ>9S%Uo( zMw{eC+`&pb=GZRou|3;Po1}m46H6NGd$t<2mQh}kaK-WFfmj_66_17BX0|j-E2fe3Jat}ijpc53 zJV$$;PC<5aW`{*^Z6e5##^`Ed#a0nwJDT#Qq~^e8^JTA=z^Kl>La|(UQ!bI@#ge{Dzz@61p-I)kc2?ZxFt^QQ}f%ldLjO*GPj(5)V9IyuUakJX=~GnTgZ4$5!3E=V#t`yOG4U z(gphZB6u2zsj=qNFLYShhg$}lNpO`P9xOSnO*$@@UdMYES*{jJVj|9z-}F^riksLK zbsU+4-{281P9e2UjY6tse^&a)WM1MFw;p#_dHhWI7p&U*9TR0zKdVuQed%6{otTsq z$f~S!;wg#Bd9kez=Br{m|66Wv z#g1xMup<0)H;c2ZO6su_ii&m8j&+jJz4iKnGZ&wxoQX|5a>v&_e#6WA!MB_4asTxLRGQCC5cI(em z%$ZfeqP>!*q5kU>a+BO&ln=4Jm>Ef(QE8o&RgLkk%2}4Tf}U%IFP&uS7}&|Q-)`5< z+e>;s#4cJ-z%&-^&!xsYx777Wt(wZY9(3(avmr|gRe4cD+a8&!LY`1^T?7x{E<=kdY9NYw>A;FtTvQ=Y&1M%lyZPl$ss1oY^Sl8we}n}Aob#6 zl4jERwnt9BlSoWb@3HxYgga(752Vu6Y)k4yk9u~Kw>cA5&LHcrvn1Y-HoIuFWg~}4 zEw4bR`mXZQIyOAzo)FYqg?$5W<;^+XX%Uz61{-L6@eP|lLH%|w?g=rFc;OvEW;^qh z&iYXGhVt(G-q<+_j}CTbPS_=K>RKN0&;dubh0NxJyDOHFF;<1k!{k#7b{|Qok9hac z;gHz}6>H6C6RnB`Tt#oaSrX0p-j-oRJ;_WvS-qS--P*8}V943RT6kou-G=A+7QPGQ z!ze^UGxtW3FC0$|(lY9^L!Lx^?Q8cny(rR`es5U;-xBhphF%_WNu|aO<+e9%6LuZq zt(0PoagJG<%hyuf;te}n+qIl_Ej;czWdc{LX^pS>77s9t*2b4s5dvP_!L^3cwlc)E!(!kGrg~FescVT zZCLeua3f4;d;Tk4iXzt}g}O@nlK3?_o91_~@UMIl?@77Qc$IAlLE95#Z=TES>2E%z zxUKpK{_HvGF;5%Q7n&vA?`{%8ohlYT_?(3A$cZSi)MvIJygXD}TS-3UwyUxGLGiJP znblO~G|*uA^|ac8E-w#}uBtg|s_~s&t>-g0X%zIZ@;o_wNMr_;{KDg^O=rg`fhDZu zFp(VKd1Edj%F zWHPl+)FGj%J1BO3bOHVfH^3d1F{)*PL&sRX`~(-Zy3&9UQX)Z;c51tvaI2E*E7!)q zcz|{vpK7bjxix(k&6=OEIBJC!9lTkUbgg?4-yE{9+pFS)$Ar@vrIf`D0Bnsed(Cf? zObt2CJ>BKOl>q8PyFO6w)+6Iz`LW%T5^R`U_NIW0r1dWv6OY=TVF?N=EfA(k(~7VBW(S;Tu5m4Lg8emDG-(mOSSs=M9Q&N8jc^Y4&9RqIsk(yO_P(mcCr}rCs%1MW1VBrn=0-oQN(Xj!k%iKV zb%ricBF3G4S1;+8lzg5PbZ|$Se$)I=PwiK=cDpHYdov2QO1_a-*dL4KUi|g&oh>(* zq$<`dQ^fat`+VW?m)?_KLn&mp^-@d=&7yGDt<=XwZZC=1scwxO2^RRI7n@g-1o8ps z)&+et_~)vr8aIF1VY1Qrq~Xe``KJrQSnAZ{CSq3yP;V*JC;mmCT6oRLSs7=GA?@6g zUooM}@tKtx(^|aKK8vbaHlUQqwE0}>j&~YlN3H#vKGm@u)xxS?n9XrOWUfCRa< z`20Fld2f&;gg7zpo{Adh+mqNntMc-D$N^yWZAZRI+u1T1zWHPxk{+?vcS1D>08>@6 zLhE@`gt1Y9mAK6Z4p|u(5I%EkfU7rKFSM=E4?VG9tI;a*@?6!ey{lzN5=Y-!$WFSe z&2dtO>^0@V4WRc#L&P%R(?@KfSblMS+N+?xUN$u3K4Ys%OmEh+tq}fnU}i>6YHM?< zlnL2gl~sF!j!Y4E;j3eIU-lfa`RsOL*Tt<%EFC0gPzoHfNWAfKFIKZN8}w~(Yi~=q z>=VNLO2|CjkxP}RkutxjV#4fWYR1KNrPYq5ha9Wl+u>ipsk*I(HS@iLnmGH9MFlTU zaFZ*KSR0px>o+pL7BbhB2EC1%PJ{67_ z#kY&#O4@P=OV#-79y_W>Gv2dxL*@G7%LksNSqgId9v;2xJ zrh8uR!F-eU$NMx@S*+sk=C~Dxr9Qn7TfWnTupuHKuQ$;gGiBcU>GF5sWx(~4IP3`f zWE;YFO*?jGwYh%C3X<>RKHC-DZ!*r;cIr}GLOno^3U4tFSSoJp%oHPiSa%nh=Zgn% z14+8v@ygy0>UgEN1bczD6wK45%M>psM)y^)IfG*>3ItX|TzV*0i%@>L(VN!zdKb8S?Qf7BhjNpziA zR}?={-eu>9JDcl*R=OP9B8N$IcCETXah9SUDhr{yrld{G;PnCWRsPD7!eOOFBTWUQ=LrA_~)mFf&!zJX!Oc-_=kT<}m|K52 z)M=G#;p;Rdb@~h5D{q^K;^fX-m5V}L%!wVC2iZ1uu401Ll}#rocTeK|7FAeBRhNdQ zCc2d^aQnQp=MpOmak60N$OgS}a;p(l9CL`o4r(e-nN}mQ?M&isv-P&d$!8|1D1I(3-z!wi zTgoo)*Mv`gC?~bm?S|@}I|m-E2yqPEvYybiD5azInexpK8?9q*$9Yy9-t%5jU8~ym zgZDx>!@ujQ=|HJnwp^wv-FdD{RtzO9SnyfB{mH_(c!jHL*$>0o-(h(eqe*ZwF6Lvu z{7rkk%PEqaA>o+f{H02tzZ@TWy&su?VNw43! z-X+rN`6llvpUms3ZiSt)JMeztB~>9{J8SPmYs&qohxdYFi!ra8KR$35Zp9oR)eFC4 zE;P31#3V)n`w$fZ|4X-|%MX`xZDM~gJyl2W;O$H25*=+1S#%|53>|LyH za@yh+;325%Gq3;J&a)?%7X%t@WXcWL*BaaR*7UEZad4I8iDt7^R_Fd`XeUo256;sAo2F!HcIQKk;h})QxEsPE5BcKc7WyerTchgKmrfRX z!x#H_%cL#B9TWAqkA4I$R^8{%do3Y*&(;WFmJ zU7Dih{t1<{($VtJRl9|&EB?|cJ)xse!;}>6mSO$o5XIx@V|AA8ZcoD88ZM?C*;{|f zZVmf94_l1OmaICt`2sTyG!$^UeTHx9YuUP!omj(r|7zpm5475|yXI=rR>>fteLI+| z)MoiGho0oEt=*J(;?VY0QzwCqw@cVm?d7Y!z0A@u#H?sCJ*ecvyhj& z-F77lO;SH^dmf?L>3i>?Z*U}Em4ZYV_CjgfvzYsRZ+1B!Uo6H6mbS<-FFL`ytqvb& zE7+)2ahv-~dz(Hs+f})z{*4|{)b=2!RZK;PWwOnO=hG7xG`JU5>bAvUbdYd_CjvtHBHgtGdlO+s^9ca^Bv3`t@VRX2_AD$Ckg36OcQRF zXD6QtGfHdw*hx~V(MV-;;ZZF#dJ-piEF+s27z4X1qi5$!o~xBnvf=uopcn7ftfsZc zy@(PuOk`4GL_n(H9(E2)VUjqRCk9kR?w)v@xO6Jm_Mx})&WGEl=GS0#)0FAq^J*o! zAClhvoTsNP*-b~rN{8Yym3g{01}Ep^^Omf=SKqvN?{Q*C4HNNAcrowIa^mf+3PRy! z*_G-|3i8a;+q;iP@~Of_$(vtFkB8yOyWt2*K)vAn9El>=D;A$CEx6b*XF@4y_6M+2 zpeW`RHoI_p(B{%(&jTHI->hmNmZjHUj<@;7w0mx3&koy!2$@cfX{sN19Y}euYJFn& z1?)+?HCkD0MRI$~uB2UWri})0bru_B;klFdwsLc!ne4YUE;t41JqfG# zZJq6%vbsdx!wYeE<~?>o4V`A3?lN%MnKQ`z=uUivQN^vzJ|C;sdQ37Qn?;lpzg})y z)_2~rUdH}zNwX;Tp0tJ78+&I=IwOQ-fl30R79O8@?Ub8IIA(6I`yHn%lARVL`%b8+ z4$8D-|MZZWxc_)vu6@VZN!HsI$*2NOV&uMxBNzIbRgy%ob_ zhwEH{J9r$!dEix9XM7n&c{S(h>nGm?el;gaX0@|QnzFD@bne`el^CO$yXC?BDJ|Qg z+y$GRoR`?ST1z^e*>;!IS@5Ovb7*RlN>BV_UC!7E_F;N#ky%1J{+iixp(dUJj93aK zzHNN>R-oN7>kykHClPnoPTIj7zc6KM(Pnlb(|s??)SMb)4!sMHU^-ntJwY5Big7xv zb1Ew`Xj;|D2kzGja*C$eS44(d&RMU~c_Y14V9_TLTz0J#uHlsx`S6{nhsA0dWZ#cG zJ?`fO50E>*X4TQLv#nl%3GOk*UkAgt=IY+u0LNXqeln3Z zv$~&Li`ZJOKkFuS)dJRA>)b_Da%Q~axwA_8zNK{BH{#}#m}zGcuckz}riDE-z_Ms> zR8-EqAMcfyGJCtvTpaUVQtajhUS%c@Yj}&6Zz;-M7MZzqv3kA7{SuW$oW#=0az2wQ zg-WG@Vb4|D`pl~Il54N7Hmsauc_ne-a!o5#j3WaBBh@Wuefb!QJIOn5;d)%A#s+5% zuD$H=VNux9bE-}1&bcYGZ+>1Fo;3Z@e&zX^n!?JK*adSbONm$XW9z;Q^L>9U!}Toj2WdafJ%oL#h|yWWwyAGxzfrAWdDTtaKl zK4`5tDpPg5>z$MNv=X0LZ0d6l%D{(D8oT@+w0?ce$DZ6pv>{1&Ok67Ix1 zH}3=IEhPJEhItCC8E=`T`N5(k?G=B4+xzZ?<4!~ ze~z6Wk9!CHTI(0rLJ4{JU?E-puc;xusR?>G?;4vt;q~iI9=kDL=z0Rr%O$vU`30X$ zDZRFyZ`(omOy@u|i6h;wtJlP;+}$|Ak|k2dea7n?U1*$T!sXqqOjq^NxLPMmk~&qI zYg0W?yK8T(6+Ea+$YyspKK?kP$+B`~t3^Pib_`!6xCs32!i@pqXfFV6PmBIR<-QW= zN8L{pt0Vap0x`Gzn#E@zh@H)0FfVfA_Iu4fjYZ+umO1LXIbVc$pY+E234u)ttcrl$ z>s92z4vT%n6cMb>=XT6;l0+9e(|CZG)$@C7t7Z7Ez@a)h)!hyuV&B5K%%)P5?Lk|C zZZSVzdXp{@OXSP0hoU-gF8s8Um(#xzjP2Vem zec#-^JqTa&Y#QJ>-FBxd7tf`XB6e^JPUgagB8iBSEps;92KG`!#mvVcPQ5yNC-GEG zTiHEDYfH+0O15}r^+ z#jxj=@x8iNHWALe!P3R67TwmhItn**0JwnzSV2O&KE8KcT+0hWH^OPD1pwiuyx=b@ zNf5Jh0{9X)8;~Es)$t@%(3!OnbY+`@?i{mGX7Yy}8T_*0a6g;kaFPq;*=px5EhO{Cp%1kI<0?*|h8v!6WnO3cCJRF2-CRrU3JiLJnj@6;L)!0kWYAc_}F{2P))3HmCrz zQ&N&gE70;`!6*eJ4^1IR{f6j4(-l&X!tjHxkbHA^Zhrnhr9g{exN|xrS`5Pq=#Xf& zG%P=#ra-TyVFfgW%cZo5OSIwFL9WtXAlFOa+ubmI5t*3=g#Y zF%;70p5;{ZeFL}&}yOY1N1*Q;*<(kTB!7vM$QokF)yr2FlIU@$Ph58$Bz z0J?xQG=MlS4L6jA22eS42g|9*9pX@$#*sUeM(z+t?hr@r5J&D1rx}2pW&m*_`VDCW zUYY@v-;bAO0HqoAgbbiGGC<=ryf96}3pouhy3XJrX+!!u*O_>Si38V{uJmQ&USptX zKp#l(?>%^7;2%h(q@YWS#9;a!JhKlkR#Vd)ERILlgu!Hr@jA@V;sk4BJ-H#p*4EqC zDGjC*tl=@3Oi6)Bn^QwFpul18fpkbpg0+peH$xyPBqb%`$OUhPKyWb32o7clB*9Z< zN=i~NLjavrLtwgJ01bufP+>p-jR2I95|TpmKpQL2!oV>g(4RvS2pK4*ou%m(h6r3A zX#s&`9LU1ZG&;{CkOK!4fLDTnBys`M!vuz>Q&9OZ0hGQl!~!jSDg|~s*w52opC{sB ze|Cf2luD(*G13LcOAGA!s2FjSK8&IE5#W%J25w!vM0^VyQM!t)inj&RTiJ!wXzFgz z3^IqzB7I0L$llljsGq})thBy9UOyjtFO_*hYM_sgcMk>44jeH0V1FDyELc{S1F-;A zS;T^k^~4biG&V*Irq}O;e}j$$+E_#G?HKIn05iP3j|87TkGK~SqG!-KBg5+mN(aLm z8ybhIM`%C19UX$H$KY6JgXbY$0AT%rEpHC;u`rQ$Y=rxUdsc5*Kvc8jaYaO$^)cI6){P6K0r)I6DY4Wr4&B zLQUBraey#0HV|&c4v7PVo3n$zHj99(TZO^3?Ly%C4nYvJTL9eLBLHsM3WKKD>5!B` zQ=BsR3aR6PD(Fa>327E2HAu5TM~Wusc!)>~(gM)+3~m;92Jd;FnSib=M5d6;;5{%R zb4V7DEJ0V!CP-F*oU?gkc>ksUtAYP&V4ND5J>J2^jt*vcFflQWCrB&fLdT%O59PVJ zhid#toR=FNgD!q3&r8#wEBr`!wzvQu5zX?Q>nlSJ4i@WC*CN*-xU66F^V5crWevQ9gsq$I@z1o(a=k7LL~ z7m_~`o;_Ozha1$8Q}{WBehvAlO4EL60y5}8GDrZ< zXh&F}71JbW2A~8KfEWj&UWV#4+Z4p`b{uAj4&WC zha`}X@3~+Iz^WRlOHU&KngK>#j}+_o@LdBC1H-`gT+krWX3-;!)6?{FBp~%20a}FL zFP9%Emqcwa#(`=G>BBZ0qZDQhmZKJg_g8<=bBFKWr!dyg(YkpE+|R*SGpDVU!+VlU zFC54^DLv}`qa%49T>nNiA9Q7Ips#!Xx90tCU2gvK`(F+GPcL=J^>No{)~we#o@&mUb6c$ zCc*<|NJBk-#+{j9xkQ&ujB zI~`#kN~7W!f*-}wkG~Ld!JqZ@tK}eeSnsS5J1fMFXm|`LJx&}5`@dK3W^7#Wnm+_P zBZkp&j1fa2Y=eIjJ0}gh85jt43kaIXXv?xmo@eHrka!Z|vQv12HN#+!I5E z`(fbuW>gFiJL|uXJ!vKt#z3e3HlVdboH7;e#i3(2<)Fg-I@BR!qY#eof3MFZ&*Y@l zI|KJf&ge@p2Dq09Vu$$Qxb7!}{m-iRk@!)%KL)txi3;~Z4Pb}u@GsW;ELiWeG9V51 znX#}B&4Y2E7-H=OpNE@q{%hFLxwIpBF2t{vPREa8_{linXT;#1vMRWjOzLOP$-hf( z>=?$0;~~PnkqY;~K{EM6Vo-T(0K{A0}VUGmu*hR z{tw3hvBN%N3G3Yw`X5Te+F{J`(3w1s3-+1EbnFQKcrgrX1Jqvs@ADGe%M0s$EbK$$ zK)=y=upBc6SjGYAACCcI=Y*6Fi8_jgwZlLxD26fnQfJmb8^gHRN5(TemhX@0e=vr> zg`W}6U>x6VhoA3DqsGGD9uL1DhB3!OXO=k}59TqD@(0Nb{)Ut_luTioK_>7wjc!5C zIr@w}b`Fez3)0wQfKl&bae7;PcTA7%?f2xucM0G)wt_KO!Ewx>F~;=BI0j=Fb4>pp zv}0R^xM4eti~+^+gE$6b81p(kwzuDti(-K9bc|?+pJEl@H+jSYuxZQV8rl8 zjp@M{#%qItIUFN~KcO9Hed*`$5A-2~pAo~K&<-Q+`9`$CK>rzqAI4w~$F%vs9s{~x zg4BP%Gy*@m?;D6=SRX?888Q6peF@_4Z->8wAH~Cn!R$|Hhq2cIzFYqT_+cDourHbY z0qroxJnrZ4Gh+Ay+F`_c%+KRT>y3qw{)89?=hJ@=KO=@ep)aBJ$c!JHfBMJpsP*3G za7|)VJJ8B;4?n{~ldJF7%jmb`-ftIvNd~ekoufG(`K(3=LNc;HBY& z(lp#q8XAD#cIf}k49zX_i`*fO+#!zKA&%T3j@%)R+#yag067CU%yUEe47>wzGU8^` z1EXFT^@I!{J!F8!X?S6ph8J=gUi5tl93*W>7}_uR<2N2~e}FaG?}KPyugQ=-OGEZs z!GBoyYY+H*ANn4?Z)X4l+7H%`17i5~zRlRIX?t)6_eu=g2Q`3WBhxSUeea+M-S?RL zX9oBGKn%a!H+*hx4d2(I!gsi+@SQK%<{X22M~2tMulJoa)0*+z9=-YO+;DFEm5eE1U9b^B(Z}2^9!Qk`!A$wUE z7$Ar5?NRg2&G!AZqnmE64eh^Anss3i!{}%6@Et+4rr!=}!SBF8eZ2*J3ujCWbl;3; z48H~goPSv(8X61fKKdpP!Z7$88NL^Z?j`!^*I?-P4X^pMxyWz~@$(UeAcTSDd(`vO z{~rc;9|GfMJcApU3k}22a!&)k4{CU!e_ny^Y3cO;tOvOMKEyWz!vG(Kp*;hB?d|R3`2X~=5a6#^o5@qn?J-bI8Ppip{-yG z!k|VcGsq!jF~}7DMr49Wap-s&>o=U^T0!Lcy}!(bhtYsPQy z4|EJe{12QL#=c(suQ89Mhw9<`bui%nx7Nep`C&*M3~vMEACmcRYYRGtANq$F%zh&V zc)cEVeHz*Z1N)L7k-(k3np#{GcDh2Q@ya0YHl*n7fl*ZPAsbU-a94MYYtA#&!c`xGIaV;yzsmrjfieTEtqB_WgZp2*NplHx=$O{M~2#i_vJ{ps-NgK zQsxKK_CBM2PP_je+Xft`(vYfXXgIUr{=PA=7a8`2EHk)Ym2QKIforz# tySWtj{oF3N9@_;i*Fv5S)9x^z=nlWP>jpp-9)52ZmLVA=i*%6g{{fxOO~wEK literal 0 HcmV?d00001 diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.cpp b/packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.cpp new file mode 100644 index 000000000..2d6636ab6 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.cpp @@ -0,0 +1,66 @@ +#include "run_loop.h" + +#include + +#include + +RunLoop::RunLoop() {} + +RunLoop::~RunLoop() {} + +void RunLoop::Run() { + bool keep_running = true; + TimePoint next_flutter_event_time = TimePoint::clock::now(); + while (keep_running) { + std::chrono::nanoseconds wait_duration = + std::max(std::chrono::nanoseconds(0), + next_flutter_event_time - TimePoint::clock::now()); + ::MsgWaitForMultipleObjects( + 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), + QS_ALLINPUT); + bool processed_events = false; + MSG message; + // All pending Windows messages must be processed; MsgWaitForMultipleObjects + // won't return again for items left in the queue after PeekMessage. + while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { + processed_events = true; + if (message.message == WM_QUIT) { + keep_running = false; + break; + } + ::TranslateMessage(&message); + ::DispatchMessage(&message); + // Allow Flutter to process messages each time a Windows message is + // processed, to prevent starvation. + next_flutter_event_time = + std::min(next_flutter_event_time, ProcessFlutterMessages()); + } + // If the PeekMessage loop didn't run, process Flutter messages. + if (!processed_events) { + next_flutter_event_time = + std::min(next_flutter_event_time, ProcessFlutterMessages()); + } + } +} + +void RunLoop::RegisterFlutterInstance( + flutter::FlutterEngine* flutter_instance) { + flutter_instances_.insert(flutter_instance); +} + +void RunLoop::UnregisterFlutterInstance( + flutter::FlutterEngine* flutter_instance) { + flutter_instances_.erase(flutter_instance); +} + +RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { + TimePoint next_event_time = TimePoint::max(); + for (auto instance : flutter_instances_) { + std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); + if (wait_duration != std::chrono::nanoseconds::max()) { + next_event_time = + std::min(next_event_time, TimePoint::clock::now() + wait_duration); + } + } + return next_event_time; +} diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.h b/packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.h new file mode 100644 index 000000000..000d36246 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/run_loop.h @@ -0,0 +1,40 @@ +#ifndef RUNNER_RUN_LOOP_H_ +#define RUNNER_RUN_LOOP_H_ + +#include + +#include +#include + +// A runloop that will service events for Flutter instances as well +// as native messages. +class RunLoop { + public: + RunLoop(); + ~RunLoop(); + + // Prevent copying + RunLoop(RunLoop const&) = delete; + RunLoop& operator=(RunLoop const&) = delete; + + // Runs the run loop until the application quits. + void Run(); + + // Registers the given Flutter instance for event servicing. + void RegisterFlutterInstance( + flutter::FlutterEngine* flutter_instance); + + // Unregisters the given Flutter instance from event servicing. + void UnregisterFlutterInstance( + flutter::FlutterEngine* flutter_instance); + + private: + using TimePoint = std::chrono::steady_clock::time_point; + + // Processes all currently pending messages for registered Flutter instances. + TimePoint ProcessFlutterMessages(); + + std::set flutter_instances_; +}; + +#endif // RUNNER_RUN_LOOP_H_ diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/runner.exe.manifest b/packages/syncfusion_flutter_officecore/example/windows/runner/runner.exe.manifest new file mode 100644 index 000000000..c977c4a42 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/utils.cpp b/packages/syncfusion_flutter_officecore/example/windows/runner/utils.cpp new file mode 100644 index 000000000..d19bdbbcc --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/utils.h b/packages/syncfusion_flutter_officecore/example/windows/runner/utils.h new file mode 100644 index 000000000..3879d5475 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.cpp b/packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.cpp new file mode 100644 index 000000000..c10f08dc7 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.h b/packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.h new file mode 100644 index 000000000..17ba43112 --- /dev/null +++ b/packages/syncfusion_flutter_officecore/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/packages/syncfusion_officecore/lib/officecore.dart b/packages/syncfusion_flutter_officecore/lib/officecore.dart similarity index 100% rename from packages/syncfusion_officecore/lib/officecore.dart rename to packages/syncfusion_flutter_officecore/lib/officecore.dart diff --git a/packages/syncfusion_officecore/lib/src/built_in_properties.dart b/packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart similarity index 100% rename from packages/syncfusion_officecore/lib/src/built_in_properties.dart rename to packages/syncfusion_flutter_officecore/lib/src/built_in_properties.dart diff --git a/packages/syncfusion_officecore/pubspec.yaml b/packages/syncfusion_flutter_officecore/pubspec.yaml similarity index 100% rename from packages/syncfusion_officecore/pubspec.yaml rename to packages/syncfusion_flutter_officecore/pubspec.yaml diff --git a/packages/syncfusion_officecore/syncfusion_officecore.iml b/packages/syncfusion_flutter_officecore/syncfusion_officecore.iml similarity index 100% rename from packages/syncfusion_officecore/syncfusion_officecore.iml rename to packages/syncfusion_flutter_officecore/syncfusion_officecore.iml diff --git a/packages/syncfusion_flutter_pdf/CHANGELOG.md b/packages/syncfusion_flutter_pdf/CHANGELOG.md index e3abd4900..38c7a5182 100644 --- a/packages/syncfusion_flutter_pdf/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdf/CHANGELOG.md @@ -1,4 +1,24 @@ -## Unreleased +## [19.2.56-beta.1] - 08/17/2021 + +**Bugs** + +* Resolved the RangeError while extracting text lines from the PDF document. +* The TextLine extraction bounds related issue has been resolved now. + +## [19.2.56-beta] - 08/17/2021 + +**Features** + +* Provided the support to get or set rotation in an existing PDF page. + +## [19.2.48-beta] - 07/20/2021 + +**Bugs** + +* The white space missing issue while extracting text has been resolved now. +* The unhandled exception when encrypting PDF document is resolved now. + +## [19.2.44-beta] - 06/30/2021 **Features** diff --git a/packages/syncfusion_flutter_pdf/README.md b/packages/syncfusion_flutter_pdf/README.md index a24bc7ddf..4a4f2582f 100644 --- a/packages/syncfusion_flutter_pdf/README.md +++ b/packages/syncfusion_flutter_pdf/README.md @@ -661,7 +661,7 @@ Refer to our [documentation](https://help.syncfusion.com/flutter/pdf/working-wit Founded in 2001 and headquartered in Research Triangle Park, N.C., Syncfusion has more than 22,000 customers and more than 1 million users, including large financial institutions, Fortune 500 companies, and global IT consultancies. -Today we provide 1,600+ 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-controls)). We provide ready-to deploy enterprise software in our Bold line of products for dashboarding and reporting. Many customers have saved millions in licensing fees by deploying our software. +Today we provide 1,600+ 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-controls) and [WinUI](https://www.syncfusion.com/winui-controls)). We provide ready-to deploy enterprise software in our Bold line of products for dashboarding and reporting. Many customers have saved millions in licensing fees by deploying our software. diff --git a/packages/syncfusion_flutter_pdf/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_pdf/example/lib/helper/save_file_mobile.dart deleted file mode 100644 index 7caf3b0bd..000000000 --- a/packages/syncfusion_flutter_pdf/example/lib/helper/save_file_mobile.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:io'; -import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; -import 'package:open_file/open_file.dart' as open_file; - -///To save the pdf file in the device -class FileSaveHelper { - ///To save the pdf file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - //Get the storage folder location using path_provider package. - String? path; - if (Platform.isAndroid || - Platform.isIOS || - Platform.isLinux || - Platform.isWindows) { - final Directory directory = - await path_provider.getApplicationSupportDirectory(); - path = directory.path; - } else { - path = await PathProviderPlatform.instance.getApplicationSupportPath(); - } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); - await file.writeAsBytes(bytes, flush: true); - if (Platform.isAndroid || Platform.isIOS) { - //Launch the file (used open_file package) - await open_file.OpenFile.open('$path/$fileName'); - } else if (Platform.isWindows) { - await Process.run('start', ['$path\\$fileName'], - runInShell: true); - } else if (Platform.isMacOS) { - await Process.run('open', ['$path/$fileName'], runInShell: true); - } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); - } - } -} diff --git a/packages/syncfusion_flutter_pdf/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_pdf/example/lib/helper/save_file_web.dart deleted file mode 100644 index d975d6976..000000000 --- a/packages/syncfusion_flutter_pdf/example/lib/helper/save_file_web.dart +++ /dev/null @@ -1,18 +0,0 @@ -///Dart imports -import 'dart:async'; -import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; - -///To save the pdf file in the device -class FileSaveHelper { - ///To save the pdf file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); - } -} diff --git a/packages/syncfusion_flutter_pdf/example/lib/main.dart b/packages/syncfusion_flutter_pdf/example/lib/main.dart index ecc8cef4c..a43bbad77 100644 --- a/packages/syncfusion_flutter_pdf/example/lib/main.dart +++ b/packages/syncfusion_flutter_pdf/example/lib/main.dart @@ -3,8 +3,7 @@ import 'package:intl/intl.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; //Local imports -import 'helper/save_file_mobile.dart' - if (dart.library.html) 'helper/save_file_web.dart'; +import 'save_file_mobile.dart' if (dart.library.html) 'save_file_web.dart'; void main() { runApp(CreatePdfWidget()); @@ -79,7 +78,7 @@ class _CreatePdfState extends State { //Dispose the document. document.dispose(); //Save and launch the file. - await FileSaveHelper.saveAndLaunchFile(bytes, 'Invoice.pdf'); + await saveAndLaunchFile(bytes, 'Invoice.pdf'); } //Draws the invoice header diff --git a/packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart b/packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart new file mode 100644 index 000000000..1662842ce --- /dev/null +++ b/packages/syncfusion_flutter_pdf/example/lib/save_file_mobile.dart @@ -0,0 +1,35 @@ +import 'dart:io'; +import 'package:path_provider/path_provider.dart' as path_provider; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +// ignore: directives_ordering +import 'package:open_file/open_file.dart' as open_file; + +///To save the pdf file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + //Get the storage folder location using path_provider package. + String? path; + if (Platform.isAndroid || + Platform.isIOS || + Platform.isLinux || + Platform.isWindows) { + final Directory directory = + await path_provider.getApplicationSupportDirectory(); + path = directory.path; + } else { + path = await PathProviderPlatform.instance.getApplicationSupportPath(); + } + final File file = + File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + await file.writeAsBytes(bytes, flush: true); + if (Platform.isAndroid || Platform.isIOS) { + //Launch the file (used open_file package) + await open_file.OpenFile.open('$path/$fileName'); + } else if (Platform.isWindows) { + await Process.run('start', ['$path\\$fileName'], runInShell: true); + } else if (Platform.isMacOS) { + await Process.run('open', ['$path/$fileName'], runInShell: true); + } else if (Platform.isLinux) { + await Process.run('xdg-open', ['$path/$fileName'], + runInShell: true); + } +} diff --git a/packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart b/packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart new file mode 100644 index 000000000..99d117c52 --- /dev/null +++ b/packages/syncfusion_flutter_pdf/example/lib/save_file_web.dart @@ -0,0 +1,14 @@ +///Dart imports +import 'dart:async'; +import 'dart:convert'; +// ignore: avoid_web_libraries_in_flutter +import 'dart:html'; + +///To save the pdf file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + AnchorElement( + href: + 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') + ..setAttribute('download', fileName) + ..click(); +} diff --git a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_pdf/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.cc index 4bfa0f3a3..8b6d4680a 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.h index 9846246b4..dc139d85a 100644 --- a/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_pdf/example/windows/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_pdf/lib/pdf.dart b/packages/syncfusion_flutter_pdf/lib/pdf.dart index 14ea40954..6d76d2005 100644 --- a/packages/syncfusion_flutter_pdf/lib/pdf.dart +++ b/packages/syncfusion_flutter_pdf/lib/pdf.dart @@ -7,6 +7,7 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:math'; import 'dart:ui'; + import 'package:convert/convert.dart'; import 'package:crypto/crypto.dart'; import 'package:intl/date_symbol_data_local.dart'; @@ -73,7 +74,6 @@ part 'src/pdf/implementation/io/enums.dart'; part 'src/pdf/implementation/io/pdf_operators.dart'; part 'src/pdf/implementation/io/decode_big_endian.dart'; part 'src/pdf/implementation/general/pdf_collection.dart'; -part 'src/pdf/implementation/general/utils.dart'; part 'src/pdf/implementation/graphics/pdf_transparency.dart'; part 'src/pdf/implementation/graphics/fonts/pdf_font.dart'; part 'src/pdf/implementation/graphics/fonts/pdf_standard_font.dart'; @@ -88,7 +88,6 @@ part 'src/pdf/implementation/graphics/fonts/pdf_string_layouter.dart'; part 'src/pdf/implementation/graphics/fonts/pdf_string_layout_result.dart'; part 'src/pdf/implementation/graphics/fonts/string_tokenizer.dart'; part 'src/pdf/implementation/graphics/fonts/pdf_cid_font.dart'; -part 'src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart'; part 'src/pdf/implementation/graphics/brushes/pdf_brush.dart'; part 'src/pdf/implementation/graphics/brushes/pdf_solid_brush.dart'; part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart'; @@ -98,7 +97,6 @@ part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_composite_field.d part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_multiple_value_field.dart'; part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_template_value_pair.dart'; part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_page_number_field.dart'; -part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart'; part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_single_value_field.dart'; part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart'; part 'src/pdf/implementation/pdf_document/automatic_fields/pdf_static_field.dart'; @@ -272,4 +270,3 @@ part 'src/pdf/implementation/security/digital_signature/pdf_pkcs_certificate.dar part 'src/pdf/implementation/security/digital_signature/pdf_signature.dart'; part 'src/pdf/implementation/security/digital_signature/pdf_signature_dictionary.dart'; part 'src/pdf/implementation/security/digital_signature/pdf_external_signer.dart'; - diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart index b8f9e4e57..c9176b65c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/annotations/pdf_paintparams.dart @@ -8,8 +8,7 @@ class _PaintParams { Rect? bounds, PdfBorderStyle? style, int? borderWidth, - PdfBrush? shadowBrush, - int? rotationAngle}) { + PdfBrush? shadowBrush}) { _backBrush = backBrush; _foreBrush = foreBrush; _borderPen = borderPen; @@ -17,7 +16,6 @@ class _PaintParams { _style = style; _borderWidth = borderWidth; _shadowBrush = shadowBrush; - _rotationAngle = rotationAngle; } // Fields PdfBrush? _backBrush; @@ -27,5 +25,4 @@ class _PaintParams { PdfBorderStyle? _style; int? _borderWidth; PdfBrush? _shadowBrush; - int? _rotationAngle; } 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 3304263f9..d26b9e388 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 @@ -298,7 +298,7 @@ class _CompressedStreamReader { } arrDecoderCodeLengths[ - _Utils.def_huffman_dyntree_codelengths_order[iCurrentCode++]] = + _defHuffmanDyntreeCodelengthsOrder[iCurrentCode++]] = len.toUnsigned(8); } final _DecompressorHuffmanTree treeInternalDecoder = @@ -556,12 +556,12 @@ class _CompressedStreamReader { final int end = (_dataLength % _maxValue).toInt(); if (start < end) { - _checksumUpdate(_blockBuffer, start, end - start); + _updateChecksum(_blockBuffer, start, end - start); } else { - _checksumUpdate(_blockBuffer, start, _maxValue - start); + _updateChecksum(_blockBuffer, start, _maxValue - start); if (end > 0) { - _checksumUpdate(_blockBuffer, 0, end); + _updateChecksum(_blockBuffer, 0, end); } } } @@ -581,9 +581,8 @@ class _CompressedStreamReader { }; } - void _checksumUpdate(List? buffer, int offset, int length) { - _checkSum = - _ChecksumCalculator._checksumUpdate(_checkSum, buffer, offset, length); + void _updateChecksum(List? buffer, int offset, int length) { + _checkSum = _checksumUpdate(_checkSum, buffer, offset, length); } int _readPackedBytes(List buffer, int offset, int length) { @@ -620,8 +619,8 @@ class _CompressedStreamReader { } if (length > 0) { - final Map value = _read(buffer, offset, length); - final int val = value['length'] as int; + final Map value = _readBytes(buffer, offset, length); + final int val = value['count'] as int; result += val.toInt(); buffer = value['buffer'] as List; } 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 934269c6e..7ef8d4f2c 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 @@ -1,52 +1,71 @@ part of pdf; -class _Utils { - static const List def_reverse_bits = [ - 0, - 8, - 4, - 12, - 2, - 10, - 6, - 14, - 1, - 9, - 5, - 13, - 3, - 11, - 7, - 15 - ]; - static const List def_huffman_dyntree_codelengths_order = [ - 16, - 17, - 18, - 0, - 8, - 7, - 9, - 6, - 10, - 5, - 11, - 4, - 12, - 3, - 13, - 2, - 14, - 1, - 15 - ]; - 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)]) - .toSigned(16); - } +int _checkSumBitOffset = 16; +int _checksumBase = 65521; +int _checksumIterationCount = 3800; +int _checksumUpdate(int checksum, List? buffer, int offset, int length) { + int checksumUint = checksum.toUnsigned(32); + int s1 = checksumUint & 65535; + int s2 = checksumUint >> _checkSumBitOffset; + while (length > 0) { + int steps = min(length, _checksumIterationCount); + length -= steps; + while (--steps >= 0) { + s1 = s1 + (buffer![offset++] & 255).toUnsigned(32); + s2 += s1; + } + s1 %= _checksumBase; + s2 %= _checksumBase; + } + checksumUint = (s2 << _checkSumBitOffset) | s1; + return checksumUint; +} + +List _defReverseBits = [ + 0, + 8, + 4, + 12, + 2, + 10, + 6, + 14, + 1, + 9, + 5, + 13, + 3, + 11, + 7, + 15 +]; +List _defHuffmanDyntreeCodelengthsOrder = [ + 16, + 17, + 18, + 0, + 8, + 7, + 9, + 6, + 10, + 5, + 11, + 4, + 12, + 3, + 13, + 2, + 14, + 1, + 15 +]; +int _bitReverse(int value) { + return (_defReverseBits[(value & 15)] << 12 | + _defReverseBits[((value >> 4) & 15)] << 8 | + _defReverseBits[((value >> 8) & 15)] << 4 | + _defReverseBits[(value >> 12)]) + .toSigned(16); } class _CompressedStreamWriter { @@ -260,19 +279,19 @@ class _CompressedStreamWriter { List.filled(def_huffman_literal_alphabet_length, 0); int i = 0; while (i < 144) { - _arrLiteralCodes![i] = _Utils.bitReverse((0x030 + i) << 8); + _arrLiteralCodes![i] = _bitReverse((0x030 + i) << 8); _arrLiteralLengths[i++] = 8; } while (i < 256) { - _arrLiteralCodes![i] = _Utils.bitReverse(((0x190 - 144) + i) << 7); + _arrLiteralCodes![i] = _bitReverse(((0x190 - 144) + i) << 7); _arrLiteralLengths[i++] = 9; } while (i < 280) { - _arrLiteralCodes![i] = _Utils.bitReverse(((0x000 - 256) + i) << 9); + _arrLiteralCodes![i] = _bitReverse(((0x000 - 256) + i) << 9); _arrLiteralLengths[i++] = 7; } while (i < def_huffman_literal_alphabet_length) { - _arrLiteralCodes![i] = _Utils.bitReverse(((0x0c0 - 280) + i) << 8); + _arrLiteralCodes![i] = _bitReverse(((0x0c0 - 280) + i) << 8); _arrLiteralLengths[i++] = 8; } _arrDistanceCodes = @@ -281,7 +300,7 @@ class _CompressedStreamWriter { List.filled(def_huffman_distances_alphabet_length, 0); for (i = 0; i < def_huffman_distances_alphabet_length; i++) { - _arrDistanceCodes[i] = _Utils.bitReverse(i << 11); + _arrDistanceCodes[i] = _bitReverse(i << 11); _arrDistanceLengths[i] = 5; } } @@ -317,8 +336,7 @@ class _CompressedStreamWriter { if (_bStreamClosed) { throw Exception('Stream was closed.'); } - _checksum = _ChecksumCalculator._checksumUpdate( - _checksum, _inputBuffer, offset, length); + _checksum = _checksumUpdate(_checksum, _inputBuffer, offset, length); while (!_needsInput || !_pendingBufferIsFlushed) { _pendingBufferFlush(); @@ -899,28 +917,3 @@ class _CompressedStreamWriter { } } } - -class _ChecksumCalculator { - static const int checkSumBitOffset = 16; - static const int checksumBase = 65521; - static const int checksumIterationCount = 3800; - - static int _checksumUpdate( - int checksum, List? buffer, int offset, int length) { - int checksumUint = checksum.toUnsigned(32); - int s1 = checksumUint & 65535; - int s2 = checksumUint >> checkSumBitOffset; - while (length > 0) { - int steps = min(length, checksumIterationCount); - length -= steps; - while (--steps >= 0) { - s1 = s1 + (buffer![offset++] & 255).toUnsigned(32); - s2 += s1; - } - s1 %= checksumBase; - s2 %= checksumBase; - } - checksumUint = (s2 << checkSumBitOffset) | s1; - return checksumUint; - } -} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart index fb7b514e6..752ad5064 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/compression/decompressor_huffman_tree.dart @@ -106,7 +106,7 @@ class _DecompressorHuffmanTree { code -= blCount[bits] << (16 - bits); final int start = code & 0x1ff80; for (int i = start; i < end; i += increment) { - tree[_Utils.bitReverse(i)] = ((-pointer << 4) | bits).toSigned(16); + tree[_bitReverse(i)] = ((-pointer << 4) | bits).toSigned(16); pointer += 1 << (bits - 9); } } @@ -117,7 +117,7 @@ class _DecompressorHuffmanTree { continue; } code = nextCode[bits]; - int revcode = _Utils.bitReverse(code); + int revcode = _bitReverse(code); if (bits <= 9) { do { tree[revcode] = ((i << 4) | bits).toSigned(16); 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 b9dc670a0..02b0ff41d 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 @@ -2529,6 +2529,9 @@ class _FontStructure { if (decodedText.contains('\u0092')) { decodedText = decodedText.replaceAll('\u0092', '’'); } + if (decodedText.contains(RegExp('[\n-\r]'))) { + decodedText = decodedText.replaceAll(RegExp('[\n-\r]'), '’'); + } isWhiteSpace = decodedText.isEmpty || (decodedText.trimRight() == ''); return decodedText; } @@ -2726,13 +2729,17 @@ class _FontStructure { isHex = true; } else { listElement = encodedText; - decodedList.add(listElement); + if (listElement.trim() != '') { + decodedList.add(listElement); + } break; } } if (textEnd < 0 && encodedText.isNotEmpty) { listElement = encodedText; - decodedList.add(listElement); + if (listElement.trim() != '') { + decodedList.add(listElement); + } break; } else if (textEnd > 0) { while (encodedText[textEnd - 1] == r'\') { @@ -2748,7 +2755,9 @@ class _FontStructure { } if (textStart != 0) { listElement = encodedText.substring(0, textStart); - decodedList.add(listElement); + if (listElement.trim() != '') { + decodedList.add(listElement); + } } final String tempString = encodedText.substring(textStart + 1, textEnd); @@ -2784,6 +2793,15 @@ class _FontStructure { if (cidToGidTable != null && !isTextExtraction) { listElement = mapCidToGid(listElement); } + if (encodedText + .substring(textEnd + 1, encodedText.length) + .trim() + .isEmpty) { + listElement = listElement.trimRight(); + } + if (listElement.contains(RegExp('[\n-\r]'))) { + listElement = listElement.replaceAll(RegExp('[\n-\r]'), ''); + } if (listElement.isNotEmpty) { if (listElement[0].codeUnitAt(0) >= 3584 && listElement[0].codeUnitAt(0) <= 3711 && @@ -2815,7 +2833,7 @@ class _FontStructure { decodedList.add(listElement); } } else { - listElement += 's'; + listElement = listElement.trimRight() + 's'; decodedList.add(listElement); } encodedText = 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 3adf22a2a..8cf746276 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 @@ -618,7 +618,7 @@ class _ImageRenderer { if (_isCurrentPositionChanged) { _isCurrentPositionChanged = false; _endTextPosition = currentLocation!; - _textElementWidth = element._render( + final Map renderedResult = element._render( _graphicsObject, Offset(_endTextPosition.dx, _endTextPosition.dy + ((-textLeading!) / 4)), @@ -629,10 +629,12 @@ class _ImageRenderer { structure.differencesDictionary, structure.differenceEncoding, tempTextMatrix); + _textElementWidth = renderedResult['textElementWidth'] as double; + textMatrix = renderedResult['tempTextMatrix'] as _MatrixHelper; } else { _endTextPosition = Offset( _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); - _textElementWidth = element._render( + final Map renderedResult = element._render( _graphicsObject, Offset( _endTextPosition.dx, _endTextPosition.dy + (-textLeading! / 4)), @@ -643,6 +645,8 @@ class _ImageRenderer { structure.differencesDictionary, structure.differenceEncoding, tempTextMatrix); + _textElementWidth = renderedResult['textElementWidth'] as double; + textMatrix = renderedResult['tempTextMatrix'] as _MatrixHelper; } if (!structure.isWhiteSpace) { if (_whiteSpace.isNotEmpty && @@ -654,8 +658,10 @@ class _ImageRenderer { element.textLineMatrix!.offsetY && _whiteSpace[0].textLineMatrix!.offsetY == element.textLineMatrix!.offsetY) { - element.textElementGlyphList - .insert(0, _whiteSpace[0].textElementGlyphList[0]); + if (_whiteSpace[0]._text.isNotEmpty) { + element.textElementGlyphList + .insert(0, _whiteSpace[0].textElementGlyphList[0]); + } extractTextElement.add(_whiteSpace[0]); } _whiteSpace = <_TextElement>[]; @@ -744,7 +750,7 @@ class _ImageRenderer { _endTextPosition = Offset( _endTextPosition.dx + _textElementWidth, _endTextPosition.dy); } - _textElementWidth = element._renderWithSpacing( + final Map renderedResult = element._renderWithSpacing( _graphicsObject, Offset(_endTextPosition.dx, _endTextPosition.dy - fontSize!), decodedList, @@ -756,6 +762,8 @@ class _ImageRenderer { structure.differencesDictionary, structure.differenceEncoding, tempTextMatrix); + _textElementWidth = renderedResult['textElementWidth'] as double; + textMatrix = renderedResult['tempTextMatrix'] as _MatrixHelper; if (!structure.isWhiteSpace) { if (_whiteSpace.isNotEmpty && extractTextElement.isNotEmpty && @@ -765,7 +773,8 @@ class _ImageRenderer { .offsetY == element.textLineMatrix!.offsetY && _whiteSpace[0].textLineMatrix!.offsetY == - element.textLineMatrix!.offsetY) { + element.textLineMatrix!.offsetY && + _whiteSpace[0].textElementGlyphList.isNotEmpty) { element.textElementGlyphList .insert(0, _whiteSpace[0].textElementGlyphList[0]); extractTextElement.add(_whiteSpace[0]); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart index b45c7cfbc..2e3aa503d 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/exporting/pdf_text_extractor/page_resource_loader.dart @@ -60,11 +60,11 @@ class _PageResourceLoader { } } // m_commonMatrix = commonMatrix; - if (page._rotation == PdfPageRotateAngle.rotateAngle90) { + if (page.rotation == PdfPageRotateAngle.rotateAngle90) { pageResources.resources[_DictionaryProperties.rotate] = 90.toDouble(); - } else if (page._rotation == PdfPageRotateAngle.rotateAngle180) { + } else if (page.rotation == PdfPageRotateAngle.rotateAngle180) { pageResources.resources[_DictionaryProperties.rotate] = 180.toDouble(); - } else if (page._rotation == PdfPageRotateAngle.rotateAngle270) { + } else if (page.rotation == PdfPageRotateAngle.rotateAngle270) { pageResources.resources[_DictionaryProperties.rotate] = 270.toDouble(); } return pageResources; 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 d03a5b045..fe2189fb4 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 @@ -328,7 +328,7 @@ class PdfTextExtractor { renderer.imageRenderGlyphList[m].toUnicode, textElement.fontName, textElement.fontStyle, - glyphBounds, + _calculateBounds(glyphBounds), textElement.fontSize); tempText += textGlyph.text; glyphs.add(textGlyph); @@ -611,7 +611,7 @@ class PdfTextExtractor { renderer.imageRenderGlyphList[i].toUnicode, textElement.fontName, textElement.fontStyle, - glyphBounds, + _calculateBounds(glyphBounds), textElement.fontSize); dx = renderer.imageRenderGlyphList[i].boundingRect.left; dy = renderer.imageRenderGlyphList[i].boundingRect.top; @@ -663,8 +663,8 @@ class PdfTextExtractor { 0.001) { if (x > endGlyph.boundingRect.left) { width = (x - endGlyph.boundingRect.left) + endGlyph.boundingRect.width; - if (page._rotation == PdfPageRotateAngle.rotateAngle0 || - page._rotation == PdfPageRotateAngle.rotateAngle180) { + if (page.rotation == PdfPageRotateAngle.rotateAngle0 || + page.rotation == PdfPageRotateAngle.rotateAngle180) { width = startGlyph.boundingRect.height; for (int i = 0; i < length; i++) { height += glyphs[index + i].boundingRect.width; @@ -692,8 +692,8 @@ class PdfTextExtractor { !((startGlyph.boundingRect.top - endGlyph.boundingRect.top).abs() < 0.001)) { height = 0; - if (page._rotation == PdfPageRotateAngle.rotateAngle0 || - page._rotation == PdfPageRotateAngle.rotateAngle180) { + if (page.rotation == PdfPageRotateAngle.rotateAngle0 || + page.rotation == PdfPageRotateAngle.rotateAngle180) { width = startGlyph.boundingRect.height; for (int i = 0; i < length; i++) { height += glyphs[index + i].boundingRect.width; @@ -718,7 +718,7 @@ class PdfTextExtractor { } } } - return Rect.fromLTWH(x, y, width, height); + return _calculateBounds(Rect.fromLTWH(x, y, width, height)); } bool _hasEscapeCharacter(String text) { @@ -735,6 +735,17 @@ class PdfTextExtractor { text.contains(r'\u0000'); } + Rect _calculateBounds(Rect bounds) { + if (_currentPage != null && + _currentPage!._cropBox != Rect.zero && + _currentPage!._cropBox != _currentPage!._mediaBox) { + final double x = bounds.left - _currentPage!._cropBox.left; + final double y = bounds.top + _currentPage!._cropBox.top; + return Rect.fromLTWH(x, y, bounds.width, bounds.height); + } + return bounds; + } + TextLine _prepareTextLine(TextLine textLine, _ImageRenderer renderer, int lineStartIndex, int glyphIndex) { bool isSameFontName = true; @@ -750,7 +761,7 @@ class PdfTextExtractor { renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.right - renderer.imageRenderGlyphList[lineStartIndex].boundingRect.left, renderer.imageRenderGlyphList[glyphIndex - 1].boundingRect.height); - + textLine.bounds = _calculateBounds(textLine.bounds); for (int i = lineStartIndex; i < glyphIndex; i++) { final _Glyph glyph = renderer.imageRenderGlyphList[i]; if (i == 0) { @@ -901,10 +912,11 @@ class PdfTextExtractor { final double d = double.tryParse(elements[3])!; final double e = double.tryParse(elements[4])!; final double f = double.tryParse(elements[5])!; - _textLineMatrix = _textMatrix = _MatrixHelper(a, b, c, d, e, f); + _textLineMatrix = _MatrixHelper(a, b, c, d, e, f); + _textMatrix = _MatrixHelper(a, b, c, d, e, f); if (_textMatrix!.offsetY == _textLineMatrix!.offsetY && _textMatrix!.offsetX != _textLineMatrix!.offsetX) { - _textLineMatrix = _textMatrix; + _textLineMatrix = _textMatrix!._clone(); } if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || ((_textLineMatrix!.offsetX != _currentTextMatrix!.offsetX) && @@ -943,7 +955,7 @@ class PdfTextExtractor { case 'TD': { textLeading = double.tryParse(elements![1]); - _textLineMatrix = _textMatrix = _MatrixHelper( + _textMatrix = _MatrixHelper( 1, 0, 0, @@ -951,6 +963,7 @@ class PdfTextExtractor { double.tryParse(elements[0])!, double.tryParse(elements[1])!) * _textLineMatrix!; + _textLineMatrix = _textMatrix!._clone(); if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || (_hasBDC && _textLineMatrix!.offsetX != _currentTextMatrix!.offsetX && @@ -962,7 +975,7 @@ class PdfTextExtractor { } case 'Td': { - _textLineMatrix = _textMatrix = _MatrixHelper( + _textMatrix = _MatrixHelper( 1, 0, 0, @@ -970,6 +983,7 @@ class PdfTextExtractor { double.tryParse(elements![0])!, double.tryParse(elements[1])!) * _textLineMatrix!; + _textLineMatrix = _textMatrix!._clone(); if (_textLineMatrix!.offsetY != _currentTextMatrix!.offsetY || (_hasBDC && _textLineMatrix!.offsetX != @@ -995,13 +1009,15 @@ class PdfTextExtractor { } case 'BT': { - _textLineMatrix = _textMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + _textMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); + _textLineMatrix = _MatrixHelper(0, 0, 0, 0, 0, 0); break; } case 'T*': { - _textLineMatrix = _textMatrix = + _textMatrix = _MatrixHelper(1, 0, 0, 1, 0, textLeading!) * _textLineMatrix!; + _textLineMatrix = _textMatrix!._clone(); break; } case 'Tf': @@ -1050,12 +1066,12 @@ class PdfTextExtractor { ? _renderTextElementTJ( elements!, token, pageResources, horizontalScaling) : _renderTextElement(elements!, token, pageResources); - _currentTextMatrix = _textLineMatrix; + _currentTextMatrix = _textLineMatrix!._clone(); prevY = currentY; resultantText += currentText!; - _textMatrix = _textLineMatrix; + _textMatrix = _textLineMatrix!._clone(); if (currentToken == 'TJ') { - _hasBDC = true; + _hasBDC = false; } break; } @@ -1085,10 +1101,11 @@ class PdfTextExtractor { if ((prevXPosition - currentXPosition) > 0) { hasNoSpacing = true; } - _textLineMatrix = _textMatrix = + _textMatrix = _MatrixHelper(1, 0, 0, 1, 0, textLeading!) * _textLineMatrix!; + _textLineMatrix = _textMatrix!._clone(); currentText = _renderTextElement(elements!, token, pageResources); - _currentTextMatrix = _textLineMatrix; + _currentTextMatrix = _textLineMatrix!._clone(); resultantText += currentText!; break; } @@ -1252,7 +1269,7 @@ class PdfTextExtractor { _textLineMatrix = _updateTextMatrix(_characterWidth, horizontalScaling); _tempBoundingRectangle = boundingRect; - _textMatrix = _textLineMatrix; + _textMatrix = _textLineMatrix!._clone(); } } } 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 051bedf17..f05ef5dd3 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 @@ -214,7 +214,7 @@ class _TextElement { currentTransformationMatrix!; } - double _render( + Map _render( _GraphicsObject? g, Offset currentLocation, double? textScaling, @@ -300,10 +300,24 @@ class _TextElement { if (charCode.toUnsigned(8) > 126 && fontEncoding == 'MacRomanEncoding' && !isEmbeddedFont) { - txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); + isTextGlyphAdded = true; + final _MatrixHelper? tempMatrix = + drawSystemFontGlyphShape(letter, g!, txtMatrix); + if (tempMatrix != null) { + txtMatrix = tempMatrix; + } else { + isTextGlyphAdded = false; + } } else { if (renderingMode == 1) { - txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); + isTextGlyphAdded = true; + final _MatrixHelper? tempMatrix = + drawSystemFontGlyphShape(letter, g!, txtMatrix); + if (tempMatrix != null) { + txtMatrix = tempMatrix; + } else { + isTextGlyphAdded = false; + } } else if (reverseMapTable!.isNotEmpty && reverseMapTable!.containsKey(letter)) { final int tempCharCode = reverseMapTable![letter]!.toInt(); @@ -322,7 +336,13 @@ class _TextElement { characterMapTable.containsKey(charCode)) { final String tempLetter = characterMapTable[charCode]![0]; isTextGlyphAdded = true; - txtMatrix = drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); + final _MatrixHelper? tempMatrix = + drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); + if (tempMatrix != null) { + txtMatrix = tempMatrix; + } else { + isTextGlyphAdded = false; + } } } if (!isTextGlyphAdded) { @@ -383,10 +403,13 @@ class _TextElement { } } changeInX = location.dx - changeInX; - return changeInX; + return { + 'textElementWidth': changeInX, + 'tempTextMatrix': txtMatrix + }; } - double _renderWithSpacing( + Map _renderWithSpacing( _GraphicsObject? g, Offset currentLocation, List decodedList, @@ -488,8 +511,15 @@ class _TextElement { characterMapTable.containsKey(charCode)) { final String tempLetter = characterMapTable[charCode]!; isTextGlyphAdded = true; - txtMatrix = drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); isComplexScript = true; + final _MatrixHelper? tempMatrix = + drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); + if (tempMatrix != null) { + txtMatrix = tempMatrix; + } else { + isTextGlyphAdded = false; + isComplexScript = false; + } } } if (!isComplexScript) { @@ -501,10 +531,24 @@ class _TextElement { if (charCode.toUnsigned(8) > 126 && fontEncoding == 'MacRomanEncoding' && !isEmbeddedFont) { - txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); + isTextGlyphAdded = true; + final _MatrixHelper? tempMatrix = + drawSystemFontGlyphShape(letter, g!, txtMatrix); + if (tempMatrix != null) { + txtMatrix = tempMatrix; + } else { + isTextGlyphAdded = false; + } } else { if (renderingMode == 1) { - txtMatrix = drawSystemFontGlyphShape(letter, g!, txtMatrix); + isTextGlyphAdded = true; + final _MatrixHelper? tempMatrix = + drawSystemFontGlyphShape(letter, g!, txtMatrix); + if (tempMatrix != null) { + txtMatrix = tempMatrix; + } else { + isTextGlyphAdded = false; + } } else { if (reverseMapTable!.isNotEmpty && reverseMapTable!.containsKey(letter)) { @@ -514,8 +558,13 @@ class _TextElement { characterMapTable.containsKey(charCode)) { final String tempLetter = characterMapTable[charCode]![0]; isTextGlyphAdded = true; - txtMatrix = + final _MatrixHelper? tempMatrix = drawSystemFontGlyphShape(tempLetter, g!, txtMatrix); + if (tempMatrix != null) { + txtMatrix = tempMatrix; + } else { + isTextGlyphAdded = false; + } } } if (characterMapTable.isNotEmpty && @@ -581,7 +630,10 @@ class _TextElement { } }); changeInX = location.dx - changeInX; - return changeInX; + return { + 'textElementWidth': changeInX, + 'tempTextMatrix': txtMatrix + }; } _MatrixHelper? drawGlyphs(double? glyphwidth, _GraphicsObject g, @@ -704,6 +756,8 @@ class _TextElement { final int charCode = reverseMapTable![letter]!.toInt(); if (fontGlyphWidths!.containsKey(charCode)) { systemFontGlyph = fontGlyphWidths![charCode]! * charSizeMultiplier; + } else { + return null; } } else if (fontGlyphWidths!.containsKey(letter.codeUnitAt(0))) { systemFontGlyph = diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_button_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_button_field.dart index 3b34bf050..4bd2f060b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_button_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_button_field.dart @@ -213,8 +213,7 @@ class PdfButtonField extends PdfField { borderPen: _borderPen, style: _borderStyle, borderWidth: _borderWidth, - shadowBrush: PdfSolidBrush(_backColor), - rotationAngle: 0); + shadowBrush: PdfSolidBrush(_backColor)); _FieldPainter().drawButton( template.graphics!, paintParams, @@ -235,8 +234,7 @@ class PdfButtonField extends PdfField { borderPen: _borderPen, style: _borderStyle, borderWidth: _borderWidth, - shadowBrush: PdfSolidBrush(_backColor), - rotationAngle: 0); + shadowBrush: PdfSolidBrush(_backColor)); _FieldPainter().drawPressedButton( template.graphics!, paintParams, @@ -340,8 +338,7 @@ class PdfButtonField extends PdfField { borderPen: _borderPen, style: borderStyle, borderWidth: borderWidth, - shadowBrush: _shadowBrush, - rotationAngle: 0); + shadowBrush: _shadowBrush); final PdfTemplate template = PdfTemplate(rect.width, rect.height); _FieldPainter() .drawButton(template.graphics!, params, text, font, _stringFormat); @@ -430,11 +427,10 @@ class PdfButtonField extends PdfField { borderPen: gp._borderPen, style: gp._style, borderWidth: gp._borderWidth, - shadowBrush: gp._shadowBrush, - rotationAngle: 0); + shadowBrush: gp._shadowBrush); if (_dictionary.containsKey(_DictionaryProperties.ap) && !(graphics!._layer != null && - graphics._page!._rotation != PdfPageRotateAngle.rotateAngle0)) { + graphics._page!.rotation != PdfPageRotateAngle.rotateAngle0)) { _IPdfPrimitive? buttonAppearance = _dictionary[_DictionaryProperties.ap]; buttonAppearance ??= widget![_DictionaryProperties.ap]; _PdfDictionary? buttonResource = @@ -455,7 +451,7 @@ class PdfButtonField extends PdfField { } else if (_dictionary.containsKey(_DictionaryProperties.kids) && item != null && !(graphics!._layer != null && - graphics._page!._rotation != PdfPageRotateAngle.rotateAngle0)) { + graphics._page!.rotation != PdfPageRotateAngle.rotateAngle0)) { _IPdfPrimitive? buttonAppearance = item._dictionary[_DictionaryProperties.ap]; buttonAppearance ??= widget![_DictionaryProperties.ap]; 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 cc01394b8..0734d8c05 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 @@ -2771,10 +2771,11 @@ class _GraphicsProperties { _shadowBrush = field._shadowBrush; _font = field._font; _stringFormat = field._format; - if (field.page != null && - field.page!._rotation != PdfPageRotateAngle.rotateAngle0) { + if ((!field._isLoadedField) && + field.page != null && + field.page!.rotation != PdfPageRotateAngle.rotateAngle0) { _bounds = - _rotateTextbox(field.bounds, field.page!.size, field.page!._rotation); + _rotateTextbox(field.bounds, field.page!.size, field.page!.rotation); } } @@ -2788,10 +2789,11 @@ class _GraphicsProperties { _shadowBrush = item._shadowBrush; _font = item._font; _stringFormat = item._format; - if (item.page != null && - item.page!._rotation != PdfPageRotateAngle.rotateAngle0) { + if ((!item._field._isLoadedField) && + item.page != null && + item.page!.rotation != PdfPageRotateAngle.rotateAngle0) { _bounds = - _rotateTextbox(item.bounds, item.page!.size, item.page!._rotation); + _rotateTextbox(item.bounds, item.page!.size, item.page!.rotation); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_painter.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_painter.dart index b821fde3a..9f14a8999 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_painter.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_field_painter.dart @@ -264,46 +264,9 @@ class _FieldPainter { void drawButton(PdfGraphics g, _PaintParams paintParams, String text, PdfFont font, PdfStringFormat? format) { drawRectangularControl(g, paintParams); - Rect? rectangle = paintParams._bounds; - - if ((g._layer != null && - g._page != null && - g._page!._rotation != PdfPageRotateAngle.rotateAngle0) || - (paintParams._rotationAngle! > 0)) { - final PdfGraphicsState state = g.save(); - - if ((g._layer != null) && - (g._page!._rotation != PdfPageRotateAngle.rotateAngle0)) { - if (g._page!._rotation == PdfPageRotateAngle.rotateAngle90) { - g.translateTransform(g.size.height, 0); - g.rotateTransform(90); - final double y = - g._page!.size.height - (rectangle!.left + rectangle.width); - final double x = rectangle.top; - rectangle = Rect.fromLTWH(x, y, rectangle.height, rectangle.width); - } else if (g._page!._rotation == PdfPageRotateAngle.rotateAngle180) { - g.translateTransform(g._page!.size.width, g._page!.size.height); - g.rotateTransform(-180); - final Size size = g._page!.size; - final double x = size.width - (rectangle!.left + rectangle.width); - final double y = size.height - (rectangle.top + rectangle.height); - rectangle = Rect.fromLTWH(x, y, rectangle.width, rectangle.height); - } else if (g._page!._rotation == PdfPageRotateAngle.rotateAngle270) { - g.translateTransform(0, g.size.width); - g.rotateTransform(270); - final double x = - g._page!.size.width - (rectangle!.top + rectangle.height); - final double y = rectangle.left; - rectangle = Rect.fromLTWH(x, y, rectangle.height, rectangle.width); - } - } - g.drawString(text, font, - brush: paintParams._foreBrush, bounds: rectangle, format: format); - g.restore(state); - } else { - g.drawString(text, font, - brush: paintParams._foreBrush, bounds: rectangle, format: format); - } + final Rect? rectangle = paintParams._bounds; + g.drawString(text, font, + brush: paintParams._foreBrush, bounds: rectangle, format: format); } void drawPressedButton(PdfGraphics g, _PaintParams paintParams, String text, @@ -424,46 +387,9 @@ class _FieldPainter { void drawComboBox(PdfGraphics graphics, _PaintParams paintParams, String? text, PdfFont? font, PdfStringFormat? format) { drawRectangularControl(graphics, paintParams); - Rect? rectangle = paintParams._bounds; - if (graphics._layer != null && - graphics._page!._rotation != PdfPageRotateAngle.rotateAngle0) { - final PdfGraphicsState state = graphics.save(); - final Size size = graphics._page!.size; - if (graphics._page!._rotation == PdfPageRotateAngle.rotateAngle90) { - graphics.translateTransform(graphics.size.height, 0); - graphics.rotateTransform(90); - rectangle = Rect.fromLTWH( - rectangle!.left, - size.height - rectangle.left - rectangle.width, - rectangle.height, - rectangle.width); - } else if (graphics._page!._rotation == - PdfPageRotateAngle.rotateAngle180) { - graphics.translateTransform( - graphics._page!.size.width, graphics._page!.size.height); - graphics.rotateTransform(-180); - rectangle = Rect.fromLTWH( - size.width - rectangle!.left - rectangle.width, - size.height - rectangle.top - rectangle.height, - rectangle.width, - rectangle.height); - } else if (graphics._page!._rotation == - PdfPageRotateAngle.rotateAngle270) { - graphics.translateTransform(0, graphics.size.width); - graphics.rotateTransform(270); - rectangle = Rect.fromLTWH( - size.width - rectangle!.top - rectangle.height, - rectangle.left, - rectangle.height, - rectangle.width); - } - graphics.drawString(text!, font!, - brush: paintParams._foreBrush, bounds: rectangle, format: format); - graphics.restore(state); - } else { - graphics.drawString(text!, font!, - brush: paintParams._foreBrush, bounds: rectangle, format: format); - } + final Rect? rectangle = paintParams._bounds; + graphics.drawString(text!, font!, + brush: paintParams._foreBrush, bounds: rectangle, format: format); } //Draws the list box diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_box_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_box_field.dart index 2c7663c28..9096bf31f 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_box_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_box_field.dart @@ -147,28 +147,9 @@ class PdfListBoxField extends PdfListField { page!.graphics.drawPdfTemplate(template, bounds.topLeft, rect.size); } } else { - final PdfGraphics graphics = page!.graphics; final PdfTemplate template = PdfTemplate(bounds.width, bounds.height); - if (_flattenField && graphics._page != null) { - graphics.save(); - final double width = page!.graphics.size.width; - final double height = page!.graphics.size.height; - if (graphics._page!._rotation == PdfPageRotateAngle.rotateAngle90) { - page!.graphics.translateTransform(width, height); - page!.graphics.rotateTransform(90); - } else if (graphics._page!._rotation == - PdfPageRotateAngle.rotateAngle180) { - page!.graphics.translateTransform(width, height); - page!.graphics.rotateTransform(-180); - } else if (graphics._page!._rotation == - PdfPageRotateAngle.rotateAngle270) { - page!.graphics.translateTransform(width, height); - page!.graphics.rotateTransform(270); - } - } _drawListBox(template.graphics!); page!.graphics.drawPdfTemplate(template, bounds.topLeft); - graphics.restore(); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item.dart index 8f40809fc..0c0d4a92e 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/forms/pdf_list_field_item.dart @@ -66,8 +66,12 @@ class PdfListFieldItem implements _IPdfWrapper { final _PdfArray array = _crossTable! ._getObject(fieldDic[_DictionaryProperties.opt])! as _PdfArray; final _PdfArray item = isText - ? (_PdfArray().._add(_PdfString(_value!)).._add(_PdfString(value))) - : (_PdfArray().._add(_PdfString(value)).._add(_PdfString(_text!))); + ? (_PdfArray() + .._add(_PdfString(_value!)) + .._add(_PdfString(value))) + : (_PdfArray() + .._add(_PdfString(value)) + .._add(_PdfString(_text!))); for (int i = 0; i < array.count; ++i) { final _IPdfPrimitive primitive = _crossTable!._getObject(array[i])!; final _PdfArray arr = primitive as _PdfArray; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/utils.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/utils.dart deleted file mode 100644 index c5bb69b55..000000000 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/general/utils.dart +++ /dev/null @@ -1,31 +0,0 @@ -part of pdf; - -class _PdfUtils { - static const List _jpegSignature = [255, 216]; - static const List _pngSignature = [137, 80, 78, 71, 13, 10, 26, 10]; - static bool isPng(List imageData) { - if (imageData.length >= _pngSignature.length) { - for (int i = 0; i < _pngSignature.length; i++) { - if (_pngSignature[i] != imageData[i]) { - return false; - } - } - return true; - } else { - return false; - } - } - - static bool isJpeg(List imageData) { - if (imageData.length >= _jpegSignature.length) { - for (int i = 0; i < _jpegSignature.length; i++) { - if (_jpegSignature[i] != imageData[i]) { - return false; - } - } - return true; - } else { - return false; - } - } -} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart index 99a26ef54..6a4cd3376 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cid_font.dart @@ -12,8 +12,7 @@ class _PdfCidFont extends _PdfDictionary { _PdfNumber((fontMetrics._widthTable! as _CjkWidthTable).defaultWidth); this[_DictionaryProperties.w] = fontMetrics._widthTable!.toArray(); this[_DictionaryProperties.fontDescriptor] = - _PdfCjkFontDescryptorFactory.getFontDescryptor( - fontFamily, fontStyle, fontMetrics); + _getFontDescryptor(fontFamily, fontStyle, fontMetrics); this[_DictionaryProperties.cidSystemInfo] = _getSystemInfo(fontFamily); } @@ -46,4 +45,208 @@ class _PdfCidFont extends _PdfDictionary { } return sysInfo; } + + _PdfDictionary _getFontDescryptor(PdfCjkFontFamily? fontFamily, + int? fontStyle, _PdfFontMetrics fontMetrics) { + final _PdfDictionary fontDescryptor = _PdfDictionary(); + switch (fontFamily) { + case PdfCjkFontFamily.hanyangSystemsGothicMedium: + _fillHanyangSystemsGothicMedium( + fontDescryptor, fontFamily, fontMetrics); + break; + case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: + _fillHanyangSystemsShinMyeongJoMedium( + fontDescryptor, fontFamily, fontMetrics); + break; + case PdfCjkFontFamily.heiseiKakuGothicW5: + _fillHeiseiKakuGothicW5( + fontDescryptor, fontStyle!, fontFamily, fontMetrics); + break; + case PdfCjkFontFamily.heiseiMinchoW3: + _fillHeiseiMinchoW3(fontDescryptor, fontFamily, fontMetrics); + break; + case PdfCjkFontFamily.monotypeHeiMedium: + _fillMonotypeHeiMedium(fontDescryptor, fontFamily, fontMetrics); + break; + case PdfCjkFontFamily.monotypeSungLight: + _fillMonotypeSungLight(fontDescryptor, fontFamily, fontMetrics); + break; + case PdfCjkFontFamily.sinoTypeSongLight: + _fillSinoTypeSongLight(fontDescryptor, fontFamily, fontMetrics); + break; + default: + break; + } + return fontDescryptor; + } + + /// Fills the monotype sung light font descryptor. + void _fillMonotypeSungLight(_PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + final _Rectangle fontBBox = _Rectangle(-160, -249, 1175, 1137); + _fillFontBBox(fontDescryptor, fontBBox); + _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); + final _PdfNumber stem = _PdfNumber(93); + fontDescryptor[_DictionaryProperties.stemV] = stem; + fontDescryptor[_DictionaryProperties.stemH] = stem; + final _PdfNumber width = _PdfNumber(1000); + fontDescryptor[_DictionaryProperties.avgWidth] = width; + fontDescryptor[_DictionaryProperties.maxWidth] = width; + fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); + fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); + fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); + } + + /// Fills the heisei kaku gothic w5 font descryptor. + void _fillHeiseiKakuGothicW5(_PdfDictionary fontDescryptor, int fontStyle, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + final _Rectangle fontBBox = _Rectangle(-92, -250, 1102, 1175); + final _Rectangle fontBBoxI = _Rectangle(-92, -250, 1102, 1932); + if ((fontStyle & + (PdfFont._getPdfFontStyle(PdfFontStyle.italic) | + PdfFont._getPdfFontStyle(PdfFontStyle.bold))) != + PdfFont._getPdfFontStyle(PdfFontStyle.italic)) { + _fillFontBBox(fontDescryptor, fontBBox); + } else { + _fillFontBBox(fontDescryptor, fontBBoxI); + } + _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); + final _PdfNumber stem = _PdfNumber(93); + fontDescryptor[_DictionaryProperties.stemV] = stem; + fontDescryptor[_DictionaryProperties.stemH] = stem; + final _PdfNumber width = _PdfNumber(1000); + fontDescryptor[_DictionaryProperties.avgWidth] = _PdfNumber(689); + fontDescryptor[_DictionaryProperties.maxWidth] = width; + fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(718); + fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(500); + fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); + } + + /// Fills the hanyang systems shin myeong jo medium font descryptor. + void _fillHanyangSystemsShinMyeongJoMedium(_PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + final _Rectangle fontBBox = _Rectangle(0, -148, 1001, 1028); + _fillFontBBox(fontDescryptor, fontBBox); + _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); + final _PdfNumber stem = _PdfNumber(93); + fontDescryptor[_DictionaryProperties.stemV] = stem; + fontDescryptor[_DictionaryProperties.stemH] = stem; + final _PdfNumber width = _PdfNumber(1000); + fontDescryptor[_DictionaryProperties.avgWidth] = width; + fontDescryptor[_DictionaryProperties.maxWidth] = width; + fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); + fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); + fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); + } + + /// Fills the heisei mincho w3 font descryptor. + void _fillHeiseiMinchoW3(_PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + final _Rectangle fontBBox = _Rectangle(-123, -257, 1124, 1167); + _fillFontBBox(fontDescryptor, fontBBox); + _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); + final _PdfNumber stem = _PdfNumber(93); + fontDescryptor[_DictionaryProperties.stemV] = stem; + fontDescryptor[_DictionaryProperties.stemH] = stem; + final _PdfNumber width = _PdfNumber(1000); + fontDescryptor[_DictionaryProperties.avgWidth] = _PdfNumber(702); + fontDescryptor[_DictionaryProperties.maxWidth] = width; + fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(718); + fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(500); + fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); + } + + /// Fills the sino type song light font descryptor. + void _fillSinoTypeSongLight(_PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + final _Rectangle fontBBox = _Rectangle(-25, -254, 1025, 1134); + _fillFontBBox(fontDescryptor, fontBBox); + _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); + final _PdfNumber stem = _PdfNumber(93); + fontDescryptor[_DictionaryProperties.stemV] = stem; + fontDescryptor[_DictionaryProperties.stemH] = stem; + final _PdfNumber width = _PdfNumber(1000); + fontDescryptor[_DictionaryProperties.avgWidth] = width; + fontDescryptor[_DictionaryProperties.maxWidth] = width; + fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); + fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); + fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); + } + + /// Fills the monotype hei medium font descryptor. + void _fillMonotypeHeiMedium(_PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + final _Rectangle fontBBox = _Rectangle(-45, -250, 1060, 1137); + _fillFontBBox(fontDescryptor, fontBBox); + _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); + final _PdfNumber stem = _PdfNumber(93); + fontDescryptor[_DictionaryProperties.stemV] = stem; + fontDescryptor[_DictionaryProperties.stemH] = stem; + final _PdfNumber width = _PdfNumber(1000); + fontDescryptor[_DictionaryProperties.avgWidth] = width; + fontDescryptor[_DictionaryProperties.maxWidth] = width; + fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); + fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); + fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); + } + + /// Fills the hanyang systems gothic medium font descryptor. + void _fillHanyangSystemsGothicMedium(_PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + final _Rectangle fontBBox = _Rectangle(-6, -145, 1009, 1025); + _fillFontBBox(fontDescryptor, fontBBox); + _fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); + fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(4); + final _PdfNumber stem = _PdfNumber(93); + fontDescryptor[_DictionaryProperties.stemV] = stem; + fontDescryptor[_DictionaryProperties.stemH] = stem; + final _PdfNumber width = _PdfNumber(1000); + fontDescryptor[_DictionaryProperties.avgWidth] = width; + fontDescryptor[_DictionaryProperties.maxWidth] = width; + fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); + fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); + fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); + } + + /// Fills the known info. + void _fillKnownInfo(_PdfDictionary fontDescryptor, + PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { + fontDescryptor[_DictionaryProperties.fontName] = + _PdfName(fontMetrics.postScriptName); + fontDescryptor[_DictionaryProperties.type] = + _PdfName(_DictionaryProperties.fontDescriptor); + fontDescryptor[_DictionaryProperties.italicAngle] = _PdfNumber(0); + fontDescryptor[_DictionaryProperties.missingWidth] = + _PdfNumber((fontMetrics._widthTable! as _CjkWidthTable).defaultWidth); + fontDescryptor[_DictionaryProperties.ascent] = + _PdfNumber(fontMetrics.ascent); + fontDescryptor[_DictionaryProperties.descent] = + _PdfNumber(fontMetrics.descent); + _fillFlags(fontDescryptor, fontFamily); + } + + /// Fills the flags. + void _fillFlags(_PdfDictionary fontDescryptor, PdfCjkFontFamily? fontFamily) { + switch (fontFamily) { + case PdfCjkFontFamily.monotypeHeiMedium: + case PdfCjkFontFamily.hanyangSystemsGothicMedium: + case PdfCjkFontFamily.heiseiKakuGothicW5: + fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(4); + break; + case PdfCjkFontFamily.sinoTypeSongLight: + case PdfCjkFontFamily.monotypeSungLight: + case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: + case PdfCjkFontFamily.heiseiMinchoW3: + fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(6); + break; + default: + break; + } + } + + /// Fills the font BBox. + void _fillFontBBox(_PdfDictionary fontDescryptor, _Rectangle fontBBox) { + fontDescryptor[_DictionaryProperties.fontBBox] = + _PdfArray.fromRectangle(fontBBox); + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart deleted file mode 100644 index 3688dafba..000000000 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/fonts/pdf_cjk_font_descryptor_factory.dart +++ /dev/null @@ -1,212 +0,0 @@ -part of pdf; - -class _PdfCjkFontDescryptorFactory { - static _PdfDictionary getFontDescryptor(PdfCjkFontFamily? fontFamily, - int? fontStyle, _PdfFontMetrics fontMetrics) { - final _PdfDictionary fontDescryptor = _PdfDictionary(); - switch (fontFamily) { - case PdfCjkFontFamily.hanyangSystemsGothicMedium: - fillHanyangSystemsGothicMedium(fontDescryptor, fontFamily, fontMetrics); - break; - case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: - fillHanyangSystemsShinMyeongJoMedium( - fontDescryptor, fontFamily, fontMetrics); - break; - case PdfCjkFontFamily.heiseiKakuGothicW5: - fillHeiseiKakuGothicW5( - fontDescryptor, fontStyle!, fontFamily, fontMetrics); - break; - case PdfCjkFontFamily.heiseiMinchoW3: - fillHeiseiMinchoW3(fontDescryptor, fontFamily, fontMetrics); - break; - case PdfCjkFontFamily.monotypeHeiMedium: - fillMonotypeHeiMedium(fontDescryptor, fontFamily, fontMetrics); - break; - case PdfCjkFontFamily.monotypeSungLight: - fillMonotypeSungLight(fontDescryptor, fontFamily, fontMetrics); - break; - case PdfCjkFontFamily.sinoTypeSongLight: - fillSinoTypeSongLight(fontDescryptor, fontFamily, fontMetrics); - break; - default: - break; - } - return fontDescryptor; - } - - /// Fills the monotype sung light font descryptor. - static void fillMonotypeSungLight(_PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { - final _Rectangle fontBBox = _Rectangle(-160, -249, 1175, 1137); - fillFontBBox(fontDescryptor, fontBBox); - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); - fontDescryptor[_DictionaryProperties.stemV] = stem; - fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); - fontDescryptor[_DictionaryProperties.avgWidth] = width; - fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); - fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); - } - - /// Fills the heisei kaku gothic w5 font descryptor. - static void fillHeiseiKakuGothicW5( - _PdfDictionary fontDescryptor, - int fontStyle, - PdfCjkFontFamily? fontFamily, - _PdfFontMetrics fontMetrics) { - final _Rectangle fontBBox = _Rectangle(-92, -250, 1102, 1175); - final _Rectangle fontBBoxI = _Rectangle(-92, -250, 1102, 1932); - if ((fontStyle & - (PdfFont._getPdfFontStyle(PdfFontStyle.italic) | - PdfFont._getPdfFontStyle(PdfFontStyle.bold))) != - PdfFont._getPdfFontStyle(PdfFontStyle.italic)) { - fillFontBBox(fontDescryptor, fontBBox); - } else { - fillFontBBox(fontDescryptor, fontBBoxI); - } - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); - fontDescryptor[_DictionaryProperties.stemV] = stem; - fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); - fontDescryptor[_DictionaryProperties.avgWidth] = _PdfNumber(689); - fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(718); - fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(500); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); - } - - /// Fills the hanyang systems shin myeong jo medium font descryptor. - static void fillHanyangSystemsShinMyeongJoMedium( - _PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, - _PdfFontMetrics fontMetrics) { - final _Rectangle fontBBox = _Rectangle(0, -148, 1001, 1028); - fillFontBBox(fontDescryptor, fontBBox); - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); - fontDescryptor[_DictionaryProperties.stemV] = stem; - fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); - fontDescryptor[_DictionaryProperties.avgWidth] = width; - fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); - fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); - } - - /// Fills the heisei mincho w3 font descryptor. - static void fillHeiseiMinchoW3(_PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { - final _Rectangle fontBBox = _Rectangle(-123, -257, 1124, 1167); - fillFontBBox(fontDescryptor, fontBBox); - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); - fontDescryptor[_DictionaryProperties.stemV] = stem; - fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); - fontDescryptor[_DictionaryProperties.avgWidth] = _PdfNumber(702); - fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(718); - fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(500); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); - } - - /// Fills the sino type song light font descryptor. - static void fillSinoTypeSongLight(_PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { - final _Rectangle fontBBox = _Rectangle(-25, -254, 1025, 1134); - fillFontBBox(fontDescryptor, fontBBox); - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); - fontDescryptor[_DictionaryProperties.stemV] = stem; - fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); - fontDescryptor[_DictionaryProperties.avgWidth] = width; - fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); - fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); - } - - /// Fills the monotype hei medium font descryptor. - static void fillMonotypeHeiMedium(_PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { - final _Rectangle fontBBox = _Rectangle(-45, -250, 1060, 1137); - fillFontBBox(fontDescryptor, fontBBox); - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - final _PdfNumber stem = _PdfNumber(93); - fontDescryptor[_DictionaryProperties.stemV] = stem; - fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); - fontDescryptor[_DictionaryProperties.avgWidth] = width; - fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); - fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); - } - - /// Fills the hanyang systems gothic medium font descryptor. - static void fillHanyangSystemsGothicMedium(_PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { - final _Rectangle fontBBox = _Rectangle(-6, -145, 1009, 1025); - fillFontBBox(fontDescryptor, fontBBox); - fillKnownInfo(fontDescryptor, fontFamily, fontMetrics); - fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(4); - final _PdfNumber stem = _PdfNumber(93); - fontDescryptor[_DictionaryProperties.stemV] = stem; - fontDescryptor[_DictionaryProperties.stemH] = stem; - final _PdfNumber width = _PdfNumber(1000); - fontDescryptor[_DictionaryProperties.avgWidth] = width; - fontDescryptor[_DictionaryProperties.maxWidth] = width; - fontDescryptor[_DictionaryProperties.capHeight] = _PdfNumber(880); - fontDescryptor[_DictionaryProperties.xHeight] = _PdfNumber(616); - fontDescryptor[_DictionaryProperties.leading] = _PdfNumber(250); - } - - /// Fills the known info. - static void fillKnownInfo(_PdfDictionary fontDescryptor, - PdfCjkFontFamily? fontFamily, _PdfFontMetrics fontMetrics) { - fontDescryptor[_DictionaryProperties.fontName] = - _PdfName(fontMetrics.postScriptName); - fontDescryptor[_DictionaryProperties.type] = - _PdfName(_DictionaryProperties.fontDescriptor); - fontDescryptor[_DictionaryProperties.italicAngle] = _PdfNumber(0); - fontDescryptor[_DictionaryProperties.missingWidth] = - _PdfNumber((fontMetrics._widthTable! as _CjkWidthTable).defaultWidth); - fontDescryptor[_DictionaryProperties.ascent] = - _PdfNumber(fontMetrics.ascent); - fontDescryptor[_DictionaryProperties.descent] = - _PdfNumber(fontMetrics.descent); - fillFlags(fontDescryptor, fontFamily); - } - - /// Fills the flags. - static void fillFlags( - _PdfDictionary fontDescryptor, PdfCjkFontFamily? fontFamily) { - switch (fontFamily) { - case PdfCjkFontFamily.monotypeHeiMedium: - case PdfCjkFontFamily.hanyangSystemsGothicMedium: - case PdfCjkFontFamily.heiseiKakuGothicW5: - fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(4); - break; - case PdfCjkFontFamily.sinoTypeSongLight: - case PdfCjkFontFamily.monotypeSungLight: - case PdfCjkFontFamily.hanyangSystemsShinMyeongJoMedium: - case PdfCjkFontFamily.heiseiMinchoW3: - fontDescryptor[_DictionaryProperties.flags] = _PdfNumber(6); - break; - default: - break; - } - } - - /// Fills the font BBox. - static void fillFontBBox(_PdfDictionary fontDescryptor, _Rectangle fontBBox) { - fontDescryptor[_DictionaryProperties.fontBBox] = - _PdfArray.fromRectangle(fontBBox); - } -} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart index 09090059c..db4f9856b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/graphics/images/decoders/image_decoder.dart @@ -13,9 +13,9 @@ abstract class _ImageDecoder { //Static methods static _ImageDecoder? getDecoder(List data) { _ImageDecoder? decoder; - if (_PdfUtils.isPng(data)) { - decoder = _PngDecoder(data, _PdfUtils._pngSignature.length); - } else if (_PdfUtils.isJpeg(data)) { + if (_isPng(data)) { + decoder = _PngDecoder(data, _pngSignature.length); + } else if (_isJpeg(data)) { decoder = _JpegDecoder(data); } return decoder; @@ -90,4 +90,33 @@ abstract class _ImageDecoder { //Abstract methods void readHeader(); _PdfStream? getImageDictionary(); + + //Utilities + static const List _jpegSignature = [255, 216]; + static const List _pngSignature = [137, 80, 78, 71, 13, 10, 26, 10]; + static bool _isPng(List imageData) { + if (imageData.length >= _pngSignature.length) { + for (int i = 0; i < _pngSignature.length; i++) { + if (_pngSignature[i] != imageData[i]) { + return false; + } + } + return true; + } else { + return false; + } + } + + static bool _isJpeg(List imageData) { + if (imageData.length >= _jpegSignature.length) { + for (int i = 0; i < _jpegSignature.length; i++) { + if (_jpegSignature[i] != imageData[i]) { + return false; + } + } + return true; + } else { + return false; + } + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart index 0f98e9802..5752add34 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/io/pdf_reader.dart @@ -299,7 +299,7 @@ class _PdfReader { _skipWhiteSpace(); character = _peek(); if (_isDelimiter(_getEqualChar(character))) { - final Map result = _appendChar(token); + final Map result = _appendCharacter(token); character = result['character'] as int?; token = result['token'] as String?; return token; @@ -307,7 +307,7 @@ class _PdfReader { while (character != -1 && !_isSeparator(_getEqualChar(character)) && token != '\u0000') { - final Map result = _appendChar(token); + final Map result = _appendCharacter(token); character = result['character'] as int?; token = result['token'] as String?; character = _peek(); @@ -315,7 +315,7 @@ class _PdfReader { return token; } - Map _appendChar(String? token) { + Map _appendCharacter(String? token) { final int character = _read(); if (character != -1) { token = token! + String.fromCharCode(character); @@ -366,7 +366,7 @@ class _PdfReader { character = _peek(); //Return the character if it is a delimiter character. if (_isJsonDelimiter(_getEqualChar(character))) { - final Map result = _appendChar(token); + final Map result = _appendCharacter(token); character = result['character'] as int; return token = result['token'] as String; } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart index f77ab58d0..b6a5c4566 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_layer.dart @@ -188,7 +188,7 @@ class PdfLayer implements _IPdfWrapper { _graphics!._setTransparencyGroup(page); } if (page._isLoadedPage && - (page._rotation != PdfPageRotateAngle.rotateAngle0 || + (page.rotation != PdfPageRotateAngle.rotateAngle0 || page._dictionary.containsKey(_DictionaryProperties.rotate))) { _PdfArray? cropBox; if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { @@ -224,12 +224,12 @@ class PdfLayer implements _IPdfWrapper { rotation = page._dictionary[_DictionaryProperties.rotate] as _PdfNumber?; rotation ??= rotation = _PdfCrossTable._dereference( page._dictionary[_DictionaryProperties.rotate]) as _PdfNumber?; - } else if (page._rotation != PdfPageRotateAngle.rotateAngle0) { - if (page._rotation == PdfPageRotateAngle.rotateAngle90) { + } else if (page.rotation != PdfPageRotateAngle.rotateAngle0) { + if (page.rotation == PdfPageRotateAngle.rotateAngle90) { rotation = _PdfNumber(90); - } else if (page._rotation == PdfPageRotateAngle.rotateAngle180) { + } else if (page.rotation == PdfPageRotateAngle.rotateAngle180) { rotation = _PdfNumber(180); - } else if (page._rotation == PdfPageRotateAngle.rotateAngle270) { + } else if (page.rotation == PdfPageRotateAngle.rotateAngle270) { rotation = _PdfNumber(270); } } 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 804ce887a..389e7ec6d 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 @@ -69,6 +69,9 @@ class PdfPage implements _IPdfWrapper { late bool _graphicStateUpdated; bool _isDefaultGraphics = false; PdfFormFieldsTabOrder _formFieldsTabOrder = PdfFormFieldsTabOrder.none; + PdfPageRotateAngle? _rotation; + Rect _cBox = Rect.zero; + Rect _mBox = Rect.zero; /// Raises before the page saves. Function? _beginSave; @@ -90,10 +93,18 @@ class PdfPage implements _IPdfWrapper { if (_size == null || (_size!.width == 0 && _size!.height == 0)) { double width = 0; double height = 0; - final _IPdfPrimitive? primitive = _dictionary._getValue( + final _IPdfPrimitive? mBox = _dictionary._getValue( _DictionaryProperties.mediaBox, _DictionaryProperties.parent); - if (primitive is _PdfArray) { - final _PdfArray mBox = primitive; + final _IPdfPrimitive? cBox = _dictionary._getValue( + _DictionaryProperties.cropBox, _DictionaryProperties.parent); + if (cBox != null && cBox is _PdfArray) { + final num c0 = (cBox[0]! as _PdfNumber).value!; + final num? c1 = (cBox[1]! as _PdfNumber).value; + final num c2 = (cBox[2]! as _PdfNumber).value!; + final num? c3 = (cBox[3]! as _PdfNumber).value; + width = (c2 - c0).toDouble(); + height = c3 != 0 ? (c3! - c1!).toDouble() : c1!.toDouble(); + } else if (mBox != null && mBox is _PdfArray) { final num m0 = (mBox[0]! as _PdfNumber).value!; final num? m1 = (mBox[1]! as _PdfNumber).value; final num m2 = (mBox[2]! as _PdfNumber).value!; @@ -135,6 +146,42 @@ class PdfPage implements _IPdfWrapper { } } + /// Gets the crop box. + Rect get _cropBox { + if (_cBox.isEmpty) { + final _IPdfPrimitive? cBox = _dictionary._getValue( + _DictionaryProperties.cropBox, _DictionaryProperties.parent); + if (cBox != null && cBox is _PdfArray) { + final double width = (cBox[2]! as _PdfNumber).value!.toDouble(); + final double height = (cBox[3]! as _PdfNumber).value != 0 + ? (cBox[3]! as _PdfNumber).value!.toDouble() + : (cBox[1]! as _PdfNumber).value!.toDouble(); + final double x = (cBox[0]! as _PdfNumber).value!.toDouble(); + final double y = (cBox[1]! as _PdfNumber).value!.toDouble(); + _cBox = _calculateBounds(x, y, width, height); + } + } + return _cBox; + } + + /// Gets the media box. + Rect get _mediaBox { + if (_mBox.isEmpty) { + final _IPdfPrimitive? mBox = _dictionary._getValue( + _DictionaryProperties.mediaBox, _DictionaryProperties.parent); + if (mBox != null && mBox is _PdfArray) { + final double width = (mBox[2]! as _PdfNumber).value!.toDouble(); + final double height = (mBox[3]! as _PdfNumber).value != 0 + ? (mBox[3]! as _PdfNumber).value!.toDouble() + : (mBox[1]! as _PdfNumber).value!.toDouble(); + final double x = (mBox[0]! as _PdfNumber).value!.toDouble(); + final double y = (mBox[1]! as _PdfNumber).value!.toDouble(); + _mBox = _calculateBounds(x, y, width, height); + } + } + return _mBox; + } + /// Gets a collection of the annotations of the page- Read only. /// ```dart /// //Creates a new PDF document @@ -313,7 +360,32 @@ class PdfPage implements _IPdfWrapper { PdfPageOrientation get _orientation => _obtainOrientation(); - PdfPageRotateAngle get _rotation => _obtainRotation(); + /// Gets or sets the rotation of PDF page + /// + /// This property only works on existing PDF document pages + /// + /// ```dart + /// //Create a new PDF document. + /// PdfDocument document = PdfDocument(inputBytes: data); + /// //Rotation of the PDF page + /// PdfPageRotateAngle rotation = document.pages[0].rotation; + /// //Save the document. + /// List bytes = document.save(); + /// //Dispose the document. + /// document.dispose(); + /// ``` + PdfPageRotateAngle get rotation { + _rotation ??= _obtainRotation(); + return _rotation!; + } + + set rotation(PdfPageRotateAngle angle) { + if (_isLoadedPage && rotation != angle) { + _rotation = angle; + _dictionary[_DictionaryProperties.rotate] = + _PdfNumber(PdfSectionCollection._rotateFactor * angle.index); + } + } //Public methods /// Get the PDF page size reduced by page margins and @@ -420,11 +492,11 @@ class PdfPage implements _IPdfWrapper { } PdfPageRotateAngle _getRotationFromAngle(int angle) { - if (angle == 90) { + if (angle == 1) { return PdfPageRotateAngle.rotateAngle90; - } else if (angle == 180) { + } else if (angle == 2) { return PdfPageRotateAngle.rotateAngle180; - } else if (angle == 270) { + } else if (angle == 3) { return PdfPageRotateAngle.rotateAngle270; } else { return PdfPageRotateAngle.rotateAngle0; @@ -799,6 +871,14 @@ class PdfPage implements _IPdfWrapper { as _PdfArray?; } + Rect _calculateBounds(double x, double y, double width, double height) { + width = width - x; + if (height != y) { + height = height - y; + } + return Rect.fromLTWH(x, y, width, height); + } + //_IPdfWrapper elements @override _IPdfPrimitive? get _element => _dictionary; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart index 3ca77383b..42ecf1f9c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_collection.dart @@ -149,7 +149,7 @@ class PdfPageCollection { final _PdfDictionary parent = result['node'] as _PdfDictionary; localIndex = result['index'] as int?; if (parent.containsKey(_DictionaryProperties.rotate)) { - final int rotationValue = page._rotation.index * 90; + final int rotationValue = page.rotation.index * 90; final _PdfNumber parentRotation = parent[_DictionaryProperties.rotate]! as _PdfNumber; if (parentRotation.value!.toInt() != rotationValue && diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart index c75404775..644e07bba 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pages/pdf_page_layer.dart @@ -205,8 +205,8 @@ class PdfPageLayer implements _IPdfWrapper { _graphics!._setTransparencyGroup(page!); } if (page != null && - page._isLoadedPage && - (page._rotation != PdfPageRotateAngle.rotateAngle0 || + !page._isLoadedPage && + (page.rotation != PdfPageRotateAngle.rotateAngle0 || page._dictionary.containsKey(_DictionaryProperties.rotate))) { _PdfArray? cropBox; if (page._dictionary.containsKey(_DictionaryProperties.cropBox)) { @@ -239,12 +239,12 @@ class PdfPageLayer implements _IPdfWrapper { rotation = page._dictionary[_DictionaryProperties.rotate] as _PdfNumber?; rotation ??= rotation = _PdfCrossTable._dereference( page._dictionary[_DictionaryProperties.rotate]) as _PdfNumber?; - } else if (page._rotation != PdfPageRotateAngle.rotateAngle0) { - if (page._rotation == PdfPageRotateAngle.rotateAngle90) { + } else if (page.rotation != PdfPageRotateAngle.rotateAngle0) { + if (page.rotation == PdfPageRotateAngle.rotateAngle90) { rotation = _PdfNumber(90); - } else if (page._rotation == PdfPageRotateAngle.rotateAngle180) { + } else if (page.rotation == PdfPageRotateAngle.rotateAngle180) { rotation = _PdfNumber(180); - } else if (page._rotation == PdfPageRotateAngle.rotateAngle270) { + } else if (page.rotation == PdfPageRotateAngle.rotateAngle270) { rotation = _PdfNumber(270); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart index f5018ceea..4f9ffeeb5 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_automatic_field.dart @@ -300,4 +300,111 @@ abstract class PdfAutomaticField { return bounds.size; } } + + // fields + static const double _letterLimit = 26.0; + static const int _acsiiStartIndex = 65 - 1; + + // methods + static String _convert(int intArabic, PdfNumberStyle numberStyle) { + switch (numberStyle) { + case PdfNumberStyle.none: + return ''; + case PdfNumberStyle.numeric: + return intArabic.toString(); + case PdfNumberStyle.lowerLatin: + return _arabicToLetter(intArabic).toLowerCase(); + case PdfNumberStyle.lowerRoman: + return _arabicToRoman(intArabic).toLowerCase(); + case PdfNumberStyle.upperLatin: + return _arabicToLetter(intArabic); + case PdfNumberStyle.upperRoman: + return _arabicToRoman(intArabic); + } + } + + static String _arabicToRoman(int intArabic) { + final StringBuffer retval = StringBuffer(); + List result = _generateNumber(intArabic, 1000, 'M'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 900, 'CM'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 500, 'D'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 400, 'CD'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 100, 'C'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 90, 'XC'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 50, 'L'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 40, 'XL'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 10, 'X'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 9, 'IX'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 5, 'V'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 4, 'IV'); + retval.write(result.elementAt(0)); + result = _generateNumber(result.elementAt(1) as int, 1, 'I'); + retval.write(result.elementAt(0)); + return retval.toString(); + } + + static List _generateNumber(int value, int magnitude, String letter) { + final StringBuffer numberString = StringBuffer(); + while (value >= magnitude) { + value -= magnitude; + numberString.write(letter); + } + final List result = []; + result.add(numberString.toString()); + result.add(value); + return result; + } + + static String _arabicToLetter(int arabic) { + final List stack = _convertToLetter(arabic.toDouble()); + final StringBuffer result = StringBuffer(); + + while (stack.isNotEmpty) { + final int n = stack.removeLast(); + _appendChar(result, n); + } + return result.toString(); + } + + static List _convertToLetter(double arabic) { + if (arabic <= 0) { + throw ArgumentError.value('arabic value can not be less 0'); + } + final List stack = []; + while ((arabic.toInt()) > _letterLimit) { + double remainder = arabic % _letterLimit; + + if (remainder == 0.0) { + arabic = arabic / _letterLimit - 1; + remainder = _letterLimit; + } else { + arabic /= _letterLimit; + } + + stack.add(remainder.toInt()); + } + if (arabic > 0) { + stack.add(arabic.toInt()); + } + return stack; + } + + static void _appendChar(StringBuffer result, int number) { + if (number <= 0 || number > 26) { + throw ArgumentError.value('Value can not be less 0 and greater 26'); + } + final String letter = (_acsiiStartIndex + number).toString(); + result.write(String.fromCharCode(int.parse(letter))); + } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart deleted file mode 100644 index cdb2f9e53..000000000 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_number_convertor.dart +++ /dev/null @@ -1,110 +0,0 @@ -part of pdf; - -class _PdfNumberConvertor { - // fields - static const double _letterLimit = 26.0; - static const int _acsiiStartIndex = 65 - 1; - - // methods - static String _convert(int intArabic, PdfNumberStyle numberStyle) { - switch (numberStyle) { - case PdfNumberStyle.none: - return ''; - case PdfNumberStyle.numeric: - return intArabic.toString(); - case PdfNumberStyle.lowerLatin: - return _arabicToLetter(intArabic).toLowerCase(); - case PdfNumberStyle.lowerRoman: - return _arabicToRoman(intArabic).toLowerCase(); - case PdfNumberStyle.upperLatin: - return _arabicToLetter(intArabic); - case PdfNumberStyle.upperRoman: - return _arabicToRoman(intArabic); - } - } - - static String _arabicToRoman(int intArabic) { - final StringBuffer retval = StringBuffer(); - List result = _generateNumber(intArabic, 1000, 'M'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 900, 'CM'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 500, 'D'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 400, 'CD'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 100, 'C'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 90, 'XC'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 50, 'L'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 40, 'XL'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 10, 'X'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 9, 'IX'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 5, 'V'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 4, 'IV'); - retval.write(result.elementAt(0)); - result = _generateNumber(result.elementAt(1) as int, 1, 'I'); - retval.write(result.elementAt(0)); - return retval.toString(); - } - - static List _generateNumber(int value, int magnitude, String letter) { - final StringBuffer numberString = StringBuffer(); - while (value >= magnitude) { - value -= magnitude; - numberString.write(letter); - } - final List result = []; - result.add(numberString.toString()); - result.add(value); - return result; - } - - static String _arabicToLetter(int arabic) { - final List stack = _convertToLetter(arabic.toDouble()); - final StringBuffer result = StringBuffer(); - - while (stack.isNotEmpty) { - final int n = stack.removeLast(); - _appendChar(result, n); - } - return result.toString(); - } - - static List _convertToLetter(double arabic) { - if (arabic <= 0) { - throw ArgumentError.value('arabic value can not be less 0'); - } - final List stack = []; - while ((arabic.toInt()) > _letterLimit) { - double remainder = arabic % _letterLimit; - - if (remainder == 0.0) { - arabic = arabic / _letterLimit - 1; - remainder = _letterLimit; - } else { - arabic /= _letterLimit; - } - - stack.add(remainder.toInt()); - } - if (arabic > 0) { - stack.add(arabic.toInt()); - } - return stack; - } - - static void _appendChar(StringBuffer result, int number) { - if (number <= 0 || number > 26) { - throw ArgumentError.value('Value can not be less 0 and greater 26'); - } - final String letter = (_acsiiStartIndex + number).toString(); - result.write(String.fromCharCode(int.parse(letter))); - } -} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart index ef17acea0..28a59555c 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/pdf_document/automatic_fields/pdf_page_count_field.dart @@ -140,11 +140,11 @@ class PdfPageCountField extends _PdfSingleValueField { if (_isSectionPageCount) { final PdfSection section = page._section!; final int count = section._count; - return _PdfNumberConvertor._convert(count, numberStyle); + return PdfAutomaticField._convert(count, numberStyle); } else { final PdfDocument document = page._section!._parent!._document!; final int number = document.pages.count; - return _PdfNumberConvertor._convert(number, numberStyle); + return PdfAutomaticField._convert(number, numberStyle); } } return result; 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 d91f9bfaf..c94fbdcc6 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 @@ -145,11 +145,11 @@ class PdfPageNumberField extends _PdfMultipleValueField { if (_isSectionPageNumber) { final PdfSection section = page!._section!; final int index = section._indexOf(page) + 1; - return _PdfNumberConvertor._convert(index, numberStyle); + return PdfAutomaticField._convert(index, numberStyle); } else { final PdfDocument document = page!._section!._parent!._document!; final int pageIndex = document.pages.indexOf(page) + 1; - return _PdfNumberConvertor._convert(pageIndex, numberStyle); + return PdfAutomaticField._convert(pageIndex, numberStyle); } } } 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 2f0f8f076..9038cc51a 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 @@ -586,17 +586,12 @@ class PdfBookmark extends PdfBookmarkBase { } if (page != null) { - double? topValue = + final double topValue = (top == null) ? 0 : page.size.height - top.value!; final double leftValue = (left == null) ? 0 : left.value! as double; - - if (page._isLoadedPage && - page._rotation != PdfPageRotateAngle.rotateAngle0) { - topValue = _checkRotation(page, top, left); - } _destination = - PdfDestination(page, Offset(leftValue, topValue!)); + PdfDestination(page, Offset(leftValue, topValue)); if (zoom != null) { _destination!.zoom = zoom.value!.toDouble(); } @@ -752,19 +747,4 @@ class PdfBookmark extends PdfBookmarkBase { } return _destination; } - - double? _checkRotation(PdfPage page, _PdfNumber? top, _PdfNumber? left) { - double? topValue = 0; - - left = (left == null) ? _PdfNumber(0) : left; - - if (page._rotation == PdfPageRotateAngle.rotateAngle90) { - topValue = (top == null) ? 0 : left.value! as double; - } else if (page._rotation == PdfPageRotateAngle.rotateAngle180) { - topValue = (top == null) ? 0 : top.value! as double; - } else if (page._rotation == PdfPageRotateAngle.rotateAngle270) { - topValue = (top == null) ? 0 : page.size.width - left.value!; - } - return topValue; - } } 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 14934687b..1a1c6f78a 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 @@ -17,22 +17,18 @@ class _PdfName implements _IPdfPrimitive { //Implementation String _escapeString(String value) { - if (value.isEmpty) { - throw ArgumentError.value(value, 'empty string'); - } else { - String result = ''; - for (int i = 0; i < value.length; i++) { - final int code = value.codeUnitAt(i); - if (code == _replacements[3]) { - result += r'\r'; - } else if (code == _replacements[2]) { - result += '\n'; - } else { - result += value[i]; - } + String result = ''; + for (int i = 0; i < value.length; i++) { + final int code = value.codeUnitAt(i); + if (code == _replacements[3]) { + result += r'\r'; + } else if (code == _replacements[2]) { + result += '\n'; + } else { + result += value[i]; } - return result; } + return result; } @override 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 85db29e05..1ae0127f2 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 @@ -188,6 +188,67 @@ abstract class _Asn1 extends _Asn1Encode { } return value; } + + static const String nullValue = 'NULL'; + static const String der = 'DER'; + static const String desEde = 'DESede'; + static const String des = 'DES'; + static const String rsa = 'RSA'; + static const String pkcs7 = 'PKCS7'; + static int getHashCode(List? data) { + if (data == null) { + return 0; + } + int i = data.length; + int hc = i + 1; + while (--i >= 0) { + hc = (hc * 257).toSigned(32); + hc = (hc ^ data[i].toUnsigned(8)).toSigned(32); + } + return hc; + } + + static bool areEqual(List? a, List? b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + return haveSameContents(a, b); + } + + static bool haveSameContents(List a, List b) { + int i = a.length; + if (i != b.length) { + return false; + } + while (i != 0) { + --i; + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + static List clone(List data) { + return List.generate(data.length, (int i) => data[i]); + } + + static void uInt32ToBe(int n, List bs, int off) { + bs[off] = (n >> 24).toUnsigned(8); + bs[off + 1] = (n >> 16).toUnsigned(8); + bs[off + 2] = (n >> 8).toUnsigned(8); + bs[off + 3] = n.toUnsigned(8); + } + + static int beToUInt32(List bs, int off) { + return bs[off].toUnsigned(8) << 24 | + bs[off + 1].toUnsigned(8) << 16 | + bs[off + 2].toUnsigned(8) << 8 | + bs[off + 3].toUnsigned(8); + } } abstract class _Asn1Encode implements _IAsn1 { @@ -199,7 +260,7 @@ abstract class _Asn1Encode implements _IAsn1 { if (encoding == null) { return (_Asn1DerStream([])..writeObject(this))._stream; } else { - if (encoding == _Asn1Constants.der) { + if (encoding == _Asn1.der) { final _DerStream stream = _DerStream([])..writeObject(this); return stream._stream; } @@ -208,7 +269,7 @@ abstract class _Asn1Encode implements _IAsn1 { } List? getDerEncoded() { - return getEncoded(_Asn1Constants.der); + return getEncoded(_Asn1.der); } @override @@ -252,7 +313,7 @@ class _Asn1Octet extends _Asn1 implements _IAsn1Octet { _value = value; } _Asn1Octet.fromObject(_Asn1Encode obj) { - _value = obj.getEncoded(_Asn1Constants.der); + _value = obj.getEncoded(_Asn1.der); } //Fields List? _value; @@ -270,14 +331,14 @@ class _Asn1Octet extends _Asn1 implements _IAsn1Octet { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return _Asn1Constants.getHashCode(getOctets()); + return _Asn1.getHashCode(getOctets()); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object asn1) { if (asn1 is _DerOctet) { - return _Asn1Constants.areEqual(getOctets(), asn1.getOctets()); + return _Asn1.areEqual(getOctets(), asn1.getOctets()); } else { return false; } @@ -330,7 +391,7 @@ abstract class _Asn1Null extends _Asn1 { @override String toString() { - return _Asn1Constants.nullValue; + return _Asn1.nullValue; } @override @@ -955,69 +1016,6 @@ class _OctetStream extends _StreamReader { } } -class _Asn1Constants { - static const String nullValue = 'NULL'; - static const String der = 'DER'; - static const String desEde = 'DESede'; - static const String des = 'DES'; - static const String rsa = 'RSA'; - static const String pkcs7 = 'PKCS7'; - static int getHashCode(List? data) { - if (data == null) { - return 0; - } - int i = data.length; - int hc = i + 1; - while (--i >= 0) { - hc = (hc * 257).toSigned(32); - hc = (hc ^ data[i].toUnsigned(8)).toSigned(32); - } - return hc; - } - - static bool areEqual(List? a, List? b) { - if (a == b) { - return true; - } - if (a == null || b == null) { - return false; - } - return haveSameContents(a, b); - } - - static bool haveSameContents(List a, List b) { - int i = a.length; - if (i != b.length) { - return false; - } - while (i != 0) { - --i; - if (a[i] != b[i]) { - return false; - } - } - return true; - } - - static List clone(List data) { - return List.generate(data.length, (int i) => data[i]); - } - - static void uInt32ToBe(int n, List bs, int off) { - bs[off] = (n >> 24).toUnsigned(8); - bs[off + 1] = (n >> 16).toUnsigned(8); - bs[off + 2] = (n >> 8).toUnsigned(8); - bs[off + 3] = n.toUnsigned(8); - } - - static int beToUInt32(List bs, int off) { - return bs[off].toUnsigned(8) << 24 | - bs[off + 1].toUnsigned(8) << 16 | - bs[off + 2].toUnsigned(8) << 8 | - bs[off + 3].toUnsigned(8); - } -} - class _Asn1Tags { static const int boolean = 0x01; static const int integer = 0x02; 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 ca28d2a34..ef416472d 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 @@ -169,15 +169,14 @@ class _DerBitString extends _DerString { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return _extra.hashCode ^ _Asn1Constants.getHashCode(_data); + return _extra.hashCode ^ _Asn1.getHashCode(_data); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object asn1) { if (asn1 is _DerBitString) { - return _extra == asn1._extra && - _Asn1Constants.areEqual(_data, asn1._data); + return _extra == asn1._extra && _Asn1.areEqual(_data, asn1._data); } else { return false; } @@ -372,14 +371,14 @@ class _DerInteger extends _Asn1 { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return _Asn1Constants.getHashCode(_value); + return _Asn1.getHashCode(_value); } @override // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object asn1) { if (asn1 is _DerInteger) { - return _Asn1Constants.areEqual(_value, asn1._value); + return _Asn1.areEqual(_value, asn1._value); } else { return false; } @@ -444,7 +443,7 @@ class _DerObjectID extends _Asn1 { } _DerObjectID.fromBytes(List bytes) { _id = getObjectID(bytes); - _bytes = _Asn1Constants.clone(bytes); + _bytes = _Asn1.clone(bytes); } String? _id; @override @@ -628,10 +627,10 @@ class _DerObjectID extends _Asn1 { } static _DerObjectID? fromOctetString(List bytes) { - final int hashCode = _Asn1Constants.getHashCode(bytes); + final int hashCode = _Asn1.getHashCode(bytes); final int first = hashCode & 1023; final _DerObjectID? entry = _objects[first]; - if (entry != null && _Asn1Constants.areEqual(bytes, entry.getBytes())) { + if (entry != null && _Asn1.areEqual(bytes, entry.getBytes())) { return entry; } _objects[first] = _DerObjectID.fromBytes(bytes); @@ -902,7 +901,7 @@ class _DerCatalogue extends _Asn1 { // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object asn1) { if (asn1 is _DerCatalogue) { - return _Asn1Constants.areEqual(_bytes, asn1._bytes); + return _Asn1.areEqual(_bytes, asn1._bytes); } else { return false; } @@ -911,7 +910,7 @@ class _DerCatalogue extends _Asn1 { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode { - return _Asn1Constants.getHashCode(_bytes); + return _Asn1.getHashCode(_bytes); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart index 6edbbfddf..b4fd29100 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/buffered_block_padding_base.dart @@ -454,7 +454,7 @@ class _Pkcs7Padding implements _IPadding { _Pkcs7Padding(); //Properties @override - String get paddingName => _Asn1Constants.pkcs7; + String get paddingName => _Asn1.pkcs7; //Implementation @override void initialize(Random? random) {} diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart index 113316b92..2975f57a1 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_ede_algorithm.dart @@ -11,7 +11,7 @@ class _DesEdeAlogorithm extends _DataEncryption { @override int? get blockSize => _blockSize; @override - String get algorithmName => _Asn1Constants.desEde; + String get algorithmName => _Asn1.desEde; //Implementation @override void initialize(bool? forEncryption, _ICipherParameter? parameters) { diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart index 102474e47..0ddced610 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/data_encryption.dart @@ -689,7 +689,7 @@ class _DataEncryption implements _ICipher { //Properties List? get keys => _keys; @override - String get algorithmName => _Asn1Constants.des; + String get algorithmName => _Asn1.des; @override bool get isBlock => false; @override @@ -787,8 +787,8 @@ class _DataEncryption implements _ICipher { void encryptData(List? keys, List inputBytes, int inOffset, List outBytes, int outOffset) { - int left = _Asn1Constants.beToUInt32(inputBytes, inOffset); - int right = _Asn1Constants.beToUInt32(inputBytes, inOffset + 4); + int left = _Asn1.beToUInt32(inputBytes, inOffset); + int right = _Asn1.beToUInt32(inputBytes, inOffset + 4); int data = (((left >> 4) ^ right) & 0x0f0f0f0f).toUnsigned(32); right ^= data; left ^= data << 4; @@ -849,7 +849,7 @@ class _DataEncryption implements _ICipher { data = ((right >> 4) ^ left) & 0x0f0f0f0f; left ^= data; right ^= data << 4; - _Asn1Constants.uInt32ToBe(right, outBytes, outOffset); - _Asn1Constants.uInt32ToBe(left, outBytes, outOffset + 4); + _Asn1.uInt32ToBe(right, outBytes, outOffset); + _Asn1.uInt32ToBe(left, outBytes, outOffset + 4); } } diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart index 3dbf23f7b..413164201 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/pdf_cms_signer.dart @@ -81,7 +81,7 @@ class _PdfCmsSigner { } final List dig = alg .getMessageDigest(hashAlgorithm!) - .convert(_signCert!._c!.getEncoded(_Asn1Constants.der)) + .convert(_signCert!._c!.getEncoded(_Asn1.der)) .bytes as List; aaV2._encodableObjects.add(_DerOctet(dig)); v._encodableObjects.add(_DerSet(array: <_Asn1Encode>[ @@ -145,7 +145,7 @@ class _PdfCmsSigner { // ignore: avoid_function_literals_in_foreach_calls _certificates.forEach((_X509Certificate? xcert) { v._encodableObjects.add( - _Asn1Stream(_StreamReader(xcert!._c!.getEncoded(_Asn1Constants.der))) + _Asn1Stream(_StreamReader(xcert!._c!.getEncoded(_Asn1.der))) .readAsn1()); }); final _DerSet dercertificates = _DerSet(collection: v); @@ -153,8 +153,8 @@ class _PdfCmsSigner { signerinfo._encodableObjects .add(_DerInteger(_bigIntToBytes(BigInt.from(_signerVersion)))); v = _Asn1EncodeCollection(); - v._encodableObjects.add(getIssuer( - _signCert!._c!.tbsCertificate!.getEncoded(_Asn1Constants.der))); + 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)); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart index e1073c435..8eb249687 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/cryptography/rsa_algorithm.dart @@ -12,7 +12,7 @@ class _RsaAlgorithm implements _ICipherBlock { //Properties @override - String get algorithmName => _Asn1Constants.rsa; + String get algorithmName => _Asn1.rsa; @override int get inputBlock => _rsaCoreEngine.inputBlockSize; @override 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 00a11b3b1..da479cecc 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 @@ -589,7 +589,7 @@ class _CertificateIdentifier { // ignore: avoid_equals_and_hash_code_on_mutable_classes bool operator ==(Object other) { if (other is _CertificateIdentifier) { - return _Asn1Constants.areEqual(_id, other._id); + return _Asn1.areEqual(_id, other._id); } else { return false; } @@ -597,5 +597,5 @@ class _CertificateIdentifier { @override // ignore: avoid_equals_and_hash_code_on_mutable_classes - int get hashCode => _Asn1Constants.getHashCode(_id); + int get hashCode => _Asn1.getHashCode(_id); } 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 4c424d7af..903c47f74 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 @@ -339,7 +339,7 @@ class _PdfSignatureDictionary implements _IPdfWrapper { final List? sh = pkcs7 .getSequenceDataSet( hash, ocspByte, crlBytes, _sig!.cryptographicStandard) - .getEncoded(_Asn1Constants.der); + .getEncoded(_Asn1.der); List? extSignature; if (externalSigner != null) { final SignerResult? signerResult = externalSigner.sign(sh!); diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart index 194bac8eb..d46b69989 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/security/digital_signature/pkcs/password_utility.dart @@ -310,8 +310,8 @@ abstract class _PasswordGenerator { int? _count; _ICipherParameter generateParam(int? keySize, [String? algorithm, int? size]); void init(List password, List value, int count) { - _password = _Asn1Constants.clone(password); - _value = _Asn1Constants.clone(value); + _password = _Asn1.clone(password); + _value = _Asn1.clone(value); _count = count; } 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 96a22d87b..1ad836b7d 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 @@ -965,7 +965,9 @@ class _PdfGridLayouter extends _ElementLayouter { i + row.cells[i].columnSpan > _cellEndIndex + 1)) && row.cells[i].columnSpan - _cellEndIndex + i - 1 > 0) { row.cells[row._rowOverflowIndex + 1].value = - stringResult != null ? stringResult._remainder : null; + stringResult != null && stringResult._remainder != null + ? stringResult._remainder + : ''; row.cells[row._rowOverflowIndex + 1].stringFormat = row.cells[i].stringFormat; row.cells[row._rowOverflowIndex + 1].style = row.cells[i].style; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart index a5c0e8548..eb7550511 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/grid/pdf_grid_cell.dart @@ -1358,11 +1358,10 @@ class PdfGridCell { for (int i = currentColIndex; i < currentColIndex + span; i++) { if (_row!._grid.style.allowHorizontalOverflow) { double width; - final double compWidth = - _row!._grid._gridSize.width < g!.clientSize.width - ? _row!._grid._gridSize.width - : g.clientSize.width; - if (_row!._grid._gridSize.width > g.clientSize.width) { + final double compWidth = _row!._grid._size.width < g!.clientSize.width + ? _row!._grid._size.width + : g.clientSize.width; + if (_row!._grid._size.width > g.clientSize.width) { width = bounds.x + totalWidth + _row!._grid.columns[i].width; } else { width = totalWidth + _row!._grid.columns[i].width; diff --git a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart index 9943d9cd7..220e5de7b 100644 --- a/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart +++ b/packages/syncfusion_flutter_pdf/lib/src/pdf/implementation/structured_elements/lists/bullets/pdf_ordered_marker.dart @@ -176,6 +176,6 @@ class PdfOrderedMarker extends PdfMarker { //Implementation /// Gets the marker number. String _getNumber() { - return _PdfNumberConvertor._convert(_startNumber + _currentIndex, style); + return PdfAutomaticField._convert(_startNumber + _currentIndex, style); } } diff --git a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md index 630f1298f..11fc4c56f 100644 --- a/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdfviewer/CHANGELOG.md @@ -1,4 +1,25 @@ -## [unreleased] +## Unreleased + +**Features** + +* Support for screen reading has been provided. +* Now, PDF document can be viewed page by page horizontally. +* Horizontal scrolling support has been provided. +* Support for text selection and text search in rotated document has been provided. + +# [19.2.57-beta] - 08/24/2021 + +* Now, SfPdfViewer widget won't be rebuilding continuously without any user interaction. + +# [19.2.51-beta] - 08/03/2021 + +* Now, `searchText` method works properly in `onDocumentLoaded` callback. + +# [19.2.46-beta] - 07/06/2021 + +* Now, Grayscale images will be displayed properly in a PDF document while viewing in iOS 14.1 or later versions. + +## [19.2.44-beta] - 06/30/2021 * The macOS platform support has been provided. diff --git a/packages/syncfusion_flutter_pdfviewer/README.md b/packages/syncfusion_flutter_pdfviewer/README.md index 4da5f7ae1..a73bb5bda 100644 --- a/packages/syncfusion_flutter_pdfviewer/README.md +++ b/packages/syncfusion_flutter_pdfviewer/README.md @@ -16,6 +16,8 @@ The Flutter PDF Viewer plugin lets you view the PDF documents seamlessly and eff - [Customize the visibility of scroll head and scroll status](#customize-the-visibility-of-scroll-head-and-scroll-status) - [Customize the visibility of page navigation dialog](#customize-the-visibility-of-page-navigation-dialog) - [Enable or disable the double-tap zoom ](#enable-or-disable-the-double-tap-zoom) + - [Change the page layout](#change-the-page-layout) + - [Switch scroll direction](#switch-scroll-direction) - [Change the zoom level factor](#change-the-zoom-level-factor) - [Navigate to the desired pages](#navigate-to-the-desired-pages) - [Navigate to the desired bookmark topics](#navigate-to-the-desired-bookmark-topics) @@ -31,6 +33,8 @@ The Flutter PDF Viewer plugin lets you view the PDF documents seamlessly and eff * **Magnification** - The content of the document can be efficiently zoomed in and out. +* **Page Layout and Scroll Options** - Layout the pages efficiently in a page by page (single page) scrolling mode or continuous scrolling mode. Also, scroll through pages in both horizontal and vertical direction. + * **Page navigation** - Navigate to the desired pages instantly. ![syncfusion_flutter_pdfviewer_page_navigation](https://cdn.syncfusion.com/content/images/PDFViewer/pagination-dialog.png) @@ -146,6 +150,36 @@ Widget build(BuildContext context) { } ``` +## Change the page layout + +Page layout modes describe how the PDF page is displayed. As a default, the page layout will be in continuous mode. You can change the page layout mode using the **pageLayoutMode** property. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Container( + child: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + pageLayoutMode: PdfPageLayoutMode.single))); +} +``` + +## Switch scroll direction + +Scrolling options describe how the PDF pages can be scrolled. As a default, the page will be scrolled in vertical direction. You can change the scroll direction using the **scrollDirection** property. In Single page layout mode, only horizontal scrolling is supported. + +```dart +@override +Widget build(BuildContext context) { + return Scaffold( + body: Container( + child: SfPdfViewer.network( + 'https://cdn.syncfusion.com/content/PDFViewer/flutter-succinctly.pdf', + scrollDirection: PdfScrollDirection.horizontal))); +} +``` + ## Enable or disable the double-tap zoom As a default, the SfPdfViewer will be zoomed in and out when double-tapped. You can enable or disable the double-tap zoom using the **enableDoubleTapZooming** property. @@ -436,4 +470,4 @@ Widget build(BuildContext context) { 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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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 +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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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/example/lib/generated_plugin_registrant.dart b/packages/syncfusion_flutter_pdfviewer/example/lib/generated_plugin_registrant.dart deleted file mode 100644 index 7a098ac12..000000000 --- a/packages/syncfusion_flutter_pdfviewer/example/lib/generated_plugin_registrant.dart +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// ignore_for_file: lines_longer_than_80_chars - -import 'package:syncfusion_flutter_pdfviewer_web/pdfviewer_web.dart'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -// ignore: public_member_api_docs -void registerPlugins(Registrar registrar) { - SyncfusionFlutterPdfViewerPlugin.registerWith(registrar); - registrar.registerMessageHandler(); -} diff --git a/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart b/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart index bcff884ff..30293062e 100644 --- a/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart +++ b/packages/syncfusion_flutter_pdfviewer/example/lib/main.dart @@ -32,6 +32,7 @@ class _HomePage extends State { icon: const Icon( Icons.bookmark, color: Colors.white, + semanticLabel: 'Bookmark', ), onPressed: () { _pdfViewerKey.currentState?.openBookmarkView(); diff --git a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift index 0161e5f31..70d4af6c1 100644 --- a/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift +++ b/packages/syncfusion_flutter_pdfviewer/ios/Classes/SwiftSyncfusionFlutterPdfViewerPlugin.swift @@ -126,17 +126,20 @@ public class SwiftSyncfusionFlutterPdfViewerPlugin: NSObject, FlutterPlugin { let page = self.document!.page(at: Int(index)) var pageRect = page!.getBoxRect(.mediaBox) if #available(iOS 10.0, *) { - let renderer = UIGraphicsImageRenderer(size: pageRect.size) - + let format = UIGraphicsImageRendererFormat() + if #available(iOS 12.0, *) { + format.preferredRange = .standard + } else { + format.prefersExtendedRange = false + } + let renderer = UIGraphicsImageRenderer(size: pageRect.size,format: format) let img = renderer.image { ctx in - let mediaBox = page!.getBoxRect(.mediaBox) ctx.cgContext.beginPage(mediaBox: &pageRect) let transform = page!.getDrawingTransform(.mediaBox, rect: mediaBox, rotate: 0, preserveAspectRatio: true) ctx.cgContext.translateBy(x: 0.0, y: mediaBox.size.height) ctx.cgContext.scaleBy(x: 1, y: -1) ctx.cgContext.concatenate(transform) - ctx.cgContext.drawPDFPage(page!) ctx.cgContext.endPage() } 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 47becf956..2941952cc 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_item.dart @@ -193,6 +193,7 @@ class _BookmarkItemState extends State { Icons.arrow_back, size: _kPdfBackIconSize, color: _pdfViewerThemeData!.bookmarkViewStyle.backIconColor, + semanticLabel: 'Previous level bookmark', ), ), ), @@ -221,6 +222,7 @@ class _BookmarkItemState extends State { size: _kPdfExpandIconSize, color: _pdfViewerThemeData! .bookmarkViewStyle.navigationIconColor, + semanticLabel: 'Next level bookmark', ), ), ), 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 3c0e30445..c59b02a1d 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,6 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; /// Height of the bookmark header bar. const double _kPdfHeaderBarHeight = 53.0; @@ -81,41 +81,46 @@ class _BookmarkToolbarState extends State { offset: Offset(0, 1), ), ]; - return Container( - height: _kPdfHeaderBarHeight, - margin: const EdgeInsets.only(bottom: 3), - decoration: BoxDecoration( - color: _pdfViewerThemeData!.bookmarkViewStyle.headerBarColor, - boxShadow: boxShadows, - ), - child: Stack( - children: [ - Positioned( - top: _kPdfHeaderTextTopPosition, - left: _kPdfHeaderTextLeftPosition, - height: _kPdfHeaderTextHeight, - child: Text( - _localizations!.pdfBookmarksLabel, - style: _pdfViewerThemeData!.bookmarkViewStyle.headerTextStyle, + return Semantics( + label: _localizations!.pdfBookmarksLabel, + child: Container( + height: _kPdfHeaderBarHeight, + margin: const EdgeInsets.only(bottom: 3), + decoration: BoxDecoration( + color: _pdfViewerThemeData!.bookmarkViewStyle.headerBarColor, + boxShadow: boxShadows, + ), + child: Stack( + children: [ + Positioned( + top: _kPdfHeaderTextTopPosition, + left: _kPdfHeaderTextLeftPosition, + height: _kPdfHeaderTextHeight, + child: Text( + _localizations!.pdfBookmarksLabel, + style: _pdfViewerThemeData!.bookmarkViewStyle.headerTextStyle, + semanticsLabel: '', + ), ), - ), - Positioned( - top: _kPdfCloseIconTopPosition, - right: _kPdfCloseIconRightPosition, - height: _kPdfCloseIconHeight, - width: _kPdfCloseIconWidth, - child: RawMaterialButton( - onPressed: () { - widget.onCloseButtonPressed(); - }, - child: Icon( - Icons.close, - size: _kPdfCloseIconSize, - color: _pdfViewerThemeData!.bookmarkViewStyle.closeIconColor, + Positioned( + top: _kPdfCloseIconTopPosition, + right: _kPdfCloseIconRightPosition, + height: _kPdfCloseIconHeight, + width: _kPdfCloseIconWidth, + child: RawMaterialButton( + onPressed: () { + widget.onCloseButtonPressed(); + }, + child: Icon( + Icons.close, + size: _kPdfCloseIconSize, + color: _pdfViewerThemeData!.bookmarkViewStyle.closeIconColor, + 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 2fe5a9d00..984d489f1 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/bookmark/bookmark_view.dart @@ -1,15 +1,19 @@ import 'dart:math'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_pdf/pdf.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; import 'bookmark_item.dart'; import 'bookmark_toolbar.dart'; +///Triggers when the bookmark is opened or closed +typedef _BookmarkView = void Function(bool); + /// Standard tablet width of the bookmark view. const double _kPdfTabletBookmarkWidth = 400.0; @@ -22,7 +26,8 @@ const double _kPdfSubBookmarkTitlePosition = 55.0; /// BookmarkView of PdfViewer class BookmarkView extends StatefulWidget { /// BookmarkView Constructor. - const BookmarkView(Key key, this.pdfDocument, this.controller) + const BookmarkView( + Key key, this.pdfDocument, this.controller, this._bookmarkView) : super(key: key); /// [PdfViewerController] instance of PdfViewer. @@ -31,6 +36,9 @@ class BookmarkView extends StatefulWidget { /// [PdfDocument] instance of PDF library. final PdfDocument? pdfDocument; + ///Triggers when the bookmark is opened or closed + final _BookmarkView _bookmarkView; + @override State createState() => BookmarkViewControllerState(); } @@ -98,6 +106,7 @@ class BookmarkViewControllerState extends State { } setState(() { showBookmark = true; + widget._bookmarkView(true); }); } @@ -106,6 +115,7 @@ class BookmarkViewControllerState extends State { setState(() { _isExpanded = false; showBookmark = false; + widget._bookmarkView(false); }); if (_historyEntry != null && Navigator.canPop(context)) { await Navigator.of(context).maybePop(); diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart index 3faf625bc..c79e4aa23 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/mobile_helper.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + /// Checks whether focus node of pdf page view has primary focus. bool hasPrimaryFocus = false; @@ -6,3 +8,8 @@ void preventDefaultMenu() { // ignore: avoid_returning_null_for_void return null; } + +/// Gets platform type. +String getPlatformType() { + return Platform.operatingSystem; +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart index dcddeb65c..868e2bc31 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdf_provider.dart @@ -1,11 +1,10 @@ import 'dart:io'; - import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:http/http.dart' as http; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; /// Represents a base class of PDF document provider. /// The PDF provider can be from Asset, Memory, File and Network. diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart index 9b36c3bc5..27ee3e71c 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_helper.dart @@ -1,15 +1,19 @@ import 'dart:io'; import 'dart:ui'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/common/mobile_helper.dart' + if (dart.library.html) 'package:syncfusion_flutter_pdfviewer/src/common/web_helper.dart' + as helper; import 'package:syncfusion_flutter_pdfviewer/src/control/pdftextline.dart'; /// Indicates whether the current environment is running in Desktop bool kIsDesktop = kIsWeb || Platform.isMacOS; /// Indicates whether the current environment is running in macOS -bool kIsMacOS = !kIsWeb && Platform.isMacOS; +bool kIsMacOS = helper.getPlatformType() == 'macos'; /// TextSelectionHelper for storing information of text selection. class TextSelectionHelper { 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 8da665f4e..269eeb221 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/pdfviewer_plugin.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'dart:typed_data'; + import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; - import 'package:syncfusion_flutter_pdfviewer_platform_interface/pdfviewer_platform_interface.dart'; -import 'package:flutter/services.dart'; /// Establishes communication between native(Android and iOS) code /// and flutter code using [MethodChannel] diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart index 188c1224e..74ca6ed06 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/common/web_helper.dart @@ -22,3 +22,11 @@ void _preventSpecificDefaultMenu(html.KeyboardEvent e) { e.preventDefault(); } } + +/// Gets platform type. +String getPlatformType() { + if (html.window.navigator.platform!.toLowerCase().contains('macintel')) { + return 'macos'; + } + return html.window.navigator.platform!.toString().toLowerCase(); +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart index 0a49dfd19..472394f07 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/enums.dart @@ -6,3 +6,21 @@ enum PdfInteractionMode { /// Enables the panning mode in a desktop browser to move or scroll through the pages using mouse dragging. pan } + +/// Represents different scrolling direction. +enum PdfScrollDirection { + /// Pages will be scrolled up and down. + vertical, + + /// Pages will be scrolled left and right. + horizontal +} + +/// Represents different pdf layout mode. +enum PdfPageLayoutMode { + /// Continuous transition of pages. + continuous, + + /// Page by page transition of pages. + single +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/control/interactive_scrollable.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/control/interactive_scrollable.dart deleted file mode 100644 index 414f811e1..000000000 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/interactive_scrollable.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'package:flutter/material.dart'; - -/// [InteractiveScrollable] enables pan and zoom interactions with its child. -@immutable -class InteractiveScrollable extends StatefulWidget { - /// Constructor for InteractiveScrollable. - const InteractiveScrollable(this.child, - {Key? key, - this.clipBehavior = Clip.hardEdge, - this.alignPanAxis = false, - this.boundaryMargin = EdgeInsets.zero, - // These default scale values were eyeballed as reasonable limits for common - // use cases. - this.maxScale = 3, - this.minScale = 1, - this.onInteractionStart, - this.onInteractionUpdate, - this.onInteractionEnd, - this.panEnabled = true, - this.scaleEnabled = true, - this.constrained = true, - this.transformationController}) - : super(key: key); - - /// Whether the normal size constraints at this point in the widget tree are - /// applied to the child. - /// - /// If set to false, then the child will be given infinite constraints. This - /// is often useful when a child should be bigger than the InteractiveScrollable. - final bool constrained; - - /// If set to [Clip.none], the child may extend beyond the size of the InteractiveScrollable, - /// but it will not receive gestures in these areas. - /// Be sure that the InteractiveScrollable is the desired size when using [Clip.none]. - /// - /// Defaults to [Clip.hardEdge]. - final Clip clipBehavior; - - /// If true, panning is only allowed in the direction of the horizontal axis - /// or the vertical axis. - /// - /// In other words, when this is true, diagonal panning is not allowed. A - /// single gesture begun along one axis cannot also cause panning along the - /// other axis without stopping and beginning a new gesture. This is a common - /// pattern in tables where data is displayed in columns and rows. - /// - /// See also: - /// * [constrained], which has an example of creating a table that uses - /// alignPanAxis. - final bool alignPanAxis; - - /// A margin for the visible boundaries of the child. - /// - /// Any transformation that results in the viewport being able to view outside - /// of the boundaries will be stopped at the boundary. The boundaries do not - /// rotate with the rest of the scene, so they are always aligned with the - /// viewport. - /// - /// To produce no boundaries at all, pass infinite [EdgeInsets], such as - /// `EdgeInsets.all(double.infinity)`. - /// - /// No edge can be NaN. - /// - /// Defaults to [EdgeInsets.zero], which results in boundaries that are the - /// exact same size and position as the [child]. - final EdgeInsets boundaryMargin; - - /// The Widget to perform the transformations on. - /// - /// Cannot be null. - final Widget child; - - /// If false, the user will be prevented from panning. - /// - /// Defaults to true. - /// - /// See also: - /// - /// * [scaleEnabled], which is similar but for scale. - final bool panEnabled; - - /// If false, the user will be prevented from scaling. - /// - /// Defaults to true. - /// - /// See also: - /// - /// * [panEnabled], which is similar but for panning. - final bool scaleEnabled; - - /// The maximum allowed scale. - /// - /// The scale will be clamped between this and [minScale] inclusively. - /// - /// Defaults to 3. - /// - /// Cannot be null, and must be greater than zero and greater than minScale. - final double maxScale; - - /// The minimum allowed scale. - /// - /// The scale will be clamped between this and [maxScale] inclusively. - /// - /// Scale is also affected by [boundaryMargin]. If the scale would result in - /// viewing beyond the boundary, then it will not be allowed. By default, - /// boundaryMargin is EdgeInsets.zero, so scaling below 1.0 will not be - /// allowed in most cases without first increasing the boundaryMargin. - /// - /// Defaults to 1. - /// - /// Cannot be null, and must be a finite number greater than zero and less - /// than maxScale. - final double minScale; - - /// Called when the user begins a pan or scale gesture on the widget. - final GestureScaleStartCallback? onInteractionStart; - - /// Called when the user updates a pan or scale gesture on the widget. - final GestureScaleUpdateCallback? onInteractionUpdate; - - /// Called when the user ends a pan or scale gesture on the widget. - final GestureScaleEndCallback? onInteractionEnd; - - /// A [TransformationController] for the transformation performed on the - /// child. - final TransformationController? transformationController; - - @override - _InteractiveScrollableState createState() => _InteractiveScrollableState(); -} - -/// State for [InteractiveScrollable]. -class _InteractiveScrollableState extends State { - @override - Widget build(BuildContext context) { - return InteractiveViewer( - minScale: widget.minScale, - maxScale: widget.maxScale, - constrained: widget.constrained, - onInteractionStart: widget.onInteractionStart, - onInteractionUpdate: widget.onInteractionUpdate, - onInteractionEnd: widget.onInteractionEnd, - scaleEnabled: widget.scaleEnabled, - panEnabled: widget.panEnabled, - alignPanAxis: widget.alignPanAxis, - transformationController: widget.transformationController, - boundaryMargin: widget.boundaryMargin, - clipBehavior: widget.clipBehavior, - child: widget.child, - ); - } -} 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 3ffe7c214..05b1628fc 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 @@ -1,4 +1,5 @@ import 'dart:typed_data'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -6,15 +7,13 @@ import 'package:flutter/services.dart'; import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_pdf/pdf.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/pdfviewer_canvas.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:syncfusion_flutter_pdfviewer/src/common/mobile_helper.dart' if (dart.library.html) 'package:syncfusion_flutter_pdfviewer/src/common/web_helper.dart' as helper; - -import 'enums.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdfviewer_canvas.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/single_page_view.dart'; /// Wrapper class of [Image] widget which shows the PDF pages as an image class PdfPageView extends StatefulWidget { @@ -37,12 +36,18 @@ class PdfPageView extends StatefulWidget { this.onTextSelectionChanged, this.onTextSelectionDragStarted, this.onTextSelectionDragEnded, - this.onDocumentLinkNavigationInvoked, this.searchTextHighlightColor, this.textCollection, this.isMobileWebView, this.pdfTextSearchResult, this.pdfScrollableStateKey, + this.singlePageViewStateKey, + this.scrollDirection, + this.onPdfPagePointerDown, + this.onPdfPagePointerMove, + this.onPdfPagePointerUp, + this.semanticLabel, + this.isSinglePageView, ) : super(key: key); /// Image stream @@ -102,15 +107,33 @@ class PdfPageView extends StatefulWidget { /// PdfTextSearchResult instance final PdfTextSearchResult pdfTextSearchResult; - /// Triggered while document link navigation. - final Function(double value) onDocumentLinkNavigationInvoked; - /// If true,MobileWebView is enabled.Default value is false. final bool isMobileWebView; /// Key to access scrollable. final GlobalKey pdfScrollableStateKey; + /// Key to access single page view state. + final GlobalKey singlePageViewStateKey; + + /// Represents the scroll direction of PdfViewer. + final PdfScrollDirection scrollDirection; + + /// Triggers when pointer down event is called on pdf page. + final PointerDownEventListener onPdfPagePointerDown; + + /// Triggers when pointer move event is called on pdf page. + final PointerMoveEventListener onPdfPagePointerMove; + + /// Triggers when pointer up event is called on pdf page. + final PointerUpEventListener onPdfPagePointerUp; + + /// A Semantic description of the page. + final String? semanticLabel; + + /// Determines layout option in PdfViewer. + final bool isSinglePageView; + @override State createState() { return PdfPageViewState(); @@ -121,12 +144,12 @@ class PdfPageView extends StatefulWidget { class PdfPageViewState extends State { SfPdfViewerThemeData? _pdfViewerThemeData; final GlobalKey _canvasKey = GlobalKey(); - final double _jumpOffset = 10.0; int _lastTap = DateTime.now().millisecondsSinceEpoch; int _consecutiveTaps = 1; + final double _jumpOffset = 10.0; /// Mouse cursor for mouse region widget - SystemMouseCursor cursor = SystemMouseCursors.basic; + SystemMouseCursor _cursor = SystemMouseCursors.basic; /// focus node of pdf page view. FocusNode focusNode = FocusNode(); @@ -164,354 +187,298 @@ class PdfPageViewState extends State { super.dispose(); } - void _scroll(bool isReachedTop, bool isSelectionScroll) { - if (isSelectionScroll) { - canvasRenderBox!.getSelectionDetails().endBubbleY = - canvasRenderBox!.getSelectionDetails().endBubbleY! + - (isReachedTop ? -3 : 3); - } - - final double position = widget.pdfViewerController.scrollOffset.dy + - (isReachedTop ? -_jumpOffset : _jumpOffset); - - WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { - widget.pdfScrollableStateKey.currentState?.jumpTo(yOffset: position); - }); - } - - void _scrollWhileSelection() { - if (canvasRenderBox != null && - canvasRenderBox!.getSelectionDetails().isCursorExit && - canvasRenderBox!.getSelectionDetails().mouseSelectionEnabled) { - final TextSelectionHelper details = - canvasRenderBox!.getSelectionDetails(); - final int viewId = canvasRenderBox!.getSelectionDetails().viewId ?? 0; - if (details.isCursorReachedTop && - widget.pdfViewerController.pageNumber >= viewId + 1) { - _scroll(details.isCursorReachedTop, true); - } else if (!details.isCursorReachedTop && - widget.pdfViewerController.pageNumber <= viewId + 1) { - _scroll(details.isCursorReachedTop, true); - } - if (widget.onTextSelectionChanged != null) { - widget.onTextSelectionChanged!( - PdfTextSelectionChangedDetails(null, null)); - } - } - } - @override Widget build(BuildContext context) { PaintingBinding.instance?.imageCache?.clear(); PaintingBinding.instance?.imageCache?.clearLiveImages(); + final double pageSpacing = + widget.pageIndex == widget.pdfViewerController.pageCount - 1 + ? 0.0 + : widget.pageSpacing; + final double heightSpacing = + widget.scrollDirection == PdfScrollDirection.horizontal + ? 0.0 + : pageSpacing; + final double widthSpacing = + widget.scrollDirection == PdfScrollDirection.horizontal && + !widget.isSinglePageView + ? pageSpacing + : 0.0; if (widget.imageStream != null) { - _scrollWhileSelection(); - final Widget page = Container( - height: widget.height + widget.pageSpacing, - alignment: Alignment.topCenter, - child: Column(children: [ - Image.memory(widget.imageStream!, - width: widget.width, - height: widget.height, - fit: BoxFit.fitWidth, - alignment: Alignment.center), - Container( - height: widget.pageSpacing, - color: _pdfViewerThemeData!.backgroundColor, - ) - ])); - final Widget pdfPage = (kIsDesktop && !widget.isMobileWebView) - ? Container( - height: widget.height + widget.pageSpacing, - width: widget.width, - color: Colors.white, - alignment: Alignment.topCenter, - child: page, - ) - : Container( - height: widget.height + widget.pageSpacing, - width: widget.width, - color: Colors.white, - alignment: Alignment.topCenter, - child: page, - ); + final PdfPageRotateAngle rotatedAngle = + widget.pdfDocument!.pages[widget.pageIndex].rotation; + final Widget image = Image.memory( + widget.imageStream!, + width: widget.width, + height: widget.height, + fit: BoxFit.fitWidth, + semanticLabel: widget.semanticLabel, + alignment: Alignment.center, + ); + final Widget pdfPage = Container( + height: widget.height + heightSpacing, + width: widget.width + widthSpacing, + color: Colors.white, + alignment: Alignment.topCenter, + child: widget.scrollDirection == PdfScrollDirection.vertical + ? Column(children: [ + image, + Container( + height: pageSpacing, + color: _pdfViewerThemeData!.backgroundColor, + ) + ]) + : Row(children: [ + image, + Container( + width: widget.isSinglePageView ? 0.0 : pageSpacing, + color: _pdfViewerThemeData!.backgroundColor, + ) + ]), + ); + 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 Widget canvasContainer = Container( - height: widget.height, - width: widget.width, + height: isRotatedTo90or270 ? widget.width : widget.height, + width: isRotatedTo90or270 ? widget.height : widget.width, alignment: Alignment.topCenter, child: PdfViewerCanvas( - _canvasKey, - widget.height, - widget.width, - widget.pdfDocument, - widget.pageIndex, - widget.pdfPages, - widget.interactionMode, - widget.pdfViewerController, - widget.enableDocumentLinkAnnotation, - widget.enableTextSelection, - widget.onTextSelectionChanged, - widget.onTextSelectionDragStarted, - widget.onTextSelectionDragEnded, - widget.onDocumentLinkNavigationInvoked, - widget.textCollection, - widget.searchTextHighlightColor, - widget.pdfTextSearchResult, - widget.isMobileWebView, - widget.pdfScrollableStateKey, - widget.viewportGlobalRect, - )); + _canvasKey, + isRotatedTo90or270 ? widget.width : widget.height, + isRotatedTo90or270 ? widget.height : widget.width, + widget.pdfDocument, + widget.pageIndex, + widget.pdfPages, + widget.interactionMode, + widget.pdfViewerController, + widget.enableDocumentLinkAnnotation, + widget.enableTextSelection, + widget.onTextSelectionChanged, + widget.onTextSelectionDragStarted, + widget.onTextSelectionDragEnded, + widget.textCollection, + widget.searchTextHighlightColor, + widget.pdfTextSearchResult, + widget.isMobileWebView, + widget.pdfScrollableStateKey, + widget.singlePageViewStateKey, + widget.viewportGlobalRect, + widget.scrollDirection, + widget.isSinglePageView)); final Widget canvas = (kIsDesktop && !widget.isMobileWebView && canvasRenderBox != null) - ? Listener( - onPointerSignal: (PointerSignalEvent details) { - canvasRenderBox!.updateContextMenuPosition(); - }, - onPointerDown: (PointerDownEvent details) { - if (kIsDesktop && !widget.isMobileWebView) { - final int now = DateTime.now().millisecondsSinceEpoch; - if (now - _lastTap <= 500) { - _consecutiveTaps++; - if (_consecutiveTaps == 2 && - details.buttons != kSecondaryButton) { - canvasRenderBox!.handleDoubleTapDown(details); - } - if (_consecutiveTaps == 3 && - details.buttons != kSecondaryButton) { - canvasRenderBox!.handleTripleTapDown(details); - } - } else { - _consecutiveTaps = 1; + ? RotatedBox( + quarterTurns: quarterTurns, + child: Listener( + onPointerSignal: (PointerSignalEvent details) { + if (widget.isSinglePageView && + details is PointerScrollEvent) { + widget.singlePageViewStateKey.currentState?.jumpTo( + yOffset: widget.pdfViewerController.scrollOffset.dy + + (details.scrollDelta.dy.isNegative + ? -_jumpOffset + : _jumpOffset)); } - _lastTap = now; - } - }, - child: RawKeyboardListener( - autofocus: true, - focusNode: focusNode, - onKey: (RawKeyEvent event) { - final bool isPrimaryKeyPressed = - kIsWeb ? event.isControlPressed : event.isMetaPressed; - if ((canvasRenderBox! - .getSelectionDetails() - .mouseSelectionEnabled || - canvasRenderBox! - .getSelectionDetails() - .selectionEnabled) && - isPrimaryKeyPressed && - event.logicalKey == LogicalKeyboardKey.keyC) { - Clipboard.setData(ClipboardData( - text: - canvasRenderBox!.getSelectionDetails().copiedText ?? - '')); - } - if (isPrimaryKeyPressed && - event.logicalKey == LogicalKeyboardKey.digit0) { - widget.pdfViewerController.zoomLevel = 1.0; - } - if (isPrimaryKeyPressed && - event.logicalKey == LogicalKeyboardKey.minus) { - if (event.runtimeType.toString() == 'RawKeyDownEvent') { - double zoomLevel = widget.pdfViewerController.zoomLevel; - if (zoomLevel >= 1.0 && zoomLevel <= 1.25) { - zoomLevel = 1.0; - } else if (zoomLevel > 1.25 && zoomLevel <= 1.50) { - zoomLevel = 1.25; - } else if (zoomLevel > 1.50 && zoomLevel <= 2.0) { - zoomLevel = 1.50; - } else { - zoomLevel = 2.0; + canvasRenderBox!.updateContextMenuPosition(); + }, + onPointerDown: (PointerDownEvent details) { + widget.onPdfPagePointerDown(details); + if (kIsDesktop && !widget.isMobileWebView) { + final int now = DateTime.now().millisecondsSinceEpoch; + if (now - _lastTap <= 500) { + _consecutiveTaps++; + if (_consecutiveTaps == 2 && + details.buttons != kSecondaryButton) { + focusNode.requestFocus(); + canvasRenderBox!.handleDoubleTapDown(details); } - widget.pdfViewerController.zoomLevel = zoomLevel; - } - } - if (isPrimaryKeyPressed && - event.logicalKey == LogicalKeyboardKey.equal) { - if (event.runtimeType.toString() == 'RawKeyDownEvent') { - double zoomLevel = widget.pdfViewerController.zoomLevel; - if (zoomLevel >= 1.0 && zoomLevel < 1.25) { - zoomLevel = 1.25; - } else if (zoomLevel >= 1.25 && zoomLevel < 1.50) { - zoomLevel = 1.50; - } else if (zoomLevel >= 1.50 && zoomLevel < 2.0) { - zoomLevel = 2.0; - } else { - zoomLevel = 3.0; + if (_consecutiveTaps == 3 && + details.buttons != kSecondaryButton) { + focusNode.requestFocus(); + canvasRenderBox!.handleTripleTapDown(details); } - widget.pdfViewerController.zoomLevel = zoomLevel; - } - } - if (event.runtimeType.toString() == 'RawKeyDownEvent') { - if (event.logicalKey == LogicalKeyboardKey.home || - (kIsMacOS && - event.logicalKey == LogicalKeyboardKey.fn && - event.logicalKey == LogicalKeyboardKey.arrowLeft)) { - widget.pdfViewerController.jumpToPage(1); - } else if (event.logicalKey == LogicalKeyboardKey.end || - (kIsMacOS && - event.logicalKey == LogicalKeyboardKey.fn && - event.logicalKey == - LogicalKeyboardKey.arrowRight)) { - widget.pdfViewerController - .jumpToPage(widget.pdfViewerController.pageCount); - } else if (event.logicalKey == - LogicalKeyboardKey.arrowRight) { - widget.pdfViewerController.nextPage(); - } else if (event.logicalKey == - LogicalKeyboardKey.arrowLeft) { - widget.pdfViewerController.previousPage(); + } else { + _consecutiveTaps = 1; } + _lastTap = now; } - if (event.logicalKey == LogicalKeyboardKey.arrowUp) { - _scroll(true, false); + }, + onPointerMove: (PointerMoveEvent details) { + focusNode.requestFocus(); + widget.onPdfPagePointerMove(details); + if (widget.interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grabbing; } - if (event.logicalKey == LogicalKeyboardKey.arrowDown) { - _scroll(false, false); + }, + onPointerUp: (PointerUpEvent details) { + widget.onPdfPagePointerUp(details); + if (widget.interactionMode == PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grab; } }, - child: MouseRegion( - cursor: cursor, - onHover: (PointerHoverEvent details) { - if (canvasRenderBox != null) { - if (widget.interactionMode == - PdfInteractionMode.selection) { - final bool isText = canvasRenderBox! - .findTextWhileHover(details.localPosition) != - null; - final bool isTOC = - canvasRenderBox!.findTOC(details.localPosition); - if (isTOC) { - cursor = SystemMouseCursors.click; - } else if (isText && !isTOC) { - cursor = SystemMouseCursors.text; + child: RawKeyboardListener( + focusNode: focusNode, + onKey: (RawKeyEvent event) { + final bool isPrimaryKeyPressed = + kIsMacOS ? event.isMetaPressed : event.isControlPressed; + if ((canvasRenderBox! + .getSelectionDetails() + .mouseSelectionEnabled || + canvasRenderBox! + .getSelectionDetails() + .selectionEnabled) && + isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.keyC) { + Clipboard.setData(ClipboardData( + text: canvasRenderBox! + .getSelectionDetails() + .copiedText ?? + '')); + } + if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.digit0) { + widget.pdfViewerController.zoomLevel = 1.0; + } + if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.minus) { + if (event.runtimeType.toString() == 'RawKeyDownEvent') { + double zoomLevel = widget.pdfViewerController.zoomLevel; + if (zoomLevel >= 1.0 && zoomLevel <= 1.25) { + zoomLevel = 1.0; + } else if (zoomLevel > 1.25 && zoomLevel <= 1.50) { + zoomLevel = 1.25; + } else if (zoomLevel > 1.50 && zoomLevel <= 2.0) { + zoomLevel = 1.50; } else { - cursor = SystemMouseCursors.basic; + zoomLevel = 2.0; } - } else { - final bool isTOC = - canvasRenderBox!.findTOC(details.localPosition); - if (isTOC) { - cursor = SystemMouseCursors.click; - } else if (cursor != SystemMouseCursors.grab) { - cursor = SystemMouseCursors.grab; + widget.pdfViewerController.zoomLevel = zoomLevel; + } + } + if (isPrimaryKeyPressed && + event.logicalKey == LogicalKeyboardKey.equal) { + if (event.runtimeType.toString() == 'RawKeyDownEvent') { + double zoomLevel = widget.pdfViewerController.zoomLevel; + if (zoomLevel >= 1.0 && zoomLevel < 1.25) { + zoomLevel = 1.25; + } else if (zoomLevel >= 1.25 && zoomLevel < 1.50) { + zoomLevel = 1.50; + } else if (zoomLevel >= 1.50 && zoomLevel < 2.0) { + zoomLevel = 2.0; + } else { + zoomLevel = 3.0; } + widget.pdfViewerController.zoomLevel = zoomLevel; } } + if (event.runtimeType.toString() == 'RawKeyDownEvent') { + if (event.logicalKey == LogicalKeyboardKey.home || + (kIsMacOS && + event.logicalKey == LogicalKeyboardKey.fn && + event.logicalKey == + LogicalKeyboardKey.arrowLeft)) { + widget.pdfViewerController.jumpToPage(1); + } else if (event.logicalKey == LogicalKeyboardKey.end || + (kIsMacOS && + event.logicalKey == LogicalKeyboardKey.fn && + event.logicalKey == + LogicalKeyboardKey.arrowRight)) { + widget.pdfViewerController + .jumpToPage(widget.pdfViewerController.pageCount); + } else if (event.logicalKey == + LogicalKeyboardKey.arrowRight) { + widget.pdfViewerController.nextPage(); + } else if (event.logicalKey == + LogicalKeyboardKey.arrowLeft) { + widget.pdfViewerController.previousPage(); + } + } + if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + canvasRenderBox!.scroll(true, false); + } + if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + canvasRenderBox!.scroll(false, false); + } }, - child: canvasContainer, + child: MouseRegion( + cursor: _cursor, + onHover: (PointerHoverEvent details) { + setState(() { + if (canvasRenderBox != null) { + if (widget.interactionMode == + PdfInteractionMode.selection) { + final bool isText = canvasRenderBox! + .findTextWhileHover( + details.localPosition) != + null; + final bool isTOC = + canvasRenderBox!.findTOC(details.localPosition); + if (isTOC) { + _cursor = SystemMouseCursors.click; + } else if (isText && !isTOC) { + if (isRotatedTo90or270) { + _cursor = SystemMouseCursors.verticalText; + } else { + _cursor = SystemMouseCursors.text; + } + } else { + _cursor = SystemMouseCursors.basic; + } + } else { + final bool isTOC = + canvasRenderBox!.findTOC(details.localPosition); + if (isTOC) { + _cursor = SystemMouseCursors.click; + } else if (_cursor != SystemMouseCursors.grab) { + _cursor = SystemMouseCursors.grab; + } + } + } + }); + }, + child: canvasContainer, + ), ), ), ) - : canvasContainer; - final List child = [ + : RotatedBox( + quarterTurns: quarterTurns, + child: Listener( + onPointerDown: (PointerDownEvent details) { + widget.onPdfPagePointerDown(details); + }, + child: canvasContainer)); + return Stack(children: [ pdfPage, canvas, - ]; - if (kIsDesktop && !widget.isMobileWebView) { - final double widthFactor = - widget.pdfScrollableStateKey.currentState!.paddingWidthScale == 0 - ? widget.pdfViewerController.zoomLevel - : widget.pdfScrollableStateKey.currentState!.paddingWidthScale; - child.insert( - 0, - MouseRegion( - cursor: cursor, - onHover: (PointerHoverEvent details) { - if (widget.interactionMode == PdfInteractionMode.pan) { - cursor = SystemMouseCursors.grab; - } else { - cursor = SystemMouseCursors.basic; - } - }, - child: FittedBox( - fit: BoxFit.fitWidth, - clipBehavior: Clip.hardEdge, - child: Container( - alignment: Alignment.topLeft, - height: widget.height + widget.pageSpacing, - width: widget.parentViewport.width / widthFactor.clamp(1, 3), - ), - ), - )); - } else { - // ignore: cast_nullable_to_non_nullable - if (((widget.pdfScrollableStateKey.currentWidget as PdfScrollable) - .pdfDimension - .height) * - (widget.pdfScrollableStateKey.currentState! - .paddingHeightScale == - 0 - ? widget.pdfViewerController.zoomLevel - : widget.pdfScrollableStateKey.currentState! - .paddingHeightScale) < - widget.parentViewport.height) { - final double paddingHeight = (widget.height < - (widget.parentViewport.height / - (widget.pdfScrollableStateKey.currentState! - .paddingHeightScale == - 0 - ? widget.pdfViewerController.zoomLevel - : widget.pdfScrollableStateKey.currentState! - .paddingHeightScale))) - ? (widget.parentViewport.height / - (widget.pdfScrollableStateKey.currentState! - .paddingHeightScale == - 0 - ? widget.pdfViewerController.zoomLevel - : widget.pdfScrollableStateKey.currentState! - .paddingHeightScale)) - - widget.height - : 0; - if (paddingHeight > 0) { - final Widget emptyContainer = Container( - alignment: Alignment.topCenter, - width: widget.width, - height: (paddingHeight / 2) - .clamp(0, widget.parentViewport.height - widget.height), - ); - return Column( - children: [ - emptyContainer, - Stack(alignment: Alignment.topCenter, children: child), - ], - ); - } - } - } - return Stack(alignment: Alignment.topCenter, children: child); + ]); } else { + final BorderSide borderSide = BorderSide( + width: widget.isSinglePageView ? pageSpacing / 2 : pageSpacing, + color: _pdfViewerThemeData!.backgroundColor); final Widget child = Container( - height: widget.height + widget.pageSpacing, - width: widget.width, - alignment: Alignment.topCenter, + height: widget.height + heightSpacing, + width: widget.width + widthSpacing, color: Colors.white, foregroundDecoration: BoxDecoration( - border: Border( - bottom: BorderSide( - width: widget.pageSpacing, - color: _pdfViewerThemeData!.backgroundColor), - ), + border: widget.isSinglePageView + ? Border(left: borderSide, right: borderSide) + : widget.scrollDirection == PdfScrollDirection.horizontal + ? Border(right: borderSide) + : Border(bottom: borderSide), ), ); - if (kIsDesktop && - !widget.isMobileWebView && - widget.parentViewport.width > - (widget.width * widget.pdfViewerController.zoomLevel)) { - return FittedBox( - fit: BoxFit.fitWidth, - clipBehavior: Clip.hardEdge, - child: Container( - alignment: Alignment.topCenter, - height: widget.height + widget.pageSpacing, - width: widget.parentViewport.width / - widget.pdfViewerController.zoomLevel, - child: child, - ), - ); - } else { - return child; - } + return child; } } } 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 4bf6fd4ab..0d33bca3d 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdf_scrollable.dart @@ -23,6 +23,7 @@ class PdfScrollable extends StatefulWidget { this.pdfViewerController, this.isMobileWebView, this.pdfDimension, + this.totalImageSize, this.viewportDimension, this.onPdfOffsetChanged, this.isPanEnabled, @@ -34,6 +35,8 @@ class PdfScrollable extends StatefulWidget { this.scaleEnabled, this.maxScrollExtent, this.pdfPages, + this.scrollDirection, + this.isBookmarkViewOpen, this.child, {Key? key, this.onDoubleTap}) @@ -96,6 +99,16 @@ class PdfScrollable extends StatefulWidget { /// Maximum scroll extent final double maxScrollExtent; + /// Represents the scroll direction of PdfViewer. + final PdfScrollDirection scrollDirection; + + /// Total image size of the PdfViewer. + final Size totalImageSize; + + /// Indicates whether the built-in bookmark view in the [SfPdfViewer] is + /// opened or not. + final bool isBookmarkViewOpen; + @override PdfScrollableState createState() => PdfScrollableState(); } @@ -155,17 +168,19 @@ class PdfScrollableState extends State { widget.pdfViewerController, widget.isMobileWebView, widget.pdfDimension, + widget.totalImageSize, widget.viewportDimension, currentOffset, widget.maxScale, widget.minScale, - scaleTo, - forcePixels, - widget.onDoubleTap, + _onDoubleTapZoomInvoked, widget.enableDoubleTapZooming, widget.interactionMode, widget.scaleEnabled, widget.maxPdfPageWidth, + widget.pdfPages, + widget.scrollDirection, + widget.isBookmarkViewOpen, widget.child, isPanEnabled: widget.isPanEnabled, onInteractionStart: _handleInteractionStart, @@ -178,11 +193,20 @@ class PdfScrollableState extends State { /// Handles interaction start and updates the UI void _handleInteractionStart(ScaleStartDetails details) { - previousZoomLevel = widget.pdfViewerController.zoomLevel; + if (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled)) { + previousZoomLevel = widget.pdfViewerController.zoomLevel; + } paddingWidthScale = 0; paddingHeightScale = 0; } + void _onDoubleTapZoomInvoked(double scale) { + widget.onDoubleTap?.call(); + previousZoomLevel = scale; + } + /// Handles interaction update and updates the UI void _handleInteractionUpdate(ScaleUpdateDetails details) { currentOffset = _transformationController.toScene(Offset.zero); @@ -197,10 +221,16 @@ class PdfScrollableState extends State { }); } } else { - if (widget.viewportDimension.height.round() == + if (widget.scrollDirection == PdfScrollDirection.horizontal && + widget.viewportDimension.width.round() == + (widget.pdfDimension.width * _currentScale!).round()) { + setState(() { + paddingWidthScale = details.scale * _currentScale!; + }); + } else if (widget.viewportDimension.height.round() == (widget.pdfDimension.height * _currentScale!).round()) { setState(() { - paddingHeightScale = details.scale * _currentScale!; + paddingHeightScale = (details.scale) * _currentScale!; }); } } @@ -214,19 +244,34 @@ class PdfScrollableState extends State { void _handleInteractionEnd(ScaleEndDetails details) { paddingWidthScale = 0; paddingHeightScale = 0; + final double totalPdfPageWidth = widget + .pdfPages[widget.pdfViewerController.pageCount]!.pageOffset + + widget.pdfPages[widget.pdfViewerController.pageCount]!.pageSize.width; if (_currentScale != widget.pdfViewerController.zoomLevel && _currentScale != null && - _currentScale != 0.0) { + _currentScale != 0.0 && + (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled))) { widget.pdfViewerController.zoomLevel = _currentScale!; } - if (kIsDesktop && - !widget.isMobileWebView && - widget.maxPdfPageWidth * widget.pdfViewerController.zoomLevel < - widget.viewportDimension.width) { + if ((kIsDesktop && + !widget.isMobileWebView && + widget.scrollDirection == PdfScrollDirection.vertical && + widget.maxPdfPageWidth * widget.pdfViewerController.zoomLevel < + widget.viewportDimension.width) || + (widget.scrollDirection == PdfScrollDirection.horizontal && + widget.viewportDimension.width.round() > + (totalPdfPageWidth * widget.pdfViewerController.zoomLevel) + .round())) { _transformationController.value.translate(currentOffset.dx); _isOverFlowed = false; } else { - if (kIsDesktop && !widget.isMobileWebView) { + if ((kIsDesktop && + !widget.isMobileWebView && + widget.scrollDirection == PdfScrollDirection.vertical) || + (widget.scrollDirection == PdfScrollDirection.horizontal && + totalPdfPageWidth < widget.viewportDimension.width)) { /// Invoked when pdf pages width greater viewport width if (_isOverFlowed == false) { _transformationController.value.translate(currentOffset.dx); @@ -246,10 +291,8 @@ class PdfScrollableState extends State { } ///Triggers when scrolling performed by touch. - void receivedPointerMoveSignal(PointerMoveEvent event) { - if (event.kind == PointerDeviceKind.touch && - kIsDesktop && - !widget.isMobileWebView) { + void receivedPointerMove(PointerMoveEvent event) { + if (event.kind == PointerDeviceKind.touch) { currentOffset = _transformationController.toScene(Offset.zero); } } @@ -270,6 +313,12 @@ class PdfScrollableState extends State { (!kIsDesktop || widget.isMobileWebView)) { offset = Offset(currentOffset.dx, offset.dy); //Need to do for webs } + if (widget.scrollDirection == PdfScrollDirection.horizontal && + widget.viewportDimension.height > + widget.pdfDimension.height * + widget.pdfViewerController.zoomLevel) { + offset = Offset(offset.dx, 0); + } final double widthFactor = widget.pdfDimension.width - (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); @@ -339,9 +388,9 @@ class PdfScrollableState extends State { double scaleTo(double zoomLevel, {bool isZoomed = true}) { currentZoomLevel = _transformationController.value.getMaxScaleOnAxis(); if (currentZoomLevel != zoomLevel) { + previousZoomLevel = currentZoomLevel; _setZoomLevel = true; final double zoomChangeFactor = zoomLevel / currentZoomLevel; - previousZoomLevel = zoomLevel; final Offset previousOffset = _transformationController.toScene(Offset.zero); _transformationController.value.scale(zoomChangeFactor, zoomChangeFactor); 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 b511ae98f..0cdd4e34f 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/pdfviewer_canvas.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -10,6 +8,7 @@ import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_page_view.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdftextline.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/single_page_view.dart'; import 'enums.dart'; /// Instance of TextSelectionHelper. @@ -32,24 +31,26 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { this.onTextSelectionChanged, this.onTextSelectionDragStarted, this.onTextSelectionDragEnded, - this.onDocumentLinkNavigationInvoked, this.textCollection, this.searchTextHighlightColor, this.pdfTextSearchResult, this.isMobileWebView, this.pdfScrollableStateKey, + this.singlePageViewStateKey, this.viewportGlobalRect, + this.scrollDirection, + this.isSinglePageView, ) : super(key: key); /// Height of page final double height; - /// If true, document link annotation is enabled. - final bool enableDocumentLinkNavigation; - /// Width of Page final double width; + /// If true, document link annotation is enabled. + final bool enableDocumentLinkNavigation; + /// Instance of [PdfDocument] final PdfDocument? pdfDocument; @@ -83,9 +84,6 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { /// PdfTextSearchResult instance final PdfTextSearchResult pdfTextSearchResult; - /// Triggered while document link navigation. - final Function(double value) onDocumentLinkNavigationInvoked; - /// Indicates interaction mode of pdfViewer. final PdfInteractionMode interactionMode; @@ -98,6 +96,15 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { /// Key to access scrollable. final GlobalKey pdfScrollableStateKey; + /// Key to access single page view state. + final GlobalKey singlePageViewStateKey; + + /// Represents the scroll direction of PdfViewer. + final PdfScrollDirection scrollDirection; + + /// Determines layout option in PdfViewer. + final bool isSinglePageView; + @override RenderObject createRenderObject(BuildContext context) { return CanvasRenderBox( @@ -114,13 +121,15 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { onTextSelectionChanged, onTextSelectionDragStarted, onTextSelectionDragEnded, - onDocumentLinkNavigationInvoked, textCollection, searchTextHighlightColor, isMobileWebView, pdfTextSearchResult, pdfScrollableStateKey, - viewportGlobalRect); + singlePageViewStateKey, + viewportGlobalRect, + scrollDirection, + isSinglePageView); } @override @@ -128,16 +137,19 @@ class PdfViewerCanvas extends LeafRenderObjectWidget { renderObject ..height = height ..width = width + ..onTextSelectionChanged = onTextSelectionChanged ..pageIndex = pageIndex ..textCollection = textCollection ..interactionMode = interactionMode ..enableTextSelection = enableTextSelection ..enableDocumentLinkNavigation = enableDocumentLinkNavigation ..searchTextHighlightColor = searchTextHighlightColor + ..scrollDirection = scrollDirection + ..isSinglePageView = isSinglePageView + ..viewportGlobalRect = viewportGlobalRect ..pdfTextSearchResult = pdfTextSearchResult; - renderObject.markNeedsPaint(); - + renderObject._scrollWhileSelection(); super.updateRenderObject(context, renderObject); } } @@ -159,13 +171,15 @@ class CanvasRenderBox extends RenderBox { this.onTextSelectionChanged, this.onTextSelectionDragStarted, this.onTextSelectionDragEnded, - this.onDocumentLinkNavigationInvoked, this.textCollection, this.searchTextHighlightColor, this.isMobileWebView, this.pdfTextSearchResult, this.pdfScrollableStateKey, - this.viewportGlobalRect) { + this.singlePageViewStateKey, + this.viewportGlobalRect, + this.scrollDirection, + this.isSinglePageView) { final GestureArenaTeam team = GestureArenaTeam(); _tapRecognizer = TapGestureRecognizer() ..onTapUp = handleTapUp @@ -182,7 +196,8 @@ class CanvasRenderBox extends RenderBox { ..team = team ..onStart = handleDragStart ..onUpdate = handleDragUpdate - ..onEnd = handleDragEnd; + ..onEnd = handleDragEnd + ..onDown = handleDragDown; } /// Height of Page @@ -219,10 +234,7 @@ class CanvasRenderBox extends RenderBox { late final VoidCallback onTextSelectionDragEnded; /// Triggers when text selection is changed. - late final PdfTextSelectionChangedCallback? onTextSelectionChanged; - - /// Triggered while document link navigation. - late final Function(double value) onDocumentLinkNavigationInvoked; + late PdfTextSelectionChangedCallback? onTextSelectionChanged; /// Indicates interaction mode of pdfViewer. late PdfInteractionMode interactionMode; @@ -242,11 +254,21 @@ class CanvasRenderBox extends RenderBox { /// Key to access scrollable. final GlobalKey pdfScrollableStateKey; + /// Key to access single page view state. + final GlobalKey singlePageViewStateKey; + /// Global rect of viewport region. - final Rect? viewportGlobalRect; + late Rect? viewportGlobalRect; + + /// Represents the scroll direction of PdfViewer. + late PdfScrollDirection scrollDirection; + + /// Determines layout option in PdfViewer. + late bool isSinglePageView; int? _viewId; - late double _totalPageOffset; + int? _destinationPageIndex; + late Offset _totalPageOffset; bool _isTOCTapped = false; bool _isMousePointer = false; double _startBubbleTapX = 0; @@ -268,20 +290,28 @@ class CanvasRenderBox extends RenderBox { late LongPressGestureRecognizer _longPressRecognizer; late VerticalDragGestureRecognizer _verticalDragRecognizer; late PdfDocumentLinkAnnotation? _documentLinkAnnotation; + late final PdfPageRotateAngle _rotatedAngle = + pdfDocument!.pages[pageIndex].rotation; @override void handleEvent(PointerEvent event, BoxHitTestEntry entry) { - if (event is PointerDownEvent && - ((interactionMode == PdfInteractionMode.selection && kIsDesktop) || - !kIsDesktop)) { + if (event is PointerDownEvent) { _tapRecognizer.addPointer(event); - _longPressRecognizer.addPointer(event); - _dragRecognizer.addPointer(event); - if (_textSelectionHelper.selectionEnabled) { - final bool isStartDragPossible = _checkStartBubblePosition(); - final bool isEndDragPossible = _checkEndBubblePosition(); - if (isStartDragPossible || isEndDragPossible) { + if ((interactionMode == PdfInteractionMode.selection && kIsDesktop) || + !kIsDesktop) { + _longPressRecognizer.addPointer(event); + if (event.kind == PointerDeviceKind.mouse) { + _dragRecognizer.addPointer(event); _verticalDragRecognizer.addPointer(event); + } else if (_textSelectionHelper.selectionEnabled) { + final bool isStartDragPossible = + _checkStartBubblePosition(event.localPosition); + final bool isEndDragPossible = + _checkEndBubblePosition(event.localPosition); + if (isStartDragPossible || isEndDragPossible) { + _dragRecognizer.addPointer(event); + _verticalDragRecognizer.addPointer(event); + } } } } @@ -294,28 +324,43 @@ class CanvasRenderBox extends RenderBox { @override bool get sizedByParent => true; - /// Handles the tap up event - void handleTapUp(TapUpDetails details) { + /// This replaces the old performResize method. + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + /// Handles the tap down event + void handleTapDown(TapDownDetails details) { + _tapDetails = details.localPosition; if (kIsDesktop && !isMobileWebView && - (!_textSelectionHelper.enableTapSelection || - (_textSelectionHelper.globalSelectedRegion != null && - !_textSelectionHelper.globalSelectedRegion! - .contains(details.globalPosition)))) { - clearMouseSelection(); + enableTextSelection && + interactionMode == PdfInteractionMode.selection) { + if (details.kind == PointerDeviceKind.mouse) { + _isMousePointer = true; + } else { + _isMousePointer = false; + } } + } + + /// Handles the tap up event + void handleTapUp(TapUpDetails details) { if (textCollection == null && !_textSelectionHelper.enableTapSelection) { clearSelection(); } if (_textSelectionHelper.enableTapSelection && _textSelectionHelper.mouseSelectionEnabled) { - triggerValueCallback(); + updateContextMenuPosition(); _textSelectionHelper.enableTapSelection = false; } if (enableDocumentLinkNavigation && pdfDocument != null) { _viewId = pageIndex; final double heightPercentage = pdfDocument!.pages[_viewId!].size.height / height; + final double widthPercentage = + pdfDocument!.pages[_viewId!].size.width / width; final PdfPage page = pdfDocument!.pages[pageIndex]; final int length = page.annotations.count; for (int index = 0; index < length; index++) { @@ -339,12 +384,32 @@ class CanvasRenderBox extends RenderBox { _documentLinkAnnotation!.destination!.page; final int destinationPageIndex = pdfDocument!.pages.indexOf(destinationPage) + 1; - final double destinationPageOffset = - _documentLinkAnnotation!.destination!.location.dy / - heightPercentage; - _totalPageOffset = pdfPages[destinationPageIndex]!.pageOffset + - destinationPageOffset; + Offset destinationPageOffset = + _documentLinkAnnotation!.destination!.location; + destinationPageOffset = getRotatedOffset(destinationPageOffset, + destinationPageIndex - 1, destinationPage.rotation); + final double positionX = + destinationPageOffset.dx / widthPercentage; + final double positionY = + destinationPageOffset.dy / heightPercentage; + final double pageOffset = + pdfPages[destinationPageIndex]!.pageOffset; + if (isSinglePageView) { + _totalPageOffset = Offset(positionX, positionY); + } else { + if (scrollDirection == PdfScrollDirection.horizontal) { + if (pdfViewerController.zoomLevel == 1) { + _totalPageOffset = Offset(pageOffset, positionY); + } else { + _totalPageOffset = + Offset(pageOffset + positionX, positionY); + } + } else { + _totalPageOffset = Offset(positionX, pageOffset + positionY); + } + } _viewId = pageIndex; + _destinationPageIndex = destinationPageIndex; /// Mark this render object as having changed its visual appearance. /// @@ -375,25 +440,6 @@ class CanvasRenderBox extends RenderBox { } } - /// Handles the tap down event - void handleTapDown(TapDownDetails details) { - if (_textSelectionHelper.enableTapSelection && - _textSelectionHelper.mouseSelectionEnabled) { - triggerValueCallback(); - } - _tapDetails = details.localPosition; - if (kIsDesktop && - !isMobileWebView && - enableTextSelection && - interactionMode == PdfInteractionMode.selection) { - if (details.kind == PointerDeviceKind.mouse) { - _isMousePointer = true; - } else { - _isMousePointer = false; - } - } - } - /// Handles the long press started event.cursorMode void handleLongPressStart(LongPressStartDetails details) { if (kIsDesktop && !isMobileWebView && pdfDocument != null) { @@ -413,8 +459,9 @@ class CanvasRenderBox extends RenderBox { void handleDragStart(DragStartDetails details) { _enableMouseSelection(details, 'DragStart'); if (_textSelectionHelper.selectionEnabled) { - final bool isStartDragPossible = _checkStartBubblePosition(); - final bool isEndDragPossible = _checkEndBubblePosition(); + final bool isStartDragPossible = + _checkStartBubblePosition(_dragDownDetails!); + final bool isEndDragPossible = _checkEndBubblePosition(_dragDownDetails!); if (isStartDragPossible) { _startBubbleDragging = true; onTextSelectionDragStarted(); @@ -423,31 +470,17 @@ class CanvasRenderBox extends RenderBox { onTextSelectionDragStarted(); } } + if (details.kind == PointerDeviceKind.mouse) { + _isMousePointer = true; + } else { + _isMousePointer = false; + } } /// Handles the drag update event. void handleDragUpdate(DragUpdateDetails details) { if (kIsDesktop && !isMobileWebView && _isMousePointer) { _updateSelectionPan(details); - final double currentOffset = - pdfScrollableStateKey.currentState?.currentOffset.dy ?? 0; - if (interactionMode == PdfInteractionMode.pan) { - final double newOffset = currentOffset - details.delta.dy; - if (details.delta.dy.isNegative) { - pdfScrollableStateKey.currentState - ?.jumpTo(yOffset: max(0, newOffset)); - } else { - pdfScrollableStateKey.currentState?.jumpTo(yOffset: newOffset); - } - } - if (_textSelectionHelper.mouseSelectionEnabled && - !_textSelectionHelper.isCursorExit) { - _textSelectionHelper.endBubbleX = details.localPosition.dx; - _textSelectionHelper.endBubbleY = details.localPosition.dy + - (_textSelectionHelper.finalScrollOffset - - _textSelectionHelper.initialScrollOffset); - markNeedsPaint(); - } } if (_textSelectionHelper.selectionEnabled) { _dragDetails = details.localPosition; @@ -462,11 +495,6 @@ class CanvasRenderBox extends RenderBox { onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); } triggerNullCallback(); - } else { - pdfScrollableStateKey.currentState?.forcePixels(Offset( - (pdfViewerController.scrollOffset.dx + (-details.delta.dx * 2)) - .abs(), - pdfViewerController.scrollOffset.dy)); } } } @@ -476,8 +504,8 @@ class CanvasRenderBox extends RenderBox { if (kIsDesktop && !isMobileWebView && _textSelectionHelper.mouseSelectionEnabled) { - if (getSelectionDetails().isCursorExit) { - getSelectionDetails().isCursorExit = false; + if (_textSelectionHelper.isCursorExit) { + _textSelectionHelper.isCursorExit = false; } onTextSelectionDragEnded(); triggerValueCallback(); @@ -518,33 +546,41 @@ class CanvasRenderBox extends RenderBox { if (kIsDesktop && !isMobileWebView && enableTextSelection && - interactionMode == PdfInteractionMode.selection) { + interactionMode == PdfInteractionMode.selection && + _isMousePointer) { final bool isTOC = findTOC(details.localPosition); _textSelectionHelper.initialScrollOffset = 0; _textSelectionHelper.finalScrollOffset = 0; if (details.kind == PointerDeviceKind.mouse && !isTOC) { if (_textSelectionHelper.selectionEnabled) { - final bool isStartDragPossible = _checkStartBubblePosition(); - final bool isEndDragPossible = _checkEndBubblePosition(); + final bool isStartDragPossible = + _checkStartBubblePosition(details.localPosition); + final bool isEndDragPossible = + _checkEndBubblePosition(details.localPosition); if (isStartDragPossible || isEndDragPossible) { _textSelectionHelper.mouseSelectionEnabled = false; } else { clearSelection(); } } - if (gestureType == 'DragStart' && - _textSelectionHelper.mouseSelectionEnabled) { - // ignore: avoid_as - _textSelectionHelper.endBubbleX = details.localPosition.dx as double; - // ignore: avoid_as - _textSelectionHelper.endBubbleY = details.localPosition.dy as double; - } if (_textSelectionHelper.textLines == null || _textSelectionHelper.viewId != pageIndex) { _textSelectionHelper.viewId = pageIndex; _textSelectionHelper.textLines = PdfTextExtractor(pdfDocument!) .extractTextLines(startPageIndex: pageIndex); } + final double heightPercentage = + pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / + height; + if (gestureType == 'DragStart' && + _textSelectionHelper.mouseSelectionEnabled) { + // ignore: avoid_as + _textSelectionHelper.endBubbleX = + (details.localPosition.dx as double) * heightPercentage; + // ignore: avoid_as + _textSelectionHelper.endBubbleY = + (details.localPosition.dy as double) * heightPercentage; + } for (int textLineIndex = 0; textLineIndex < _textSelectionHelper.textLines!.length; textLineIndex++) { @@ -562,13 +598,15 @@ class CanvasRenderBox extends RenderBox { glyphIndex++) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; if (gestureType == 'DragStart') { - if (textGlyph.bounds.contains(details.localPosition)) { + if (textGlyph.bounds + .contains(details.localPosition * heightPercentage)) { _textSelectionHelper.firstSelectedGlyph = textGlyph; _enableSelection(gestureType); } } else if (gestureType == 'DoubleTap') { triggerNullCallback(); - if (textWord.bounds.contains(details.localPosition)) { + if (textWord.bounds + .contains(details.localPosition * heightPercentage)) { _textSelectionHelper.firstSelectedGlyph = textWord.glyphs.first; _textSelectionHelper.endBubbleX = @@ -579,7 +617,8 @@ class CanvasRenderBox extends RenderBox { } } else if (gestureType == 'TripleTap') { triggerNullCallback(); - if (textLine.bounds.contains(details.localPosition)) { + if (textLine.bounds + .contains(details.localPosition * heightPercentage)) { _textSelectionHelper.firstSelectedGlyph = textLine.wordCollection.first.glyphs.first; _textSelectionHelper.endBubbleX = @@ -592,6 +631,7 @@ class CanvasRenderBox extends RenderBox { } } } + markNeedsPaint(); } } } @@ -644,79 +684,124 @@ class CanvasRenderBox extends RenderBox { scrollStarted(); if (_textSelectionHelper.selectionEnabled || _textSelectionHelper.mouseSelectionEnabled) { - double top; - double bottom; - if (kIsDesktop && - !isMobileWebView && - _textSelectionHelper.globalSelectedRegion != null) { - top = _textSelectionHelper.globalSelectedRegion!.top; - bottom = _textSelectionHelper.globalSelectedRegion!.bottom; - } else { - final double _heightPercentage = _textSelectionHelper.heightPercentage!; - top = _textSelectionHelper.firstSelectedGlyph != null - ? _textSelectionHelper.firstSelectedGlyph!.bounds.top / - _heightPercentage - : 0; - bottom = _textSelectionHelper.endBubbleY != null - ? _textSelectionHelper.endBubbleY! / _heightPercentage - : 0; - } - Future.delayed(const Duration(milliseconds: 200), () async { - if ((pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + bottom >= - pdfViewerController.scrollOffset.dy && - pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + - bottom <= - pdfViewerController.scrollOffset.dy + paintBounds.height) || - (pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + top >= - pdfViewerController.scrollOffset.dy && - pdfPages[_textSelectionHelper.viewId! + 1]!.pageOffset + top <= - pdfViewerController.scrollOffset.dy + paintBounds.height)) { - triggerValueCallback(); - } + Future.delayed(const Duration(milliseconds: 400), () async { + triggerValueCallback(); }); } } /// Updates the selection details when panning over the viewport. void _updateSelectionPan(DragUpdateDetails details) { - final TextSelectionHelper helper = getSelectionDetails(); - final double currentOffset = - pdfScrollableStateKey.currentState?.currentOffset.dy ?? 0; + _scrollWhileSelection(); + final double currentOffset = pdfViewerController.scrollOffset.dy; if (viewportGlobalRect != null && !viewportGlobalRect!.contains(details.globalPosition) && details.globalPosition.dx <= viewportGlobalRect!.right && details.globalPosition.dx >= viewportGlobalRect!.left) { if (details.globalPosition.dy <= viewportGlobalRect!.top) { - helper.isCursorReachedTop = true; + _textSelectionHelper.isCursorReachedTop = true; + } else { + _textSelectionHelper.isCursorReachedTop = false; + } + _textSelectionHelper.isCursorExit = true; + if (_textSelectionHelper.initialScrollOffset == 0) { + _textSelectionHelper.initialScrollOffset = currentOffset; + } + } else if (_textSelectionHelper.isCursorExit) { + if (_textSelectionHelper.isCursorReachedTop) { + _textSelectionHelper.finalScrollOffset = currentOffset - _jumpOffset; } else { - helper.isCursorReachedTop = false; + _textSelectionHelper.finalScrollOffset = currentOffset + _jumpOffset; } - helper.isCursorExit = true; - if (helper.initialScrollOffset == 0) { - helper.initialScrollOffset = currentOffset; + _textSelectionHelper.isCursorExit = false; + } + if (_textSelectionHelper.mouseSelectionEnabled && + !_textSelectionHelper.isCursorExit) { + double endBubbleValue; + if (_rotatedAngle == PdfPageRotateAngle.rotateAngle180) { + endBubbleValue = -(_textSelectionHelper.finalScrollOffset - + _textSelectionHelper.initialScrollOffset); + } else { + endBubbleValue = _textSelectionHelper.finalScrollOffset - + _textSelectionHelper.initialScrollOffset; + } + final double heightPercentage = + pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; + _textSelectionHelper.endBubbleX = + details.localPosition.dx * heightPercentage; + _textSelectionHelper.endBubbleY = + (details.localPosition.dy + endBubbleValue) * heightPercentage; + markNeedsPaint(); + } + } + + void _scrollWhileSelection() { + if (_textSelectionHelper.isCursorExit && + _textSelectionHelper.mouseSelectionEnabled) { + final int viewId = _textSelectionHelper.viewId ?? 0; + if (_textSelectionHelper.isCursorReachedTop && + pdfViewerController.pageNumber >= viewId + 1 || + (pdfViewerController.scrollOffset.dy > 0 && + (scrollDirection == PdfScrollDirection.horizontal || + isSinglePageView))) { + scroll(_textSelectionHelper.isCursorReachedTop, true); + } else if ((!_textSelectionHelper.isCursorReachedTop && + pdfViewerController.pageNumber <= viewId + 1) || + scrollDirection == PdfScrollDirection.horizontal || + isSinglePageView) { + scroll(_textSelectionHelper.isCursorReachedTop, true); + } + if (onTextSelectionChanged != null) { + onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); + } + } + } + + /// Used to scroll manually. + void scroll(bool isReachedTop, bool isSelectionScroll) { + if (isSelectionScroll) { + double endBubbleValue = isReachedTop ? -3 : 3; + if (pdfDocument!.pages[pageIndex].rotation == + PdfPageRotateAngle.rotateAngle180) { + endBubbleValue = -endBubbleValue; + } else { + endBubbleValue = endBubbleValue; } - } else if (helper.isCursorExit) { - if (helper.isCursorReachedTop) { - helper.finalScrollOffset = currentOffset - _jumpOffset; + _textSelectionHelper.endBubbleY = + _textSelectionHelper.endBubbleY! + endBubbleValue; + } + final double position = pdfViewerController.scrollOffset.dy + + (isReachedTop ? -_jumpOffset : _jumpOffset); + + if (isSelectionScroll) { + WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + if (isSinglePageView) { + singlePageViewStateKey.currentState?.jumpTo(yOffset: position); + _scrollWhileSelection(); + } else { + pdfScrollableStateKey.currentState?.jumpTo(yOffset: position); + } + }); + } else { + if (isSinglePageView) { + singlePageViewStateKey.currentState?.jumpTo(yOffset: position); } else { - helper.finalScrollOffset = currentOffset + _jumpOffset; + pdfScrollableStateKey.currentState?.jumpTo(yOffset: position); } - helper.isCursorExit = false; } } /// Check the tap position same as the start bubble position. - bool _checkStartBubblePosition() { - if (_textSelectionHelper.selectionEnabled && _dragDownDetails != null) { + bool _checkStartBubblePosition(Offset details) { + if (_textSelectionHelper.selectionEnabled) { final double startBubbleX = _textSelectionHelper.startBubbleX! / _textSelectionHelper.heightPercentage!; final double startBubbleY = _textSelectionHelper.startBubbleY! / _textSelectionHelper.heightPercentage!; - if (_dragDownDetails!.dx >= - startBubbleX - (_bubbleSize * _maximumZoomLevel) && - _dragDownDetails!.dx <= startBubbleX && - _dragDownDetails!.dy >= startBubbleY - _bubbleSize && - _dragDownDetails!.dy <= startBubbleY + _bubbleSize) { + if (details.dx >= startBubbleX - (_bubbleSize * _maximumZoomLevel) && + details.dx <= startBubbleX && + details.dy >= startBubbleY - _bubbleSize && + details.dy <= startBubbleY + _bubbleSize) { return true; } } @@ -724,42 +809,94 @@ class CanvasRenderBox extends RenderBox { } /// Check the tap position same as the end bubble position. - bool _checkEndBubblePosition() { - if (_textSelectionHelper.selectionEnabled && _dragDownDetails != null) { + bool _checkEndBubblePosition(Offset details) { + if (_textSelectionHelper.selectionEnabled) { final double endBubbleX = _textSelectionHelper.endBubbleX! / _textSelectionHelper.heightPercentage!; final double endBubbleY = _textSelectionHelper.endBubbleY! / _textSelectionHelper.heightPercentage!; - if (_dragDownDetails!.dx >= endBubbleX && - _dragDownDetails!.dx <= - endBubbleX + (_bubbleSize * _maximumZoomLevel) && - _dragDownDetails!.dy >= endBubbleY - _bubbleSize && - _dragDownDetails!.dy <= endBubbleY + _bubbleSize) { + if (details.dx >= endBubbleX && + details.dx <= endBubbleX + (_bubbleSize * _maximumZoomLevel) && + details.dy >= endBubbleY - _bubbleSize && + details.dy <= endBubbleY + _bubbleSize) { return true; } } return false; } - void _sortTextLines() { - if (_textSelectionHelper.textLines != null) { + List _sortTextLines(List textLines) { + for (int textLineIndex = 0; + textLineIndex < textLines.length; + textLineIndex++) { + for (int index = textLineIndex + 1; index < textLines.length; index++) { + if (textLines[textLineIndex].bounds.bottom > + textLines[index].bounds.bottom) { + final TextLine textLine = textLines[textLineIndex]; + textLines[textLineIndex] = textLines[index]; + textLines[index] = textLine; + } + } + } + return textLines; + } + + /// Gets rotated offset. + Offset getRotatedOffset( + Offset offset, int pageIndex, PdfPageRotateAngle angle) { + if (angle != PdfPageRotateAngle.rotateAngle0) { + List textLines = PdfTextExtractor(pdfDocument!) + .extractTextLines(startPageIndex: pageIndex); + textLines = _sortTextLines(textLines); + TextLine? textLine; for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines!.length; + textLineIndex < textLines.length; textLineIndex++) { - for (int index = textLineIndex + 1; - index < _textSelectionHelper.textLines!.length; - index++) { - if (_textSelectionHelper.textLines![textLineIndex].bounds.bottom > - _textSelectionHelper.textLines![index].bounds.bottom) { - final TextLine textLine = - _textSelectionHelper.textLines![textLineIndex]; - _textSelectionHelper.textLines![textLineIndex] = - _textSelectionHelper.textLines![index]; - _textSelectionHelper.textLines![index] = textLine; - } + if (textLines[textLineIndex].bounds.topLeft.dy >= offset.dy) { + textLine = textLines[textLineIndex]; + break; } } + final Rect bounds = + getRotatedTextBounds(textLine!.bounds, pageIndex, angle); + offset = bounds.topLeft; } + return offset; + } + + /// Gets rotated text bounds. + Rect getRotatedTextBounds( + Rect textBounds, int pageIndex, PdfPageRotateAngle angle) { + Rect? rotatedTextBounds; + final double height = pdfDocument!.pages[pageIndex].size.height; + final double width = pdfDocument!.pages[pageIndex].size.width; + switch (angle) { + case PdfPageRotateAngle.rotateAngle0: + rotatedTextBounds = textBounds; + break; + case PdfPageRotateAngle.rotateAngle90: + rotatedTextBounds = Rect.fromLTRB( + height - textBounds.bottom, + textBounds.left, + (height - textBounds.bottom) + textBounds.height, + textBounds.left + textBounds.width); + break; + case PdfPageRotateAngle.rotateAngle180: + rotatedTextBounds = Rect.fromLTRB( + width - textBounds.right, + height - textBounds.bottom, + (width - textBounds.right) + textBounds.width, + (height - textBounds.bottom) + textBounds.height); + break; + case PdfPageRotateAngle.rotateAngle270: + rotatedTextBounds = Rect.fromLTRB( + textBounds.top, + width - textBounds.right, + textBounds.top + textBounds.height, + (width - textBounds.right) + textBounds.width); + break; + } + return rotatedTextBounds; } /// Enable text selection. @@ -814,14 +951,14 @@ class CanvasRenderBox extends RenderBox { markNeedsPaint(); triggerNullCallback(); if (!kIsDesktop || (kIsDesktop && isMobileWebView)) { - dispose(); + disposeSelection(); } } return clearTextSelection; } /// Dispose the text selection. - void dispose() { + void disposeSelection() { disposeMouseSelection(); _textSelectionHelper.firstSelectedGlyph = null; _textSelectionHelper.startBubbleX = null; @@ -834,30 +971,23 @@ class CanvasRenderBox extends RenderBox { } /// Find the text while hover by mouse. - TextGlyph? findTextWhileHover(Offset details) { + TextLine? findTextWhileHover(Offset details) { if (_textSelectionHelper.cursorTextLines == null || _textSelectionHelper.cursorPageNumber != pageIndex) { _textSelectionHelper.cursorPageNumber = pageIndex; _textSelectionHelper.cursorTextLines = PdfTextExtractor(pdfDocument!) .extractTextLines(startPageIndex: pageIndex); } + final double heightPercentage = + pdfDocument!.pages[_textSelectionHelper.cursorPageNumber!].size.height / + height; if (_textSelectionHelper.cursorTextLines != null) { for (int textLineIndex = 0; textLineIndex < _textSelectionHelper.cursorTextLines!.length; textLineIndex++) { - final TextLine line = - _textSelectionHelper.cursorTextLines![textLineIndex]; - for (int wordIndex = 0; - wordIndex < line.wordCollection.length; - wordIndex++) { - final TextWord textWord = line.wordCollection[wordIndex]; - for (int glyphIndex = 0; - glyphIndex < textWord.glyphs.length; - glyphIndex++) { - if (textWord.glyphs[glyphIndex].bounds.contains(details)) { - return textWord.glyphs[glyphIndex]; - } - } + if (_textSelectionHelper.cursorTextLines![textLineIndex].bounds + .contains(details * heightPercentage)) { + return _textSelectionHelper.cursorTextLines![textLineIndex]; } } } @@ -871,15 +1001,19 @@ class CanvasRenderBox extends RenderBox { } final PdfPage page = pdfDocument!.pages[_textSelectionHelper.cursorPageNumber!]; + final double heightPercentage = + pdfDocument!.pages[_textSelectionHelper.cursorPageNumber!].size.height / + height; + final Offset hoverDetails = details * heightPercentage; for (int index = 0; index < page.annotations.count; index++) { if (page.annotations[index] is PdfDocumentLinkAnnotation) { _documentLinkAnnotation = // ignore: avoid_as page.annotations[index] as PdfDocumentLinkAnnotation; - if ((details.dy >= (_documentLinkAnnotation!.bounds.top)) && - (details.dy <= (_documentLinkAnnotation!.bounds.bottom)) && - (details.dx >= (_documentLinkAnnotation!.bounds.left)) && - (details.dx <= (_documentLinkAnnotation!.bounds.right))) { + if ((hoverDetails.dy >= (_documentLinkAnnotation!.bounds.top)) && + (hoverDetails.dy <= (_documentLinkAnnotation!.bounds.bottom)) && + (hoverDetails.dx >= (_documentLinkAnnotation!.bounds.left)) && + (hoverDetails.dx <= (_documentLinkAnnotation!.bounds.right))) { return true; } } @@ -977,12 +1111,6 @@ class CanvasRenderBox extends RenderBox { canvas.drawRect(textRectOffset, textPaint); } - /// This replaces the old performResize method. - @override - Size computeDryLayout(BoxConstraints constraints) { - return constraints.biggest; - } - /// Gets the selected text lines. List? getSelectedTextLines() { if (_textSelectionHelper.selectionEnabled || @@ -992,105 +1120,8 @@ class CanvasRenderBox extends RenderBox { return null; } - @override - void paint(PaintingContext context, Offset offset) { - if (pdfDocument == null) { - return; - } - final Canvas canvas = context.canvas; - final ThemeData theme = Theme.of(this.context); - final TextSelectionThemeData selectionTheme = - TextSelectionTheme.of(this.context); - final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(this.context); - switch (theme.platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - _selectionColor ??= selectionTheme.selectionColor ?? - cupertinoTheme.primaryColor.withOpacity(0.40); - _selectionHandleColor ??= - selectionTheme.selectionHandleColor ?? cupertinoTheme.primaryColor; - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - _selectionColor ??= selectionTheme.selectionColor ?? - theme.colorScheme.primary.withOpacity(0.40); - _selectionHandleColor ??= - selectionTheme.selectionHandleColor ?? theme.colorScheme.primary; - break; - } - final Paint textPaint = Paint()..color = _selectionColor!; - final Paint bubblePaint = Paint()..color = _selectionHandleColor!; - _zoomPercentage = pdfViewerController.zoomLevel > _maximumZoomLevel - ? _maximumZoomLevel - : pdfViewerController.zoomLevel; - - if (_textSelectionHelper.mouseSelectionEnabled && - _textSelectionHelper.textLines != null && - _textSelectionHelper.endBubbleX != null && - _textSelectionHelper.endBubbleY != null) { - final TextGlyph startGlyph = _textSelectionHelper.firstSelectedGlyph!; - final Offset details = Offset( - _textSelectionHelper.endBubbleX!, _textSelectionHelper.endBubbleY!); - if (_textSelectionHelper.viewId == pageIndex) { - _textSelectionHelper.copiedText = ''; - _textSelectionHelper.selectedTextLines.clear(); - for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines!.length; - textLineIndex++) { - final TextLine textLine = - _textSelectionHelper.textLines![textLineIndex]; - Rect? startPoint; - Rect? endPoint; - String glyphText = ''; - for (int wordIndex = 0; - wordIndex < - _textSelectionHelper - .textLines![textLineIndex].wordCollection.length; - wordIndex++) { - final TextWord textWord = _textSelectionHelper - .textLines![textLineIndex].wordCollection[wordIndex]; - for (int glyphIndex = 0; - glyphIndex < textWord.glyphs.length; - glyphIndex++) { - final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; - final bool canSelectGlyph = - checkGlyphInRegion(textGlyph, startGlyph, details); - if (canSelectGlyph) { - startPoint ??= textGlyph.bounds; - endPoint = textGlyph.bounds; - glyphText = glyphText + textGlyph.text; - _textSelectionHelper.copiedText = - _textSelectionHelper.copiedText! + textGlyph.text; - final Rect textRectOffset = offset.translate( - textGlyph.bounds.left, textGlyph.bounds.top) & - Size(textGlyph.bounds.width, textGlyph.bounds.height); - canvas.drawRect(textRectOffset, textPaint); - } - if (startPoint != null && - endPoint != null && - textGlyph == textLine.wordCollection.last.glyphs.last) { - _textSelectionHelper.selectedTextLines.add(PdfTextLine( - Rect.fromLTRB(startPoint.left, startPoint.top, - endPoint.right, endPoint.bottom), - glyphText, - _textSelectionHelper.viewId!)); - Offset startOffset = - Offset(startGlyph.bounds.left, startGlyph.bounds.top); - final Offset endOffset = - Offset(endPoint.right, endPoint.bottom); - if (details.dy < startGlyph.bounds.top) { - startOffset = Offset(details.dx, details.dy); - } - _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( - localToGlobal(startOffset), localToGlobal(endOffset)); - } - } - } - } - } - } + /// Perform document link navigation. + void _performDocumentLinkNavigation(Canvas canvas, Offset offset) { if (pageIndex == _viewId) { if (_isTOCTapped) { final double heightPercentage = @@ -1107,83 +1138,200 @@ class CanvasRenderBox extends RenderBox { // For the ripple kind of effect so used Future.delayed Future.delayed(Duration.zero, () async { - pdfViewerController.jumpTo(yOffset: _totalPageOffset); + if (isSinglePageView) { + singlePageViewStateKey.currentState!.jumpOnZoomedDocument( + _destinationPageIndex!, + Offset(_totalPageOffset.dx, _totalPageOffset.dy)); + } else { + pdfViewerController.jumpTo( + xOffset: _totalPageOffset.dx, yOffset: _totalPageOffset.dy); + } }); _isTOCTapped = false; } } + } + + /// Perform text search. + void _performTextSearch(Canvas canvas, Offset offset) { if (textCollection != null && !_textSelectionHelper.selectionEnabled) { final Paint searchTextPaint = Paint() ..color = searchTextHighlightColor.withOpacity(0.3); - final Paint currentInstancePaint = Paint() ..color = searchTextHighlightColor.withOpacity(0.6); - int _pageNumber = 0; for (int i = 0; i < textCollection!.length; i++) { final MatchedItem item = textCollection![i]; - final double _heightPercentage = + final double heightPercentage = pdfDocument!.pages[item.pageIndex].size.height / height; if (pageIndex == item.pageIndex) { canvas.drawRect( offset.translate( - textCollection![i].bounds.left / _heightPercentage, - textCollection![i].bounds.top / _heightPercentage) & - Size(textCollection![i].bounds.width / _heightPercentage, - textCollection![i].bounds.height / _heightPercentage), + textCollection![i].bounds.left / heightPercentage, + textCollection![i].bounds.top / heightPercentage) & + Size(textCollection![i].bounds.width / heightPercentage, + textCollection![i].bounds.height / heightPercentage), searchTextPaint); } - - if (textCollection![pdfTextSearchResult.currentInstanceIndex - 1] - .pageIndex == - pageIndex) { - if (textCollection![pdfTextSearchResult.currentInstanceIndex - 1] - .pageIndex + - 1 != - _pageNumber) { + final MatchedItem matchedItem = + textCollection![pdfTextSearchResult.currentInstanceIndex - 1]; + if (matchedItem.pageIndex == pageIndex) { + if (matchedItem.pageIndex + 1 != _pageNumber) { + final Rect bounds = matchedItem.bounds; canvas.drawRect( - offset.translate( - textCollection![ - pdfTextSearchResult.currentInstanceIndex - - 1] - .bounds - .left / - _heightPercentage, - textCollection![ - pdfTextSearchResult.currentInstanceIndex - - 1] - .bounds - .top / - _heightPercentage) & - Size( - textCollection![ - pdfTextSearchResult.currentInstanceIndex - - 1] - .bounds - .width / - _heightPercentage, - textCollection![ - pdfTextSearchResult.currentInstanceIndex - - 1] - .bounds - .height / - _heightPercentage), + offset.translate(bounds.left / heightPercentage, + bounds.top / heightPercentage) & + Size(bounds.width / heightPercentage, + bounds.height / heightPercentage), currentInstancePaint); - _pageNumber = - textCollection![pdfTextSearchResult.currentInstanceIndex - 1] - .pageIndex + - 1; + _pageNumber = matchedItem.pageIndex + 1; } } else if (item.pageIndex > pageIndex) { break; } } } + } + + /// Perform mouse or touch selection. + void _performSelection( + Canvas canvas, Offset offset, Paint textPaint, Paint bubblePaint) { + if (_textSelectionHelper.viewId == pageIndex) { + final TextGlyph startGlyph = _textSelectionHelper.firstSelectedGlyph!; + final Offset details = Offset( + _textSelectionHelper.endBubbleX!, _textSelectionHelper.endBubbleY!); + final double heightPercentage = + pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; + _textSelectionHelper.heightPercentage = heightPercentage; + _textSelectionHelper.copiedText = ''; + _textSelectionHelper.selectedTextLines.clear(); + for (int textLineIndex = 0; + textLineIndex < _textSelectionHelper.textLines!.length; + textLineIndex++) { + final TextLine line = _textSelectionHelper.textLines![textLineIndex]; + Rect? startPoint; + Rect? endPoint; + String glyphText = ''; + final List textWordCollection = line.wordCollection; + for (int wordIndex = 0; + wordIndex < textWordCollection.length; + wordIndex++) { + final TextWord textWord = textWordCollection[wordIndex]; + for (int glyphIndex = 0; + glyphIndex < textWord.glyphs.length; + glyphIndex++) { + final TextGlyph glyph = textWord.glyphs[glyphIndex]; + final bool canSelectGlyph = + checkGlyphInRegion(glyph, startGlyph, details); + if (canSelectGlyph) { + startPoint ??= glyph.bounds; + endPoint = glyph.bounds; + glyphText = glyphText + glyph.text; + _textSelectionHelper.copiedText = + _textSelectionHelper.copiedText! + glyph.text; + final Rect textRectOffset = offset.translate( + glyph.bounds.left / heightPercentage, + glyph.bounds.top / heightPercentage) & + Size(glyph.bounds.width / heightPercentage, + glyph.bounds.height / heightPercentage); + _drawTextRect(canvas, textPaint, textRectOffset); + } + if (startPoint != null && + endPoint != null && + glyph == line.wordCollection.last.glyphs.last) { + _textSelectionHelper.selectedTextLines.add(PdfTextLine( + Rect.fromLTRB(startPoint.left, startPoint.top, endPoint.right, + endPoint.bottom), + glyphText, + _textSelectionHelper.viewId!)); + if (kIsDesktop && !isMobileWebView) { + Offset startOffset = Offset( + startGlyph.bounds.left / heightPercentage, + startGlyph.bounds.top / heightPercentage); + final Offset endOffset = Offset( + endPoint.right / heightPercentage, + endPoint.bottom / heightPercentage); + if (details.dy < startGlyph.bounds.top) { + startOffset = Offset(details.dx / heightPercentage, + details.dy / heightPercentage); + } + _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( + localToGlobal(startOffset), localToGlobal(endOffset)); + } + } + } + } + } + if (_textSelectionHelper.selectionEnabled) { + final Offset startBubbleOffset = offset.translate( + _textSelectionHelper.startBubbleX! / heightPercentage, + _textSelectionHelper.startBubbleY! / heightPercentage); + final Offset endBubbleOffset = offset.translate( + _textSelectionHelper.endBubbleX! / heightPercentage, + _textSelectionHelper.endBubbleY! / heightPercentage); + _drawStartBubble(canvas, bubblePaint, startBubbleOffset); + _drawEndBubble(canvas, bubblePaint, endBubbleOffset); + _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( + localToGlobal(Offset( + _textSelectionHelper.firstSelectedGlyph!.bounds.left / + heightPercentage, + _textSelectionHelper.firstSelectedGlyph!.bounds.top / + heightPercentage)), + localToGlobal(Offset( + _textSelectionHelper.endBubbleX! / heightPercentage, + _textSelectionHelper.endBubbleY! / heightPercentage))); + } + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (pdfDocument == null) { + return; + } + final Canvas canvas = context.canvas; + final ThemeData theme = Theme.of(this.context); + final TextSelectionThemeData selectionTheme = + TextSelectionTheme.of(this.context); + final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(this.context); + switch (theme.platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + _selectionColor ??= selectionTheme.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + _selectionHandleColor ??= + selectionTheme.selectionHandleColor ?? cupertinoTheme.primaryColor; + break; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + _selectionColor ??= selectionTheme.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + _selectionHandleColor ??= + selectionTheme.selectionHandleColor ?? theme.colorScheme.primary; + break; + } + final Paint textPaint = Paint()..color = _selectionColor!; + final Paint bubblePaint = Paint()..color = _selectionHandleColor!; + _zoomPercentage = pdfViewerController.zoomLevel > _maximumZoomLevel + ? _maximumZoomLevel + : pdfViewerController.zoomLevel; + + _performDocumentLinkNavigation(canvas, offset); + _performTextSearch(canvas, offset); + + if (_textSelectionHelper.mouseSelectionEnabled && + _textSelectionHelper.textLines != null && + _textSelectionHelper.endBubbleX != null && + _textSelectionHelper.endBubbleY != null) { + _performSelection(canvas, offset, textPaint, bubblePaint); + } if (_longPressed) { - final double _heightPercentage = + final double heightPercentage = pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; - _textSelectionHelper.heightPercentage = _heightPercentage; + _textSelectionHelper.heightPercentage = heightPercentage; _textSelectionHelper.textLines = PdfTextExtractor(pdfDocument!) .extractTextLines(startPageIndex: _textSelectionHelper.viewId); for (int textLineIndex = 0; @@ -1197,56 +1345,56 @@ class CanvasRenderBox extends RenderBox { final TextWord textWord = textWordCollection[wordIndex]; final Rect wordBounds = textWord.bounds; if (_tapDetails != null && - wordBounds.contains(_tapDetails! * _heightPercentage)) { + wordBounds.contains(_tapDetails! * heightPercentage)) { _textSelectionHelper.startBubbleLine = _textSelectionHelper.textLines![textLineIndex]; _textSelectionHelper.copiedText = textWord.text; _textSelectionHelper.endBubbleLine = _textSelectionHelper.textLines![textLineIndex]; - _startBubbleTapX = - textWord.bounds.bottomLeft.dx / _heightPercentage; + _startBubbleTapX = textWord.bounds.bottomLeft.dx / heightPercentage; _textSelectionHelper.startBubbleY = textWord.bounds.bottomLeft.dy; - _endBubbleTapX = textWord.bounds.bottomRight.dx / _heightPercentage; + _endBubbleTapX = textWord.bounds.bottomRight.dx / heightPercentage; _textSelectionHelper.endBubbleY = textWord.bounds.bottomRight.dy; _textSelectionHelper.startBubbleX = textWord.bounds.bottomLeft.dx; _textSelectionHelper.endBubbleX = textWord.bounds.bottomRight.dx; final Rect textRectOffset = offset.translate( - textWord.bounds.left / _heightPercentage, - textWord.bounds.top / _heightPercentage) & - Size(wordBounds.width / _heightPercentage, - wordBounds.height / _heightPercentage); + textWord.bounds.left / heightPercentage, + textWord.bounds.top / heightPercentage) & + Size(wordBounds.width / heightPercentage, + wordBounds.height / heightPercentage); _drawTextRect(canvas, textPaint, textRectOffset); final Offset startBubbleOffset = offset.translate( - textWord.bounds.bottomLeft.dx / _heightPercentage, - textWord.bounds.bottomLeft.dy / _heightPercentage); + textWord.bounds.bottomLeft.dx / heightPercentage, + textWord.bounds.bottomLeft.dy / heightPercentage); final Offset endBubbleOffset = offset.translate( - textWord.bounds.bottomRight.dx / _heightPercentage, - textWord.bounds.bottomRight.dy / _heightPercentage); + textWord.bounds.bottomRight.dx / heightPercentage, + textWord.bounds.bottomRight.dy / heightPercentage); _drawStartBubble(canvas, bubblePaint, startBubbleOffset); _drawEndBubble(canvas, bubblePaint, endBubbleOffset); _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( localToGlobal(Offset( - textWord.bounds.topLeft.dx / _heightPercentage, - textWord.bounds.topLeft.dy / _heightPercentage)), + textWord.bounds.topLeft.dx / heightPercentage, + textWord.bounds.topLeft.dy / heightPercentage)), localToGlobal(Offset( - textWord.bounds.bottomRight.dx / _heightPercentage, - textWord.bounds.bottomRight.dy / _heightPercentage))); + textWord.bounds.bottomRight.dx / heightPercentage, + textWord.bounds.bottomRight.dy / heightPercentage))); _textSelectionHelper.firstSelectedGlyph = textWord.glyphs.first; _textSelectionHelper.selectionEnabled = true; _textSelectionHelper.selectedTextLines.clear(); _textSelectionHelper.selectedTextLines.add(PdfTextLine( textWord.bounds, textWord.text, _textSelectionHelper.viewId!)); _ensureHistoryEntry(); - _sortTextLines(); + _textSelectionHelper.textLines = + _sortTextLines(_textSelectionHelper.textLines!); } } } _longPressed = false; } else if (_textSelectionHelper.selectionEnabled && pageIndex == _textSelectionHelper.viewId) { - final double _heightPercentage = + final double heightPercentage = pdfDocument!.pages[_textSelectionHelper.viewId!].size.height / height; - _textSelectionHelper.heightPercentage = _heightPercentage; + _textSelectionHelper.heightPercentage = heightPercentage; if (_startBubbleDragging) { for (int textLineIndex = 0; textLineIndex < _textSelectionHelper.textLines!.length; @@ -1254,14 +1402,14 @@ class CanvasRenderBox extends RenderBox { final TextLine line = _textSelectionHelper.textLines![textLineIndex]; if (_dragDetails != null && _dragDetails!.dy <= - _textSelectionHelper.endBubbleY! / _heightPercentage && - _dragDetails!.dy >= (line.bounds.top / _heightPercentage)) { + _textSelectionHelper.endBubbleY! / heightPercentage && + _dragDetails!.dy >= (line.bounds.top / heightPercentage)) { _textSelectionHelper.startBubbleLine = line; _textSelectionHelper.startBubbleY = line.bounds.bottomLeft.dy; } if (_dragDetails != null && _dragDetails!.dy >= - _textSelectionHelper.endBubbleY! / _heightPercentage) { + _textSelectionHelper.endBubbleY! / heightPercentage) { _textSelectionHelper.startBubbleLine = _textSelectionHelper.endBubbleLine; _textSelectionHelper.startBubbleY = @@ -1278,9 +1426,9 @@ class CanvasRenderBox extends RenderBox { glyphIndex++) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; if (_startBubbleTapX >= - (textGlyph.bounds.bottomLeft.dx / _heightPercentage) && + (textGlyph.bounds.bottomLeft.dx / heightPercentage) && _startBubbleTapX <= - (textGlyph.bounds.bottomRight.dx / _heightPercentage)) { + (textGlyph.bounds.bottomRight.dx / heightPercentage)) { _textSelectionHelper.startBubbleX = textGlyph.bounds.bottomLeft.dx; _textSelectionHelper.firstSelectedGlyph = textGlyph; @@ -1289,7 +1437,7 @@ class CanvasRenderBox extends RenderBox { } if (_startBubbleTapX < (_textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx / - _heightPercentage)) { + heightPercentage)) { _textSelectionHelper.startBubbleX = _textSelectionHelper.startBubbleLine!.bounds.bottomLeft.dx; _textSelectionHelper.firstSelectedGlyph = _textSelectionHelper @@ -1297,7 +1445,7 @@ class CanvasRenderBox extends RenderBox { } if (_startBubbleTapX >= (_textSelectionHelper.startBubbleLine!.bounds.bottomRight.dx / - _heightPercentage)) { + heightPercentage)) { _textSelectionHelper.startBubbleX = _textSelectionHelper .startBubbleLine! .wordCollection @@ -1311,9 +1459,9 @@ class CanvasRenderBox extends RenderBox { .startBubbleLine!.wordCollection.last.glyphs.last; } if (_textSelectionHelper.startBubbleLine!.bounds.bottom / - _heightPercentage == + heightPercentage == _textSelectionHelper.endBubbleLine!.bounds.bottom / - _heightPercentage && + heightPercentage && _startBubbleTapX >= _endBubbleTapX) { for (int wordIndex = 0; wordIndex < @@ -1325,8 +1473,8 @@ class CanvasRenderBox extends RenderBox { glyphIndex < textWord.glyphs.length; glyphIndex++) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; - if (textGlyph.bounds.bottomRight.dx / _heightPercentage == - _textSelectionHelper.endBubbleX! / _heightPercentage) { + if (textGlyph.bounds.bottomRight.dx / heightPercentage == + _textSelectionHelper.endBubbleX! / heightPercentage) { _textSelectionHelper.startBubbleX = textGlyph.bounds.bottomLeft.dx; _textSelectionHelper.firstSelectedGlyph = textGlyph; @@ -1344,9 +1492,8 @@ class CanvasRenderBox extends RenderBox { if (_dragDetails != null && _dragDetails!.dy >= (_textSelectionHelper.startBubbleLine!.bounds.top / - _heightPercentage) && - _dragDetails!.dy >= - (line.bounds.topLeft.dy / _heightPercentage)) { + heightPercentage) && + _dragDetails!.dy >= (line.bounds.topLeft.dy / heightPercentage)) { _textSelectionHelper.endBubbleLine = line; _textSelectionHelper.endBubbleY = line.bounds.bottomRight.dy; } @@ -1361,9 +1508,9 @@ class CanvasRenderBox extends RenderBox { glyphIndex++) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; if (_endBubbleTapX >= - (textGlyph.bounds.bottomLeft.dx / _heightPercentage) && + (textGlyph.bounds.bottomLeft.dx / heightPercentage) && _endBubbleTapX <= - (textGlyph.bounds.bottomRight.dx / _heightPercentage)) { + (textGlyph.bounds.bottomRight.dx / heightPercentage)) { _textSelectionHelper.endBubbleX = textGlyph.bounds.bottomRight.dx; } @@ -1371,14 +1518,14 @@ class CanvasRenderBox extends RenderBox { } if (_endBubbleTapX.floor() > (_textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx / - _heightPercentage) + heightPercentage) .floor()) { _textSelectionHelper.endBubbleX = _textSelectionHelper.endBubbleLine!.bounds.bottomRight.dx; } if (_endBubbleTapX.floor() <= (_textSelectionHelper.endBubbleLine!.bounds.bottomLeft.dx / - _heightPercentage) + heightPercentage) .floor()) { _textSelectionHelper.endBubbleX = _textSelectionHelper .endBubbleLine! @@ -1391,9 +1538,9 @@ class CanvasRenderBox extends RenderBox { .dx; } if (_textSelectionHelper.endBubbleLine!.bounds.bottom / - _heightPercentage == + heightPercentage == _textSelectionHelper.startBubbleLine!.bounds.bottom / - _heightPercentage && + heightPercentage && _endBubbleTapX < _startBubbleTapX) { for (int wordIndex = 0; wordIndex < @@ -1405,8 +1552,8 @@ class CanvasRenderBox extends RenderBox { glyphIndex < textWord.glyphs.length; glyphIndex++) { final TextGlyph textGlyph = textWord.glyphs[glyphIndex]; - if (textGlyph.bounds.bottomLeft.dx / _heightPercentage == - _textSelectionHelper.startBubbleX! / _heightPercentage) { + if (textGlyph.bounds.bottomLeft.dx / heightPercentage == + _textSelectionHelper.startBubbleX! / heightPercentage) { _textSelectionHelper.endBubbleX = textGlyph.bounds.bottomRight.dx; break; @@ -1416,103 +1563,7 @@ class CanvasRenderBox extends RenderBox { } } } - _textSelectionHelper.copiedText = ''; - _textSelectionHelper.selectedTextLines.clear(); - for (int textLineIndex = 0; - textLineIndex < _textSelectionHelper.textLines!.length; - textLineIndex++) { - final TextLine line = _textSelectionHelper.textLines![textLineIndex]; - Rect? startPoint; - Rect? endPoint; - String glyphText = ''; - final List textWordCollection = line.wordCollection; - for (int wordIndex = 0; - wordIndex < textWordCollection.length; - wordIndex++) { - final TextWord textWord = textWordCollection[wordIndex]; - for (int glyphIndex = 0; - glyphIndex < textWord.glyphs.length; - glyphIndex++) { - final TextGlyph glyph = textWord.glyphs[glyphIndex]; - if (glyph.bounds.bottom / _heightPercentage == - _textSelectionHelper.startBubbleLine!.bounds.bottom / - _heightPercentage) { - if ((glyph.bounds.bottomCenter.dx / _heightPercentage >= - _textSelectionHelper.startBubbleX! / - _heightPercentage && - glyph.bounds.bottomCenter.dx / _heightPercentage < - _textSelectionHelper.endBubbleX! / - _heightPercentage) || - (glyph.bounds.bottomCenter.dx / _heightPercentage >= - _textSelectionHelper.startBubbleX! / - _heightPercentage && - _textSelectionHelper.endBubbleY! / _heightPercentage > - glyph.bounds.bottom / _heightPercentage)) { - startPoint ??= glyph.bounds; - endPoint = glyph.bounds; - glyphText = glyphText + glyph.text; - _textSelectionHelper.copiedText = - _textSelectionHelper.copiedText! + glyph.text; - final Rect textRectOffset = offset.translate( - glyph.bounds.left / _heightPercentage, - glyph.bounds.top / _heightPercentage) & - Size(glyph.bounds.width / _heightPercentage, - glyph.bounds.height / _heightPercentage); - _drawTextRect(canvas, textPaint, textRectOffset); - } - } else if ((glyph.bounds.bottomLeft.dy / _heightPercentage >= - _textSelectionHelper.startBubbleY! / - _heightPercentage && - _textSelectionHelper.endBubbleX! / _heightPercentage > - glyph.bounds.bottomCenter.dx / _heightPercentage && - _textSelectionHelper.endBubbleY! / _heightPercentage > - glyph.bounds.top / _heightPercentage) || - (_textSelectionHelper.endBubbleY! / _heightPercentage > - glyph.bounds.bottom / _heightPercentage && - glyph.bounds.bottomLeft.dy / _heightPercentage >= - _textSelectionHelper.startBubbleY! / - _heightPercentage)) { - startPoint ??= glyph.bounds; - endPoint = glyph.bounds; - glyphText = glyphText + glyph.text; - _textSelectionHelper.copiedText = - _textSelectionHelper.copiedText! + glyph.text; - final Rect textRectOffset = offset.translate( - glyph.bounds.left / _heightPercentage, - glyph.bounds.top / _heightPercentage) & - Size(glyph.bounds.width / _heightPercentage, - glyph.bounds.height / _heightPercentage); - _drawTextRect(canvas, textPaint, textRectOffset); - } - if (startPoint != null && - endPoint != null && - glyph == line.wordCollection.last.glyphs.last) { - _textSelectionHelper.selectedTextLines.add(PdfTextLine( - Rect.fromLTRB(startPoint.left, startPoint.top, endPoint.right, - endPoint.bottom), - glyphText, - _textSelectionHelper.viewId!)); - } - } - } - } - final Offset startBubbleOffset = offset.translate( - _textSelectionHelper.startBubbleX! / _heightPercentage, - _textSelectionHelper.startBubbleY! / _heightPercentage); - final Offset endBubbleOffset = offset.translate( - _textSelectionHelper.endBubbleX! / _heightPercentage, - _textSelectionHelper.endBubbleY! / _heightPercentage); - _drawStartBubble(canvas, bubblePaint, startBubbleOffset); - _drawEndBubble(canvas, bubblePaint, endBubbleOffset); - _textSelectionHelper.globalSelectedRegion = Rect.fromPoints( - localToGlobal(Offset( - _textSelectionHelper.firstSelectedGlyph!.bounds.left / - _heightPercentage, - _textSelectionHelper.firstSelectedGlyph!.bounds.top / - _heightPercentage)), - localToGlobal(Offset( - _textSelectionHelper.endBubbleX! / _heightPercentage, - _textSelectionHelper.endBubbleY! / _heightPercentage))); + _performSelection(canvas, offset, textPaint, bubblePaint); } } } 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 3172a74f4..744f0e718 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_head.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; /// Height of the ScrollHead. @@ -16,10 +16,17 @@ const double kPdfScrollHeadHeight = 32.0; class ScrollHead extends StatefulWidget { /// Constructor for ScrollHead. const ScrollHead( - this.scrollHeadOffset, this.pdfViewerController, this.isMobileWebView); + this.canShowHorizontalScrollBar, + this.canShowVerticalScrollBar, + this.scrollHeadOffset, + this.pdfViewerController, + this.isMobileWebView, + this.scrollDirection, + this.isBookmarkViewOpen, + this.pageLayoutMode); /// Position of the [ScrollHead] in [SfPdfViewer]. - final double scrollHeadOffset; + final Offset scrollHeadOffset; /// PdfViewer controller of PdfViewer final PdfViewerController pdfViewerController; @@ -27,6 +34,22 @@ class ScrollHead extends StatefulWidget { /// If true,MobileWebView is enabled.Default value is false. final bool isMobileWebView; + /// Represents the scroll direction of PdfViewer. + final PdfScrollDirection scrollDirection; + + /// If true,Horizontal scrollbar of the desktop. + final bool canShowHorizontalScrollBar; + + /// If true,vertical scrollbar of the desktop. + final bool canShowVerticalScrollBar; + + /// Indicates whether the built-in bookmark view in the [SfPdfViewer] is + /// opened or not. + final bool isBookmarkViewOpen; + + /// Represents page layout mode of the PdfViewer. + final PdfPageLayoutMode pageLayoutMode; + @override _ScrollHeadState createState() => _ScrollHeadState(); } @@ -47,22 +70,49 @@ class _ScrollHeadState extends State { super.dispose(); } - @override - Widget build(BuildContext context) { - if (kIsDesktop) { - return Container( - alignment: Alignment.topRight, - margin: EdgeInsets.only(top: widget.scrollHeadOffset), + Widget _createScrollBar( + bool visible, Alignment alignment, EdgeInsets edgeInsets, Size size) { + return Visibility( + visible: visible, + child: Container( + alignment: alignment, + margin: edgeInsets, child: Material( color: Colors.grey, borderRadius: const BorderRadius.all(Radius.circular(7.0)), child: Container( constraints: BoxConstraints.tight( - const Size(10.0, 54.0), + size, ), ), ), - ); + ), + ); + } + + @override + Widget build(BuildContext context) { + if (kIsDesktop) { + final Widget verticalScrollBar = _createScrollBar( + widget.canShowVerticalScrollBar, + Alignment.topRight, + EdgeInsets.only(top: widget.scrollHeadOffset.dy), + const Size(10.0, 54.0)); + final Widget horizontalScrollBar = _createScrollBar( + widget.canShowHorizontalScrollBar, + Alignment.bottomLeft, + EdgeInsets.only(left: widget.scrollHeadOffset.dx), + const Size(54.0, 10.0)); + + if (widget.scrollDirection == PdfScrollDirection.horizontal && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + return Stack( + children: [verticalScrollBar, horizontalScrollBar]); + } else if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + return horizontalScrollBar; + } else { + return verticalScrollBar; + } } const List boxShadows = [ BoxShadow( @@ -81,39 +131,59 @@ class _ScrollHeadState extends State { offset: Offset(0, 1), ), ]; - return Container( - margin: EdgeInsets.only(top: widget.scrollHeadOffset), - child: Stack( - children: [ - Material( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(kPdfScrollHeadHeight), - bottomLeft: Radius.circular(kPdfScrollHeadHeight), - ), + final EdgeInsets edgeInsets = + widget.scrollDirection == PdfScrollDirection.horizontal + ? EdgeInsets.only(left: widget.scrollHeadOffset.dx) + : EdgeInsets.only(top: widget.scrollHeadOffset.dy); + final BorderRadius borderRadius = + widget.scrollDirection == PdfScrollDirection.horizontal + ? const BorderRadius.only( + topRight: Radius.circular(kPdfScrollHeadHeight), + topLeft: Radius.circular(kPdfScrollHeadHeight), + ) + : const BorderRadius.only( + topLeft: Radius.circular(kPdfScrollHeadHeight), + bottomLeft: Radius.circular(kPdfScrollHeadHeight), + ); + final Alignment alignment = + widget.scrollDirection == PdfScrollDirection.horizontal + ? Alignment.bottomLeft + : Alignment.topRight; + return Align( + alignment: alignment, + child: Container( + alignment: alignment, + margin: edgeInsets, + constraints: const BoxConstraints.tightFor(width: 48.0, height: 48.0), + child: Semantics( + container: true, + button: true, + child: Align( + alignment: widget.scrollDirection == PdfScrollDirection.horizontal + ? Alignment.bottomCenter + : Alignment.centerRight, child: Container( decoration: BoxDecoration( color: _pdfViewerThemeData!.scrollHeadStyle.backgroundColor, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(kPdfScrollHeadHeight), - bottomLeft: Radius.circular(kPdfScrollHeadHeight), - ), - // ignore: unnecessary_new + borderRadius: borderRadius, boxShadow: boxShadows, ), constraints: const BoxConstraints.tightFor( width: kPdfScrollHeadHeight, height: kPdfScrollHeadHeight), - ), - ), - Positioned.fill( - child: Align( - alignment: Alignment.center, - child: Text( - '${widget.pdfViewerController.pageNumber}', - style: _pdfViewerThemeData!.scrollHeadStyle.pageNumberTextStyle, + child: Align( + alignment: Alignment.center, + child: Text( + '${widget.pdfViewerController.pageNumber}', + style: + _pdfViewerThemeData!.scrollHeadStyle.pageNumberTextStyle, + semanticsLabel: widget.isBookmarkViewOpen + ? '' + : widget.pdfViewerController.pageNumber.toString(), + ), ), ), ), - ], + ), ), ); } 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 bbc2a596f..10a911c0d 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 @@ -1,18 +1,19 @@ import 'package:flutter/foundation.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 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; -import 'package:syncfusion_flutter_pdfviewer/src/control/interactive_scrollable.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_page_view.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_head.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; -import 'package:syncfusion_flutter_core/localizations.dart'; import '../common/pdfviewer_helper.dart'; import 'scroll_status.dart'; /// Height of the scroll head. -const double _kPdfScrollHeadHeight = 32.0; +const double _kPdfScrollHeadHeight = 48.0; /// Height of the scroll bar const double _kPdfScrollBarHeight = 54.0; @@ -31,17 +32,19 @@ class ScrollHeadOverlay extends StatefulWidget { this.pdfViewerController, this.isMobileWebView, this.pdfDimension, + this.totalImageSize, this.viewportDimension, this.currentOffset, this.maxScale, this.minScale, this.onDoubleTapZoomInvoked, - this.forcePixel, - this.onDoubleTap, this.enableDoubleTapZooming, this.interactionMode, this.scaleEnabled, this.maxPdfPageWidth, + this.pdfPages, + this.scrollDirection, + this.isBookmarkViewOpen, this.child, {Key? key, this.transformationController, @@ -107,14 +110,8 @@ class ScrollHeadOverlay extends StatefulWidget { /// onPdfOffsetChanged callback final OffsetChangedCallback? onPdfOffsetChanged; - /// Triggers after double tap zoom for set pixel - final Function(Offset value, {bool isZoomInitiated}) forcePixel; - /// Trigger while double tap zoom for set zoom level - final double Function(double value) onDoubleTapZoomInvoked; - - /// Triggered when double tap - final GestureTapCallback? onDoubleTap; + final Function(double value) onDoubleTapZoomInvoked; /// Indicates interaction mode of pdfViewer. final PdfInteractionMode interactionMode; @@ -125,6 +122,19 @@ class ScrollHeadOverlay extends StatefulWidget { /// Represents the maximum page width. final double maxPdfPageWidth; + /// Represents the scroll direction of PdfViewer. + final PdfScrollDirection scrollDirection; + + /// PdfPages collection. + final Map pdfPages; + + /// Total image size of the PdfViewer. + final Size totalImageSize; + + /// Indicates whether the built-in bookmark view in the [SfPdfViewer] is + /// opened or not. + final bool isBookmarkViewOpen; + @override ScrollHeadOverlayState createState() => ScrollHeadOverlayState(); } @@ -138,11 +148,13 @@ class ScrollHeadOverlayState extends State { SfLocalizations? _localizations; final GlobalKey _childKey = GlobalKey(); bool _isInteractionEnded = true; - Offset _tapPosition = Offset.zero; double _scale = 1; - /// Scroll head Offset - double _scrollHeadOffset = 0; + /// Scroll head y position. + double _scrollHeadPositionY = 0; + + /// Scroll head x position. + double _scrollHeadPositionX = 0; /// If true,scroll head dragging is ended. bool _isScrollHeadDragged = false; @@ -167,47 +179,96 @@ class ScrollHeadOverlayState extends State { super.dispose(); } - /// This method use to get the tap positions - void _handleDoubleTapDown(TapDownDetails details) { - _tapPosition = details.localPosition; - } - - // Handles the double tap behavior. - void _handleDoubleTap() { - widget.onDoubleTap?.call(); + Offset _onDoubleTapZoomInvoked(Offset offset, Offset tapPosition) { + widget.onDoubleTapZoomInvoked(widget.pdfViewerController.zoomLevel); + widget.pdfViewerController.zoomLevel = + widget.transformationController!.value.getMaxScaleOnAxis(); + final double pdfPageHeight = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.height; + final double totalPageOffset = widget + .pdfPages[widget.pdfViewerController.pageCount]!.pageOffset + + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width; if (widget.pdfViewerController.zoomLevel <= 1) { - final Offset normalizedOffset = (-_tapPosition - - widget.currentOffset * widget.pdfViewerController.zoomLevel) / - widget.pdfViewerController.zoomLevel; - widget.pdfViewerController.zoomLevel = 2; - final Offset offset = (-_tapPosition - normalizedOffset * 2) / 2; - widget.pdfViewerController.zoomLevel = widget.onDoubleTapZoomInvoked(2); + //check if the total page offset less than viewport width in horizontal scroll direction + if (widget.scrollDirection == PdfScrollDirection.vertical || + (widget.scrollDirection == PdfScrollDirection.horizontal && + (totalPageOffset < widget.viewportDimension.width))) { + // set x offset as zero + offset = Offset(0, offset.dy); + } + } else { if (kIsDesktop && !widget.isMobileWebView) { - if (widget.viewportDimension.width > + if (widget.viewportDimension.width < widget.maxPdfPageWidth * widget.pdfViewerController.zoomLevel) { - widget.forcePixel(Offset(0, offset.dy), isZoomInitiated: true); - } else { - final double clampedX = _tapPosition.dx > widget.maxPdfPageWidth + final double clampedX = tapPosition.dx > widget.maxPdfPageWidth ? ((widget.maxPdfPageWidth * 2) - widget.viewportDimension.width) / 2 : 0; - widget.forcePixel(Offset(clampedX, offset.dy), isZoomInitiated: true); + offset = Offset( + (widget.scrollDirection == PdfScrollDirection.vertical) + ? clampedX + : offset.dx, + offset.dy); } - } else { - widget.forcePixel(offset, isZoomInitiated: true); } + } + final double widthFactor = (widget.pdfDimension.width) - + (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); + if (widget.viewportDimension.height > pdfPageHeight && + (widget.scrollDirection == PdfScrollDirection.horizontal || + (widget.pdfViewerController.pageCount == 1 && + widget.scrollDirection == PdfScrollDirection.vertical))) { + offset = Offset( + (widget.scrollDirection == PdfScrollDirection.vertical) + ? offset.dx.clamp(-widthFactor, widthFactor.abs()) + : offset.dx, + ((tapPosition.dy > widget.viewportDimension.height / 2) + ? offset.dy + + (widget.viewportDimension.height - + widget + .pdfPages[ + widget.pdfViewerController.pageNumber]! + .pageSize + .height) / + 2 + : offset.dy / 2) + .clamp( + 0, + ((widget.viewportDimension.height - + widget + .pdfPages[widget + .pdfViewerController.pageNumber]! + .pageSize + .height) / + 2 + + widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height) / + 2)); } else { - final Offset normalizedOffset = (-_tapPosition - - widget.currentOffset * widget.pdfViewerController.zoomLevel) / - widget.pdfViewerController.zoomLevel; - widget.pdfViewerController.zoomLevel = 1; - final Offset offset = (-_tapPosition - normalizedOffset * 1) / 1; - widget.pdfViewerController.zoomLevel = widget.onDoubleTapZoomInvoked(1); - if (!kIsDesktop || kIsDesktop && widget.isMobileWebView) { - widget.forcePixel(offset, isZoomInitiated: true); + if ((widget.viewportDimension.width > totalPageOffset) && + (widget.pdfDimension.width >= widget.viewportDimension.width)) { + offset = Offset( + (offset.dx - (widget.viewportDimension.width - totalPageOffset)) + .clamp( + 0, + (offset.dx - + (widget.viewportDimension.width - totalPageOffset)) + .abs()), + offset.dy); } + offset = Offset( + offset.dx, + offset.dy.clamp( + 0, + (widget.pdfDimension.height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + .abs())); } + return offset; } @override @@ -215,71 +276,95 @@ class ScrollHeadOverlayState extends State { WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { _updateScrollHeadPosition(); }); - final Widget scrollable = GestureDetector( - behavior: HitTestBehavior.translucent, - onDoubleTapDown: ((!kIsDesktop && widget.enableDoubleTapZooming) || - (kIsDesktop && - widget.interactionMode == PdfInteractionMode.pan) || - (kIsDesktop && - widget.isMobileWebView && - widget.enableDoubleTapZooming)) - ? _handleDoubleTapDown - : null, - onDoubleTap: ((!kIsDesktop && widget.enableDoubleTapZooming) || - (kIsDesktop && - widget.interactionMode == PdfInteractionMode.pan) || - (kIsDesktop && - widget.isMobileWebView && - widget.enableDoubleTapZooming)) - ? _handleDoubleTap - : null, - child: InteractiveScrollable( - widget.child, - minScale: widget.minScale, - maxScale: widget.maxScale, - transformationController: widget.transformationController, - key: _childKey, - // ignore: avoid_bool_literals_in_conditional_expressions - scaleEnabled: ((kIsDesktop && widget.isMobileWebView) || - !kIsDesktop || - (kIsDesktop && widget.scaleEnabled)) - ? true - : false, - panEnabled: widget.isPanEnabled, - onInteractionStart: _handleInteractionStart, - onInteractionUpdate: _handleInteractionChanged, - onInteractionEnd: _handleInteractionEnd, - constrained: false, - )); - + // ignore: avoid_bool_literals_in_conditional_expressions + final bool _enableDoubleTapZoom = ((!kIsDesktop && + widget.enableDoubleTapZooming) || + (kIsDesktop && widget.interactionMode == PdfInteractionMode.pan) || + (kIsDesktop && + widget.isMobileWebView && + widget.enableDoubleTapZooming)) + ? true + : false; + final Widget scrollable = InteractiveScrollViewer( + widget.child, + minScale: widget.minScale, + maxScale: widget.maxScale, + onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, + transformationController: widget.transformationController, + key: _childKey, + enableDoubleTapZooming: _enableDoubleTapZoom, + // ignore: avoid_bool_literals_in_conditional_expressions + scaleEnabled: ((kIsDesktop && widget.isMobileWebView) || + !kIsDesktop || + (kIsDesktop && widget.scaleEnabled)) + ? true + : false, + panEnabled: widget.isPanEnabled, + onInteractionStart: _handleInteractionStart, + onInteractionUpdate: _handleInteractionChanged, + onInteractionEnd: _handleInteractionEnd, + constrained: false, + ); + final Offset scrollHeadOffset = + Offset(_scrollHeadPositionX, _scrollHeadPositionY); + final bool hasBiggerWidth = + widget.totalImageSize.width > widget.viewportDimension.width; + final bool hasBiggerHeight = + widget.totalImageSize.height > widget.viewportDimension.height; + final bool enableScrollHead = hasBiggerWidth || hasBiggerHeight; + bool canShowScrollHead = + !enableScrollHead ? enableScrollHead : widget.canShowScrollHead; + if (kIsDesktop && enableScrollHead) { + canShowScrollHead = true; + } + if (widget.pdfViewerController.pageCount == 1) { + canShowScrollHead = false; + } return Stack( children: [ scrollable, - Align( - alignment: Alignment.topRight, - child: GestureDetector( - onVerticalDragStart: _handleScrollHeadDragStart, - onVerticalDragUpdate: _handleScrollHeadDragUpdate, - onVerticalDragEnd: _handleScrollHeadDragEnd, - onTap: () { - if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { - _textFieldController.clear(); - if (!FocusScope.of(context).hasPrimaryFocus) { - FocusScope.of(context).unfocus(); - } - if (widget.canShowPaginationDialog) { - _showPaginationDialog(); - } + GestureDetector( + onVerticalDragStart: (DragStartDetails details) { + _handleScrollHeadDragStart(details, true); + }, + onVerticalDragUpdate: _handleVerticalScrollHeadDragUpdate, + onVerticalDragEnd: _handleScrollHeadDragEnd, + onHorizontalDragStart: + (widget.scrollDirection == PdfScrollDirection.horizontal) + ? (DragStartDetails details) { + _handleScrollHeadDragStart(details, false); + } + : null, + onHorizontalDragUpdate: + (widget.scrollDirection == PdfScrollDirection.horizontal) + ? _handleScrollHeadDragUpdate + : null, + onHorizontalDragEnd: + (widget.scrollDirection == PdfScrollDirection.horizontal) + ? _handleScrollHeadDragEnd + : null, + onTap: () { + if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { + _textFieldController.clear(); + if (!FocusScope.of(context).hasPrimaryFocus) { + FocusScope.of(context).unfocus(); } - }, - child: Visibility( - visible: kIsDesktop - ? widget.pdfViewerController.pageCount > 1 - : widget.canShowScrollHead && - widget.pdfViewerController.pageCount > 1, - child: ScrollHead(_scrollHeadOffset, widget.pdfViewerController, - widget.isMobileWebView)), - ), + if (widget.canShowPaginationDialog) { + _showPaginationDialog(); + } + } + }, + child: Visibility( + visible: canShowScrollHead, + child: ScrollHead( + hasBiggerWidth, + hasBiggerHeight, + scrollHeadOffset, + widget.pdfViewerController, + widget.isMobileWebView, + widget.scrollDirection, + widget.isBookmarkViewOpen, + PdfPageLayoutMode.continuous)), ), Visibility( visible: _isScrollHeadDragged && widget.canShowScrollStatus, @@ -420,14 +505,48 @@ class ScrollHeadOverlayState extends State { } /// updates UI when scroll head drag is started. - void _handleScrollHeadDragStart(DragStartDetails details) { - _isScrollHeadDragged = true; + void _handleScrollHeadDragStart( + DragStartDetails details, bool isVerticalDrag) { + _isInteractionEnded = false; + if (widget.scrollDirection == PdfScrollDirection.horizontal && + isVerticalDrag) { + _isScrollHeadDragged = false; + } else { + _isScrollHeadDragged = true; + } } /// updates UI when scroll head drag is updating. void _handleScrollHeadDragUpdate(DragUpdateDetails details) { + _isInteractionEnded = false; + if (!widget.viewportDimension.isEmpty) { + final double dragOffset = details.delta.dx + _scrollHeadPositionX; + final double scrollHeadPosition = widget.viewportDimension.width - + (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); + if (dragOffset < scrollHeadPosition && dragOffset >= 0) { + widget.onPdfOffsetChanged!(Offset( + (widget.pdfDimension.width - + (widget.viewportDimension.width / _scale)) * + (dragOffset / scrollHeadPosition), + widget.currentOffset.dy)); + _scrollHeadPositionX = dragOffset; + } else { + if (dragOffset < 0) { + widget.onPdfOffsetChanged!(Offset(0, widget.currentOffset.dy)); + } else { + widget.onPdfOffsetChanged!(Offset( + widget.pdfDimension.width - + (widget.viewportDimension.width / _scale), + widget.currentOffset.dy)); + } + } + } + } + + /// updates UI when scroll head drag is updating. + void _handleVerticalScrollHeadDragUpdate(DragUpdateDetails details) { if (!widget.viewportDimension.isEmpty) { - final double dragOffset = details.delta.dy + _scrollHeadOffset; + final double dragOffset = details.delta.dy + _scrollHeadPositionY; final double scrollHeadPosition = widget.viewportDimension.height - (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); if (dragOffset < scrollHeadPosition && dragOffset >= 0) { @@ -436,7 +555,7 @@ class ScrollHeadOverlayState extends State { (widget.pdfDimension.height - (widget.viewportDimension.height / _scale)) * (dragOffset / scrollHeadPosition))); - _scrollHeadOffset = dragOffset; + _scrollHeadPositionY = dragOffset; } else { if (dragOffset < 0) { widget.onPdfOffsetChanged!(Offset(widget.currentOffset.dx, 0)); @@ -452,6 +571,7 @@ class ScrollHeadOverlayState extends State { /// updates UI when scroll head is dragged. void _handleScrollHeadDragEnd(DragEndDetails details) { + _isInteractionEnded = true; _isScrollHeadDragged = false; } @@ -476,22 +596,35 @@ class ScrollHeadOverlayState extends State { /// updates the scroll head position based on the interaction results. void _updateScrollHeadPosition() { - if (widget.pdfDimension.height > 0 && - widget.viewportDimension.height > 0 && - widget.pdfDimension.height > widget.viewportDimension.height) { + if (widget.pdfDimension.height > 0 && widget.viewportDimension.height > 0) { _scale = widget.transformationController!.value.getMaxScaleOnAxis(); - final double currentOffset = + final double currentOffsetX = + widget.transformationController!.toScene(Offset.zero).dx; + final double currentOffsetY = widget.transformationController!.toScene(Offset.zero).dy; - final double scrollPercent = currentOffset.abs() / + final double scrollPercentX = currentOffsetX.abs() / + (widget.pdfDimension.width - + (widget.viewportDimension.width / _scale)); + final double scrollPercentY = currentOffsetY.abs() / (widget.pdfDimension.height - (widget.viewportDimension.height / _scale)); - final double scrollHeadMaxExtent = widget.viewportDimension.height - + final double scrollHeadMaxExtentX = widget.viewportDimension.width - + (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); + final double scrollHeadMaxExtentY = widget.viewportDimension.height - (kIsDesktop ? _kPdfScrollBarHeight : _kPdfScrollHeadHeight); - final double newPosition = - (scrollPercent * scrollHeadMaxExtent).clamp(1, scrollHeadMaxExtent); - if (newPosition.round() != _scrollHeadOffset.round() && + final double newPositionX = (scrollPercentX * scrollHeadMaxExtentX) + .clamp(1, scrollHeadMaxExtentX); + final double newPositionY = (scrollPercentY * scrollHeadMaxExtentY) + .clamp(1, scrollHeadMaxExtentY); + if (newPositionX.round() != _scrollHeadPositionX.round() && + _isInteractionEnded) { + _scrollHeadPositionX = newPositionX; + widget.onPdfOffsetChanged!( + widget.transformationController!.toScene(Offset.zero)); + } + if (newPositionY.round() != _scrollHeadPositionY.round() && _isInteractionEnded) { - _scrollHeadOffset = newPosition; + _scrollHeadPositionY = newPositionY; widget.onPdfOffsetChanged!( widget.transformationController!.toScene(Offset.zero)); } 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 8b2896fbb..a8b3d4745 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/scroll_status.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; -import 'package:syncfusion_flutter_core/theme.dart'; import 'package:syncfusion_flutter_core/localizations.dart'; +import 'package:syncfusion_flutter_core/theme.dart'; +import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; /// Bottom position of the [ScrollStatus] widget. const double _kPdfScrollStatusBottomPosition = 25.0; @@ -10,11 +10,14 @@ const double _kPdfScrollStatusBottomPosition = 25.0; @immutable class ScrollStatus extends StatefulWidget { /// Constructs the Scroll status for PdfViewer Widget - const ScrollStatus(this.pdfViewerController); + const ScrollStatus(this.pdfViewerController, {this.isSinglePageView = false}); /// PdfViewer controller of PdfViewer final PdfViewerController pdfViewerController; + /// Determines layout option in PdfViewer. + final bool isSinglePageView; + @override _ScrollStatusState createState() => _ScrollStatusState(); } 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 new file mode 100644 index 000000000..0b35f2fa8 --- /dev/null +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/control/single_page_view.dart @@ -0,0 +1,962 @@ +import 'dart:ui'; +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 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/common/pdfviewer_helper.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_page_view.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/pdf_scrollable.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_head.dart'; +import 'package:syncfusion_flutter_pdfviewer/src/control/scroll_status.dart'; + +/// Signature for [SfPdfViewer.onPageChanged] callback. +typedef PageChangedCallback = void Function(int newPage); + +/// Height of the scroll head. +const double _kPdfScrollHeadHeight = 48.0; + +/// Height of the pagination text field. +const double _kPdfPaginationTextFieldWidth = 328.0; + +/// SinglePageView class for PdfViewer. +@immutable +class SinglePageView extends StatefulWidget { + /// Constructor for PdfScrollable. + const SinglePageView( + Key key, + this.pdfViewerController, + this.pageController, + this.onPageChanged, + this.interactionUpdate, + this.viewportDimension, + this.canShowPaginationDialog, + this.canShowScrollHead, + this.canShowScrollStatus, + this.pdfPages, + this.isMobileWebView, + this.enableDoubleTapZooming, + this.interactionMode, + this.scaleEnabled, + this.onZoomLevelChanged, + this.onDoubleTap, + this.onPdfOffsetChanged, + this.isBookmarkViewOpen, + this.children) + : super(key: key); + + /// PdfViewer controller of PdfViewer. + final PdfViewerController pdfViewerController; + + /// Page controller of the PdfViewer. + final PageController pageController; + + /// Children of single page view. + final List children; + + /// Invoked when page changed in single page view. + final PageChangedCallback onPageChanged; + + /// Invoked when interaction update called in interactive viewer. + final Function(double) interactionUpdate; + + /// Invoked when zoom level changed in single page view. + final Function(double) onZoomLevelChanged; + + /// Triggered when double tap. + final GestureTapCallback? onDoubleTap; + + /// Viewport dimension of PdfViewer. + final Size viewportDimension; + + /// Indicates whether page navigation dialog must be shown or not. + final bool canShowPaginationDialog; + + /// Indicates whether scroll head must be shown or not. + final bool canShowScrollHead; + + /// Indicates whether scroll status must be shown or not. + final bool canShowScrollStatus; + + /// If true,MobileWebView is enabled.Default value is false. + final bool isMobileWebView; + + /// If true, double tap zooming is enabled. + final bool enableDoubleTapZooming; + + /// Indicates interaction mode of pdfViewer. + final PdfInteractionMode interactionMode; + + /// PdfPages collection. + final Map pdfPages; + + /// If True, scale is enabled + final bool scaleEnabled; + + /// Triggered when current offset is changed. + final OffsetChangedCallback? onPdfOffsetChanged; + + /// Indicates whether the built-in bookmark view in the [SfPdfViewer] is + /// opened or not. + final bool isBookmarkViewOpen; + + @override + SinglePageViewState createState() => SinglePageViewState(); +} + +/// SinglePageView state class. +class SinglePageViewState extends State { + SfPdfViewerThemeData? _pdfViewerThemeData; + SfLocalizations? _localizations; + bool _isScrollHeadDragged = false; + double _scrollHeadPosition = 0; + bool _canScroll = false; + bool _isOverFlowed = false; + bool _setZoomLevel = false; + double _paddingWidthScale = 0; + double _paddingHeightScale = 0; + Offset _currentOffsetOfInteractionUpdate = Offset.zero; + TransformationController _transformationController = + TransformationController(); + bool _canPanOnZoom = false; + final TextEditingController _textFieldController = TextEditingController(); + final GlobalKey _formKey = GlobalKey(); + final FocusNode _focusNode = FocusNode(); + Size _oldLayoutSize = Size.zero; + double _topMargin = 0, _leftMargin = 0; + double _panStartOffset = 0.0; + double _panUpdateOffset = 0.0; + bool _canJumpPrevious = false; + bool _canJumpNext = false; + bool _isMousePointer = false; + + /// If true , when API jump is enable + bool isJumpOnZoomedDocument = false; + + /// Represent the old previous zoom level. + double _oldPreviousZoomLevel = 1; + + /// Size of grey area + double greyAreaSize = 0; + + /// Represent the previous zoom level. + double previousZoomLevel = 1; + + /// Current zoom level of single page view. + double currentZoomLevel = 1; + + /// Current offset of single page view + Offset currentOffset = Offset.zero; + + @override + void initState() { + super.initState(); + } + + @override + void didChangeDependencies() { + _pdfViewerThemeData = SfPdfViewerTheme.of(context); + _localizations = SfLocalizations.of(context); + super.didChangeDependencies(); + } + + @override + void dispose() { + _pdfViewerThemeData = null; + _localizations = null; + _focusNode.dispose(); + super.dispose(); + } + + Size _getChildSize(Size viewportDimension) { + double widthFactor = 1.0, heightFactor = 1.0; + widthFactor = _paddingWidthScale == 0 + ? widget.pdfViewerController.zoomLevel + : _paddingWidthScale; + heightFactor = _paddingHeightScale == 0 + ? widget.pdfViewerController.zoomLevel + : _paddingHeightScale; + final double zoomLevel = + _transformationController.value.getMaxScaleOnAxis(); + final double imageWidth = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width * + zoomLevel; + final double childWidth = viewportDimension.width > imageWidth + ? viewportDimension.width / widthFactor.clamp(1, 3) + : imageWidth / widthFactor.clamp(1, 3); + final double imageHeight = widget + .pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.height * + zoomLevel; + double childHeight = viewportDimension.height > imageHeight + ? viewportDimension.height / heightFactor.clamp(1, 3) + : imageHeight / heightFactor.clamp(1, 3); + if (childHeight > viewportDimension.height) { + childHeight = widget.viewportDimension.height; + } + + return Size(childWidth, childHeight); + } + + ///Jump to the desired offset. + void jumpTo({double? xOffset, double? yOffset}) { + xOffset ??= 0.0; + yOffset ??= 0.0; + _handlePdfOffsetChanged(Offset(xOffset, yOffset)); + } + + /// Handles PDF offset changed and updates the matrix translation based on it. + void _handlePdfOffsetChanged(Offset offset) { + final Offset currentOffset = _transformationController.toScene(Offset.zero); + final Size pdfDimension = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize; + if (pdfDimension.height != 0) { + if (widget.viewportDimension.width > + (pdfDimension.width * widget.pdfViewerController.zoomLevel) && + (!kIsDesktop || widget.isMobileWebView)) { + offset = Offset(currentOffset.dx, offset.dy); //Need to do for webs + } + if (widget.viewportDimension.height > + pdfDimension.height * widget.pdfViewerController.zoomLevel) { + offset = Offset(offset.dx, 0); + } + final double widthFactor = pdfDimension.width - + (widget.viewportDimension.width / + widget.pdfViewerController.zoomLevel); + if (isJumpOnZoomedDocument) { + final double actualMargin = greyAreaSize / 2; + bool skipY = false; + final double pageHeight = widget + .pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.height; + if (widget.pdfViewerController.zoomLevel > 1 && + pageHeight * widget.pdfViewerController.zoomLevel < + widget.viewportDimension.height) { + skipY = true; + } + offset = Offset( + offset.dx.clamp( + _setZoomLevel == true ? -widthFactor : 0, widthFactor.abs()), + skipY + ? 0 + : offset.dy.clamp( + actualMargin, + ((pdfDimension.height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + + actualMargin) + .abs())); + } else { + offset = Offset( + offset.dx.clamp( + _setZoomLevel == true ? -widthFactor : 0, widthFactor.abs()), + offset.dy.clamp( + 0, + (pdfDimension.height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + .abs())); + } + _setZoomLevel = false; + if (kIsDesktop && !widget.isMobileWebView) { + if (widget.viewportDimension.width > + pdfDimension.width * widget.pdfViewerController.zoomLevel) { + offset = Offset(0, offset.dy); + } else { + offset = Offset(offset.dx, offset.dy); + } + } + if (isJumpOnZoomedDocument) { + if (MediaQuery.of(context).orientation == Orientation.landscape || + (kIsDesktop && !widget.isMobileWebView)) { + if (widget.viewportDimension.width > + pdfDimension.width * widget.pdfViewerController.zoomLevel) { + offset = Offset(0, offset.dy); + } + } else { + final double greyAreaWidthSize = widget.viewportDimension.width - + (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .width); + offset = Offset(greyAreaWidthSize / 2 + offset.dx, offset.dy); + } + } + final Offset previousOffset = + _transformationController.toScene(Offset.zero); + setState(() { + _transformationController.value.translate( + previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); + }); + } + widget.onPdfOffsetChanged! + .call(_transformationController.toScene(Offset.zero)); + } + + @override + Widget build(BuildContext context) { + final Size childSize = _getChildSize(widget.viewportDimension); + currentOffset = _transformationController.toScene(Offset.zero); + // ignore: avoid_bool_literals_in_conditional_expressions + final bool _enableDoubleTapZoom = ((!kIsDesktop && + widget.enableDoubleTapZooming) || + (kIsDesktop && widget.interactionMode == PdfInteractionMode.pan) || + (kIsDesktop && + widget.isMobileWebView && + widget.enableDoubleTapZooming)) + ? true + : false; + final List pages = []; + for (int pageIndex = 0; pageIndex < widget.children.length; pageIndex++) { + final Widget page = widget.children[pageIndex]; + final bool isLandscape = + MediaQuery.of(context).orientation == Orientation.landscape; + double imageSize = widget.pdfPages[pageIndex + 1]!.pageSize.height * + widget.pdfViewerController.zoomLevel; + _topMargin = (widget.pdfPages[pageIndex + 1]!.pageSize.height - + widget.viewportDimension.height) / + 2; + greyAreaSize = widget.viewportDimension.height - + (widget.pdfPages[pageIndex + 1]!.pageSize.height); + bool isHeightFitted = false; + if (_topMargin == 0) { + isHeightFitted = true; + _leftMargin = (widget.pdfPages[pageIndex + 1]!.pageSize.width - + widget.viewportDimension.width) / + 2; + imageSize = widget.pdfPages[pageIndex + 1]!.pageSize.width * + double.parse( + widget.pdfViewerController.zoomLevel.toStringAsFixed(1)) + .round(); + } + pages.add(InteractiveScrollViewer( + Container( + height: isLandscape && !kIsDesktop + ? childSize.height + : widget.viewportDimension.height, + width: + isLandscape ? childSize.width : widget.viewportDimension.width, + child: Center(child: page)), + clipBehavior: Clip.none, + boundaryMargin: EdgeInsets.only( + top: isHeightFitted || isLandscape + ? 0 + : (imageSize.round() <= widget.viewportDimension.height.round() + ? (childSize.height - widget.viewportDimension.height) / 2 + : _topMargin), + bottom: isHeightFitted || isLandscape + ? 0 + : (imageSize.round() <= widget.viewportDimension.height.round() + ? (childSize.height - widget.viewportDimension.height) / 2 + : _topMargin), + right: isHeightFitted + ? (imageSize <= widget.viewportDimension.width ? 0 : _leftMargin) + : 0, + left: isHeightFitted + ? (imageSize <= widget.viewportDimension.width ? 0 : _leftMargin) + : 0, + ), + constrained: false, + panEnabled: true, + onDoubleTapZoomInvoked: _onDoubleTapZoomInvoked, + // ignore: avoid_bool_literals_in_conditional_expressions + scaleEnabled: ((kIsDesktop && widget.isMobileWebView) || + !kIsDesktop || + (kIsDesktop && widget.scaleEnabled)) + ? true + : false, + enableDoubleTapZooming: _enableDoubleTapZoom, + transformationController: _transformationController, + onInteractionStart: (ScaleStartDetails details) { + _panStartOffset = details.localFocalPoint.dx; + if (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled)) { + if (previousZoomLevel != _oldPreviousZoomLevel) { + _oldPreviousZoomLevel = previousZoomLevel; + } + previousZoomLevel = widget.pdfViewerController.zoomLevel; + } + _paddingWidthScale = 0; + _paddingHeightScale = 0; + }, + onInteractionUpdate: (ScaleUpdateDetails details) { + if (widget.interactionMode == PdfInteractionMode.pan) { + _panUpdateOffset = details.localFocalPoint.dx; + if (_panStartOffset != _panUpdateOffset) { + if (_panStartOffset < details.localFocalPoint.dx) { + _canJumpPrevious = true; + } else { + _canJumpNext = true; + } + } + } + _currentOffsetOfInteractionUpdate = + _transformationController.toScene(Offset.zero); + if (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled)) { + 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(Offset(widget.viewportDimension.width, 0)) + .dx + .round() + + _leftMargin.abs().round() >= + widget.viewportDimension.width || + _transformationController.toScene(Offset.zero).dx.round() <= + _leftMargin.abs().round()) { + _canPanOnZoom = true; + } else { + _canScroll = true; + } + } else { + _canScroll = true; + } + widget.onPdfOffsetChanged! + .call(_transformationController.toScene(Offset.zero)); + }, + onInteractionEnd: (ScaleEndDetails details) { + if (widget.interactionMode == PdfInteractionMode.pan) { + final double pannedDistance = + (_panStartOffset - _panUpdateOffset).abs(); + if (pannedDistance > 300) { + if (_canJumpPrevious && + widget.pdfViewerController.pageNumber != 1) { + widget.pageController.animateToPage( + widget.pdfViewerController.pageNumber - 2, + duration: const Duration(milliseconds: 500), + curve: Curves.ease); + } else if (_canJumpNext && + widget.pdfViewerController.pageNumber != + widget.pdfViewerController.pageCount) { + widget.pageController.animateToPage( + widget.pdfViewerController.pageNumber, + duration: const Duration(milliseconds: 500), + curve: Curves.ease); + } + _canJumpPrevious = false; + _canJumpNext = false; + } else { + if (_canJumpPrevious) { + widget.pageController.animateTo( + widget.pageController.offset - 10, + duration: const Duration(milliseconds: 100), + curve: Curves.ease); + } else if (_canJumpNext) { + widget.pageController.animateTo( + widget.pageController.offset + 10, + duration: const Duration(milliseconds: 100), + curve: Curves.ease); + } + } + } + if (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView) || + (kIsDesktop && widget.scaleEnabled)) { + widget.onZoomLevelChanged( + _transformationController.value.getMaxScaleOnAxis()); + } + currentOffset = _transformationController.toScene(Offset.zero); + if (_canScroll) { + _canPanOnZoom = false; + _canScroll = false; + } + _paddingWidthScale = 0; + _paddingHeightScale = 0; + if (widget.viewportDimension.width > + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .width * + _transformationController.value.getMaxScaleOnAxis()) { + setState(() { + if (!_isMousePointer) { + _transformationController.value + .translate(_currentOffsetOfInteractionUpdate.dx); + _isOverFlowed = false; + } + }); + } else { + if (kIsDesktop && !widget.isMobileWebView) { + /// Invoked when pdf pages width greater viewport width + if (_isOverFlowed == false) { + _transformationController.value + .translate(_currentOffsetOfInteractionUpdate.dx); + _isOverFlowed = true; + } + } + } + _isMousePointer = false; + setState(() {}); + }, + )); + } + _scrollHeadPosition = widget.pdfViewerController.pageNumber == 1 + ? 0 + : (widget.pdfViewerController.pageNumber / + widget.pdfViewerController.pageCount) * + (widget.viewportDimension.width - _kPdfScrollHeadHeight); + return Stack( + children: [ + LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + if (_oldLayoutSize != constraints.biggest) { + final Offset previousOffset = + _transformationController.toScene(Offset.zero); + double yPosition = !_oldLayoutSize.isEmpty + ? previousOffset.dy / _oldLayoutSize.height + : 0; + final double greyArea = widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height - + constraints.biggest.height; + yPosition = yPosition * constraints.biggest.height; + if (!greyArea.isNegative && + greyArea < + (constraints.biggest.height / + widget.pdfViewerController.zoomLevel)) { + yPosition = + yPosition.clamp(0, (constraints.biggest.height) - greyArea); + } + double xPosition = !_oldLayoutSize.isEmpty + ? previousOffset.dx / _oldLayoutSize.width + : 0; + xPosition = + MediaQuery.of(context).orientation == Orientation.landscape + ? 0 + : xPosition * constraints.biggest.width; + _transformationController.value.translate( + previousOffset.dx - xPosition, previousOffset.dy - yPosition); + _oldLayoutSize = constraints.biggest; + _canPanOnZoom = false; + } + return Listener( + onPointerDown: (PointerDownEvent details) { + if (details.kind == PointerDeviceKind.mouse) { + _isMousePointer = true; + } + }, + child: PageView( + controller: widget.pageController, + onPageChanged: (int value) { + _transformationController = TransformationController(); + widget.onPageChanged(value); + }, + physics: (_canPanOnZoom || + _transformationController.value.getMaxScaleOnAxis() == + 1) || + (kIsDesktop && !widget.isMobileWebView) || + (MediaQuery.of(context).orientation == + Orientation.landscape && + (!kIsDesktop || + (kIsDesktop && widget.isMobileWebView))) + ? const BouncingScrollPhysics() + : const NeverScrollableScrollPhysics(), + children: pages, + ), + ); + }), + GestureDetector( + onHorizontalDragStart: _handleDragStart, + onHorizontalDragEnd: _handleDragEnd, + onHorizontalDragUpdate: _handleDragUpdate, + onTap: () { + if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { + _textFieldController.clear(); + if (!FocusScope.of(context).hasPrimaryFocus) { + FocusScope.of(context).unfocus(); + } + if (widget.canShowPaginationDialog) { + _showPaginationDialog(); + } + } + }, + child: Visibility( + visible: widget.pdfViewerController.pageCount > 1 && + ((widget.canShowScrollHead && !kIsDesktop) || kIsDesktop), + child: ScrollHead( + true, + false, + Offset(_scrollHeadPosition, widget.viewportDimension.height), + widget.pdfViewerController, + false, + PdfScrollDirection.horizontal, + widget.isBookmarkViewOpen, + PdfPageLayoutMode.single), + ), + ), + Visibility( + visible: _isScrollHeadDragged && widget.canShowScrollStatus, + child: ScrollStatus(widget.pdfViewerController)) + ], + ); + } + + Offset _onDoubleTapZoomInvoked(Offset offset, Offset tapPosition) { + widget.onDoubleTap!(); + previousZoomLevel = widget.pdfViewerController.zoomLevel; + _oldPreviousZoomLevel = previousZoomLevel; + widget.pdfViewerController.zoomLevel = + _transformationController.value.getMaxScaleOnAxis(); + final double pdfPageHeight = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.height; + final double totalPageOffset = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width; + if (widget.pdfViewerController.zoomLevel <= 1) { + if (kIsDesktop && !widget.isMobileWebView) { + offset = Offset.zero; + } else { + offset = Offset(0, offset.dy); + } + } + if (widget.viewportDimension.height > pdfPageHeight) { + offset = Offset( + offset.dx, + (tapPosition.dy > widget.viewportDimension.height / 2) + ? offset.dy + + (widget.viewportDimension.height - + widget + .pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize + .height) / + 2 + : (offset.dy / 2)); + } + offset = Offset( + (offset.dx - (widget.viewportDimension.width - totalPageOffset)).clamp( + 0, + (offset.dx - (widget.viewportDimension.width - totalPageOffset)) + .abs()), + ((widget.pdfViewerController.zoomLevel) > 1 + ? offset.dy + greyAreaSize / 2 + : 0.0) + .clamp( + 0, + (((widget.viewportDimension.height - + widget + .pdfPages[ + widget.pdfViewerController.pageNumber]! + .pageSize + .height) + + widget + .pdfPages[ + widget.pdfViewerController.pageNumber]! + .pageSize + .height) / + 2) - + greyAreaSize / 2)); + + setState(() { + _canPanOnZoom = false; + }); + return offset; + } + + void _handleDragStart(DragStartDetails details) { + _isScrollHeadDragged = true; + } + + void _handleDragUpdate(DragUpdateDetails details) { + _scrollHeadPosition = details.localPosition.dx; + final int pageNumber = (_scrollHeadPosition / + (widget.viewportDimension.width - _kPdfScrollHeadHeight) * + widget.pdfViewerController.pageCount) + .round(); + if (pageNumber > 0 && pageNumber != widget.pdfViewerController.pageNumber) { + widget.pdfViewerController.jumpToPage(pageNumber); + setState(() {}); + } + } + + void _handleDragEnd(DragEndDetails details) { + setState(() { + _isScrollHeadDragged = false; + }); + } + + /// Scale to PDF + double scaleTo(double zoomLevel) { + currentZoomLevel = _transformationController.value.getMaxScaleOnAxis(); + if (currentZoomLevel != zoomLevel) { + previousZoomLevel = currentZoomLevel; + _oldPreviousZoomLevel = previousZoomLevel; + _setZoomLevel = true; + final double zoomChangeFactor = zoomLevel / currentZoomLevel; + final Offset previousOffset = + _transformationController.toScene(Offset.zero); + _transformationController.value.scale(zoomChangeFactor, zoomChangeFactor); + if (kIsDesktop && + !widget.isMobileWebView && + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .width * + zoomLevel < + widget.viewportDimension.width) { + _isOverFlowed = false; + } + final Offset currentOffset = + _transformationController.toScene(Offset.zero); + if ((kIsDesktop && !widget.isMobileWebView) || + (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .width * + currentZoomLevel < + widget.viewportDimension.width)) { + setState(() { + _transformationController.value.translate(previousOffset.dx, + currentOffset.dy / widget.pdfViewerController.zoomLevel); + }); + } else { + greyAreaSize = widget.viewportDimension.height - + (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .height); + double greyAreaOffset = 0; + setState(() { + _canPanOnZoom = false; + if (widget.viewportDimension.height > + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .height * + previousZoomLevel) { + greyAreaOffset = greyAreaSize / 2; + } else { + greyAreaOffset = 0; + } + if (widget.viewportDimension.height > + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .height * + widget.pdfViewerController.zoomLevel) { + _setPixel(Offset(previousOffset.dx, 0)); + } else { + _setPixel( + Offset(previousOffset.dx, previousOffset.dy + greyAreaOffset)); + } + }); + } + } + widget.onPdfOffsetChanged! + .call(_transformationController.toScene(Offset.zero)); + + return zoomLevel; + } + + /// Perform navigation on zoomed document + void jumpOnZoomedDocument(int pageNumber, Offset offset) { + final double currentPreviousZoomLevel = + widget.pdfViewerController.zoomLevel; + isJumpOnZoomedDocument = true; + if (pageNumber != widget.pdfViewerController.pageNumber) { + widget.pdfViewerController.jumpToPage(pageNumber); + } + widget.pdfViewerController.zoomLevel = currentPreviousZoomLevel; + previousZoomLevel = _oldPreviousZoomLevel; + if (widget.pdfViewerController.zoomLevel > 1) { + if (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .width * + widget.pdfViewerController.zoomLevel < + widget.viewportDimension.width) { + isJumpOnZoomedDocument = true; + _handlePdfOffsetChanged(Offset(0, offset.dy)); + } else { + if ((!kIsDesktop || kIsDesktop && widget.isMobileWebView) && + widget.viewportDimension.height > + (widget.pdfPages[widget.pdfViewerController.pageNumber]! + .pageSize.height * + widget.pdfViewerController.zoomLevel)) { + final Size pdfDimension = + widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize; + final double widthFactor = pdfDimension.width - + (widget.viewportDimension.width / + widget.pdfViewerController.zoomLevel); + final Offset previousOffset = + _transformationController.toScene(Offset.zero); + _transformationController.value.translate( + previousOffset.dx - offset.dx.clamp(0, widthFactor.abs()), + -(widget.viewportDimension.height - + _getChildSize(widget.viewportDimension).height) / + 2); + } else { + if (!kIsDesktop || (kIsDesktop && widget.isMobileWebView)) { + isJumpOnZoomedDocument = true; + } + _handlePdfOffsetChanged(Offset(offset.dx, offset.dy)); + } + } + isJumpOnZoomedDocument = false; + } + setState(() {}); + } + + void _setPixel(Offset offset) { + final double widthFactor = widget + .pdfPages[widget.pdfViewerController.pageNumber]!.pageSize.width - + (widget.viewportDimension.width / widget.pdfViewerController.zoomLevel); + offset = Offset( + offset.dx.clamp(-widthFactor, widthFactor.abs()), + offset.dy.clamp( + 0, + (widget.pdfPages[widget.pdfViewerController.pageNumber]!.pageSize + .height - + (widget.viewportDimension.height / + widget.pdfViewerController.zoomLevel)) + .abs())); + + final Offset previousOffset = + _transformationController.toScene(Offset.zero); + _transformationController.value.translate( + previousOffset.dx - offset.dx, previousOffset.dy - offset.dy); + widget.onPdfOffsetChanged! + .call(_transformationController.toScene(Offset.zero)); + } + + /// Clears the Text Selection. + Future _clearSelection() async { + return widget.pdfViewerController.clearSelection(); + } + + /// Show the pagination dialog box + Future _showPaginationDialog() async { + await _clearSelection(); + return showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + final Orientation orientation = MediaQuery.of(context).orientation; + return AlertDialog( + scrollable: true, + insetPadding: const EdgeInsets.all(0), + contentPadding: orientation == Orientation.portrait + ? const EdgeInsets.all(24) + : const EdgeInsets.only(top: 0, right: 24, left: 24, bottom: 0), + buttonPadding: orientation == Orientation.portrait + ? const EdgeInsets.all(8) + : const EdgeInsets.all(4), + backgroundColor: + _pdfViewerThemeData!.paginationDialogStyle.backgroundColor, + title: Text( + _localizations!.pdfGoToPageLabel, + style: _pdfViewerThemeData!.paginationDialogStyle.headerTextStyle, + ), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(4.0))), + content: SingleChildScrollView(child: _paginationTextField()), + actions: [ + TextButton( + onPressed: () { + _textFieldController.clear(); + Navigator.of(context).pop(); + }, + child: Text( + _localizations!.pdfPaginationDialogCancelLabel, + style: _pdfViewerThemeData! + .paginationDialogStyle.cancelTextStyle!.color == + null + ? _pdfViewerThemeData! + .paginationDialogStyle.cancelTextStyle! + .copyWith(color: Theme.of(context).primaryColor) + : _pdfViewerThemeData! + .paginationDialogStyle.cancelTextStyle, + ), + ), + TextButton( + onPressed: () { + _handlePageNumberValidation(); + }, + child: Text( + _localizations!.pdfPaginationDialogOkLabel, + style: _pdfViewerThemeData! + .paginationDialogStyle.okTextStyle!.color == + null + ? _pdfViewerThemeData!.paginationDialogStyle.okTextStyle! + .copyWith(color: Theme.of(context).primaryColor) + : _pdfViewerThemeData!.paginationDialogStyle.okTextStyle, + ), + ) + ], + ); + }); + } + + /// A material design Text field for pagination dialog box. + Widget _paginationTextField() { + return Form( + key: _formKey, + child: Container( + width: _kPdfPaginationTextFieldWidth, + child: TextFormField( + style: _pdfViewerThemeData!.paginationDialogStyle.inputFieldTextStyle, + focusNode: _focusNode, + decoration: InputDecoration( + isDense: true, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Theme.of(context).primaryColor), + ), + contentPadding: const EdgeInsets.symmetric(vertical: 6), + hintText: _localizations!.pdfEnterPageNumberLabel, + hintStyle: _pdfViewerThemeData!.paginationDialogStyle.hintTextStyle, + counterText: + '${widget.pdfViewerController.pageNumber}/${widget.pdfViewerController.pageCount}', + counterStyle: + _pdfViewerThemeData!.paginationDialogStyle.pageInfoTextStyle, + errorStyle: + _pdfViewerThemeData!.paginationDialogStyle.validationTextStyle, + ), + keyboardType: TextInputType.number, + enableInteractiveSelection: false, + controller: _textFieldController, + autofocus: true, + onEditingComplete: _handlePageNumberValidation, + onFieldSubmitted: (String value) { + _handlePageNumberValidation(); + }, + // ignore: missing_return + validator: (String? value) { + try { + if (value != null) { + final int index = int.parse(value); + if (index <= 0 || + index > widget.pdfViewerController.pageCount) { + _textFieldController.clear(); + return _localizations!.pdfInvalidPageNumberLabel; + } + } + } on Exception { + _textFieldController.clear(); + return _localizations!.pdfInvalidPageNumberLabel; + } + }, + ), + ), + ); + } + + /// Validates the page number entered in text field. + void _handlePageNumberValidation() { + if (_formKey.currentState != null && _formKey.currentState!.validate()) { + final int index = int.parse(_textFieldController.text); + _textFieldController.clear(); + Navigator.of(context).pop(); + widget.pdfViewerController.jumpToPage(index); + } + } +} diff --git a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart index 8051c9d20..922781dbf 100644 --- a/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart +++ b/packages/syncfusion_flutter_pdfviewer/lib/src/pdfviewer.dart @@ -1,3 +1,17 @@ +/// [SfPdfViewer] lets you display the PDF document seamlessly and efficiently. +/// It is built in the way that a large PDF document can be opened in +/// minimal time and all their pages can be accessed spontaneously. +/// +/// To use, import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=f1zEJZRdo7w} +/// +/// See also: +/// * [Syncfusion Flutter PDF Viewer product page](https://www.syncfusion.com/flutter-widgets/flutter-pdf-viewer) +/// * [User guide documentation](https://help.syncfusion.com/flutter/pdf-viewer/overview) +/// * [Video tutorials](https://www.syncfusion.com/tutorial-videos/flutter/pdf-viewer) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter) + import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; @@ -23,6 +37,7 @@ import 'control/enums.dart'; import 'control/pagination.dart'; import 'control/pdftextline.dart'; import 'control/pdfviewer_callback_details.dart'; +import 'control/single_page_view.dart'; /// Signature for [SfPdfViewer.onTextSelectionChanged] callback. typedef PdfTextSelectionChangedCallback = void Function( @@ -121,6 +136,8 @@ class SfPdfViewer extends StatefulWidget { this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, this.interactionMode = PdfInteractionMode.selection, + this.scrollDirection = PdfScrollDirection.vertical, + this.pageLayoutMode = PdfPageLayoutMode.continuous, this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = AssetPdf(name, bundle), assert(pageSpacing >= 0), @@ -167,6 +184,8 @@ class SfPdfViewer extends StatefulWidget { this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, this.interactionMode = PdfInteractionMode.selection, + this.scrollDirection = PdfScrollDirection.vertical, + this.pageLayoutMode = PdfPageLayoutMode.continuous, this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = NetworkPdf(src, headers), assert(pageSpacing >= 0), @@ -211,6 +230,8 @@ class SfPdfViewer extends StatefulWidget { this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, this.interactionMode = PdfInteractionMode.selection, + this.scrollDirection = PdfScrollDirection.vertical, + this.pageLayoutMode = PdfPageLayoutMode.continuous, this.searchTextHighlightColor = const Color(0xFFE56E00)}) : _provider = MemoryPdf(bytes), assert(pageSpacing >= 0), @@ -260,6 +281,8 @@ class SfPdfViewer extends StatefulWidget { this.initialScrollOffset = Offset.zero, this.initialZoomLevel = 1, this.interactionMode = PdfInteractionMode.selection, + this.scrollDirection = PdfScrollDirection.vertical, + this.pageLayoutMode = PdfPageLayoutMode.continuous, this.searchTextHighlightColor = const Color(0xFFE56E00), }) : _provider = FilePdf(file), assert(pageSpacing >= 0), @@ -636,6 +659,82 @@ class SfPdfViewer extends StatefulWidget { /// See also: [PdfPageChangedDetails]. final PdfPageChangedCallback? onPageChanged; + /// The direction in which the PDF page scrolls. + /// + /// Defaults to [PdfScrollDirection.vertical] + /// + /// Note: When pageLayoutMode is PdfPageLayoutMode.single then this property + /// defaults to horizontal scroll direction and does not have any effect on + /// vertical scroll direction for now. + /// + /// This example demonstrates how to set the scroll direction to the [SfPdfViewer]. + /// + /// ```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, + /// scrollDirection: PdfScrollDirection.horizontal, + /// ), + /// ), + /// ); + /// } + ///} + /// ``` + final PdfScrollDirection scrollDirection; + + /// The layout mode in which the PDF page will be rendered. + /// + /// Defaults to [PdfPageLayoutMode.continuous] + /// + /// This example demonstrates how to set the page layout mode to the [SfPdfViewer]. + /// + /// ```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, + /// pageLayoutMode: PdfPageLayoutMode.continuous, + /// ), + /// ), + /// ); + /// } + ///} + /// ``` + final PdfPageLayoutMode pageLayoutMode; + @override SfPdfViewerState createState() => SfPdfViewerState(); } @@ -652,10 +751,13 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _getWidthCancellableOperation; List? _originalHeight; List? _originalWidth; + double? _viewportHeightInLandscape; + double? _otherContextHeight; double _maxPdfPageWidth = 0.0; final double _minScale = 1; final double _maxScale = 3; bool _isScaleEnabled = !kIsDesktop; + bool _isPdfPageTapped = false; bool _isDocumentLoadInitiated = false; Orientation? _deviceOrientation; double _viewportWidth = 0.0; @@ -667,19 +769,41 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { bool _panEnabled = true; bool _isMobile = false; bool _isSearchStarted = false; + bool _isKeyPadRaised = false; bool _isTextSelectionCleared = false; final Map _pdfPages = {}; + final GlobalKey _childKey = GlobalKey(); + final GlobalKey _singlePageViewKey = GlobalKey(); final GlobalKey _bookmarkKey = GlobalKey(); - final GlobalKey _pdfScrollableState = GlobalKey(); + final GlobalKey _pdfScrollableStateKey = GlobalKey(); final Map> _pdfPagesKey = >{}; + SystemMouseCursor _cursor = SystemMouseCursors.basic; List? _textCollection; PdfTextExtractor? _pdfTextExtractor; double _maxScrollExtent = 0; - final GlobalKey _columnKey = GlobalKey(); Size _pdfDimension = Size.zero; bool _isPageChanged = false; + bool _isSinglePageViewPageChanged = false; + bool _isSearchInitiated = false; bool _isOverflowed = false; + int _startPage = 0, _endPage = 0, _bufferCount = 0; + final List _renderedImages = []; + final Map _pageTextExtractor = {}; + Size _totalImageSize = Size.zero; + late PdfScrollDirection _scrollDirection; + late PdfScrollDirection _tempScrollDirection; + late PdfPageLayoutMode _pageLayoutMode; + double _pageOffsetBeforeScrollDirectionChange = 0.0; + Size _pageSizeBeforeScrollDirectionChange = Size.zero; + Offset _scrollDirectionSwitchOffset = Offset.zero; + bool _isScrollDirectionChange = false; + PageController _pageController = PageController(); + double _previousHorizontalOffset = 0.0; + double _viewportHeight = 0.0; + bool _iskeypadClosed = false; + Offset _layoutChangeOffset = Offset.zero; + int _previousSinglePage = 1; /// PdfViewer theme data. SfPdfViewerThemeData? _pdfViewerThemeData; @@ -695,6 +819,11 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { void initState() { super.initState(); _plugin = PdfViewerPlugin(); + _scrollDirection = widget.pageLayoutMode == PdfPageLayoutMode.single + ? PdfScrollDirection.horizontal + : widget.scrollDirection; + _tempScrollDirection = _scrollDirection; + _pageLayoutMode = widget.pageLayoutMode; _pdfViewerController = widget.controller ?? PdfViewerController(); _pdfViewerController.addListener(_handleControllerValueChange); _setInitialScrollOffset(); @@ -717,7 +846,6 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { @override void didUpdateWidget(SfPdfViewer oldWidget) { super.didUpdateWidget(oldWidget); - // Handle all cases of needing to dispose and initialize // _pdfViewerController. if (oldWidget.controller == null) { @@ -738,8 +866,15 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfViewerController.addListener(_handleControllerValueChange); } } + _scrollDirection = widget.pageLayoutMode == PdfPageLayoutMode.single + ? PdfScrollDirection.horizontal + : widget.scrollDirection; _compareDocument(oldWidget._provider.getPdfBytes(context), widget._provider.getPdfBytes(context)); + if (oldWidget.pageLayoutMode != widget.pageLayoutMode) { + _updateOffsetOnLayoutChange(oldWidget.controller!.zoomLevel, + oldWidget.controller!.scrollOffset, oldWidget.pageLayoutMode); + } } /// sets the InitialScrollOffset @@ -783,6 +918,8 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _plugin.closeDocument(); _disposeCollection(_originalHeight); _disposeCollection(_originalWidth); + _renderedImages.clear(); + _pageTextExtractor.clear(); _pdfPages.clear(); _pdfPagesKey.clear(); _document?.dispose(); @@ -790,7 +927,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox - ?.dispose(); + ?.disposeSelection(); if (widget.onTextSelectionChanged != null) { widget .onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); @@ -813,23 +950,29 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ?.canvasRenderBox ?.disposeMouseSelection(); _isTextSelectionCleared = false; - _pdfScrollableState.currentState?.reset(); + _pdfScrollableStateKey.currentState?.reset(); _offsetBeforeOrientationChange = Offset.zero; _previousPageNumber = 1; _pdfViewerController._reset(); _pdfPages.clear(); _plugin.closeDocument(); + _pageTextExtractor.clear(); _document?.dispose(); _document = null; imageCache?.clear(); + _startPage = 0; + _endPage = 0; + _bufferCount = 0; + _isSearchInitiated = false; + _renderedImages.clear(); _hasError = false; _isDocumentLoadInitiated = false; _pdfPagesKey.clear(); _maxPdfPageWidth = 0; - _isScaleEnabled = !kIsDesktop; _maxScrollExtent = 0; _pdfDimension = Size.zero; _isPageChanged = false; + _isSinglePageViewPageChanged = false; } /// Loads a PDF document and gets the page count from Plugin @@ -844,6 +987,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { if (isPdfChanged) { _reset(); _plugin = PdfViewerPlugin(); + _checkMount(); } _pdfDocumentLoadCancellableOperation = CancelableOperation.fromFuture(_getPdfFile(pdfBytes)); @@ -862,7 +1006,6 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _isDocumentLoadInitiated = false; widget.onDocumentLoaded!(PdfDocumentLoadedDetails(_document!)); } - _getHeightCancellableOperation = CancelableOperation?>.fromFuture( _plugin.getPagesHeight()); @@ -922,17 +1065,36 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Notify the scroll Listener after [ScrollController] attached. void _isDocumentLoaded() { if (_pdfPages.isNotEmpty && - !_pdfDimension.isEmpty && !_isDocumentLoadInitiated && - _pdfScrollableState.currentState != null) { - _isPdfPagesLoaded(); + ((!_pdfDimension.isEmpty && + _pdfScrollableStateKey.currentState != null) || + (widget.pageLayoutMode == PdfPageLayoutMode.single && + _pageController.hasClients))) { _isDocumentLoadInitiated = true; + _previousHorizontalOffset = 0; + _isPdfPagesLoaded(); + } else if (_layoutChangeOffset != Offset.zero && + (!_pdfDimension.isEmpty && + _pdfScrollableStateKey.currentState != null)) { + final double xOffset = + widget.scrollDirection != PdfScrollDirection.vertical + ? _pdfPages[_previousSinglePage]!.pageOffset + : 0; + final double yOffset = + widget.scrollDirection == PdfScrollDirection.vertical + ? _pdfPages[_previousSinglePage]!.pageOffset + : 0; + _pdfScrollableStateKey.currentState!.jumpTo( + xOffset: xOffset + _layoutChangeOffset.dx, + yOffset: yOffset + _layoutChangeOffset.dy); + _layoutChangeOffset = Offset.zero; + _previousSinglePage = 1; } } /// Invoke the [PdfViewerController] methods on document load time. void _isPdfPagesLoaded() { - if (!_isDocumentLoadInitiated) { + if (_isDocumentLoadInitiated) { if (widget.initialScrollOffset == Offset.zero || _pdfViewerController._verticalOffset != 0.0 || _pdfViewerController._horizontalOffset != 0.0) { @@ -940,13 +1102,20 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { xOffset: _pdfViewerController._horizontalOffset, yOffset: _pdfViewerController._verticalOffset); } - _pdfViewerController.notifyPropertyChangedListeners( property: 'pageNavigate'); _pdfViewerController.notifyPropertyChangedListeners( property: 'jumpToBookmark'); - - _updateCurrentPageNumber(); + if (_pdfViewerController._searchText.isNotEmpty) { + _isSearchInitiated = true; + _pdfViewerController.notifyPropertyChangedListeners( + property: 'searchText'); + } + if (_pdfViewerController.zoomLevel > 1 && + widget.pageLayoutMode == PdfPageLayoutMode.single) { + _singlePageViewKey.currentState! + .scaleTo(_pdfViewerController.zoomLevel); + } } } @@ -965,11 +1134,22 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { Rect? viewportGlobalRect; if (kIsDesktop && !_isMobile && - _pdfScrollableState.currentContext != null) { - final RenderBox viewportRenderBox = - // ignore: avoid_as - (_pdfScrollableState.currentContext!.findRenderObject())! - as RenderBox; + ((widget.pageLayoutMode == PdfPageLayoutMode.single && + _singlePageViewKey.currentContext != null) || + (_pdfScrollableStateKey.currentContext != null && + widget.pageLayoutMode == PdfPageLayoutMode.continuous))) { + RenderBox viewportRenderBox; + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + viewportRenderBox = + // ignore: avoid_as + (_singlePageViewKey.currentContext!.findRenderObject())! + as RenderBox; + } else { + viewportRenderBox = + // ignore: avoid_as + (_pdfScrollableStateKey.currentContext!.findRenderObject())! + as RenderBox; + } final Offset position = viewportRenderBox.localToGlobal(Offset.zero); final Size containerSize = viewportRenderBox.size; viewportGlobalRect = Rect.fromLTWH( @@ -1007,7 +1187,7 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _originalWidth != null && _originalHeight != null; _pdfDimension = - (_columnKey.currentContext?.findRenderObject()?.paintBounds.size) ?? + (_childKey.currentContext?.findRenderObject()?.paintBounds.size) ?? Size.zero; return isPdfLoaded ? Listener( @@ -1024,165 +1204,353 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasData) { final dynamic _pdfImages = snapshot.data; + _renderedImages.clear(); _viewportConstraints = context .findRenderObject()! // ignore: invalid_use_of_protected_member, avoid_as .constraints as BoxConstraints; - double totalHeight = 0; - final bool isKeyPadRaised = + double totalHeight = 0.0; + _isKeyPadRaised = WidgetsBinding.instance?.window.viewInsets.bottom != 0.0; Size viewportDimension = _viewportConstraints.biggest; - if (isKeyPadRaised) { - final double keyPadHeight = - EdgeInsets.fromWindowPadding( - WidgetsBinding.instance!.window.viewInsets, - WidgetsBinding - .instance!.window.devicePixelRatio) - .bottom; + if (_isKeyPadRaised) { + _iskeypadClosed = true; + double keyPadHeight = EdgeInsets.fromWindowPadding( + WidgetsBinding.instance!.window.viewInsets, + WidgetsBinding + .instance!.window.devicePixelRatio) + .bottom; + if ((widget.scrollDirection == + PdfScrollDirection.horizontal || + widget.pageLayoutMode == + PdfPageLayoutMode.single) && + keyPadHeight > 0) { + if (viewportDimension.height + keyPadHeight != + _viewportHeight) { + keyPadHeight = + _viewportHeight - viewportDimension.height; + } else { + _viewportHeight = + viewportDimension.height + keyPadHeight; + } + } + viewportDimension = Size(viewportDimension.width, viewportDimension.height + keyPadHeight); + } else { + if (_iskeypadClosed) { + viewportDimension = + Size(viewportDimension.width, _viewportHeight); + _iskeypadClosed = false; + } else { + _viewportHeight = viewportDimension.height; + } + } + if (!isBookmarkViewOpen) { + _otherContextHeight ??= + MediaQuery.of(context).size.height - + _viewportConstraints.maxHeight; + } + if (_deviceOrientation == Orientation.landscape) { + _viewportHeightInLandscape ??= + MediaQuery.of(context).size.height - + _otherContextHeight!; } if (!_pdfDimension.isEmpty) { - _maxScrollExtent = _pdfDimension.height - - (viewportDimension.height / - _pdfViewerController.zoomLevel); + if (_scrollDirection == PdfScrollDirection.vertical) { + _maxScrollExtent = _pdfDimension.height - + (viewportDimension.height / + _pdfViewerController.zoomLevel); + } else { + _maxScrollExtent = _pdfDimension.width - + (viewportDimension.width / + _pdfViewerController.zoomLevel); + } } - final Widget child = Column( - key: _columnKey, - children: List.generate( - _pdfViewerController.pageCount, (int index) { - if (index == 0) { - totalHeight = 0; - } - if (_originalWidth!.length != - _pdfViewerController.pageCount) { - return emptyContainer; - } - final int pageIndex = index + 1; - final Size calculatedSize = _calculateSize( - BoxConstraints( - maxWidth: _viewportConstraints.maxWidth, - maxHeight: double.infinity), - _originalWidth![index], - _originalHeight![index], - _viewportConstraints.maxWidth); - if (!_pdfPagesKey.containsKey(pageIndex)) { - _pdfPagesKey[pageIndex] = GlobalKey(); - } - _isOverflowed = _originalWidth![index] > + Widget child; + final List children = List.generate( + _pdfViewerController.pageCount, (int index) { + if (index == 0) { + totalHeight = 0; + } + if (_originalWidth!.length != + _pdfViewerController.pageCount) { + return emptyContainer; + } + final int pageIndex = index + 1; + final Size calculatedSize = _calculateSize( + BoxConstraints( + maxWidth: _viewportConstraints.maxWidth, + maxHeight: double.infinity), + _originalWidth![index], + _originalHeight![index], + _viewportConstraints.maxWidth, + viewportDimension.height); + if (!_pdfPagesKey.containsKey(pageIndex)) { + _pdfPagesKey[pageIndex] = GlobalKey(); + } + _isOverflowed = _originalWidth![index] > + // ignore: avoid_as + _viewportConstraints.maxWidth as bool; + if (kIsDesktop && !_isMobile) { + if (_originalWidth![index] > _maxPdfPageWidth != + null) { + _maxPdfPageWidth = // ignore: avoid_as - _viewportConstraints.maxWidth as bool; - if (kIsDesktop && !_isMobile) { - if (_originalWidth![index] > _maxPdfPageWidth != - null) { - _maxPdfPageWidth = - // ignore: avoid_as - _originalWidth![index] as double; - } - } - Rect? viewportGlobalRect; - if (_isTextSelectionCleared) { - viewportGlobalRect = _getViewportGlobalRect(); - } - final PdfPageView page = PdfPageView( - _pdfPagesKey[pageIndex]!, - _pdfImages[pageIndex], - viewportGlobalRect, - viewportDimension, - widget.interactionMode, - (kIsDesktop && !_isMobile && !_isOverflowed) - ? _originalWidth![index] - : calculatedSize.width, - (kIsDesktop && !_isMobile && !_isOverflowed) - ? _originalHeight![index] - : calculatedSize.height, - widget.pageSpacing, - _document, - _pdfPages, - index, - _pdfViewerController, - widget.enableDocumentLinkAnnotation, - widget.enableTextSelection, - widget.onTextSelectionChanged, - _handleTextSelectionDragStarted, - _handleTextSelectionDragEnded, - _handleDocumentLinkNavigationInvoked, - widget.searchTextHighlightColor, - _textCollection, - _isMobile, - _pdfViewerController._pdfTextSearchResult, - _pdfScrollableState, - ); - if (kIsDesktop && !_isMobile && !_isOverflowed) { - _pdfPages[pageIndex] = PdfPageInfo( - totalHeight, - Size(_originalWidth![index], - _originalHeight![index])); + _originalWidth![index] as double; + } + } + if (_pdfImages[pageIndex] != null) { + if (_pageTextExtractor.isEmpty || + !_pageTextExtractor.containsKey(index)) { + _pageTextExtractor[index] = _pdfTextExtractor! + .extractText(startPageIndex: index); + } + } + Rect? viewportGlobalRect; + if (_isTextSelectionCleared) { + viewportGlobalRect = _getViewportGlobalRect(); + } + final PdfPageView page = PdfPageView( + _pdfPagesKey[pageIndex]!, + _pdfImages[pageIndex], + viewportGlobalRect, + viewportDimension, + widget.interactionMode, + (kIsDesktop && + !_isMobile && + !_isOverflowed && + widget.pageLayoutMode == + PdfPageLayoutMode.continuous) + ? _originalWidth![index] + : calculatedSize.width, + (kIsDesktop && + !_isMobile && + !_isOverflowed && + widget.pageLayoutMode == + PdfPageLayoutMode.continuous) + ? _originalHeight![index] + : calculatedSize.height, + widget.pageSpacing, + _document, + _pdfPages, + index, + _pdfViewerController, + widget.enableDocumentLinkAnnotation, + widget.enableTextSelection, + widget.onTextSelectionChanged, + _handleTextSelectionDragStarted, + _handleTextSelectionDragEnded, + widget.searchTextHighlightColor, + _textCollection, + _isMobile, + _pdfViewerController._pdfTextSearchResult, + _pdfScrollableStateKey, + _singlePageViewKey, + _scrollDirection, + _handlePdfPagePointerDown, + _handlePdfPagePointerMove, + _handlePdfPagePointerUp, + isBookmarkViewOpen ? '' : _pageTextExtractor[index], + widget.pageLayoutMode == PdfPageLayoutMode.single); + final double pageSpacing = + index == _pdfViewerController.pageCount - 1 + ? 0.0 + : widget.pageSpacing; + if (kIsDesktop && !_isMobile && !_isOverflowed) { + _pdfPages[pageIndex] = PdfPageInfo( + totalHeight, + Size(_originalWidth![index], + _originalHeight![index])); + if (_scrollDirection == PdfScrollDirection.vertical && + widget.pageLayoutMode != + PdfPageLayoutMode.single) { + totalHeight += + _originalHeight![index] + pageSpacing; + } else { + if (widget.pageLayoutMode == + PdfPageLayoutMode.continuous) { totalHeight += - _originalHeight![index] + widget.pageSpacing; - _updateOffsetOnOrientationChange( - _offsetBeforeOrientationChange, - pageIndex, - totalHeight); + _originalWidth![index] + pageSpacing; } else { _pdfPages[pageIndex] = PdfPageInfo(totalHeight, calculatedSize); totalHeight += - calculatedSize.height + widget.pageSpacing; - _updateOffsetOnOrientationChange( - _offsetBeforeOrientationChange, - pageIndex, - totalHeight); + calculatedSize.height + pageSpacing; } - if (_pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox != - null && - !_isTextSelectionCleared) { - _isTextSelectionCleared = true; - Future.delayed(Duration.zero, () async { - _clearSelection(); - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox - ?.disposeMouseSelection(); - _pdfPagesKey[_pdfViewerController.pageNumber] + } + } else { + _pdfPages[pageIndex] = + PdfPageInfo(totalHeight, calculatedSize); + if (_scrollDirection == PdfScrollDirection.vertical && + widget.pageLayoutMode != + PdfPageLayoutMode.single) { + totalHeight += calculatedSize.height + pageSpacing; + } else { + totalHeight += calculatedSize.width + pageSpacing; + } + } + _updateScrollDirectionChange( + _offsetBeforeOrientationChange, + pageIndex, + totalHeight); + _updateOffsetOnOrientationChange( + _offsetBeforeOrientationChange, + pageIndex, + totalHeight); + if (_pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState - ?.focusNode - .requestFocus(); - }); + ?.canvasRenderBox != + null && + !_isTextSelectionCleared) { + _isTextSelectionCleared = true; + Future.delayed(Duration.zero, () async { + _clearSelection(); + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.disposeMouseSelection(); + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); + }); + } + if (page.imageStream != null) { + _renderedImages.add(pageIndex); + } + return page; + }); + Widget? pdfContainer; + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + _pageController = PageController( + initialPage: _pdfViewerController.pageNumber - 1); + pdfContainer = MouseRegion( + cursor: _cursor, + onHover: (PointerHoverEvent details) { + setState(() { + if (widget.interactionMode == + PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grab; + } else { + _cursor = SystemMouseCursors.basic; + } + }); + }, + child: SinglePageView( + _singlePageViewKey, + _pdfViewerController, + _pageController, + _handleSinglePageViewPageChanged, + _interactionUpdate, + viewportDimension, + widget.canShowPaginationDialog, + widget.canShowScrollHead, + widget.canShowScrollStatus, + _pdfPages, + _isMobile, + widget.enableDoubleTapZooming, + widget.interactionMode, + _isScaleEnabled, + _handleSinglePageViewZoomLevelChanged, + _handleDoubleTap, + _handlePdfOffsetChanged, + isBookmarkViewOpen, + children), + ); + if (_isSinglePageViewPageChanged && + _renderedImages + .contains(_pdfViewerController.pageNumber)) { + Future.delayed(Duration.zero, () async { + if (_pageController.hasClients) { + _pdfViewerController._scrollPositionX = + _pageController.offset; } - return page; - })); - final Widget pdfScrollable = PdfScrollable( - widget.canShowPaginationDialog, - widget.canShowScrollStatus, - widget.canShowScrollHead, - _pdfViewerController, - _isMobile, - _pdfDimension, - viewportDimension, - _handlePdfOffsetChanged, - _panEnabled, - _maxScale, - _minScale, - widget.enableDoubleTapZooming, - widget.interactionMode, - _maxPdfPageWidth, - _isScaleEnabled, - _maxScrollExtent, - _pdfPages, - child, - key: _pdfScrollableState, - onDoubleTap: _handleDoubleTap, - ); - + if (!_isSearchStarted) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); + } + if (getSelectedTextLines().isNotEmpty && + getSelectedTextLines().first.pageNumber + 1 == + _pdfViewerController.pageNumber) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.updateContextMenuPosition(); + } + _isSinglePageViewPageChanged = false; + }); + } + } else { + final Size childSize = _getChildSize(viewportDimension); + if (_scrollDirection == PdfScrollDirection.horizontal) { + child = Row( + key: _childKey, + mainAxisAlignment: MainAxisAlignment.center, + children: children); + } else { + child = Column( + key: _childKey, + mainAxisAlignment: MainAxisAlignment.center, + children: children); + } + child = MouseRegion( + cursor: _cursor, + onHover: (PointerHoverEvent details) { + setState(() { + if (widget.interactionMode == + PdfInteractionMode.pan) { + _cursor = SystemMouseCursors.grab; + } else { + _cursor = SystemMouseCursors.basic; + } + }); + }, + child: Container( + height: childSize.height, + width: childSize.width, + child: child), + ); + pdfContainer = PdfScrollable( + widget.canShowPaginationDialog, + widget.canShowScrollStatus, + widget.canShowScrollHead, + _pdfViewerController, + _isMobile, + _pdfDimension, + _totalImageSize, + viewportDimension, + _handlePdfOffsetChanged, + _panEnabled, + _maxScale, + _minScale, + widget.enableDoubleTapZooming, + widget.interactionMode, + _maxPdfPageWidth, + _isScaleEnabled, + _maxScrollExtent, + _pdfPages, + _scrollDirection, + isBookmarkViewOpen, + child, + key: _pdfScrollableStateKey, + onDoubleTap: _handleDoubleTap, + ); + // Updates current offset when scrollDirection change occurs. + if (_isScrollDirectionChange) { + _pdfScrollableStateKey.currentState + ?.forcePixels(_scrollDirectionSwitchOffset); + _isScrollDirectionChange = false; + } + } return Stack( children: [ - pdfScrollable, - BookmarkView( - _bookmarkKey, _document, _pdfViewerController), + pdfContainer, + BookmarkView(_bookmarkKey, _document, + _pdfViewerController, _handleBookmarkViewChanged), ], ); } else if (snapshot.hasError) { @@ -1196,16 +1564,126 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { : (_hasError ? emptyContainer : emptyLinearProgressView); } + void _handleSinglePageViewPageChanged(int newPage) { + _pdfViewerController._pageNumber = newPage + 1; + _pdfViewerController._zoomLevel = 1.0; + if (_singlePageViewKey.currentState != null) { + _singlePageViewKey.currentState!.previousZoomLevel = 1; + _pdfViewerController.notifyPropertyChangedListeners( + property: 'zoomLevel'); + } + _previousHorizontalOffset = 0.0; + _pageChanged(); + _checkMount(); + _isSinglePageViewPageChanged = true; + if (widget.onTextSelectionChanged != null) { + widget + .onTextSelectionChanged!(PdfTextSelectionChangedDetails(null, null)); + } + } + + void _interactionUpdate(double zoomLevel) { + _pdfViewerController._zoomLevel = zoomLevel; + } + + void _handleSinglePageViewZoomLevelChanged(double zoomLevel) { + if (_singlePageViewKey.currentState != null) { + final double previousScale = + _singlePageViewKey.currentState!.previousZoomLevel; + if (previousScale != _pdfViewerController._zoomLevel) { + _pdfViewerController.notifyPropertyChangedListeners( + property: 'zoomLevel'); + } + } + } + + Size _getChildSize(Size viewportDimension) { + double widthFactor = 1.0, heightFactor = 1.0; + double childHeight = 0, childWidth = 0; + + if (_pdfScrollableStateKey.currentState != null) { + widthFactor = _pdfScrollableStateKey.currentState!.paddingWidthScale == 0 + ? _pdfViewerController.zoomLevel + : _pdfScrollableStateKey.currentState!.paddingWidthScale; + heightFactor = + _pdfScrollableStateKey.currentState!.paddingHeightScale == 0 + ? _pdfViewerController.zoomLevel + : _pdfScrollableStateKey.currentState!.paddingHeightScale; + } + if (_pdfPages[_pdfViewerController.pageCount] != null) { + final PdfPageInfo lastPageInfo = + _pdfPages[_pdfViewerController.pageCount]!; + final double zoomLevel = _pdfViewerController.zoomLevel; + final Size currentPageSize = + _pdfPages[_pdfViewerController.pageNumber]!.pageSize; + double totalImageWidth = + (lastPageInfo.pageOffset + lastPageInfo.pageSize.width) * zoomLevel; + if (_scrollDirection == PdfScrollDirection.vertical) { + totalImageWidth = currentPageSize.width * zoomLevel; + } + childWidth = viewportDimension.width > totalImageWidth + ? viewportDimension.width / widthFactor.clamp(1, 3) + : totalImageWidth / widthFactor.clamp(1, 3); + + double totalImageHeight = currentPageSize.height * zoomLevel; + if (_scrollDirection == PdfScrollDirection.vertical) { + totalImageHeight = + (lastPageInfo.pageOffset + lastPageInfo.pageSize.height) * + zoomLevel; + } + childHeight = viewportDimension.height > totalImageHeight + ? viewportDimension.height / heightFactor.clamp(1, 3) + : totalImageHeight / heightFactor.clamp(1, 3); + _totalImageSize = + Size(totalImageWidth / zoomLevel, totalImageHeight / zoomLevel); + if (_isMobile && + !_isKeyPadRaised && + childHeight > _viewportConstraints.maxHeight && + (totalImageHeight / zoomLevel).floor() <= + _viewportConstraints.maxHeight.floor()) { + childHeight = _viewportConstraints.maxHeight; + } + if (_isMobile && + childWidth > _viewportConstraints.maxWidth && + totalImageWidth / zoomLevel <= _viewportConstraints.maxWidth) { + childWidth = _viewportConstraints.maxWidth; + } + } + return Size(childWidth, childHeight); + } + + void _handlePdfPagePointerDown(PointerDownEvent details) { + _isPdfPageTapped = true; + } + + void _handlePdfPagePointerMove(PointerMoveEvent details) { + if (details.kind == PointerDeviceKind.touch && kIsDesktop && !_isMobile) { + setState(() { + _isScaleEnabled = true; + }); + } + } + + void _handlePdfPagePointerUp(PointerUpEvent details) { + if (details.kind == PointerDeviceKind.touch && kIsDesktop && !_isMobile) { + setState(() { + _isScaleEnabled = false; + }); + } + } + void _handlePointerSignal(PointerSignalEvent event) { if (!isBookmarkViewOpen) { - _pdfScrollableState.currentState?.receivedPointerSignal(event); + _pdfScrollableStateKey.currentState?.receivedPointerSignal(event); } } void _handlePointerDown(PointerDownEvent event) { - if (widget.interactionMode == PdfInteractionMode.pan) { - _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.cursor = - SystemMouseCursors.grabbing; + if (!_isPdfPageTapped) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.clearSelection(); } _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState @@ -1215,35 +1693,14 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { void _handlePointerMove(PointerMoveEvent event) { if (widget.interactionMode == PdfInteractionMode.pan) { - _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.cursor = - SystemMouseCursors.grabbing; + _cursor = SystemMouseCursors.grabbing; } - _pdfScrollableState.currentState!.receivedPointerMoveSignal(event); - if (event.kind == PointerDeviceKind.touch) { - final Rect pdfPageRect = _getPageViewportRect(); - if (pdfPageRect.contains(event.localPosition)) { - setState(() { - _isScaleEnabled = true; - }); - final TextSelectionHelper? details = - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox - ?.getSelectionDetails(); - if (details != null && !details.selectionEnabled) { - _pdfScrollableState.currentState!.jumpTo( - xOffset: _pdfScrollableState.currentState!.currentOffset.dx - - (event.localDelta.dx * 2)); - } - } + if (!_isScaleEnabled && + event.kind == PointerDeviceKind.touch && + (!kIsDesktop || _isMobile)) { setState(() { - _panEnabled = true; + _isScaleEnabled = true; }); - } else if (event.kind == PointerDeviceKind.mouse) { - setState(() { - _panEnabled = widget.interactionMode == PdfInteractionMode.pan; - }); - return; } _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState @@ -1252,29 +1709,18 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } void _handlePointerUp(PointerUpEvent details) { + _isPdfPageTapped = false; if (widget.interactionMode == PdfInteractionMode.pan) { - _pdfPagesKey[_pdfViewerController.pageNumber]?.currentState?.cursor = - SystemMouseCursors.grab; - } - if (!_getPageViewportRect().contains(details.localPosition) && - details.kind == PointerDeviceKind.mouse) { - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.canvasRenderBox - ?.clearSelection(); + _cursor = SystemMouseCursors.grab; } _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox ?.scrollEnded(); - if (kIsDesktop) { - setState(() { - _isScaleEnabled = false; - }); - } } void _handleDoubleTap() { + _checkMount(); if (!kIsDesktop || _isMobile) { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState @@ -1283,16 +1729,10 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } - Rect _getPageViewportRect() { - return Rect.fromLTWH( - (_viewportConstraints.biggest.width - - (_pdfPages[_pdfViewerController.pageNumber]!.pageSize.width * - _pdfViewerController.zoomLevel)) / - 2, - 0, - _pdfPages[_pdfViewerController.pageNumber]!.pageSize.width * - _pdfViewerController.zoomLevel, - _viewportConstraints.biggest.height); + void _handleBookmarkViewChanged(bool hasBookmark) { + if (!kIsWeb || (kIsWeb && _isMobile)) { + _checkMount(); + } } /// Displays the built-in bookmark view. @@ -1380,44 +1820,95 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { return selectedTextLines ?? []; } + int _findStartOrEndPage(int pageIndex, bool isLastPage) { + double pageSize = 0.0; + for (int start = isLastPage + ? _pdfViewerController.pageCount + : _pdfViewerController.pageNumber; + isLastPage ? start >= 1 : start <= _pdfViewerController.pageCount; + isLastPage ? start-- : start++) { + pageSize += _scrollDirection == PdfScrollDirection.vertical + ? _pdfPages[start]!.pageSize.height + : _pdfPages[start]!.pageSize.width; + if ((!isLastPage && start == _pdfViewerController.pageCount) || + (isLastPage && start == 1)) { + pageIndex = start; + break; + } else { + pageIndex = isLastPage ? start - 1 : start + 1; + } + final bool isPageIndexFound = + _scrollDirection == PdfScrollDirection.vertical + ? pageSize > _viewportConstraints.biggest.height + : pageSize > _viewportConstraints.biggest.width; + if (isPageIndexFound) { + break; + } + } + return pageIndex; + } + + /// Get the rendered pages from plugin. /// Get the rendered pages from plugin. Future>?>? _getImages() { - int startPage = _pdfViewerController.pageNumber; - int endPage = _pdfViewerController.pageCount == 1 ? 1 : 2; - Future>?>? renderedPages; - double pageHeight = 0; - if (_pdfPages.isNotEmpty && !_pdfDimension.isEmpty) { - for (int start = _pdfViewerController.pageNumber; - start <= _pdfViewerController.pageCount; - start++) { - if (start == _pdfViewerController.pageCount) { - endPage = start; - if (startPage == endPage && endPage != 1) { - startPage = startPage - 1; + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + Future>?>? renderedPages; + final int startPage = _pdfViewerController.pageNumber - 1 != 0 + ? _pdfViewerController.pageNumber - 1 + : _pdfViewerController.pageNumber; + final int endPage = + _pdfViewerController.pageNumber + 1 < _pdfViewerController.pageCount + ? _pdfViewerController.pageNumber + 1 + : _pdfViewerController.pageCount; + renderedPages = _plugin + .getSpecificPages(startPage, endPage) + .then((Map>? value) { + return value; + }); + if (!_renderedImages.contains(_pdfViewerController.pageNumber)) { + renderedPages.whenComplete(_checkMount); + } + return renderedPages; + } else { + int startPage = _pdfViewerController.pageNumber; + int endPage = 1; + Future>?>? renderedPages; + if (_pdfPages.isNotEmpty && !_pdfDimension.isEmpty) { + if (_pdfViewerController.pageCount == 1) { + endPage = _pdfViewerController.pageCount; + } else { + if (startPage == _pdfViewerController.pageCount) { + startPage = _findStartOrEndPage(startPage, true); + endPage = _pdfViewerController.pageCount; + } else { + endPage = _findStartOrEndPage(endPage, false); } - break; } - final double height = pageHeight + _pdfPages[start]!.pageSize.height; - if (height < _viewportConstraints.biggest.height) { - pageHeight += height; - } else { - pageHeight = 0; - if (kIsDesktop && !_isMobile) { - start = start + 1; + } + renderedPages = _plugin + .getSpecificPages(startPage, endPage) + .then((Map>? value) { + if (_pdfPages.isNotEmpty && !_pdfDimension.isEmpty) { + for (int i = startPage; i <= endPage; i++) { + if (!_renderedImages.contains(i)) { + _checkMount(); + break; + } } - endPage = start != _pdfViewerController.pageCount ? start + 1 : start; - break; } + return value; + }); + if ((_startPage != startPage && _endPage != endPage) || + (_bufferCount > 0 && _bufferCount <= (endPage - startPage) + 1)) { + renderedPages.whenComplete(_checkMount); + _startPage = startPage; + _endPage = endPage; + _bufferCount++; + } else { + _bufferCount = 0; } + return renderedPages; } - - renderedPages = _plugin - .getSpecificPages(startPage != 1 ? startPage - 1 : 1, endPage) - .then((Map>? value) { - return value; - }); - renderedPages.whenComplete(_checkMount); - return renderedPages; } // Checks whether the current Widget is mounted and then relayout the Widget. @@ -1442,7 +1933,6 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { _previousPageNumber = _pdfViewerController.pageNumber; _isPageChanged = true; } - _checkMount(); } void _updateSearchInstance({bool isNext = true}) { @@ -1466,25 +1956,63 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { Offset initialOffset, int pageIndex, double totalHeight) { if (_viewportWidth != _viewportConstraints.maxWidth && _deviceOrientation != MediaQuery.of(context).orientation) { + WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + _checkMount(); + }); if (pageIndex == 1 && !_viewportConstraints.biggest.isEmpty && - _pdfScrollableState.currentState != null) { + _pdfScrollableStateKey.currentState != null) { _offsetBeforeOrientationChange = Offset( - _pdfScrollableState.currentState!.currentOffset.dx / + _pdfScrollableStateKey.currentState!.currentOffset.dx / _pdfDimension.width, - _pdfScrollableState.currentState!.currentOffset.dy / + _pdfScrollableStateKey.currentState!.currentOffset.dy / _pdfDimension.height); + if (_pdfViewerController.pageCount == 1 && + _pdfScrollableStateKey.currentState != null) { + if (_viewportWidth != 0) { + final double targetOffset = initialOffset.dy * totalHeight; + WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.updateContextMenuPosition(); + _pdfScrollableStateKey.currentState?.forcePixels(Offset( + initialOffset.dx * _viewportConstraints.biggest.width, + targetOffset)); + }); + } + _viewportWidth = _viewportConstraints.maxWidth; + + /// Updates the orientation of device. + _deviceOrientation = MediaQuery.of(context).orientation; + } } else if (pageIndex == _pdfViewerController.pageCount) { if (_viewportWidth != 0) { - final double targetOffset = initialOffset.dy * totalHeight; + double targetOffset; + if (_scrollDirection == PdfScrollDirection.vertical && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + targetOffset = initialOffset.dy * totalHeight; + } else { + targetOffset = initialOffset.dx * totalHeight; + } WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { _pdfPagesKey[_pdfViewerController.pageNumber] ?.currentState ?.canvasRenderBox ?.updateContextMenuPosition(); - _pdfScrollableState.currentState?.forcePixels(Offset( - initialOffset.dx * _viewportConstraints.biggest.width, - targetOffset)); + if (_scrollDirection == PdfScrollDirection.vertical && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + _pdfScrollableStateKey.currentState?.forcePixels(Offset( + initialOffset.dx * _viewportConstraints.biggest.width, + targetOffset)); + } else { + _pdfScrollableStateKey.currentState?.forcePixels(Offset( + targetOffset, + initialOffset.dy * + _pdfPages[_pdfViewerController.pageNumber]! + .pageSize + .height)); + } }); } _viewportWidth = _viewportConstraints.maxWidth; @@ -1495,24 +2023,215 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } } + void _updateOffsetOnLayoutChange( + double zoomLevel, Offset scrollOffset, PdfPageLayoutMode oldLayoutMode) { + if (oldLayoutMode != widget.pageLayoutMode && + oldLayoutMode == PdfPageLayoutMode.single) { + _previousSinglePage = _pdfViewerController.pageNumber; + final double greyArea = + (_singlePageViewKey.currentState?.greyAreaSize ?? 0) / 2; + double heightPercentage = 1.0; + if (kIsDesktop && !_isMobile) { + heightPercentage = + _document!.pages[_pdfViewerController.pageNumber - 1].size.height / + _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height; + } + Offset singleOffset = + _singlePageViewKey.currentState?.currentOffset ?? Offset.zero; + singleOffset = Offset(singleOffset.dx * heightPercentage, + (singleOffset.dy - greyArea) * heightPercentage); + _layoutChangeOffset = singleOffset; + } else { + double xPosition = scrollOffset.dx; + double yPosition = scrollOffset.dy; + if (_pdfViewerController.pageNumber > 1 && + widget.scrollDirection == PdfScrollDirection.vertical) { + yPosition = scrollOffset.dy - + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + } + if (_pdfViewerController.pageNumber > 1 && + widget.scrollDirection == PdfScrollDirection.horizontal) { + xPosition = scrollOffset.dx - + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + } + Future.delayed(Duration.zero, () async { + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + _pdfViewerController.zoomLevel = 1.0; + } + _pdfViewerController.zoomLevel = zoomLevel; + double heightPercentage = 1.0; + if (kIsDesktop && !_isMobile) { + heightPercentage = _document! + .pages[_pdfViewerController.pageNumber - 1].size.height / + _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height; + } + if (widget.pageLayoutMode == PdfPageLayoutMode.single && + _singlePageViewKey.currentState != null) { + final double greyAreaHeight = + _singlePageViewKey.currentState!.greyAreaSize / 2; + if (_viewportConstraints.maxHeight > + _pdfPages[_pdfViewerController.pageNumber]!.pageSize.height * + _pdfViewerController.zoomLevel) { + _singlePageViewKey.currentState!.jumpOnZoomedDocument( + _pdfViewerController.pageNumber, + Offset(xPosition / heightPercentage, + yPosition / heightPercentage)); + } else { + _singlePageViewKey.currentState!.jumpOnZoomedDocument( + _pdfViewerController.pageNumber, + Offset(xPosition / heightPercentage, + (yPosition + greyAreaHeight) / heightPercentage)); + } + } + }); + } + } + + /// Whenever scroll direction is changed, PDF page is changed based on viewport + /// dimension so offset must be restored to avoid reading continuity loss. + void _updateScrollDirectionChange( + Offset initialOffset, int pageIndex, double totalHeight) { + if (_scrollDirection != _tempScrollDirection || + _pageLayoutMode != widget.pageLayoutMode) { + WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + _checkMount(); + }); + if (pageIndex == 1 && + !_viewportConstraints.biggest.isEmpty && + _pdfScrollableStateKey.currentState != null) { + _offsetBeforeOrientationChange = Offset( + _pdfScrollableStateKey.currentState!.currentOffset.dx / + _pdfDimension.width, + _pdfScrollableStateKey.currentState!.currentOffset.dy / + _pdfDimension.height); + } else if (pageIndex == _pdfViewerController.pageCount && + _pdfScrollableStateKey.currentState != null) { + if (_viewportWidth != 0) { + WidgetsBinding.instance + ?.addPostFrameCallback((Duration timeStamp) async { + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox + ?.updateContextMenuPosition(); + if (_pdfViewerController.zoomLevel <= 1) { + if (_scrollDirection == PdfScrollDirection.vertical && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + final dynamic pageOffset = + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + _scrollDirectionSwitchOffset = Offset(0, pageOffset); + } else { + final dynamic pageOffset = + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + _scrollDirectionSwitchOffset = Offset(pageOffset, 0); + } + } else if (_pdfScrollableStateKey.currentState != null) { + if (_scrollDirection == PdfScrollDirection.vertical && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + final dynamic pageOffset = + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + final dynamic calculatedOffsetY = pageOffset + + (initialOffset.dy * + _pdfPages[_pdfViewerController.pageNumber]! + .pageSize + .height); + final dynamic calculatedOffsetX = + (_pdfScrollableStateKey.currentState!.currentOffset.dx - + _pageOffsetBeforeScrollDirectionChange) * + (_pdfPages[_pdfViewerController.pageNumber]! + .pageSize + .width / + _pageSizeBeforeScrollDirectionChange.width); + + _scrollDirectionSwitchOffset = + Offset(calculatedOffsetX, calculatedOffsetY); + } else { + final dynamic pageOffset = + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + final dynamic calculatedOffsetX = pageOffset + + (initialOffset.dx * + _pdfPages[_pdfViewerController.pageNumber]! + .pageSize + .width); + final dynamic calculatedOffsetY = + (_pdfScrollableStateKey.currentState!.currentOffset.dy - + _pageOffsetBeforeScrollDirectionChange) / + (_pageSizeBeforeScrollDirectionChange.height / + _pdfPages[_pdfViewerController.pageNumber]! + .pageSize + .height); + + _scrollDirectionSwitchOffset = + Offset(calculatedOffsetX, calculatedOffsetY); + } + } + _isScrollDirectionChange = + true && _layoutChangeOffset == Offset.zero; + }); + } + _tempScrollDirection = _scrollDirection; + _pageLayoutMode = widget.pageLayoutMode; + } + } else if (widget.pageLayoutMode == PdfPageLayoutMode.continuous || + widget.pageLayoutMode != PdfPageLayoutMode.single) { + _pageOffsetBeforeScrollDirectionChange = + _pdfPages[_pdfViewerController.pageNumber]!.pageOffset; + _pageSizeBeforeScrollDirectionChange = + _pdfPages[_pdfViewerController.pageNumber]!.pageSize; + } + } + /// Calculates a size of PDF page image within the given constraints. Size _calculateSize(BoxConstraints constraints, double originalWidth, - double originalHeight, double newWidth) { - constraints = BoxConstraints.tightFor( - width: newWidth, - height: null, - ).enforce(constraints); + double originalHeight, double newWidth, double newHeight) { + if (_viewportConstraints.maxWidth > newHeight && + !kIsDesktop && + _scrollDirection == PdfScrollDirection.horizontal && + widget.pageLayoutMode != PdfPageLayoutMode.single) { + constraints = BoxConstraints.tightFor( + width: null, + height: _viewportHeightInLandscape, + ).enforce(constraints); + } else { + if (widget.pageLayoutMode == PdfPageLayoutMode.single && + (!_isMobile || _viewportConstraints.maxWidth > newHeight)) { + constraints = BoxConstraints.tightFor( + width: null, + height: newHeight, + ).enforce(constraints); + } else { + constraints = BoxConstraints.tightFor( + width: newWidth, + height: null, + ).enforce(constraints); + } + } // Maintained the aspect ratio while image is resized // based on original page's width and height. - return constraints.constrainSizeAndAttemptToPreserveAspectRatio( + Size newSize = constraints.constrainSizeAndAttemptToPreserveAspectRatio( Size(originalWidth, originalHeight)); + if ((widget.pageLayoutMode == PdfPageLayoutMode.single || + widget.scrollDirection == PdfScrollDirection.horizontal && + Orientation.portrait == MediaQuery.of(context).orientation) && + newSize.height > newHeight) { + BoxConstraints newConstraints = BoxConstraints( + maxWidth: _viewportConstraints.maxWidth, maxHeight: newHeight); + newConstraints = BoxConstraints.tightFor( + width: null, + height: newHeight, + ).enforce(newConstraints); + newSize = newConstraints.constrainSizeAndAttemptToPreserveAspectRatio( + Size(originalWidth, originalHeight)); + } + + return newSize; } /// Updates current page number when scrolling occurs. void _updateCurrentPageNumber({double currentOffset = 0}) { if (currentOffset > 0) { _pdfViewerController._pageNumber = - _pdfScrollableState.currentState?.getPageNumber(currentOffset) ?? 0; + _pdfScrollableStateKey.currentState?.getPageNumber(currentOffset) ?? + 0; } else { _pdfViewerController.pageCount > 0 ? _pdfViewerController._pageNumber = 1 @@ -1538,8 +2257,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { /// Jump to the desired page. void _jumpToPage(int pageNumber) { - _pdfScrollableState.currentState - ?.jumpTo(yOffset: _pdfPages[pageNumber]!.pageOffset); + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + if (_pageController.hasClients) { + _pageController.jumpToPage(pageNumber - 1); + } + } else if (_scrollDirection == PdfScrollDirection.horizontal) { + _pdfScrollableStateKey.currentState + ?.jumpTo(xOffset: _pdfPages[pageNumber]!.pageOffset); + } else { + _pdfScrollableStateKey.currentState + ?.jumpTo(yOffset: _pdfPages[pageNumber]!.pageOffset); + } } /// Jump to the bookmark location. @@ -1558,37 +2286,62 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { final int index = _document!.pages.indexOf(pdfPage) + 1; final Size revealedOffset = _pdfPages[index]!.pageSize; double yOffset = _pdfPages[index]!.pageOffset; + final bool isRotatedTo90or270 = + pdfPage.rotation == PdfPageRotateAngle.rotateAngle90 || + pdfPage.rotation == PdfPageRotateAngle.rotateAngle270; if (bookmark.namedDestination != null) { - heightPercentage = - bookmark.namedDestination!.destination!.page.size.height / - revealedOffset.height; - widthPercentage = - bookmark.namedDestination!.destination!.page.size.width / - revealedOffset.width; + heightPercentage = bookmark + .namedDestination!.destination!.page.size.height / + (isRotatedTo90or270 ? revealedOffset.width : revealedOffset.height); + widthPercentage = bookmark + .namedDestination!.destination!.page.size.width / + (isRotatedTo90or270 ? revealedOffset.height : revealedOffset.width); bookmarkOffset = bookmark.namedDestination!.destination!.location; } else { - heightPercentage = - bookmark.destination!.page.size.height / revealedOffset.height; - widthPercentage = - bookmark.destination!.page.size.width / revealedOffset.width; + heightPercentage = bookmark.destination!.page.size.height / + (isRotatedTo90or270 ? revealedOffset.width : revealedOffset.height); + widthPercentage = bookmark.destination!.page.size.width / + (isRotatedTo90or270 ? revealedOffset.height : revealedOffset.width); bookmarkOffset = bookmark.destination!.location; } - if (kIsDesktop && !_isMobile) { + if (_pdfPagesKey[_pdfViewerController.pageNumber]!.currentState != null) { + bookmarkOffset = _pdfPagesKey[_pdfViewerController.pageNumber]! + .currentState! + .canvasRenderBox! + .getRotatedOffset(bookmarkOffset, index - 1, pdfPage.rotation); + } + if (kIsDesktop && + !_isMobile && + widget.pageLayoutMode == PdfPageLayoutMode.continuous) { heightPercentage = 1.0; } yOffset = yOffset + (bookmarkOffset.dy / heightPercentage); - final double xOffset = bookmarkOffset.dx / widthPercentage; + double xOffset = bookmarkOffset.dx / widthPercentage; + if (_scrollDirection == PdfScrollDirection.horizontal) { + if (_pdfViewerController.zoomLevel == 1) { + xOffset = _pdfPages[index]!.pageOffset; + yOffset = bookmarkOffset.dy / heightPercentage; + } else { + xOffset = _pdfPages[index]!.pageOffset + + bookmarkOffset.dx / widthPercentage; + yOffset = bookmarkOffset.dy / heightPercentage; + } + } if (yOffset > _maxScrollExtent) { yOffset = _maxScrollExtent; } - _pdfScrollableState.currentState - ?.jumpTo(xOffset: xOffset, yOffset: yOffset); + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + xOffset = bookmarkOffset.dx / widthPercentage; + yOffset = bookmarkOffset.dy / heightPercentage; + _singlePageViewKey.currentState! + .jumpOnZoomedDocument(index, Offset(xOffset, yOffset)); + } else { + _pdfScrollableStateKey.currentState + ?.jumpTo(xOffset: xOffset, yOffset: yOffset); + } } } - /// Trigger when document Link navigation perform on zoomed document - void _handleDocumentLinkNavigationInvoked(double offset) {} - /// clears the text selection. bool _clearSelection() { return _pdfPagesKey[_pdfViewerController.pageNumber] @@ -1598,6 +2351,19 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { false; } + int _getPageIndex(double offset) { + int pageIndex = 1; + for (int index = 1; index <= _pdfViewerController.pageCount; index++) { + final double pageStartOffset = _pdfPages[index]!.pageOffset; + final double pageEndOffset = + _pdfPages[index]!.pageOffset + _pdfPages[index]!.pageSize.width; + if (offset >= pageStartOffset && offset < pageEndOffset) { + pageIndex = index; + } + } + return pageIndex; + } + /// Call the method according to property name. void _handleControllerValueChange({String? property}) { if (property == 'jumpToBookmark') { @@ -1610,38 +2376,64 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } else if (_pdfViewerController.zoomLevel < _minScale) { _pdfViewerController.zoomLevel = _minScale; } - if (_pdfScrollableState.currentState != null) { - setState(() { - _isScaleEnabled = true; - }); - final double previousScale = - _pdfScrollableState.currentState!.previousZoomLevel; - _pdfViewerController._zoomLevel = _pdfScrollableState.currentState! - .scaleTo(_pdfViewerController.zoomLevel); - if (widget.onZoomLevelChanged != null && - previousScale != _pdfViewerController._zoomLevel) { - final double oldZoomLevel = previousScale; - final double newZoomLevel = _pdfViewerController._zoomLevel; - if (newZoomLevel != oldZoomLevel) { - widget.onZoomLevelChanged!( - PdfZoomDetails(newZoomLevel, oldZoomLevel)); + if (widget.pageLayoutMode == PdfPageLayoutMode.continuous) { + if (_pdfScrollableStateKey.currentState != null) { + _pdfViewerController._zoomLevel = _pdfScrollableStateKey.currentState! + .scaleTo(_pdfViewerController.zoomLevel); + final double previousScale = + _pdfScrollableStateKey.currentState!.previousZoomLevel; + if (widget.onZoomLevelChanged != null && + previousScale != _pdfViewerController._zoomLevel) { + final double oldZoomLevel = previousScale; + final double newZoomLevel = _pdfViewerController._zoomLevel; + if (newZoomLevel != oldZoomLevel) { + widget.onZoomLevelChanged!( + PdfZoomDetails(newZoomLevel, oldZoomLevel)); + } } + PageStorage.of(context)?.writeState( + context, _pdfViewerController.zoomLevel, + identifier: 'zoomLevel_' + widget.key.toString()); } - PageStorage.of(context)?.writeState( - context, _pdfViewerController.zoomLevel, - identifier: 'zoomLevel_' + widget.key.toString()); - if (kIsDesktop) { - setState(() { - _isScaleEnabled = false; - }); + } else { + if (_singlePageViewKey.currentState != null) { + _pdfViewerController._zoomLevel = _singlePageViewKey.currentState! + .scaleTo(_pdfViewerController.zoomLevel); + if (!_singlePageViewKey.currentState!.isJumpOnZoomedDocument) { + final double previousScale = + _singlePageViewKey.currentState!.previousZoomLevel; + if (widget.onZoomLevelChanged != null && + previousScale != _pdfViewerController._zoomLevel) { + final double oldZoomLevel = previousScale; + final double newZoomLevel = _pdfViewerController._zoomLevel; + if (newZoomLevel != oldZoomLevel) { + widget.onZoomLevelChanged!( + PdfZoomDetails(newZoomLevel, oldZoomLevel)); + } + } + } + PageStorage.of(context)?.writeState( + context, _pdfViewerController.zoomLevel, + identifier: 'zoomLevel_' + widget.key.toString()); } } } else if (property == 'clearTextSelection') { _pdfViewerController._clearTextSelection = _clearSelection(); } else if (property == 'jumpTo') { _clearSelection(); - if (!_pdfDimension.isEmpty) { - _pdfScrollableState.currentState?.jumpTo( + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + if (_previousHorizontalOffset != + _pdfViewerController._horizontalOffset && + _pageController.hasClients) { + _jumpToPage(_getPageIndex(_pdfViewerController._horizontalOffset)); + _previousHorizontalOffset = _pdfViewerController._horizontalOffset; + } + if (_singlePageViewKey.currentState != null) { + _singlePageViewKey.currentState! + .jumpTo(yOffset: _pdfViewerController._verticalOffset); + } + } else if (!_pdfDimension.isEmpty) { + _pdfScrollableStateKey.currentState?.jumpTo( xOffset: _pdfViewerController._horizontalOffset, yOffset: _pdfViewerController._verticalOffset); } @@ -1694,13 +2486,17 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { } else { _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = _getInstanceInPage(_pdfViewerController.pageNumber); - _jumpToSearchInstance(); + if (_pdfPages.isNotEmpty && !_isSearchInitiated) { + _jumpToSearchInstance(); + } _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = _textCollection!.length; _pdfViewerController._pdfTextSearchResult._updateResult(true); + _isSearchInitiated = false; } _pdfViewerController._pdfTextSearchResult .addListener(_handleTextSearch); + setState(() {}); } } } @@ -1745,46 +2541,92 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { 1] .pageIndex + 1; + Offset topOffset = Offset.zero; - final Offset topOffset = _textCollection![ - _pdfViewerController._pdfTextSearchResult.currentInstanceIndex - 1] - .bounds - .topLeft; - - final double heightPercentage = (kIsDesktop && !_isMobile && !_isOverflowed) + if (_pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.canvasRenderBox != + null) { + topOffset = _pdfPagesKey[_pdfViewerController.pageNumber]! + .currentState! + .canvasRenderBox! + .getRotatedTextBounds( + _textCollection![_pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1] + .bounds, + currentInstancePageIndex - 1, + _document!.pages[currentInstancePageIndex - 1].rotation) + .topLeft; + } + final double heightPercentage = (kIsDesktop && + !_isMobile && + !_isOverflowed && + widget.pageLayoutMode == PdfPageLayoutMode.continuous) ? 1 : _originalHeight![currentInstancePageIndex - 1] / // ignore: avoid_as _pdfPages[currentInstancePageIndex]!.pageSize.height as double; - final double widthPercentage = (kIsDesktop && !_isMobile && !_isOverflowed) + final double widthPercentage = (kIsDesktop && + !_isMobile && + !_isOverflowed && + widget.pageLayoutMode == PdfPageLayoutMode.continuous) ? 1 : _originalWidth![currentInstancePageIndex - 1] / // ignore: avoid_as _pdfPages[currentInstancePageIndex]!.pageSize.width as double; - final double searchOffsetX = topOffset.dx / widthPercentage; + double searchOffsetX = topOffset.dx / widthPercentage; - final double searchOffsetY = - _pdfPages[currentInstancePageIndex]!.pageOffset + - (topOffset.dy / heightPercentage); + double searchOffsetY = _pdfPages[currentInstancePageIndex]!.pageOffset + + (topOffset.dy / heightPercentage); + if (_scrollDirection == PdfScrollDirection.horizontal) { + searchOffsetX = _pdfPages[currentInstancePageIndex]!.pageOffset + + topOffset.dx / widthPercentage; + searchOffsetY = topOffset.dy / heightPercentage; + } final Offset offset = - _pdfScrollableState.currentState?.currentOffset ?? Offset.zero; + _pdfScrollableStateKey.currentState?.currentOffset ?? Offset.zero; final Rect viewport = Rect.fromLTWH( offset.dx, offset.dy, _viewportConstraints.biggest.width / _pdfViewerController.zoomLevel, _viewportConstraints.biggest.height / _pdfViewerController.zoomLevel); - - if (_pdfScrollableState.currentState != null && - !viewport.contains(Offset(searchOffsetX, searchOffsetY))) { - _pdfViewerController.jumpTo( - xOffset: searchOffsetX, yOffset: searchOffsetY); + final Offset singleLayoutOffset = + _singlePageViewKey.currentState?.currentOffset ?? Offset.zero; + final Rect singleLayoutViewport = Rect.fromLTWH( + singleLayoutOffset.dx, + singleLayoutOffset.dy, + _viewportConstraints.biggest.width / _pdfViewerController.zoomLevel, + _viewportConstraints.biggest.height / _pdfViewerController.zoomLevel); + if (widget.pageLayoutMode == PdfPageLayoutMode.single) { + if (!singleLayoutViewport.contains(Offset( + topOffset.dx / widthPercentage, + (topOffset.dy / heightPercentage) + + _singlePageViewKey.currentState!.greyAreaSize)) || + currentInstancePageIndex != _pdfViewerController.pageNumber) { + _singlePageViewKey.currentState!.jumpOnZoomedDocument( + currentInstancePageIndex, + Offset(topOffset.dx / widthPercentage, + topOffset.dy / heightPercentage)); + } WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { if (_isPageChanged) { _isPageChanged = false; } }); + } else { + if (_pdfScrollableStateKey.currentState != null && + !viewport.contains(Offset(searchOffsetX, searchOffsetY))) { + _pdfViewerController.jumpTo( + xOffset: searchOffsetX, yOffset: searchOffsetY); + WidgetsBinding.instance?.addPostFrameCallback((Duration timeStamp) { + if (_isPageChanged) { + _isPageChanged = false; + } + }); + } } } @@ -1792,31 +2634,39 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { void _handleTextSearch({String? property}) { if (_pdfViewerController._pdfTextSearchResult.hasResult) { if (property == 'nextInstance') { - _pdfViewerController - ._pdfTextSearchResult._currentOccurrenceIndex = _pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex < - _pdfViewerController._pdfTextSearchResult._totalInstanceCount - ? _pdfViewerController._pdfTextSearchResult.currentInstanceIndex + 1 - : 1; - _jumpToSearchInstance(isNext: true); + setState(() { + _pdfViewerController._pdfTextSearchResult + ._currentOccurrenceIndex = _pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex < + _pdfViewerController._pdfTextSearchResult._totalInstanceCount + ? _pdfViewerController._pdfTextSearchResult.currentInstanceIndex + + 1 + : 1; + _jumpToSearchInstance(isNext: true); + }); } else if (property == 'previousInstance') { - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = - _pdfViewerController._pdfTextSearchResult.currentInstanceIndex > 1 - ? _pdfViewerController - ._pdfTextSearchResult.currentInstanceIndex - - 1 - : _pdfViewerController._pdfTextSearchResult.totalInstanceCount; - _jumpToSearchInstance(isNext: false); + setState(() { + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = + _pdfViewerController._pdfTextSearchResult.currentInstanceIndex > 1 + ? _pdfViewerController + ._pdfTextSearchResult.currentInstanceIndex - + 1 + : _pdfViewerController + ._pdfTextSearchResult.totalInstanceCount; + _jumpToSearchInstance(isNext: false); + }); } else if (property == 'clear') { - _isSearchStarted = false; - _textCollection = null; - _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; - _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; - _pdfViewerController._pdfTextSearchResult._updateResult(false); - _pdfPagesKey[_pdfViewerController.pageNumber] - ?.currentState - ?.focusNode - .requestFocus(); + setState(() { + _isSearchStarted = false; + _textCollection = null; + _pdfViewerController._pdfTextSearchResult._currentOccurrenceIndex = 0; + _pdfViewerController._pdfTextSearchResult._totalSearchTextCount = 0; + _pdfViewerController._pdfTextSearchResult._updateResult(false); + _pdfPagesKey[_pdfViewerController.pageNumber] + ?.currentState + ?.focusNode + .requestFocus(); + }); return; } } @@ -1829,8 +2679,19 @@ class SfPdfViewerState extends State with WidgetsBindingObserver { ?.focusNode .requestFocus(); } - _updateCurrentPageNumber(currentOffset: offset.dy); - _pdfViewerController._scrollPositionX = offset.dx.abs(); + if (widget.pageLayoutMode == PdfPageLayoutMode.continuous) { + if (_scrollDirection == PdfScrollDirection.horizontal) { + _updateCurrentPageNumber(currentOffset: offset.dx); + } else { + _updateCurrentPageNumber(currentOffset: offset.dy); + } + } + if (widget.pageLayoutMode == PdfPageLayoutMode.single && + _pageController.hasClients) { + _pdfViewerController._scrollPositionX = _pageController.offset; + } else { + _pdfViewerController._scrollPositionX = offset.dx.abs(); + } _pdfViewerController._scrollPositionY = offset.dy.abs(); } } @@ -2695,6 +3556,7 @@ class PdfViewerController extends _ValueChangeNotifier { _totalPages = 0; _verticalOffset = 0.0; _horizontalOffset = 0.0; + _searchText = ''; _pageNavigator = null; _pdfBookmark = null; notifyPropertyChangedListeners(); diff --git a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml index 21c2558ce..3804f8c65 100644 --- a/packages/syncfusion_flutter_pdfviewer/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer/pubspec.yaml @@ -28,7 +28,7 @@ flutter: dependencies: flutter: sdk: flutter - vector_math: ^2.1.0 + vector_math: ">=2.1.0 <=3.0.0" async: ^2.5.0 http: ^0.13.0 diff --git a/packages/syncfusion_flutter_pdfviewer_macos/CHANGELOG.md b/packages/syncfusion_flutter_pdfviewer_macos/CHANGELOG.md index 43d0fcd40..163fe2baf 100644 --- a/packages/syncfusion_flutter_pdfviewer_macos/CHANGELOG.md +++ b/packages/syncfusion_flutter_pdfviewer_macos/CHANGELOG.md @@ -1,3 +1,3 @@ -## [unreleased] +## [19.2.44-beta] - 06/30/2021 * Initial release. diff --git a/packages/syncfusion_flutter_pdfviewer_macos/example/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer_macos/example/pubspec.yaml index 0005d0a67..5e5bc6353 100644 --- a/packages/syncfusion_flutter_pdfviewer_macos/example/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer_macos/example/pubspec.yaml @@ -13,17 +13,12 @@ dependencies: sdk: flutter syncfusion_flutter_pdfviewer: - git: - url: https://buildautomation:Coolcomp299@gitlab.syncfusion.com/essential-studio/flutter-pdfviewer - path: flutter_pdfviewer/syncfusion_flutter_pdfviewer - branch: development - ref: development + path: ../../syncfusion_flutter_pdfviewer # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.3 - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/syncfusion_flutter_pdfviewer_web/example/lib/generated_plugin_registrant.dart b/packages/syncfusion_flutter_pdfviewer_web/example/lib/generated_plugin_registrant.dart deleted file mode 100644 index 7a098ac12..000000000 --- a/packages/syncfusion_flutter_pdfviewer_web/example/lib/generated_plugin_registrant.dart +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// ignore_for_file: lines_longer_than_80_chars - -import 'package:syncfusion_flutter_pdfviewer_web/pdfviewer_web.dart'; - -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; - -// ignore: public_member_api_docs -void registerPlugins(Registrar registrar) { - SyncfusionFlutterPdfViewerPlugin.registerWith(registrar); - registrar.registerMessageHandler(); -} diff --git a/packages/syncfusion_flutter_pdfviewer_web/example/pubspec.yaml b/packages/syncfusion_flutter_pdfviewer_web/example/pubspec.yaml index 3fb48d033..84db527aa 100644 --- a/packages/syncfusion_flutter_pdfviewer_web/example/pubspec.yaml +++ b/packages/syncfusion_flutter_pdfviewer_web/example/pubspec.yaml @@ -12,17 +12,12 @@ dependencies: flutter: sdk: flutter syncfusion_flutter_pdfviewer: - git: - url: https://buildautomation:Coolcomp299@gitlab.syncfusion.com/essential-studio/flutter-pdfviewer - path: flutter_pdfviewer/syncfusion_flutter_pdfviewer - branch: development - ref: development + path: ../../syncfusion_flutter_pdfviewer # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.3 - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/syncfusion_flutter_signaturepad/README.md b/packages/syncfusion_flutter_signaturepad/README.md index a86a5ea6e..d8d07239c 100644 --- a/packages/syncfusion_flutter_signaturepad/README.md +++ b/packages/syncfusion_flutter_signaturepad/README.md @@ -108,88 +108,117 @@ Update elements such as stroke color, minimum stroke width, maximum stroke width } ``` -## Save the signature as image in android and iOS platforms - -You can save the signature drawn in the SignaturePad as an image using the toImage() method, as shown in the following code snippet in the Android and iOS platforms. Since this toImage() method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method. Optionally, the pixelRatio parameter may be used to set the pixel ratio of the image. The higher the pixel ratio value, the high-quality picture you get. The default value of the pixel ratio parameter is 1. +## Save the signature as image in mobile and desktop platforms +You can save the signature drawn in the SignaturePad as an image using the [`toImage()`](https://pub.dev/documentation/syncfusion_flutter_signaturepad/latest/signaturepad/SfSignaturePadState/toImage.html) method as shown in the below code example in Android, iOS and Desktop platforms. Since this [`toImage()`](https://pub.dev/documentation/syncfusion_flutter_signaturepad/latest/signaturepad/SfSignaturePadState/toImage.html) method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method. Optionally, the `pixelRatio` parameter may be used to set the pixel ratio of the image. The higher the pixel ratio value, the high-quality picture you get. The default value of the pixel ratio parameter is 1. ```dart @override - - Widget build(BuildContext context) { - - GlobalKey _signaturePadKey = GlobalKey(); - - return Scaffold( - body: Column( - children: [ - Container( - child: SfSignaturePad( - key:_signaturePadKey, - backgroundColor: Colors.grey[200], - ), - height: 200, - width: 300, +Widget build(BuildContext context) { + GlobalKey _signaturePadKey = GlobalKey(); + return Scaffold( + body: Column( + children: [ + Container( + child: SfSignaturePad( + key: _signaturePadKey, + backgroundColor: Colors.grey[200], ), - RaisedButton( - child: Text("Save As Image"), - onPressed: () async { - ui.Image image = - await_signaturePadKey.currentState.toImage(pixelRatio: 3); - }), - ], - ), - ); - } + height: 200, + width: 300, + ), + RaisedButton( + child: Text("Save As Image"), + onPressed: () async { + ui.Image image = + await _signaturePadKey.currentState!.toImage(); + }), + ], + ), + ); +} ``` -## Save the signature as an image in a web platform +## Save the signature as image in web (Desktop browser) -You can save the signature drawn in the SignaturePad as an image using the renderToContext2D() method as shown in the following code snippet. Since this renderToContext2D () method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method. +This is similar to the mobile and desktop platforms. You can save the signature drawn in the SignaturePad as an image using the [`toImage()`](https://pub.dev/documentation/syncfusion_flutter_signaturepad/latest/signaturepad/SfSignaturePadState/toImage.html) method as shown in the below code example in web platform (Desktop browser). Since this [`toImage()`](https://pub.dev/documentation/syncfusion_flutter_signaturepad/latest/signaturepad/SfSignaturePadState/toImage.html) method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method. Optionally, the `pixelRatio` parameter may be used to set the pixel ratio of the image. The higher the pixel ratio value, the high-quality picture you get. The default value of the pixel ratio parameter is 1. ```dart - @override +@override +Widget build(BuildContext context) { + GlobalKey _signaturePadKey = GlobalKey(); + return Scaffold( + body: Column( + children: [ + Container( + child: SfSignaturePad( + key: _signaturePadKey, + backgroundColor: Colors.grey[200], + ), + height: 200, + width: 300, + ), + RaisedButton( + child: Text("Save As Image"), + onPressed: () async { + ui.Image image = + await _signaturePadKey.currentState!.toImage(); + }), + ], + ), + ); +} +``` - Widget build(BuildContext context) { +## Save the signature as image in Web (Mobile browser) - GlobalKey _signaturePadKey = GlobalKey(); +You can save the signature drawn in the SignaturePad as an image using the [`renderToContext2D`](https://pub.dev/documentation/syncfusion_flutter_signaturepad/latest/signaturepad/SfSignaturePadState/renderToContext2D.html) method as show in the below code snippet. Since this [`renderToContext2D()`](https://pub.dev/documentation/syncfusion_flutter_signaturepad/latest/signaturepad/SfSignaturePadState/renderToContext2D.html) method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method. - return Scaffold( - body: Column( - children: [ - Container( - child: SfSignaturePad( - key: _signaturePadKey, - backgroundColor: Colors.grey[200], - ), - height: 200, - width: 300, +```dart +@override +Widget build(BuildContext context) { + GlobalKey _signaturePadKey = GlobalKey(); + return Scaffold( + body: Column( + children: [ + Container( + child: SfSignaturePad( + key: _signaturePadKey, + backgroundColor: Colors.grey[200], ), - RaisedButton( - child: Text("Save As Image"), - onPressed: () async { - - //Get the html canvas context + height: 200, + width: 300, + ), + RaisedButton( + child: Text("Save As Image"), + onPressed: () async { + + //Get a html canvas context. final canvas = html.CanvasElement(width: 500, height: 500); final context = canvas.context2D; - //Get the signature in the canvas context - _signaturePadKey.currentState.renderToContext2D(context); + //Get the signature in the canvas context. + _signaturePadKey.currentState!.renderToContext2D(context); - //Get the image from the canvas context + //Get the image from the canvas context final blob = await canvas.toBlob('image/jpeg', 1.0); + + //Save the image as Uint8List to use it in local device. final completer = Completer(); final reader = html.FileReader(); reader.readAsArrayBuffer(blob); reader.onLoad.listen((_) => completer.complete(reader.result)); Uint8List imageData = await completer.future; - }), - ], - ), - ); - } + }), + ], + ), + ); +} ``` + +N> Since Flutter uses two separate default web renderers, here we have two different code snippets to convert signatures to images in desktop and mobile browsers. Please refer to this Flutter [`web-renderers`](https://flutter.dev/docs/development/tools/web-renderers) page for more details. + ## Clear the existing signature in SignaturePad You can clear the signature drawn in the SignaturePad using the clear() method as show in the following code snippet. Since this clear() method is defined in the state object of SignaturePad, you have to use a global key assigned to the SignaturePad instance to call this method. @@ -232,4 +261,4 @@ You can clear the signature drawn in the SignaturePad using the clear() method a 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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml b/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml index 6261fe471..9bee629df 100644 --- a/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml +++ b/packages/syncfusion_flutter_signaturepad/example/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: syncfusion_flutter_signaturepad: path: ../ - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart b/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart index 4920b2467..853110bba 100644 --- a/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart +++ b/packages/syncfusion_flutter_signaturepad/lib/signaturepad.dart @@ -20,10 +20,11 @@ const double _kMaximumStrokeWidth = 5.0; const double _kDefaultHeight = 250.0; const double _kDefaultWidth = 250.0; -/// Signature used by [SfSignaturePad] for [OnDraw] callback. +/// Signature used by [SfSignaturePad] for [SfSignaturePad.onDraw] callback. typedef SignatureDrawCallback = void Function(Offset offset, DateTime time); -/// Signature used by [SfSignaturePad] for [OnDrawStart] callback. +/// Signature used by [SfSignaturePad] for [SfSignaturePad.onDrawStart] +/// callback. typedef SignatureOnDrawStartCallback = bool Function(); /// The Signature Pad widget allows you to capture smooth and more realistic @@ -45,20 +46,22 @@ typedef SignatureOnDrawStartCallback = bool Function(); /// * The background color of the [SfSignaturePad] can be customized using the /// [backgroundColor] property. /// -/// The [onDrawStart], [onDraw] and [onDrawEnd] allows you to handle the gestures -/// in [SfSignaturePad] +/// The [onDrawStart], [onDraw] and [onDrawEnd] allows you to handle the +/// gestures in [SfSignaturePad] /// -/// The [toImage] allows you to export the signature in [SfSignaturePad] to an -/// image. -/// The [clear] allows you to clear all the signature strokes in -/// [SfSignaturePad]. -/// The [renderToContext2D] allows you to export the signature in -/// [SfSignaturePad] to a html canvas. [renderToContext2D] is used to export the -/// signature as an image in web platform. +/// The [SfSignaturePadState.toImage] allows you to export the signature in +/// [SfSignaturePad] to an image. +/// The [SfSignaturePadState.clear] allows you to clear all the signature +/// strokes in [SfSignaturePad]. +/// The [SfSignaturePadState.renderToContext2D] allows you to export the +/// signature in [SfSignaturePad] to a html canvas. +/// [SfSignaturePadState.renderToContext2D] is used to export the signature as +/// an image in web platform. /// -/// Note - Since the [toImage], [clear] and [renderToContext2D] are defined in -/// state object of [SfSignaturePad], you have to use a global key assigned to -/// the [SfSignaturePad] instance to call these methods. +/// Note - Since the [SfSignaturePadState.toImage], [SfSignaturePadState.clear] +/// and [SfSignaturePadState.renderToContext2D] are defined in state object of +/// [SfSignaturePad], you have to use a global key assigned to the +/// [SfSignaturePad] instance to call these methods. /// /// ## Example /// @@ -78,8 +81,8 @@ typedef SignatureOnDrawStartCallback = bool Function(); /// key: _signaturePadKey, /// ); /// ``` -/// * Handle the start, ondraw and completion of signature gestures in [SfSignaturePad] -/// from [onDrawStart], [onDraw] and [onDrawEnd]. +/// * Handle the start, ondraw and completion of signature gestures in +/// [SfSignaturePad] from [onDrawStart], [onDraw] and [onDrawEnd]. /// ```dart /// SfSignaturePad( /// onDrawStart: () { @@ -93,13 +96,13 @@ typedef SignatureOnDrawStartCallback = bool Function(); /// print("Signature has been completed in Signature Pad"); /// }); /// ``` -/// * Call [clear] using state object to clear all the drawn strokes in the -/// [SfSignaturePad]. +/// * Call [SfSignaturePadState.clear] using state object to clear all the drawn +/// strokes in the [SfSignaturePad]. /// ```dart /// _signaturePadKey.currentState!.clear(); /// ``` -/// * Call [toImage] using state object to convert the signature to an image -/// representation. +/// * Call [SfSignaturePadState.toImage] using state object to convert the +/// signature to an image representation. /// ```dart /// ui.Image image = await _signaturePadKey.currentState!.toImage(); /// ``` @@ -123,14 +126,14 @@ class SfSignaturePad extends StatefulWidget { /// ); /// ``` /// - /// * Call [clear] using state object to clear all the drawn strokes in the - /// [SfSignaturePad]. + /// * Call [SfSignaturePadState.clear] using state object to clear all the + /// drawn strokes in the [SfSignaturePad]. /// ```dart /// _signaturePadKey.currentState.clear(); ///``` /// - /// * Call [toImage] using state object to convert the signature to an image - /// representation. + /// * Call [SfSignaturePadState.toImage] using state object to convert the + /// signature to an image representation. /// ```dart /// ui.Image image = await _signaturePadKey.currentState.toImage(); /// ``` @@ -300,7 +303,8 @@ class SfSignaturePad extends StatefulWidget { /// This class maintains the state of the [SfSignaturePad] widget. class SfSignaturePadState extends State { - /// Creates an unmodifiable ui.Path collection which represents the strokes in the [SfSignaturePad]. + /// Creates an unmodifiable ui.Path collection which represents the strokes in + /// the [SfSignaturePad]. /// /// Since this method is defined in the state object of [SfSignaturePad], /// you have to use a global key assigned to the [SfSignaturePad] to call this @@ -404,7 +408,8 @@ class SfSignaturePadState extends State { /// Since this method is defined in the state object of [SfSignaturePad], /// you have to use a global key assigned to the [SfSignaturePad] instance /// to call this method. - /// This snippet shows how to use [renderToContext2D] in [SfSignaturePadState]. + /// This snippet shows how to use [renderToContext2D] in + /// [SfSignaturePadState]. /// /// Note: It requires `dart:html` import. /// diff --git a/packages/syncfusion_flutter_sliders/CHANGELOG.md b/packages/syncfusion_flutter_sliders/CHANGELOG.md index 879885c6e..f6d1edc79 100644 --- a/packages/syncfusion_flutter_sliders/CHANGELOG.md +++ b/packages/syncfusion_flutter_sliders/CHANGELOG.md @@ -2,6 +2,17 @@ ## Slider +* Provides an option to change the minimum and maximum positions of the vertical slider. + +## Range Slider + +* Provides an option to change the minimum and maximum positions of the vertical range slider. +* Provides various dragging options to control thumb dragging. The available options are `onThumb`, `betweenThumbs`, and `both`. + +## [19.2.44-beta] - 06/29/2021 + +## Slider + ### Breaking changes The following `divisor` related properties were renamed into `divider` but the behavior of those properties are same as before. The APIs changes are, diff --git a/packages/syncfusion_flutter_sliders/README.md b/packages/syncfusion_flutter_sliders/README.md index 9c30c843e..fb06f6eeb 100644 --- a/packages/syncfusion_flutter_sliders/README.md +++ b/packages/syncfusion_flutter_sliders/README.md @@ -53,6 +53,8 @@ This library is used to create three different types of sliders, namely slider, * **Orientation** - Supports both horizontal and vertical orientations. ![slider orientation](https://cdn.syncfusion.com/content/images/Flutter/pub_images/slider_images/slider_orientation.png) +* **Inversed vertical slider** - Provides an option to change the minimum and maximum positions of the vertical slider. + ## Range slider features Range slider supports all the above-mentioned features of the slider in addition to: @@ -60,11 +62,15 @@ Range slider supports all the above-mentioned features of the slider in addition * **Orientation** - Supports both horizontal and vertical orientations. ![range slider orientation](https://cdn.syncfusion.com/content/images/Flutter/pub_images/range_slider_images/range_slider_orientation.png) +* **Inversed vertical range slider** - Provides an option to change the minimum and maximum positions of the vertical range slider. + +* **Drag mode** - Various dragging options are available to control thumb dragging. The available options are `onThumb`, `betweenThumbs`, and `both`. + * **Interval selection** - Allows users to select a particular interval by tapping or clicking in it. Both thumbs will be moved to the current interval with animation. ## Range selector features -Range selector supports all the above-mentioned features(except orientation) of the range slider in addition to: +Range selector supports all the above-mentioned features(except orientation and inversed slider) of the range slider in addition to: * **Child support** - Add a child of any type inside the range selector. It is also possible to add Charts. With the built-in integration, range selector is smart enough to handle features like segment selection or zooming of a chart based on the selected range in the range selector. Similar to the range slider, it also supports both numeric and date values. @@ -390,4 +396,4 @@ The following screenshot illustrates the result of the above code sample. 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_flutter_sliders/example/lib/main.dart b/packages/syncfusion_flutter_sliders/example/lib/main.dart index a9475d7a2..00d286c7f 100644 --- a/packages/syncfusion_flutter_sliders/example/lib/main.dart +++ b/packages/syncfusion_flutter_sliders/example/lib/main.dart @@ -1,8 +1,8 @@ -import 'package:syncfusion_flutter_sliders/sliders.dart'; -import 'package:syncfusion_flutter_charts/charts.dart' hide LabelPlacement; -import 'package:intl/intl.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:intl/intl.dart'; +import 'package:syncfusion_flutter_charts/charts.dart' hide LabelPlacement; +import 'package:syncfusion_flutter_sliders/sliders.dart'; void main() { return runApp(RangeSelectorApp()); diff --git a/packages/syncfusion_flutter_sliders/example/pubspec.yaml b/packages/syncfusion_flutter_sliders/example/pubspec.yaml index 2fc34ac85..b546ee4f9 100644 --- a/packages/syncfusion_flutter_sliders/example/pubspec.yaml +++ b/packages/syncfusion_flutter_sliders/example/pubspec.yaml @@ -28,7 +28,6 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 - # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/packages/syncfusion_flutter_sliders/lib/src/common.dart b/packages/syncfusion_flutter_sliders/lib/src/common.dart index b8b21739f..08c3d8e93 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/common.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/common.dart @@ -59,8 +59,8 @@ enum DateIntervalType { /// For example, if `min` is DateTime(2000, 01, 01, 00) and /// `max` is DateTime(2000, 12, 31, 24) and `interval` is 3 and /// `dateIntervalType` is [months] then range slider will render labels - /// for [Jan 01, 2000], [Apr 01, 2000], [Jul 01, 2000], [Oct 01, 2000] - /// and [Jan 01, 2001] respectively. + /// for `Jan 01, 2000`, `Apr 01, 2000`, `Jul 01, 2000`, `Oct 01, 2000` + /// and `Jan 01, 2001` respectively. months, /// Date interval is day. @@ -68,8 +68,8 @@ enum DateIntervalType { /// For example, if `min` is DateTime(2000, 01, 01, 00) and /// `max` is DateTime(2000, 01, 25, 24) and `interval` is 5 and /// `dateIntervalType` is [days] then range slider will render labels - /// for [Jan 01, 2000], [Jan 06, 2000], [Jan 11, 2000], [Jan 16, 2000], - /// [Jan 21, 2001] and [Jan 26, 2001] respectively. + /// for `Jan 01, 2000`, `Jan 06, 2000`, `Jan 11, 2000`, `Jan 16, 2000`, + /// `Jan 21, 2001` and `Jan 26, 2001` respectively. days, /// Date interval is hour. @@ -77,7 +77,7 @@ enum DateIntervalType { /// For example, if `min` is DateTime(2000, 01, 01, 09) and /// `max` is DateTime(2000, 01, 01, 17) and `interval` is 4 and /// `dateIntervalType` is [hours] then range slider will render labels for - /// [Jan 01, 2000 09:00], [Jan 01, 2000 13:00], and [Jan 01, 2000 17:00] + /// `Jan 01, 2000 09:00`, `Jan 01, 2000 13:00`, and `Jan 01, 2000 17:00` /// respectively. hours, @@ -86,8 +86,8 @@ enum DateIntervalType { /// For example, if `min` is DateTime(2000, 01, 01, 09) and /// `max` is DateTime(2000, 01, 01, 10) and `interval` is 15 and /// `dateIntervalType` is [minutes] then range slider will render labels for - /// [Jan 01, 2000 09:00], [Jan 01, 2000 09:15], [Jan 01, 2000 09:30], - /// [Jan 01, 2000 09:45]and [Jan 01, 2000 10:00] respectively. + /// `Jan 01, 2000 09:00`, `Jan 01, 2000 09:15`, `Jan 01, 2000 09:30`, + /// `Jan 01, 2000 09:45` and `Jan 01, 2000 10:00` respectively. minutes, /// Date interval is second. @@ -95,8 +95,8 @@ enum DateIntervalType { /// For example, if `min` is DateTime(2000, 01, 01, 09, 00) and /// `max` is DateTime(2000, 01, 01, 09, 01) and `interval` is 20 and /// `dateIntervalType` is [seconds] then range slider will render labels for - /// [Jan 01, 2000 09:00:00], [Jan 01, 2000 09:00:20], [Jan 01, 2000 09:00:40], - /// and [Jan 01, 2000 09:01:00] respectively. + /// `Jan 01, 2000 09:00:00`, `Jan 01, 2000 09:00:20`, `Jan 01, 2000 09:00:40`, + /// and `Jan 01, 2000 09:01:00` respectively. seconds } @@ -106,7 +106,13 @@ enum SfThumb { start, /// end represents the [SfRangeValues.end] thumb. - end + end, + + /// both represents the [SfRangeValues.start] and [SfRangeValues.end] thumb. + both, + + /// represents none of the thumb. + none } /// Represents the dragging behavior of the [SfRangeSelector] thumbs. diff --git a/packages/syncfusion_flutter_sliders/lib/src/constants.dart b/packages/syncfusion_flutter_sliders/lib/src/constants.dart index ab20d5eff..972654287 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/constants.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/constants.dart @@ -28,8 +28,6 @@ const double defaultElevation = 1.0; const double tappedElevation = 6.0; const Color shadowColor = Colors.black; -const double minPreferredTouchWidth = 20; - enum PointerType { down, move, up } /// Represents the [SfRangeSlider] or [SfRangeSelector] child elements. diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart index 0628364ce..1e86843d5 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_selector.dart @@ -451,8 +451,8 @@ class SfRangeSelector extends StatefulWidget { /// For example, if [min] is DateTime(2000, 01, 01, 00) and /// [max] is DateTime(2005, 12, 31, 24), [interval] is 1.0, /// [dateFormat] is DateFormat.y(), and - /// [dateIntervalType] is DateIntervalType.years, then the range selector will - /// render the labels, major ticks, and dividers at 2000, 2001, 2002 and + /// [dateIntervalType] is [DateIntervalType.years], then the range selector + /// will render the labels, major ticks, and dividers at 2000, 2001, 2002 and /// so on. /// /// Defaults to null. Must be greater than 0. @@ -537,9 +537,9 @@ class SfRangeSelector extends StatefulWidget { /// /// For example, if [min] is DateTime(2015, 01, 01) and /// [max] is DateTime(2020, 01, 01) and - /// [stepDuration] is SliderDuration(years: 1, months: 6), the range selector - /// will move the thumbs at DateTime(2015, 01, 01), DateTime(2016, 07, 01), - /// DateTime(2018, 01, 01),and DateTime(2019, 07, 01). + /// [stepDuration] is SliderStepDuration(years: 1, months: 6), the range + /// selector will move the thumbs at DateTime(2015, 01, 01), + /// DateTime(2016, 07, 01), DateTime(2018, 01, 01),and DateTime(2019, 07, 01). /// /// Defaults to `null`. /// @@ -552,7 +552,7 @@ class SfRangeSelector extends StatefulWidget { /// initialValues: SfRangeValues( /// DateTime(2017, 04,01), DateTime(2018, 08, 01)), /// enableTooltip: true, - /// stepDuration: SliderDuration(years: 1, months: 6), + /// stepDuration: SliderStepDuration(years: 1, months: 6), /// interval: 2, /// showLabels: true, /// showTicks: true, @@ -836,19 +836,19 @@ class SfRangeSelector extends StatefulWidget { /// Represents the behavior of thumb dragging in the [SfRangeSelector]. /// - /// When [dragMode] is set to `SliderDragMode.onThumb`, + /// When [dragMode] is set to [SliderDragMode.onThumb], /// individual thumb can be moved by dragging it. /// - /// When [dragMode] is set to `SliderDragMode.betweenThumbs`, both the thumbs + /// When [dragMode] is set to [SliderDragMode.betweenThumbs], both the thumbs /// can be moved at the same time by dragging in the area between start and /// end thumbs. The range between the start and end thumb will always /// be the same. Hence, it is not possible to move the individual thumb. /// - /// When [dragMode] is set to `SliderDragMode.both`, individual thumb + /// When [dragMode] is set to [SliderDragMode.both], individual thumb /// can be moved by dragging it, and also both the thumbs can be moved /// at the same time by dragging in the area between start and end thumbs. /// - /// Defaults to `SliderDragMode.onThumb`. + /// Defaults to [SliderDragMode.onThumb]. /// /// This code snippet shows the behavior of thumb dragging. /// @@ -1700,6 +1700,7 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { showLabels: showLabels, showDividers: showDividers, enableTooltip: enableTooltip, + isInversed: Directionality.of(context) == TextDirection.rtl, enableIntervalSelection: enableIntervalSelection, deferUpdate: deferUpdate, dragMode: dragMode, @@ -1730,7 +1731,7 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { renderObject ..min = min ..max = max - ..isEnabled = enabled + ..isInteractive = enabled ..interval = interval ..stepSize = stepSize ..stepDuration = stepDuration @@ -1740,6 +1741,7 @@ class _RangeSelectorRenderObjectWidget extends RenderObjectWidget { ..showLabels = showLabels ..showDividers = showDividers ..enableTooltip = enableTooltip + ..isInversed = Directionality.of(context) == TextDirection.rtl ..enableIntervalSelection = enableIntervalSelection ..deferUpdate = deferUpdate ..dragMode = dragMode @@ -1874,6 +1876,7 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { required bool showLabels, required bool showDividers, required bool enableTooltip, + required bool isInversed, required bool enableIntervalSelection, required bool deferUpdate, required SliderDragMode dragMode, @@ -1912,6 +1915,7 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { showLabels: showLabels, showDividers: showDividers, enableTooltip: enableTooltip, + isInversed: isInversed, enableIntervalSelection: enableIntervalSelection, dragMode: dragMode, labelPlacement: labelPlacement, @@ -1941,16 +1945,17 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { late Color _activeRegionColor; Timer? _deferUpdateTimer; - bool get isEnabled => _isEnabled; + @override + bool get isInteractive => _isEnabled; bool _isEnabled; - set isEnabled(bool value) { + set isInteractive(bool value) { if (_isEnabled == value) { return; } - final bool wasEnabled = isEnabled; + final bool wasEnabled = isInteractive; _isEnabled = value; - if (wasEnabled != isEnabled) { - if (isEnabled) { + if (wasEnabled != isInteractive) { + if (isInteractive) { _state.stateController.forward(); } else { _state.stateController.reverse(); @@ -2132,7 +2137,7 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { void _drawRegions(PaintingContext context, Rect trackRect, Offset offset, Offset startThumbCenter, Offset endThumbCenter) { - final Paint paint = Paint() + final Paint inactivePaint = Paint() ..isAntiAlias = true ..color = _inactiveRegionColor; if (child != null && child!.size.height > 1 && child!.size.width > 1) { @@ -2160,22 +2165,23 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { context.canvas.drawRect( Rect.fromLTRB(trackRect.left, offset.dy, leftThumbCenter.dx, trackRect.top + inactiveRegionAdj), - paint); - paint.color = _activeRegionColor; + inactivePaint); + final Paint activePaint = Paint() + ..isAntiAlias = true + ..color = _activeRegionColor; context.canvas.drawRect( Rect.fromLTRB(leftThumbCenter.dx, offset.dy, rightThumbCenter.dx, trackRect.top + activeRegionAdj), - paint); - paint.color = _inactiveRegionColor; + activePaint); context.canvas.drawRect( Rect.fromLTRB(rightThumbCenter.dx, offset.dy, trackRect.right, trackRect.top + inactiveRegionAdj), - paint); + inactivePaint); } } void _increaseStartAction() { - if (isEnabled) { + if (isInteractive) { final SfRangeValues newValues = SfRangeValues(increasedStartValue, values.end); if (getNumerizedValue(newValues.start) <= @@ -2186,19 +2192,19 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { } void _decreaseStartAction() { - if (isEnabled) { + if (isInteractive) { _updateNewValues(SfRangeValues(decreasedStartValue, values.end)); } } void _increaseEndAction() { - if (isEnabled) { + if (isInteractive) { _updateNewValues(SfRangeValues(values.start, increasedEndValue)); } } void _decreaseEndAction() { - if (isEnabled) { + if (isInteractive) { final SfRangeValues newValues = SfRangeValues(values.start, decreasedEndValue); if (getNumerizedValue(newValues.start) <= @@ -2208,19 +2214,6 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { } } - @override - bool hitTestSelf(Offset position) => isEnabled; - - @override - bool hitTestChildren(BoxHitTestResult result, {Offset? position}) => false; - - @override - void setupParentData(RenderObject child) { - if (child.parentData is! BoxParentData) { - child.parentData = BoxParentData(); - } - } - @override void visitChildren(RenderObjectVisitor visitor) { children.forEach(visitor); @@ -2294,8 +2287,7 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { } final BoxConstraints contentConstraints = BoxConstraints.tightFor( - width: sliderThemeData.thumbRadius * 2, - height: sliderThemeData.thumbRadius * 2); + width: actualThumbSize.width, height: actualThumbSize.height); startThumbIcon?.layout(contentConstraints, parentUsesSize: true); endThumbIcon?.layout(contentConstraints, parentUsesSize: true); @@ -2355,9 +2347,9 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { VoidCallback decreaseAction, ) { final SemanticsConfiguration config = SemanticsConfiguration(); - config.isEnabled = isEnabled; + config.isEnabled = isInteractive; config.textDirection = textDirection; - if (isEnabled) { + if (isInteractive) { config.onIncrease = increaseAction; config.onDecrease = decreaseAction; } @@ -2437,7 +2429,7 @@ class _RenderRangeSelector extends RenderBaseRangeSlider { @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - config.isSemanticBoundary = isEnabled; + config.isSemanticBoundary = isInteractive; } @override diff --git a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart index 18fa33496..3ae28ff68 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider.dart @@ -130,6 +130,7 @@ class SfRangeSlider extends StatefulWidget { this.showDividers = false, this.enableTooltip = false, this.enableIntervalSelection = false, + this.dragMode = SliderDragMode.onThumb, this.inactiveColor, this.activeColor, this.labelPlacement = LabelPlacement.onTicks, @@ -148,7 +149,8 @@ class SfRangeSlider extends StatefulWidget { this.tooltipShape = const SfRectangularTooltipShape(), this.startThumbIcon, this.endThumbIcon}) - : _sliderType = SliderType.horizontal, + : isInversed = false, + _sliderType = SliderType.horizontal, _tooltipPosition = null, assert(min != max), assert(interval == null || interval > 0), @@ -209,6 +211,8 @@ class SfRangeSlider extends StatefulWidget { this.showDividers = false, this.enableTooltip = false, this.enableIntervalSelection = false, + this.dragMode = SliderDragMode.onThumb, + this.isInversed = false, this.inactiveColor, this.activeColor, this.labelPlacement = LabelPlacement.onTicks, @@ -350,7 +354,7 @@ class SfRangeSlider extends StatefulWidget { /// For example, if [min] is DateTime(2000, 01, 01, 00) and /// [max] is DateTime(2005, 12, 31, 24), [interval] is 1.0, /// [dateFormat] is DateFormat.y(), and - /// [dateIntervalType] is DateIntervalType.years, then the range slider will + /// [dateIntervalType] is [DateIntervalType.years], then the range slider will /// render the labels, major ticks, and dividers at 2000, 2001, 2002 and /// so on. /// @@ -438,7 +442,7 @@ class SfRangeSlider extends StatefulWidget { /// /// For example, if [min] is DateTime(2015, 01, 01) and /// [max] is DateTime(2020, 01, 01) and - /// [stepDuration] is SliderDuration(years: 1, months: 6), + /// [stepDuration] is SliderStepDuration(years: 1, months: 6), /// the range slider will move the thumbs at DateTime(2015, 01, 01), /// DateTime(2016, 07, 01), DateTime(2018, 01, 01),and DateTime(2019, 07, 01). /// @@ -456,7 +460,7 @@ class SfRangeSlider extends StatefulWidget { /// max: DateTime(2020, 01, 01), /// values: _values, /// enableTooltip: true, - /// stepDuration: SliderDuration(years: 1, months: 6), + /// stepDuration: SliderStepDuration(years: 1, months: 6), /// interval: 2, /// showLabels: true, /// showTicks: true, @@ -684,6 +688,72 @@ class SfRangeSlider extends StatefulWidget { /// ``` final bool enableIntervalSelection; + /// Represents the behavior of thumb dragging in the [SfRangeSlider]. + /// + /// When [dragMode] is set to [SliderDragMode.onThumb], individual thumb can + /// be moved by dragging it. + /// + /// When [dragMode] is set to [SliderDragMode.betweenThumbs], both the thumbs + /// can be moved at the same time by dragging in the area between start and + /// end thumbs. The range between the start and end thumb will always be the + /// same. Hence, it is not possible to move the individual thumb. + /// + /// When [dragMode] is set to [SliderDragMode.both], individual thumb can be + /// moved by dragging it, and also both the thumbs can be moved at the same + /// time by dragging in the area between start and end thumbs. + /// + /// Defaults to [SliderDragMode.onThumb]. + /// + /// This code snippet shows the behavior of thumb dragging. + /// + /// ```dart + /// SfRangeValues _values = SfRangeValues(4.0, 8.0); + /// + /// SfRangeSlider( + /// min: 0.0, + /// max: 10.0, + /// interval: 2, + /// showLabels: true, + /// dragMode: SliderDragMode.betweenThumbs, + /// values: _values, + /// onChanged: (SfRangeValues newValues) { + /// setState(() { + /// _values = newValues; + /// }); + /// } + /// ) + /// ``` + /// + /// See also: + /// * The [enableIntervalSelection], to select the particular interval based + /// on the position of the tap or click. + final SliderDragMode dragMode; + + /// Option to inverse the range slider. + /// + /// Defaults to false. + /// + /// This snippet shows how to inverse the [SfRangeSlider]. + /// + /// ```dart + /// double _value = 4.0; + /// + /// SfRangeSlider.vertical( + /// min: 0.0, + /// max: 10.0, + /// value: _value, + /// interval: 2, + /// isInversed = true, + /// onChanged: (SfRangeValues newValues) { + /// setState(() { + /// _value = newValue; + /// }); + /// }, + /// ) + /// ``` + /// + final bool isInversed; + /// Color applied to the inactive track and active dividers. /// /// The inactive side of the range slider is between the [min] value and @@ -1111,6 +1181,8 @@ class SfRangeSlider extends StatefulWidget { ); properties.add(DiagnosticsProperty('min', min)); properties.add(DiagnosticsProperty('max', max)); + properties.add(DiagnosticsProperty('isInversed', isInversed, + defaultValue: false)); properties.add(ObjectFlagProperty>( 'onChanged', onChanged, ifNull: 'disabled')); @@ -1294,24 +1366,24 @@ class _SfRangeSliderState extends State rangeSliderThemeData.inactiveDividerStrokeWidth, ); - if (widget._sliderType == SliderType.vertical) { + if (widget._sliderType == SliderType.horizontal) { return rangeSliderThemeData.copyWith( - tickSize: rangeSliderThemeData.tickSize ?? const Size(8.0, 1.0), + tickSize: rangeSliderThemeData.tickSize ?? const Size(1.0, 8.0), minorTickSize: - rangeSliderThemeData.minorTickSize ?? const Size(5.0, 1.0), + rangeSliderThemeData.minorTickSize ?? const Size(1.0, 5.0), labelOffset: rangeSliderThemeData.labelOffset ?? (widget.showTicks - ? const Offset(5.0, 0.0) - : const Offset(13.0, 0.0))); + ? const Offset(0.0, 5.0) + : const Offset(0.0, 13.0))); } else { return rangeSliderThemeData.copyWith( - tickSize: rangeSliderThemeData.tickSize ?? const Size(1.0, 8.0), + tickSize: rangeSliderThemeData.tickSize ?? const Size(8.0, 1.0), minorTickSize: - rangeSliderThemeData.minorTickSize ?? const Size(1.0, 5.0), + rangeSliderThemeData.minorTickSize ?? const Size(5.0, 1.0), labelOffset: rangeSliderThemeData.labelOffset ?? (widget.showTicks - ? const Offset(0.0, 5.0) - : const Offset(0.0, 13.0))); + ? const Offset(5.0, 0.0) + : const Offset(13.0, 0.0))); } } @@ -1366,7 +1438,11 @@ class _SfRangeSliderState extends State showLabels: widget.showLabels, showDividers: widget.showDividers, enableTooltip: widget.enableTooltip, + dragMode: widget.dragMode, enableIntervalSelection: widget.enableIntervalSelection, + isInversed: widget._sliderType == SliderType.horizontal && + Directionality.of(context) == TextDirection.rtl || + widget.isInversed, inactiveColor: widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), activeColor: widget.activeColor ?? themeData.primaryColor, @@ -1412,6 +1488,8 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { required this.showDividers, required this.enableTooltip, required this.enableIntervalSelection, + required this.dragMode, + required this.isInversed, required this.inactiveColor, required this.activeColor, required this.labelPlacement, @@ -1451,10 +1529,12 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { final bool showDividers; final bool enableTooltip; final bool enableIntervalSelection; + final bool isInversed; final Color inactiveColor; final Color activeColor; + final SliderDragMode dragMode; final LabelPlacement labelPlacement; final NumberFormat numberFormat; final DateIntervalType? dateIntervalType; @@ -1493,6 +1573,8 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { showDividers: showDividers, enableTooltip: enableTooltip, enableIntervalSelection: enableIntervalSelection, + dragMode: dragMode, + isInversed: isInversed, labelPlacement: labelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, @@ -1533,6 +1615,8 @@ class _RangeSliderRenderObjectWidget extends RenderObjectWidget { ..showDividers = showDividers ..enableTooltip = enableTooltip ..enableIntervalSelection = enableIntervalSelection + ..dragMode = dragMode + ..isInversed = isInversed ..labelPlacement = labelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat @@ -1661,6 +1745,8 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { required bool showDividers, required bool enableTooltip, required bool enableIntervalSelection, + required SliderDragMode dragMode, + required bool isInversed, required LabelPlacement labelPlacement, required NumberFormat numberFormat, required DateFormat? dateFormat, @@ -1697,7 +1783,8 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { showDividers: showDividers, enableTooltip: enableTooltip, enableIntervalSelection: enableIntervalSelection, - dragMode: SliderDragMode.onThumb, + dragMode: dragMode, + isInversed: isInversed, labelPlacement: labelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, @@ -1749,6 +1836,7 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { markNeedsSemanticsUpdate(); } + @override bool get isInteractive => onChanged != null; @override @@ -1842,8 +1930,7 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { void performLayout() { super.performLayout(); final BoxConstraints contentConstraints = BoxConstraints.tightFor( - width: sliderThemeData.thumbRadius * 2, - height: sliderThemeData.thumbRadius * 2); + width: actualThumbSize.width, height: actualThumbSize.height); startThumbIcon?.layout(contentConstraints, parentUsesSize: true); endThumbIcon?.layout(contentConstraints, parentUsesSize: true); } @@ -1869,24 +1956,21 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { } } - @override - bool hitTestSelf(Offset position) => isInteractive; - @override void paint(PaintingContext context, Offset offset) { - final Offset actualTrackOffset = sliderType == SliderType.vertical + final Offset actualTrackOffset = sliderType == SliderType.horizontal ? Offset( - offset.dx + - (size.width - actualHeight) / 2 + - trackOffset.dy - - maxTrackHeight / 2, - offset.dy) - : Offset( offset.dx, offset.dy + (size.height - actualHeight) / 2 + trackOffset.dy - - maxTrackHeight / 2); + maxTrackHeight / 2) + : Offset( + offset.dx + + (size.width - actualHeight) / 2 + + trackOffset.dx - + maxTrackHeight / 2, + offset.dy); drawRangeSliderElements(context, offset, actualTrackOffset); } @@ -1947,12 +2031,12 @@ class _RenderRangeSlider extends RenderBaseRangeSlider { _decreaseEndAction, ); // Split the semantics node area between the start and end nodes. - final Rect startRect = sliderType == SliderType.vertical - ? Rect.fromPoints(node.rect.bottomRight, node.rect.centerLeft) - : Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter); - final Rect endRect = sliderType == SliderType.vertical - ? Rect.fromPoints(node.rect.centerLeft, node.rect.topRight) - : Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight); + final Rect startRect = sliderType == SliderType.horizontal + ? Rect.fromPoints(node.rect.topLeft, node.rect.bottomCenter) + : Rect.fromPoints(node.rect.bottomRight, node.rect.centerLeft); + final Rect endRect = sliderType == SliderType.horizontal + ? Rect.fromPoints(node.rect.topCenter, node.rect.bottomRight) + : Rect.fromPoints(node.rect.centerLeft, node.rect.topRight); 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 a7428c54d..cbce8ea40 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/range_slider_base.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math' as math; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -14,6 +15,7 @@ import 'constants.dart'; import 'slider_base.dart'; import 'slider_shapes.dart'; +// ignore_for_file: public_member_api_docs /// Base render box class for both [SfRangeSlider] and [SfRangeSelector]. abstract class RenderBaseRangeSlider extends RenderBaseSlider implements MouseTrackerAnnotation { @@ -50,6 +52,7 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider required SliderTooltipPosition? tooltipPosition, required TextDirection textDirection, required MediaQueryData mediaQueryData, + required bool isInversed, }) : _values = values!, _dragMode = dragMode, _enableIntervalSelection = enableIntervalSelection, @@ -81,7 +84,8 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider sliderType: sliderType, tooltipPosition: tooltipPosition, textDirection: textDirection, - mediaQueryData: mediaQueryData) { + mediaQueryData: mediaQueryData, + isInversed: isInversed) { final GestureArenaTeam team = GestureArenaTeam(); if (sliderType == SliderType.horizontal) { horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() @@ -229,9 +233,22 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _forwardTooltipAndOverlayController(); } - double get minThumbGap => sliderType == SliderType.vertical - ? (actualMax - actualMin) * (8 / actualTrackRect.height).clamp(0.0, 1.0) - : (actualMax - actualMin) * (8 / actualTrackRect.width).clamp(0.0, 1.0); + @override + set isInversed(bool value) { + if (super.isInversed == value) { + return; + } + super.isInversed = value; + // When the isInversed property is enabled dynamically, the value of the + // start and end thumb remains at the previous position, so + // updating the position of thumb when inversed. + startPositionController.value = getFactorFromValue(actualValues.start); + endPositionController.value = getFactorFromValue(actualValues.end); + } + + double get minThumbGap => sliderType == SliderType.horizontal + ? (actualMax - actualMin) * (8 / actualTrackRect.width).clamp(0.0, 1.0) + : (actualMax - actualMin) * (8 / actualTrackRect.height).clamp(0.0, 1.0); SfRangeValues get actualValues => isDateTime ? _valuesInMilliseconds : _values; @@ -273,9 +290,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider void _onTapDown(TapDownDetails details) { currentPointerType = PointerType.down; - _interactionStartOffset = sliderType == SliderType.vertical - ? globalToLocal(details.globalPosition).dy - : globalToLocal(details.globalPosition).dx; + _interactionStartOffset = sliderType == SliderType.horizontal + ? globalToLocal(details.globalPosition).dx + : globalToLocal(details.globalPosition).dy; mainAxisOffset = _interactionStartOffset; _beginInteraction(); } @@ -347,11 +364,16 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider if ((_dragMode == SliderDragMode.both || _dragMode == SliderDragMode.betweenThumbs) && _tappedBetweenThumbs(startPosition, endPosition)) { + overlayStartController.forward(); + overlayEndController.forward(); if (_isDragStart) { _isLocked = true; } else { return; } + } else if (dragMode == SliderDragMode.betweenThumbs && + !_tappedBetweenThumbs(startPosition, endPosition)) { + return; } else if (rightThumbWidth == leftThumbWidth) { switch (activeThumb!) { case SfThumb.start: @@ -360,6 +382,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider case SfThumb.end: overlayEndController.forward(); break; + case SfThumb.both: + case SfThumb.none: + break; } } else if (rightThumbWidth > leftThumbWidth) { activeThumb = SfThumb.start; @@ -371,16 +396,24 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _forwardTooltipAnimation(); _updateRangeValues(); + if (isIntervalTapped) { + startPositionController.value = getFactorFromValue(actualValues.start); + endPositionController.value = getFactorFromValue(actualValues.end); + } markNeedsPaint(); } bool _tappedBetweenThumbs(double startPosition, double endPosition) { - return (sliderType == SliderType.vertical || - textDirection == TextDirection.rtl) - ? startPosition > (mainAxisOffset + minPreferredTouchWidth) && - (mainAxisOffset - minPreferredTouchWidth) > endPosition - : startPosition < (mainAxisOffset - minPreferredTouchWidth) && - (mainAxisOffset + minPreferredTouchWidth) < endPosition; + final double thumbRadius = (sliderType == SliderType.horizontal + ? actualThumbSize.width + : actualThumbSize.height) / + 2; + return ((sliderType == SliderType.horizontal && !isInversed) || + (sliderType == SliderType.vertical && isInversed)) + ? mainAxisOffset > (startPosition + thumbRadius) && + mainAxisOffset < (endPosition - thumbRadius) + : mainAxisOffset < (startPosition - thumbRadius) && + mainAxisOffset > (endPosition + thumbRadius); } void _forwardTooltipAnimation() { @@ -448,6 +481,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider getActualValue(valueInDouble: endValue); newValues = values.copyWith(end: actualEndValue); break; + case SfThumb.both: + case SfThumb.none: + break; } } } @@ -457,20 +493,20 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider SfRangeValues _getLockRangeValues(double? delta) { final bool isVertical = sliderType == SliderType.vertical; - final bool isRTL = textDirection == TextDirection.rtl; double startPosition = getPositionFromValue(getNumerizedValue(_beginValues.start)); double endPosition = getPositionFromValue(getNumerizedValue(_beginValues.end)); - final double lockedRangeWidth = (isVertical || isRTL) - ? startPosition - endPosition - : endPosition - startPosition; + final double lockedRangeWidth = + (!isVertical && isInversed) || (isVertical && !isInversed) + ? startPosition - endPosition + : endPosition - startPosition; startPosition += delta ?? 0.0; endPosition += delta ?? 0.0; final double actualMinInPx = getPositionFromValue(actualMin); final double actualMaxInPx = getPositionFromValue(actualMax); - if (isVertical || isRTL) { + if ((!isVertical && isInversed) || (isVertical && !isInversed)) { if (startPosition > actualMinInPx) { startPosition = actualMinInPx; endPosition = startPosition - lockedRangeWidth; @@ -478,11 +514,6 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider endPosition = actualMaxInPx; startPosition = endPosition + lockedRangeWidth; } - - if (isVertical) { - startPosition = actualTrackRect.bottom - startPosition; - endPosition = actualTrackRect.bottom - endPosition; - } } else { if (startPosition < actualMinInPx) { startPosition = actualMinInPx; @@ -492,6 +523,10 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider startPosition = endPosition - lockedRangeWidth; } } + if (isVertical) { + startPosition = actualTrackRect.bottom - startPosition; + endPosition = actualTrackRect.bottom - endPosition; + } return SfRangeValues( getValueFromPosition(startPosition), getValueFromPosition(endPosition)); } @@ -506,9 +541,6 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider if (!isInteractionEnd) { SfRangeValues newValues = values; if (_enableIntervalSelection) { - startPositionController.value = getFactorFromValue(actualValues.start); - endPositionController.value = getFactorFromValue(actualValues.end); - if (isIntervalTapped) { final double? value = lerpDouble(actualMin, actualMax, getFactorFromCurrentPosition()); @@ -605,6 +637,14 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider } } + void _handlePositionControllerStatusChange(AnimationStatus status) { + if (isInteractionEnd && + (startPositionController.status == AnimationStatus.completed && + endPositionController.status == AnimationStatus.completed)) { + isIntervalTapped = false; + } + } + void _forwardTooltipAndOverlayController() { switch (_activeThumb!) { case SfThumb.start: @@ -625,6 +665,24 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider tooltipAnimationStartController.reverse(); } break; + case SfThumb.both: + overlayStartController.forward(); + overlayEndController.forward(); + if (enableTooltip) { + willDrawTooltip = true; + tooltipAnimationStartController.forward(); + tooltipAnimationEndController.forward(); + } + break; + case SfThumb.none: + overlayStartController.reverse(); + overlayEndController.reverse(); + if (enableTooltip) { + willDrawTooltip = true; + tooltipAnimationStartController.reverse(); + tooltipAnimationEndController.reverse(); + } + break; } } @@ -647,12 +705,16 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider getPositionFromValue(actualValues.start.toDouble()); final double endThumbPosition = getPositionFromValue(actualValues.end.toDouble()); - cursorPosition = sliderType == SliderType.vertical - ? details.localPosition.dy - : details.localPosition.dx; + cursorPosition = sliderType == SliderType.horizontal + ? details.localPosition.dx + : details.localPosition.dy; final double startThumbDistance = (cursorPosition - startThumbPosition).abs(); final double endThumbDistance = (cursorPosition - endThumbPosition).abs(); + final double thumbRadius = (sliderType == SliderType.horizontal + ? actualThumbSize.width + : actualThumbSize.height) / + 2; if (startThumbDistance == endThumbDistance) { // The [activeThumb] value is null at the load time, so setting the @@ -672,16 +734,75 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _forwardTooltipAndOverlayController(); } } else { - if (endThumbDistance > startThumbDistance) { - activeThumb = SfThumb.start; - } else { - activeThumb = SfThumb.end; + if (dragMode == SliderDragMode.onThumb) { + // Using the distance calculated for start and end thumb position with + // cursor position, updated thumb with shortest distance as active. + if (endThumbDistance > startThumbDistance) { + activeThumb = SfThumb.start; + } else { + activeThumb = SfThumb.end; + } + } else if (dragMode == SliderDragMode.betweenThumbs) { + // Slider elements are plotted starting from left direction for + // horizontal and from top direction for inverted vertical slider. + // So, cursor position above start thumb and below end thumb position + // are considered as area in-between thumbs. + if ((sliderType == SliderType.horizontal && !isInversed) || + (sliderType == SliderType.vertical && isInversed)) { + if (cursorPosition > startThumbPosition + thumbRadius && + cursorPosition < endThumbPosition - thumbRadius) { + activeThumb = SfThumb.both; + } else { + activeThumb = SfThumb.none; + } + } else { + if (cursorPosition < startThumbPosition - thumbRadius && + cursorPosition > endThumbPosition + thumbRadius) { + activeThumb = SfThumb.both; + } else { + activeThumb = SfThumb.none; + } + } + } + // In case of SliderDragMode as both. + else { + // Slider elements are plotted starting from left direction for + // horizontal and from top direction for inverted vertical slider. + // + // This drag mode is combination of between thumbs and onThumb. + // + // betweenThumbs: cursor position above start thumb and below end thumb + // position are considered as area in-between thumbs. + // + // onThumb: cursor position below start thumb will consider start thumb + // as active and if above end thumb will consider end thumb as active. + if ((sliderType == SliderType.horizontal && !isInversed) || + (sliderType == SliderType.vertical && isInversed)) { + if (cursorPosition > (startThumbPosition + thumbRadius) && + cursorPosition < (endThumbPosition - thumbRadius)) { + activeThumb = SfThumb.both; + } else if (cursorPosition <= (startThumbPosition + thumbRadius)) { + activeThumb = SfThumb.start; + } else { + activeThumb = SfThumb.end; + } + } else { + if (cursorPosition < (startThumbPosition - thumbRadius) && + cursorPosition > (endThumbPosition + thumbRadius)) { + activeThumb = SfThumb.both; + } else if (cursorPosition >= (startThumbPosition - thumbRadius)) { + activeThumb = SfThumb.start; + } else { + activeThumb = SfThumb.end; + } + } } } } void _drawOverlayAndThumb( PaintingContext context, + Offset paintOffset, Offset endThumbCenter, Offset startThumbCenter, ) { @@ -690,6 +811,28 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider RenderBox? thumbIcon = isStartThumbActive ? _endThumbIcon : _startThumbIcon; // Ignore overlapping thumb stroke for bottom thumb. showOverlappingThumbStroke = false; + if (thumbIcon != null) { + (thumbIcon.parentData! as BoxParentData).offset = thumbCenter - + Offset(thumbIcon.size.width / 2, thumbIcon.size.height / 2) - + paintOffset; + } + + if (isStartThumbActive + ? _overlayEndAnimation.status != AnimationStatus.dismissed + : _overlayStartAnimation.status != AnimationStatus.dismissed) { + // Drawing overlay. + overlayShape.paint( + context, + thumbCenter, + parentBox: this, + themeData: sliderThemeData, + currentValues: _values, + animation: + isStartThumbActive ? _overlayEndAnimation : _overlayStartAnimation, + thumb: isStartThumbActive ? SfThumb.end : SfThumb.start, + paint: null, + ); + } // Drawing thumb. thumbShape.paint(context, thumbCenter, @@ -704,25 +847,35 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider thumbCenter = isStartThumbActive ? startThumbCenter : endThumbCenter; thumbIcon = isStartThumbActive ? _startThumbIcon : _endThumbIcon; + if (thumbIcon != null) { + (thumbIcon.parentData! as BoxParentData).offset = thumbCenter - + Offset(thumbIcon.size.width / 2, thumbIcon.size.height / 2) - + paintOffset; + } + + if (isStartThumbActive + ? _overlayStartAnimation.status != AnimationStatus.dismissed + : _overlayEndAnimation.status != AnimationStatus.dismissed) { + // Drawing overlay. + overlayShape.paint( + context, + thumbCenter, + parentBox: this, + themeData: sliderThemeData, + currentValues: _values, + animation: + isStartThumbActive ? _overlayStartAnimation : _overlayEndAnimation, + thumb: activeThumb, + paint: null, + ); + } - // Drawing overlay. - overlayShape.paint( - context, - thumbCenter, - parentBox: this, - themeData: sliderThemeData, - currentValues: _values, - animation: - isStartThumbActive ? _overlayStartAnimation : _overlayEndAnimation, - thumb: activeThumb, - paint: null, - ); showOverlappingThumbStroke = (getFactorFromValue(actualValues.start) - getFactorFromValue(actualValues.end)) .abs() * - (sliderType == SliderType.vertical - ? actualTrackRect.height - : actualTrackRect.width) < + (sliderType == SliderType.horizontal + ? actualTrackRect.width + : actualTrackRect.height) < actualThumbSize.width; // Drawing thumb. @@ -733,7 +886,7 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider currentValues: _values, enableAnimation: _stateAnimation, textDirection: textDirection, - thumb: activeThumb, + thumb: isStartThumbActive ? SfThumb.start : SfThumb.end, paint: null); } @@ -753,9 +906,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider final bool isStartThumbActive = activeThumb == SfThumb.start; Offset thumbCenter = isStartThumbActive ? endThumbCenter : startThumbCenter; - dynamic actualText = (sliderType == SliderType.vertical) - ? getValueFromPosition(trackRect.bottom - thumbCenter.dy) - : getValueFromPosition(thumbCenter.dx - offset.dx); + dynamic actualText = (sliderType == SliderType.horizontal) + ? getValueFromPosition(thumbCenter.dx - offset.dx) + : getValueFromPosition(trackRect.bottom - thumbCenter.dy); String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); @@ -768,6 +921,7 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider if (tooltipShape is SfPaddleTooltipShape) { bottomTooltipRect = getPaddleTooltipRect( textPainter, + actualThumbSize.width / 2, Offset(actualTrackOffset.dx, tooltipStartY), thumbCenter, trackRect, @@ -794,9 +948,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider trackRect: trackRect); thumbCenter = isStartThumbActive ? startThumbCenter : endThumbCenter; - actualText = (sliderType == SliderType.vertical) - ? getValueFromPosition(trackRect.bottom - thumbCenter.dy) - : getValueFromPosition(thumbCenter.dx - offset.dx); + actualText = (sliderType == SliderType.horizontal) + ? getValueFromPosition(thumbCenter.dx - offset.dx) + : getValueFromPosition(trackRect.bottom - thumbCenter.dy); tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); @@ -809,6 +963,7 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider if (tooltipShape is SfPaddleTooltipShape) { topTooltipRect = getPaddleTooltipRect( textPainter, + actualThumbSize.width / 2, Offset(actualTrackOffset.dx, tooltipStartY), thumbCenter, trackRect, @@ -823,9 +978,9 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider } if (bottomTooltipRect != null && topTooltipRect != null) { final Rect overlapRect = topTooltipRect.intersect(bottomTooltipRect); - showOverlappingTooltipStroke = sliderType == SliderType.vertical - ? overlapRect.top < overlapRect.bottom - : overlapRect.right > overlapRect.left; + showOverlappingTooltipStroke = sliderType == SliderType.horizontal + ? overlapRect.right > overlapRect.left + : overlapRect.top < overlapRect.bottom; } tooltipShape.paint(context, thumbCenter, @@ -858,7 +1013,11 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _overlayStartAnimation.addListener(markNeedsPaint); _overlayEndAnimation.addListener(markNeedsPaint); startPositionController.addListener(markNeedsPaint); + startPositionController + .addStatusListener(_handlePositionControllerStatusChange); endPositionController.addListener(markNeedsPaint); + endPositionController + .addStatusListener(_handlePositionControllerStatusChange); _stateAnimation.addListener(markNeedsPaint); _tooltipStartAnimation.addListener(markNeedsPaint); _tooltipStartAnimation @@ -873,7 +1032,11 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider _overlayStartAnimation.removeListener(markNeedsPaint); _overlayEndAnimation.removeListener(markNeedsPaint); startPositionController.removeListener(markNeedsPaint); + startPositionController + .removeStatusListener(_handlePositionControllerStatusChange); endPositionController.removeListener(markNeedsPaint); + endPositionController + .removeStatusListener(_handlePositionControllerStatusChange); _stateAnimation.removeListener(markNeedsPaint); _tooltipStartAnimation.removeListener(markNeedsPaint); _tooltipStartAnimation @@ -890,21 +1053,52 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider @override PointerEnterEventListener? get onEnter => null; - /// Used to handle hover interaction. - @override - // ignore: override_on_non_overriding_member - PointerHoverEventListener get onHover => _handleHover; - @override PointerExitEventListener get onExit => _handleExit; @override bool get validForMouseTracker => _validForMouseTracker; + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + if (size.contains(position) && isInteractive) { + RenderBox? thumbIcon; + if (startThumbIcon != null && + ((startThumbIcon!.parentData! as BoxParentData).offset & + startThumbIcon!.size) + .contains(position)) { + thumbIcon = startThumbIcon; + } else if (endThumbIcon != null && + ((endThumbIcon!.parentData! as BoxParentData).offset & + endThumbIcon!.size) + .contains(position)) { + thumbIcon = endThumbIcon; + } + if (thumbIcon != null) { + final Offset center = thumbIcon.size.center(Offset.zero); + result.addWithRawTransform( + transform: MatrixUtils.forceToPoint(center), + position: position, + hitTest: (BoxHitTestResult result, Offset? position) { + return thumbIcon!.hitTest(result, position: center); + }, + ); + } + result.add(BoxHitTestEntry(this, position)); + return true; + } + + return false; + } + @override void handleEvent(PointerEvent event, HitTestEntry entry) { - if (event is PointerHoverEvent) { - onHover(event); + final bool isDesktop = kIsWeb || + defaultTargetPlatform == TargetPlatform.macOS || + defaultTargetPlatform == TargetPlatform.windows || + defaultTargetPlatform == TargetPlatform.linux; + if (isDesktop && event is PointerHoverEvent) { + _handleHover(event); } super.handleEvent(event, entry); } @@ -913,33 +1107,33 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider /// dividers, thumb, and overlay. void drawRangeSliderElements( PaintingContext context, Offset offset, Offset actualTrackOffset) { + Offset startThumbCenter; + Offset endThumbCenter; // Drawing track. final Rect trackRect = trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); - final double thumbStartPosition = getFactorFromValue(isIntervalTapped - ? getValueFromFactor((sliderType == SliderType.horizontal && - textDirection == TextDirection.rtl) - ? (1 - startPositionController.value) - : startPositionController.value) - : actualValues.start) * - (sliderType == SliderType.vertical - ? trackRect.height - : trackRect.width); - final double thumbEndPosition = getFactorFromValue(isIntervalTapped - ? getValueFromFactor((sliderType == SliderType.horizontal && - textDirection == TextDirection.rtl) - ? (1 - endPositionController.value) - : endPositionController.value) - : actualValues.end) * - (sliderType == SliderType.vertical - ? trackRect.height - : trackRect.width); - final Offset startThumbCenter = sliderType == SliderType.vertical - ? Offset(trackRect.center.dx, trackRect.bottom - thumbStartPosition) - : Offset(trackRect.left + thumbStartPosition, trackRect.center.dy); - final Offset endThumbCenter = sliderType == SliderType.vertical - ? Offset(trackRect.center.dx, trackRect.bottom - thumbEndPosition) - : Offset(trackRect.left + thumbEndPosition, trackRect.center.dy); + double thumbStartPosition = isIntervalTapped + ? startPositionController.value + : getFactorFromValue(actualValues.start); + double thumbEndPosition = isIntervalTapped + ? endPositionController.value + : getFactorFromValue(actualValues.end); + + if (sliderType == SliderType.horizontal) { + thumbStartPosition = thumbStartPosition * trackRect.width; + thumbEndPosition = thumbEndPosition * trackRect.width; + startThumbCenter = + Offset(trackRect.left + thumbStartPosition, trackRect.center.dy); + endThumbCenter = + Offset(trackRect.left + thumbEndPosition, trackRect.center.dy); + } else { + thumbStartPosition = thumbStartPosition * trackRect.height; + thumbEndPosition = thumbEndPosition * trackRect.height; + startThumbCenter = + Offset(trackRect.center.dx, trackRect.bottom - thumbStartPosition); + endThumbCenter = + Offset(trackRect.center.dx, trackRect.bottom - thumbEndPosition); + } trackShape.paint( context, actualTrackOffset, null, startThumbCenter, endThumbCenter, @@ -957,7 +1151,7 @@ abstract class RenderBaseRangeSlider extends RenderBaseSlider } drawRegions(context, trackRect, offset, startThumbCenter, endThumbCenter); - _drawOverlayAndThumb(context, endThumbCenter, startThumbCenter); + _drawOverlayAndThumb(context, offset, endThumbCenter, startThumbCenter); _drawTooltip(context, endThumbCenter, startThumbCenter, offset, actualTrackOffset, trackRect); } diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider.dart b/packages/syncfusion_flutter_sliders/lib/src/slider.dart index b6b45901d..45a55e2bd 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider.dart @@ -93,7 +93,7 @@ import 'slider_shapes.dart'; /// value: _value, /// interval: 1, /// dateFormat: DateFormat.y(), -/// dateIntervalType: DateIntervalType.years, +/// dateIntervalType: DateIntervalType.years, /// onChanged: (dynamic newValue) { /// setState(() { /// _value = newValue; @@ -141,7 +141,8 @@ class SfSlider extends StatefulWidget { this.minorTickShape = const SfMinorTickShape(), this.tooltipShape = const SfRectangularTooltipShape(), this.thumbIcon}) - : _sliderType = SliderType.horizontal, + : isInversed = false, + _sliderType = SliderType.horizontal, _tooltipPosition = null, assert(min != max), assert(interval == null || interval > 0), @@ -203,6 +204,7 @@ class SfSlider extends StatefulWidget { this.showLabels = false, this.showDividers = false, this.enableTooltip = false, + this.isInversed = false, this.activeColor, this.inactiveColor, this.labelPlacement = LabelPlacement.onTicks, @@ -338,8 +340,9 @@ class SfSlider extends StatefulWidget { /// For example, if [min] is DateTime(2000, 01, 01, 00) and /// [max] is DateTime(2005, 12, 31, 24), [interval] is 1.0, /// [dateFormat] is DateFormat.y(), and - /// [dateIntervalType] is DateIntervalType.years, then the slider will render - /// the labels, major ticks, and dividers at 2000, 2001, 2002 and so on. + /// [dateIntervalType] is [DateIntervalType.years], then the slider will + /// render the labels, major ticks, and dividers at 2000, 2001, 2002 and so + /// on. /// /// Defaults to `null`. Must be greater than 0. /// @@ -424,7 +427,7 @@ class SfSlider extends StatefulWidget { /// /// For example, if [min] is `DateTime(2015, 01, 01)` and /// [max] is `DateTime(2020, 01, 01)` and - /// [stepDuration] is `SliderDuration(years: 1, months: 6)`, + /// [stepDuration] is `SliderStepDuration(years: 1, months: 6)`, /// the slider will move the thumb at DateTime(2015, 01, 01), /// DateTime(2016, 07, 01), DateTime(2018, 01, 01),and DateTime(2019, 07, 01). /// @@ -441,7 +444,7 @@ class SfSlider extends StatefulWidget { /// max: DateTime(2020, 01, 01), /// value: _value, /// enableTooltip: true, - /// stepDuration: SliderDuration(years: 1, months: 6), + /// stepDuration: SliderStepDuration(years: 1, months: 6), /// interval: 2, /// showLabels: true, /// showTicks: true, @@ -636,6 +639,30 @@ class SfSlider extends StatefulWidget { /// * [SfSliderThemeData](https://pub.dev/documentation/syncfusion_flutter_core/latest/theme/SfSliderThemeData-class.html), for customizing the appearance of the tooltip text. final bool enableTooltip; + /// Option to inverse the slider. + /// + /// Defaults to false. + /// + /// This snippet shows how to inverse the [SfSlider]. + /// + /// ```dart + /// double _value = 4.0; + /// + /// SfSlider.vertical( + /// min: 0.0, + /// max: 10.0, + /// value: _value, + /// interval: 1, + /// isInversed = true, + /// onChanged: (dynamic newValue) { + /// setState(() { + /// _value = newValue; + /// }); + /// }, + /// ) + /// ``` + final bool isInversed; + /// Color applied to the inactive track and active dividers. /// /// The inactive side of the slider is between the thumb and the [max] value. @@ -1011,6 +1038,8 @@ class SfSlider extends StatefulWidget { properties.add(DiagnosticsProperty('value', value)); properties.add(DiagnosticsProperty('min', min)); properties.add(DiagnosticsProperty('max', max)); + properties.add(DiagnosticsProperty('isInversed', isInversed, + defaultValue: false)); properties.add(ObjectFlagProperty>( 'onChanged', onChanged, ifNull: 'disabled')); @@ -1167,15 +1196,7 @@ class _SfSliderState extends State with TickerProviderStateMixin { activeDividerStrokeWidth: sliderThemeData.activeDividerStrokeWidth, inactiveDividerStrokeWidth: sliderThemeData.inactiveDividerStrokeWidth, ); - if (widget._sliderType == SliderType.vertical) { - return sliderThemeData.copyWith( - tickSize: sliderThemeData.tickSize ?? const Size(8.0, 1.0), - minorTickSize: sliderThemeData.minorTickSize ?? const Size(5.0, 1.0), - labelOffset: sliderThemeData.labelOffset ?? - (widget.showTicks - ? const Offset(5.0, 0.0) - : const Offset(13.0, 0.0))); - } else { + if (widget._sliderType == SliderType.horizontal) { return sliderThemeData.copyWith( tickSize: sliderThemeData.tickSize ?? const Size(1.0, 8.0), minorTickSize: sliderThemeData.minorTickSize ?? const Size(1.0, 5.0), @@ -1183,6 +1204,14 @@ class _SfSliderState extends State with TickerProviderStateMixin { (widget.showTicks ? const Offset(0.0, 5.0) : const Offset(0.0, 13.0))); + } else { + return sliderThemeData.copyWith( + tickSize: sliderThemeData.tickSize ?? const Size(8.0, 1.0), + minorTickSize: sliderThemeData.minorTickSize ?? const Size(5.0, 1.0), + labelOffset: sliderThemeData.labelOffset ?? + (widget.showTicks + ? const Offset(5.0, 0.0) + : const Offset(13.0, 0.0))); } } @@ -1226,6 +1255,9 @@ class _SfSliderState extends State with TickerProviderStateMixin { showLabels: widget.showLabels, showDividers: widget.showDividers, enableTooltip: widget.enableTooltip, + isInversed: widget._sliderType == SliderType.horizontal && + Directionality.of(context) == TextDirection.rtl || + widget.isInversed, inactiveColor: widget.inactiveColor ?? themeData.primaryColor.withOpacity(0.24), activeColor: widget.activeColor ?? themeData.primaryColor, @@ -1268,6 +1300,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { required this.showLabels, required this.showDividers, required this.enableTooltip, + required this.isInversed, required this.inactiveColor, required this.activeColor, required this.labelPlacement, @@ -1306,6 +1339,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { final bool showLabels; final bool showDividers; final bool enableTooltip; + final bool isInversed; final Color inactiveColor; final Color activeColor; @@ -1346,6 +1380,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { showLabels: showLabels, showDividers: showDividers, enableTooltip: enableTooltip, + isInversed: isInversed, labelPlacement: labelPlacement, numberFormat: numberFormat, dateFormat: dateFormat, @@ -1383,6 +1418,7 @@ class _SliderRenderObjectWidget extends RenderObjectWidget { ..showLabels = showLabels ..showDividers = showDividers ..enableTooltip = enableTooltip + ..isInversed = isInversed ..labelPlacement = labelPlacement ..numberFormat = numberFormat ..dateFormat = dateFormat @@ -1504,6 +1540,7 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { required bool showLabels, required bool showDividers, required bool enableTooltip, + required bool isInversed, required LabelPlacement labelPlacement, required NumberFormat numberFormat, required DateFormat? dateFormat, @@ -1529,34 +1566,36 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { _semanticFormatterCallback = semanticFormatterCallback, _onChanged = onChanged, super( - min: min, - max: max, - sliderType: sliderType, - interval: interval, - stepSize: stepSize, - stepDuration: stepDuration, - minorTicksPerInterval: minorTicksPerInterval, - showTicks: showTicks, - showLabels: showLabels, - showDividers: showDividers, - enableTooltip: enableTooltip, - labelPlacement: labelPlacement, - numberFormat: numberFormat, - dateFormat: dateFormat, - dateIntervalType: dateIntervalType, - labelFormatterCallback: labelFormatterCallback, - tooltipTextFormatterCallback: tooltipTextFormatterCallback, - trackShape: trackShape, - dividerShape: dividerShape, - overlayShape: overlayShape, - thumbShape: thumbShape, - tickShape: tickShape, - minorTickShape: minorTickShape, - tooltipShape: tooltipShape, - tooltipPosition: tooltipPosition, - sliderThemeData: sliderThemeData, - textDirection: textDirection, - mediaQueryData: mediaQueryData) { + min: min, + max: max, + sliderType: sliderType, + interval: interval, + stepSize: stepSize, + stepDuration: stepDuration, + minorTicksPerInterval: minorTicksPerInterval, + showTicks: showTicks, + showLabels: showLabels, + showDividers: showDividers, + enableTooltip: enableTooltip, + isInversed: isInversed, + labelPlacement: labelPlacement, + numberFormat: numberFormat, + dateFormat: dateFormat, + dateIntervalType: dateIntervalType, + labelFormatterCallback: labelFormatterCallback, + tooltipTextFormatterCallback: tooltipTextFormatterCallback, + trackShape: trackShape, + dividerShape: dividerShape, + overlayShape: overlayShape, + thumbShape: thumbShape, + tickShape: tickShape, + minorTickShape: minorTickShape, + tooltipShape: tooltipShape, + tooltipPosition: tooltipPosition, + sliderThemeData: sliderThemeData, + textDirection: textDirection, + mediaQueryData: mediaQueryData, + ) { final GestureArenaTeam team = GestureArenaTeam(); if (sliderType == SliderType.horizontal) { horizontalDragGestureRecognizer = HorizontalDragGestureRecognizer() @@ -1674,6 +1713,7 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { _thumbIcon = _updateChild(_thumbIcon, value, ChildElements.startThumbIcon); } + @override bool get isInteractive => onChanged != null; double get actualValue => @@ -1714,9 +1754,9 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { void _onTapDown(TapDownDetails details) { currentPointerType = PointerType.down; - mainAxisOffset = sliderType == SliderType.vertical - ? globalToLocal(details.globalPosition).dy - : globalToLocal(details.globalPosition).dx; + mainAxisOffset = sliderType == SliderType.horizontal + ? globalToLocal(details.globalPosition).dx + : globalToLocal(details.globalPosition).dy; _beginInteraction(); } @@ -1829,9 +1869,9 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { ..style = PaintingStyle.fill ..strokeWidth = 0; - final dynamic actualText = sliderType == SliderType.vertical - ? getValueFromPosition(trackRect.bottom - thumbCenter.dy) - : getValueFromPosition(thumbCenter.dx - offset.dx); + final dynamic actualText = sliderType == SliderType.horizontal + ? getValueFromPosition(thumbCenter.dx - offset.dx) + : getValueFromPosition(trackRect.bottom - thumbCenter.dy); final String tooltipText = tooltipTextFormatterCallback( actualText, getFormattedText(actualText)); final TextSpan textSpan = @@ -1923,44 +1963,62 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { @override bool get validForMouseTracker => _validForMouseTracker; - @override - bool hitTestSelf(Offset position) => isInteractive; - @override void performLayout() { super.performLayout(); final BoxConstraints contentConstraints = BoxConstraints.tightFor( - width: sliderThemeData.thumbRadius * 2, - height: sliderThemeData.thumbRadius * 2); + width: actualThumbSize.width, height: actualThumbSize.height); _thumbIcon?.layout(contentConstraints, parentUsesSize: true); } + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + if (size.contains(position) && isInteractive) { + if (_thumbIcon != null && + ((_thumbIcon!.parentData! as BoxParentData).offset & _thumbIcon!.size) + .contains(position)) { + final Offset center = _thumbIcon!.size.center(Offset.zero); + result.addWithRawTransform( + transform: MatrixUtils.forceToPoint(center), + position: position, + hitTest: (BoxHitTestResult result, Offset? position) { + return thumbIcon!.hitTest(result, position: center); + }, + ); + } + result.add(BoxHitTestEntry(this, position)); + return true; + } + + return false; + } + @override void paint(PaintingContext context, Offset offset) { - final Offset actualTrackOffset = sliderType == SliderType.vertical + final Offset actualTrackOffset = sliderType == SliderType.horizontal ? Offset( - offset.dx + - (size.width - actualHeight) / 2 + - trackOffset.dy - - maxTrackHeight / 2, - offset.dy) - : Offset( offset.dx, offset.dy + (size.height - actualHeight) / 2 + trackOffset.dy - - maxTrackHeight / 2); + maxTrackHeight / 2) + : Offset( + offset.dx + + (size.width - actualHeight) / 2 + + trackOffset.dx - + maxTrackHeight / 2, + offset.dy); // Drawing track. final Rect trackRect = trackShape.getPreferredRect(this, sliderThemeData, actualTrackOffset); final double thumbPosition = getFactorFromValue(actualValue) * - (sliderType == SliderType.vertical - ? trackRect.height - : trackRect.width); - final Offset thumbCenter = sliderType == SliderType.vertical - ? Offset(trackRect.center.dx, trackRect.bottom - thumbPosition) - : Offset(trackRect.left + thumbPosition, trackRect.center.dy); + (sliderType == SliderType.horizontal + ? trackRect.width + : trackRect.height); + final Offset thumbCenter = sliderType == SliderType.horizontal + ? Offset(trackRect.left + thumbPosition, trackRect.center.dy) + : Offset(trackRect.center.dx, trackRect.bottom - thumbPosition); trackShape.paint(context, actualTrackOffset, thumbCenter, null, null, parentBox: this, @@ -1986,6 +2044,11 @@ class _RenderSlider extends RenderBaseSlider implements MouseTrackerAnnotation { thumb: null, paint: null); + if (_thumbIcon != null) { + (_thumbIcon!.parentData! as BoxParentData).offset = thumbCenter - + Offset(_thumbIcon!.size.width / 2, _thumbIcon!.size.height / 2) - + offset; + } // Drawing thumb. thumbShape.paint(context, thumbCenter, parentBox: this, diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart b/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart index 2a013e1c3..0de0074ce 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider_base.dart @@ -11,6 +11,7 @@ import 'common.dart'; import 'constants.dart'; import 'slider_shapes.dart'; +// ignore_for_file: public_member_api_docs /// Base render box class for all three sliders such as [SfSlider], /// [SfRangeSlider], and [SfRangeSelector]. class RenderBaseSlider extends RenderProxyBox @@ -28,6 +29,7 @@ class RenderBaseSlider extends RenderProxyBox required bool showDividers, required bool enableTooltip, required LabelPlacement labelPlacement, + required bool isInversed, required NumberFormat numberFormat, required DateFormat? dateFormat, required DateIntervalType? dateIntervalType, @@ -55,6 +57,7 @@ class RenderBaseSlider extends RenderProxyBox _showLabels = showLabels, _showDividers = showDividers, _enableTooltip = enableTooltip, + _isInversed = isInversed, _labelPlacement = labelPlacement, _numberFormat = numberFormat, _dateFormat = dateFormat, @@ -145,7 +148,6 @@ class RenderBaseSlider extends RenderProxyBox dynamic get min => _min; dynamic _min; - set min(dynamic value) { if (_min == value) { return; @@ -163,7 +165,6 @@ class RenderBaseSlider extends RenderProxyBox dynamic get max => _max; dynamic _max; - set max(dynamic value) { if (_max == value) { return; @@ -181,7 +182,6 @@ class RenderBaseSlider extends RenderProxyBox double? get interval => _interval; double? _interval; - set interval(double? value) { if (_interval == value) { return; @@ -194,7 +194,6 @@ class RenderBaseSlider extends RenderProxyBox double? get stepSize => _stepSize; double? _stepSize; - set stepSize(double? value) { if (_stepSize == value) { return; @@ -205,7 +204,6 @@ class RenderBaseSlider extends RenderProxyBox SliderStepDuration? get stepDuration => _stepDuration; SliderStepDuration? _stepDuration; - set stepDuration(SliderStepDuration? value) { if (_stepDuration == value) { return; @@ -215,7 +213,6 @@ class RenderBaseSlider extends RenderProxyBox int get minorTicksPerInterval => _minorTicksPerInterval; int _minorTicksPerInterval; - set minorTicksPerInterval(int value) { if (_minorTicksPerInterval == value) { return; @@ -227,7 +224,6 @@ class RenderBaseSlider extends RenderProxyBox bool get showTicks => _showTicks; bool _showTicks; - set showTicks(bool value) { if (_showTicks == value) { return; @@ -238,7 +234,6 @@ class RenderBaseSlider extends RenderProxyBox bool get showLabels => _showLabels; bool _showLabels; - set showLabels(bool value) { if (_showLabels == value) { return; @@ -249,7 +244,6 @@ class RenderBaseSlider extends RenderProxyBox bool get showDividers => _showDividers; bool _showDividers; - set showDividers(bool value) { if (_showDividers == value) { return; @@ -260,7 +254,6 @@ class RenderBaseSlider extends RenderProxyBox bool get enableTooltip => _enableTooltip; bool _enableTooltip; - set enableTooltip(bool value) { if (_enableTooltip == value) { return; @@ -268,9 +261,21 @@ class RenderBaseSlider extends RenderProxyBox _enableTooltip = value; } + // When the directionality of horizontal sliders is set to RTL and + // the [isInversed] API of SfSlider.vertical, SfRangeSlider.vertical + // is set to true, the isInversed property is true. + bool get isInversed => _isInversed; + bool _isInversed; + set isInversed(bool value) { + if (_isInversed == value) { + return; + } + _isInversed = value; + markNeedsLayout(); + } + LabelPlacement get labelPlacement => _labelPlacement; LabelPlacement _labelPlacement; - set labelPlacement(LabelPlacement value) { if (_labelPlacement == value) { return; @@ -281,7 +286,6 @@ class RenderBaseSlider extends RenderProxyBox NumberFormat get numberFormat => _numberFormat; NumberFormat _numberFormat; - set numberFormat(NumberFormat value) { if (_numberFormat == value) { return; @@ -293,7 +297,6 @@ class RenderBaseSlider extends RenderProxyBox DateIntervalType? get dateIntervalType => _dateIntervalType; DateIntervalType? _dateIntervalType; - set dateIntervalType(DateIntervalType? value) { if (_dateIntervalType == value) { return; @@ -306,7 +309,6 @@ class RenderBaseSlider extends RenderProxyBox DateFormat? get dateFormat => _dateFormat; DateFormat? _dateFormat; - set dateFormat(DateFormat? value) { if (_dateFormat == value) { return; @@ -318,7 +320,6 @@ class RenderBaseSlider extends RenderProxyBox LabelFormatterCallback get labelFormatterCallback => _labelFormatterCallback; LabelFormatterCallback _labelFormatterCallback; - set labelFormatterCallback(LabelFormatterCallback value) { if (_labelFormatterCallback == value) { return; @@ -330,7 +331,6 @@ class RenderBaseSlider extends RenderProxyBox TooltipTextFormatterCallback get tooltipTextFormatterCallback => _tooltipTextFormatterCallback; TooltipTextFormatterCallback _tooltipTextFormatterCallback; - set tooltipTextFormatterCallback(TooltipTextFormatterCallback value) { if (_tooltipTextFormatterCallback == value) { return; @@ -341,7 +341,6 @@ class RenderBaseSlider extends RenderProxyBox SfThumbShape get thumbShape => _thumbShape; SfThumbShape _thumbShape; - set thumbShape(SfThumbShape value) { if (_thumbShape == value) { return; @@ -352,7 +351,6 @@ class RenderBaseSlider extends RenderProxyBox SfOverlayShape get overlayShape => _overlayShape; SfOverlayShape _overlayShape; - set overlayShape(SfOverlayShape value) { if (_overlayShape == value) { return; @@ -362,7 +360,6 @@ class RenderBaseSlider extends RenderProxyBox SfTrackShape get trackShape => _trackShape; SfTrackShape _trackShape; - set trackShape(SfTrackShape value) { if (_trackShape == value) { return; @@ -373,7 +370,6 @@ class RenderBaseSlider extends RenderProxyBox SfDividerShape get dividerShape => _dividerShape; SfDividerShape _dividerShape; - set dividerShape(SfDividerShape value) { if (_dividerShape == value) { return; @@ -384,7 +380,6 @@ class RenderBaseSlider extends RenderProxyBox SfTickShape get tickShape => _tickShape; SfTickShape _tickShape; - set tickShape(SfTickShape value) { if (_tickShape == value) { return; @@ -395,7 +390,6 @@ class RenderBaseSlider extends RenderProxyBox SfTickShape get minorTickShape => _minorTickShape; SfTickShape _minorTickShape; - set minorTickShape(SfTickShape value) { if (_minorTickShape == value) { return; @@ -406,7 +400,6 @@ class RenderBaseSlider extends RenderProxyBox SfTooltipShape get tooltipShape => _tooltipShape; SfTooltipShape _tooltipShape; - set tooltipShape(SfTooltipShape value) { if (_tooltipShape == value) { return; @@ -416,7 +409,6 @@ class RenderBaseSlider extends RenderProxyBox SfSliderThemeData get sliderThemeData => _sliderThemeData; SfSliderThemeData _sliderThemeData; - set sliderThemeData(SfSliderThemeData value) { if (_sliderThemeData == value) { return; @@ -427,7 +419,6 @@ class RenderBaseSlider extends RenderProxyBox TextDirection get textDirection => _textDirection; TextDirection _textDirection; - set textDirection(TextDirection value) { if (_textDirection == value) { return; @@ -439,7 +430,6 @@ class RenderBaseSlider extends RenderProxyBox SliderTooltipPosition? get tooltipPosition => _tooltipPosition; SliderTooltipPosition? _tooltipPosition; - set tooltipPosition(SliderTooltipPosition? value) { if (_tooltipPosition == value) { return; @@ -450,7 +440,6 @@ class RenderBaseSlider extends RenderProxyBox MediaQueryData get mediaQueryData => _mediaQueryData; MediaQueryData _mediaQueryData; - set mediaQueryData(MediaQueryData value) { if (_mediaQueryData == value) { return; @@ -460,6 +449,8 @@ class RenderBaseSlider extends RenderProxyBox markNeedsLayout(); } + bool get isInteractive => false; + bool get isDateTime => _min.runtimeType == DateTime && _max.runtimeType == DateTime; @@ -534,11 +525,11 @@ class RenderBaseSlider extends RenderProxyBox : 0; // Here 10 is a gap between tooltip nose and thumb. - double get tooltipStartY => (sliderType == SliderType.vertical) - ? math.max(actualThumbSize.width, actualTrackRect.width) / 2 + 10 - : _tooltipShape is SfPaddleTooltipShape + double get tooltipStartY => (sliderType == SliderType.horizontal) + ? _tooltipShape is SfPaddleTooltipShape ? math.max(actualThumbSize.height, actualTrackRect.height) / 2 - : math.max(actualThumbSize.height, actualTrackRect.height) / 2 + 10; + : math.max(actualThumbSize.height, actualTrackRect.height) / 2 + 10 + : math.max(actualThumbSize.width, actualTrackRect.width) / 2 + 10; double get adjustmentUnit => (actualMax - actualMin) / 10; @@ -594,19 +585,19 @@ class RenderBaseSlider extends RenderProxyBox ? 0.0 // ignore: avoid_as : (value - actualMin) / (actualMax - actualMin) as double; - if (sliderType == SliderType.vertical) { - return factor; + if (_isInversed) { + return 1.0 - factor; } else { - return (_textDirection == TextDirection.rtl) ? 1.0 - factor : factor; + return factor; } } double getPositionFromValue(double value) { - return sliderType == SliderType.vertical - ? actualTrackRect.bottom - - getFactorFromValue(value) * actualTrackRect.height - : getFactorFromValue(value) * actualTrackRect.width + - actualTrackRect.left; + return sliderType == SliderType.horizontal + ? getFactorFromValue(value) * actualTrackRect.width + + actualTrackRect.left + : actualTrackRect.bottom - + getFactorFromValue(value) * actualTrackRect.height; } void generateLabelsAndMajorTicks() { @@ -643,14 +634,14 @@ class RenderBaseSlider extends RenderProxyBox unformattedLabels?.add(isDateTime ? valueInMilliseconds!.toDouble() : currentValue.toDouble()); - if (sliderType == SliderType.vertical) { + if (sliderType == SliderType.horizontal) { labelPosition = getFactorFromValue( isDateTime ? valueInMilliseconds : currentValue) * - (actualTrackRect.height); + (actualTrackRect.width); } else { labelPosition = getFactorFromValue( isDateTime ? valueInMilliseconds : currentValue) * - (actualTrackRect.width); + (actualTrackRect.height); } if (!_majorTickPositions.contains(labelPosition)) { _majorTickPositions.add(labelPosition); @@ -663,7 +654,6 @@ class RenderBaseSlider extends RenderProxyBox void _generateEdgeLabelsAndMajorTicks() { String label; - double labelPosition; divisions = 1.0; label = _labelFormatterCallback(_min, getFormattedText(_min)); _visibleLabels.add(label); @@ -674,16 +664,17 @@ class RenderBaseSlider extends RenderProxyBox unformattedLabels?.add( isDateTime ? _max.millisecondsSinceEpoch.toDouble() : _max.toDouble()); - labelPosition = getFactorFromValue(actualMin) * - (sliderType == SliderType.vertical - ? actualTrackRect.height - : actualTrackRect.width); - _majorTickPositions.add(labelPosition); - labelPosition = getFactorFromValue(actualMax) * - (sliderType == SliderType.vertical - ? actualTrackRect.height - : actualTrackRect.width); - _majorTickPositions.add(labelPosition); + if (sliderType == SliderType.horizontal) { + _majorTickPositions + .add(getFactorFromValue(actualMin) * actualTrackRect.width); + _majorTickPositions + .add(getFactorFromValue(actualMax) * actualTrackRect.width); + } else { + _majorTickPositions + .add(getFactorFromValue(actualMin) * actualTrackRect.height); + _majorTickPositions + .add(getFactorFromValue(actualMax) * actualTrackRect.height); + } } void generateMinorTicks() { @@ -741,20 +732,22 @@ class RenderBaseSlider extends RenderProxyBox double _updateMinorTicksPosition(double value) { return getFactorFromValue(value) * - (sliderType == SliderType.vertical - ? actualTrackRect.height - : actualTrackRect.width); + (sliderType == SliderType.horizontal + ? actualTrackRect.width + : actualTrackRect.height); } void _generateNumericMinorTicks() { final int majorTicksCount = _majorTickPositions.length; for (int i = 0; i <= majorTicksCount - 1; i++) { - final double minorPositionDiff = ((i + 1 < majorTicksCount - ? _majorTickPositions[i + 1] - : (sliderType == SliderType.vertical - ? actualTrackRect.height - : actualTrackRect.width)) - - _majorTickPositions[i]) / + final double minorPositionDiff = (i + 1 < majorTicksCount + ? _majorTickPositions[i + 1] - _majorTickPositions[i] + : ((sliderType == SliderType.horizontal + ? actualTrackRect.width + : actualTrackRect.height) - + (isInversed + ? _majorTickPositions[0] + : _majorTickPositions[majorTicksCount - 1]))) / (_minorTicksPerInterval + 1); for (int j = 1; j <= _minorTicksPerInterval; j++) { _minorTickPositions.add(_majorTickPositions[i] + j * minorPositionDiff); @@ -814,17 +807,20 @@ class RenderBaseSlider extends RenderProxyBox } dynamic getValueFromPosition(double position) { - double valueFactor = sliderType == SliderType.vertical - ? (1 - (actualTrackRect.height - position) / actualTrackRect.height) - .clamp(0.0, 1.0) - : ((position - actualTrackRect.left) / actualTrackRect.width) - .clamp(0.0, 1.0); - if (sliderType != SliderType.vertical) { - if (_textDirection == TextDirection.rtl) { + double valueFactor; + if (sliderType == SliderType.horizontal) { + valueFactor = (position - actualTrackRect.left) / actualTrackRect.width; + if (_isInversed) { + valueFactor = 1.0 - valueFactor; + } + } else { + valueFactor = + (actualTrackRect.height - position) / actualTrackRect.height; + if (!_isInversed) { valueFactor = 1.0 - valueFactor; } } - final dynamic actualValue = getValueFromFactor(valueFactor); + final dynamic actualValue = getValueFromFactor(valueFactor.clamp(0.0, 1.0)); return getActualValue(valueInDouble: actualValue); } @@ -836,15 +832,15 @@ class RenderBaseSlider extends RenderProxyBox if (isDiscrete) { if (!isDateTime) { final double maxMinDiff = getNumerizedValue(_max - _min); - final double factorValue = (getFactorFromValue(valueInDouble ?? value) * + double factorValue = (getFactorFromValue(valueInDouble ?? value) * (maxMinDiff / _stepSize!)) .round() / (maxMinDiff / _stepSize!); - value = getValueFromFactor(((sliderType == SliderType.horizontal && - _textDirection == TextDirection.rtl) - ? (1 - factorValue) - : factorValue) - .clamp(0.0, 1.0)); + if (_isInversed) { + factorValue = 1.0 - factorValue; + } + + value = getValueFromFactor(factorValue.clamp(0.0, 1.0)); } else { // ignore: avoid_as DateTime currentDate = _min as DateTime; @@ -891,15 +887,15 @@ class RenderBaseSlider extends RenderProxyBox } double getFactorFromCurrentPosition() { - final double factor = sliderType == SliderType.vertical - ? ((actualTrackRect.bottom - mainAxisOffset) / actualTrackRect.height) + final double factor = (sliderType == SliderType.horizontal) + ? ((mainAxisOffset - actualTrackRect.left) / actualTrackRect.width) .clamp(0.0, 1.0) - : ((mainAxisOffset - actualTrackRect.left) / actualTrackRect.width) + : ((actualTrackRect.bottom - mainAxisOffset) / actualTrackRect.height) .clamp(0.0, 1.0); - if (sliderType == SliderType.vertical) { - return factor; + if (_isInversed) { + return 1.0 - factor; } else { - return (_textDirection == TextDirection.rtl) ? 1.0 - factor : factor; + return factor; } } @@ -935,16 +931,21 @@ class RenderBaseSlider extends RenderProxyBox return Rect.fromLTRB(left, top, right, bottom); } - Rect getPaddleTooltipRect(TextPainter textPainter, Offset offset, - Offset thumbCenter, Rect trackRect, SfSliderThemeData themeData) { + Rect getPaddleTooltipRect( + TextPainter textPainter, + double thumbRadius, + Offset offset, + Offset thumbCenter, + Rect trackRect, + SfSliderThemeData themeData) { final double paddleTooltipRadius = textPainter.height > minPaddleTopCircleRadius ? textPainter.height : minPaddleTopCircleRadius; final double topNeckRadius = paddleTooltipRadius - neckDifference; final double bottomNeckRadius = - themeData.thumbRadius > minPaddleTopCircleRadius * moveNeckValue - ? themeData.thumbRadius - neckDifference + thumbRadius > minPaddleTopCircleRadius * moveNeckValue + ? thumbRadius - neckDifference : 4.0; final double halfTextWidth = textPainter.width / 2 + textPadding; final double halfPaddleWidth = halfTextWidth > paddleTooltipRadius @@ -960,7 +961,7 @@ class RenderBaseSlider extends RenderProxyBox topNeckRadius - offset.dy * (1.0 - moveNeckValue) - bottomNeckRadius; - final double bottom = thumbCenter.dy + themeData.thumbRadius; + final double bottom = thumbCenter.dy + thumbRadius; return Rect.fromLTRB(left, top, right, bottom); } @@ -996,31 +997,38 @@ class RenderBaseSlider extends RenderProxyBox dynamic value, SfRangeValues? values) { int dateTimePos = 0; + bool isActive; final double dx = - sliderType == SliderType.vertical ? trackRect.bottom : trackRect.left; + sliderType == SliderType.horizontal ? trackRect.left : trackRect.bottom; final double dy = - sliderType == SliderType.vertical ? trackRect.left : trackRect.top; - final double halfTrackHeight = sliderType == SliderType.vertical - ? trackRect.width / 2 - : trackRect.height / 2; - - final bool isActive = startThumbCenter != null - ? (sliderType == SliderType.vertical - ? offset.dy <= startThumbCenter.dy && - offset.dy >= endThumbCenter!.dy - : offset.dx >= startThumbCenter.dx && - offset.dx <= endThumbCenter!.dx) - : (sliderType == SliderType.vertical - ? offset.dy <= thumbCenter!.dy - : offset.dx <= thumbCenter!.dx); + sliderType == SliderType.horizontal ? trackRect.top : trackRect.left; + final double halfTrackHeight = sliderType == SliderType.horizontal + ? trackRect.height / 2 + : trackRect.width / 2; + if (startThumbCenter != null) { + if (sliderType == SliderType.horizontal) { + isActive = + offset.dx >= startThumbCenter.dx && offset.dx <= endThumbCenter!.dx; + } else { + isActive = + offset.dy <= startThumbCenter.dy && offset.dy >= endThumbCenter!.dy; + } + } else { + if (sliderType == SliderType.horizontal) { + isActive = offset.dx <= thumbCenter!.dx; + } else { + isActive = offset.dy <= thumbCenter!.dy; + } + } + final double dividerRadius = _dividerShape .getPreferredSize(_sliderThemeData, isActive: isActive) .width / 2; - final double tickRadius = sliderType == SliderType.vertical - ? _tickShape.getPreferredSize(_sliderThemeData).height / 2 - : _tickShape.getPreferredSize(_sliderThemeData).width / 2; + final double tickRadius = sliderType == SliderType.horizontal + ? _tickShape.getPreferredSize(_sliderThemeData).width / 2 + : _tickShape.getPreferredSize(_sliderThemeData).height / 2; // ignore: avoid_as double textValue = isDateTime ? 0.0 : _min.toDouble() as double; @@ -1095,34 +1103,33 @@ class RenderBaseSlider extends RenderProxyBox // Drawing label. if (_showLabels) { - final double dx = sliderType == SliderType.vertical - ? trackRect.bottom - : trackRect.left; + final double dx = sliderType == SliderType.horizontal + ? trackRect.left + : trackRect.bottom; - final bool isRTL = _textDirection == TextDirection.rtl; - double offsetX = sliderType == SliderType.vertical - ? dx - tickPosition - : dx + tickPosition; + double offsetX = sliderType == SliderType.horizontal + ? dx + tickPosition + : dx - tickPosition; if (_labelPlacement == LabelPlacement.betweenTicks) { - if (sliderType == SliderType.vertical) { - if (dateTimePos + 1 <= divisions!) { - offsetX -= - ((_majorTickPositions[dateTimePos + 1]) - tickPosition) / 2; - } else { - break; - } - } else { + if (sliderType == SliderType.horizontal) { offsetX += ((dateTimePos + 1 <= divisions! ? _majorTickPositions[dateTimePos + 1] - : (isRTL ? trackRect.left : trackRect.width)) - + : (_isInversed ? trackRect.left : trackRect.width)) - tickPosition) / 2; - if (isRTL + if (_isInversed ? offsetX <= trackRect.left : offsetX - dx >= trackRect.width) { break; } + } else { + if (dateTimePos + 1 <= divisions!) { + offsetX -= + ((_majorTickPositions[dateTimePos + 1]) - tickPosition) / 2; + } else { + break; + } } } @@ -1171,34 +1178,48 @@ class RenderBaseSlider extends RenderProxyBox dynamic value, SfRangeValues? values, Animation stateAnimation) { - Offset actualTickOffset = sliderType == SliderType.vertical - ? Offset(dy + trackRect.width, dx - tickPosition) + - (_sliderThemeData.tickOffset ?? Offset.zero) - : Offset(dx + tickPosition, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? Offset.zero); - - if (_majorTickPositions[dateTimePos] == 0.0) { - actualTickOffset = sliderType == SliderType.vertical - ? Offset(dy + trackRect.width, dx - tickPosition - tickRadius) + - (_sliderThemeData.tickOffset ?? Offset.zero) - : Offset(dx + tickPosition + tickRadius, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? Offset.zero); - } + Offset actualTickOffset; - // Due to floating-point operations, last [_majorTickPosition] is greater - // than [trackRect.height] or [trackRect.width]. This happens in some - // specific layouts alone. To avoid this, we limit it to 8 decimal points. - // (e.g. Expected [_majorTickPosition] = 100.0909890118 - // Current [_majorTickPosition] = 100.0909890121) - else if (_majorTickPositions[dateTimePos].toStringAsFixed(8) == - (sliderType == SliderType.vertical - ? trackRect.height.toStringAsFixed(8) - : trackRect.width.toStringAsFixed(8))) { - actualTickOffset = sliderType == SliderType.vertical - ? Offset(dy + trackRect.width, dx - tickPosition + tickRadius) + - (_sliderThemeData.tickOffset ?? Offset.zero) - : Offset(dx + tickPosition - tickRadius, dy + trackRect.height) + - (_sliderThemeData.tickOffset ?? Offset.zero); + if (sliderType == SliderType.horizontal) { + if (_majorTickPositions[dateTimePos] == 0.0) { + actualTickOffset = + Offset(dx + tickPosition + tickRadius, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero); + } + // Due to floating-point operations, last [_majorTickPosition] is greater + // than [trackRect.height] or [trackRect.width]. This happens in some + // specific layouts alone. To avoid this, we limit it to 8 decimal points. + // (e.g. Expected [_majorTickPosition] = 100.0909890118 + // Current [_majorTickPosition] = 100.0909890121) + else if (_majorTickPositions[dateTimePos].toStringAsFixed(8) == + trackRect.width.toStringAsFixed(8)) { + actualTickOffset = + Offset(dx + tickPosition - tickRadius, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero); + } else { + actualTickOffset = Offset(dx + tickPosition, dy + trackRect.height) + + (_sliderThemeData.tickOffset ?? Offset.zero); + } + } else { + if (_majorTickPositions[dateTimePos] == 0.0) { + actualTickOffset = + Offset(dy + trackRect.width, dx - tickPosition - tickRadius) + + (_sliderThemeData.tickOffset ?? Offset.zero); + } + // Due to floating-point operations, last [_majorTickPosition] is greater + // than [trackRect.height] or [trackRect.width]. This happens in some + // specific layouts alone. To avoid this, we limit it to 8 decimal points. + // (e.g. Expected [_majorTickPosition] = 100.0909890118 + // Current [_majorTickPosition] = 100.0909890121) + else if (_majorTickPositions[dateTimePos].toStringAsFixed(8) == + trackRect.height.toStringAsFixed(8)) { + actualTickOffset = + Offset(dy + trackRect.width, dx - tickPosition + tickRadius) + + (_sliderThemeData.tickOffset ?? Offset.zero); + } else { + actualTickOffset = Offset(dy + trackRect.width, dx - tickPosition) + + (_sliderThemeData.tickOffset ?? Offset.zero); + } } _tickShape.paint(context, actualTickOffset, thumbCenter, startThumbCenter, endThumbCenter, @@ -1223,14 +1244,14 @@ class RenderBaseSlider extends RenderProxyBox SfRangeValues? values, Animation stateAnimation) { if (currentMinorTickPosition < - (sliderType == SliderType.vertical - ? trackRect.height - : trackRect.width) && + (sliderType == SliderType.horizontal + ? trackRect.width + : trackRect.height) && currentMinorTickPosition > 0) { - final Offset actualTickOffset = sliderType == SliderType.vertical - ? Offset(dy + trackRect.width, dx - currentMinorTickPosition) + + final Offset actualTickOffset = sliderType == SliderType.horizontal + ? Offset(dx + currentMinorTickPosition, dy + trackRect.height) + (_sliderThemeData.tickOffset ?? Offset.zero) - : Offset(dx + currentMinorTickPosition, dy + trackRect.height) + + : Offset(dy + trackRect.width, dx - currentMinorTickPosition) + (_sliderThemeData.tickOffset ?? Offset.zero); _minorTickShape.paint(context, actualTickOffset, thumbCenter, startThumbCenter, endThumbCenter, @@ -1258,28 +1279,44 @@ class RenderBaseSlider extends RenderProxyBox dynamic value, SfRangeValues? values, Animation stateAnimation) { - Offset dividerCenter = sliderType == SliderType.vertical - ? Offset(dy + halfTrackHeight, dx - tickPosition) - : Offset(dx + tickPosition, dy + halfTrackHeight); - if (_majorTickPositions[_dateTimePos] == 0.0) { - dividerCenter = sliderType == SliderType.vertical - ? Offset(dy + halfTrackHeight, dx - tickPosition - dividerRadius) - : Offset(dx + tickPosition + dividerRadius, dy + halfTrackHeight); - } + Offset dividerCenter; - // Due to floating-point operations, last [_majorTickPosition] is greater - // than [trackRect.height] or [trackRect.width]. This happens in some - // specific layouts alone. To avoid this, we limit it to 8 decimal points. - // (e.g. Expected [_majorTickPosition] = 100.0909890118 - // Current [_majorTickPosition] = 100.0909890121) - else if (_majorTickPositions[_dateTimePos].toStringAsFixed(8) == - (sliderType == SliderType.vertical - ? trackRect.height.toStringAsFixed(8) - : trackRect.width.toStringAsFixed(8))) { - dividerCenter = sliderType == SliderType.vertical - ? Offset(dy + halfTrackHeight, dx - tickPosition + dividerRadius) - : Offset(dx + tickPosition - dividerRadius, dy + halfTrackHeight); + if (sliderType == SliderType.horizontal) { + if (_majorTickPositions[_dateTimePos] == 0.0) { + dividerCenter = + Offset(dx + tickPosition + dividerRadius, dy + halfTrackHeight); + } + // Due to floating-point operations, last [_majorTickPosition] is greater + // than [trackRect.height] or [trackRect.width]. This happens in some + // specific layouts alone. To avoid this, we limit it to 8 decimal points. + // (e.g. Expected [_majorTickPosition] = 100.0909890118 + // Current [_majorTickPosition] = 100.0909890121) + else if (_majorTickPositions[_dateTimePos].toStringAsFixed(8) == + trackRect.width.toStringAsFixed(8)) { + dividerCenter = + Offset(dx + tickPosition - dividerRadius, dy + halfTrackHeight); + } else { + dividerCenter = Offset(dx + tickPosition, dy + halfTrackHeight); + } + } else { + if (_majorTickPositions[_dateTimePos] == 0.0) { + dividerCenter = + Offset(dy + halfTrackHeight, dx - tickPosition - dividerRadius); + } + // Due to floating-point operations, last [_majorTickPosition] is greater + // than [trackRect.height] or [trackRect.width]. This happens in some + // specific layouts alone. To avoid this, we limit it to 8 decimal points. + // (e.g. Expected [_majorTickPosition] = 100.0909890118 + // Current [_majorTickPosition] = 100.0909890121) + else if (_majorTickPositions[_dateTimePos].toStringAsFixed(8) == + trackRect.height.toStringAsFixed(8)) { + dividerCenter = + Offset(dy + halfTrackHeight, dx - tickPosition + dividerRadius); + } else { + dividerCenter = Offset(dy + halfTrackHeight, dx - tickPosition); + } } + _dividerShape.paint( context, dividerCenter, thumbCenter, startThumbCenter, endThumbCenter, parentBox: this, @@ -1306,12 +1343,12 @@ class RenderBaseSlider extends RenderProxyBox Animation stateAnimation, double offsetX) { final double dy = - sliderType == SliderType.vertical ? trackRect.left : trackRect.top; + sliderType == SliderType.horizontal ? trackRect.top : trackRect.left; final String labelText = _visibleLabels[_dateTimePos]; - final Offset actualLabelOffset = sliderType == SliderType.vertical - ? Offset(dy + trackRect.width + actualTickWidth, offsetX) + + final Offset actualLabelOffset = sliderType == SliderType.horizontal + ? Offset(offsetX, dy + trackRect.height + actualTickHeight) + (_sliderThemeData.labelOffset ?? Offset.zero) - : Offset(offsetX, dy + trackRect.height + actualTickHeight) + + : Offset(dy + trackRect.width + actualTickWidth, offsetX) + (_sliderThemeData.labelOffset ?? Offset.zero); _drawText(context, actualLabelOffset, thumbCenter, startThumbCenter, @@ -1335,31 +1372,42 @@ class RenderBaseSlider extends RenderProxyBox required TextPainter textPainter, required TextDirection textDirection}) { bool isInactive; - switch (textDirection) { - case TextDirection.ltr: - // Added this condition to check whether consider single thumb or - // two thumbs for finding inactive range. - isInactive = startThumbCenter != null - ? (sliderType == SliderType.vertical - ? center.dy > startThumbCenter.dy || - center.dy < endThumbCenter!.dy - : center.dx < startThumbCenter.dx || - center.dx > endThumbCenter!.dx) - : (sliderType == SliderType.vertical - ? center.dy < thumbCenter!.dy - : center.dx > thumbCenter!.dx); - break; - case TextDirection.rtl: - isInactive = startThumbCenter != null - ? (sliderType == SliderType.vertical - ? center.dy > startThumbCenter.dy || - center.dy < endThumbCenter!.dy - : center.dx > startThumbCenter.dx || - center.dx < endThumbCenter!.dx) - : (sliderType == SliderType.vertical - ? center.dy < thumbCenter!.dy - : center.dx < thumbCenter!.dx); - break; + if (sliderType == SliderType.horizontal) { + // Added this condition to check whether consider single thumb or + // two thumbs for finding inactive range. + if (startThumbCenter != null) { + if (!_isInversed) { + isInactive = + center.dx < startThumbCenter.dx || center.dx > endThumbCenter!.dx; + } else { + isInactive = + center.dx > startThumbCenter.dx || center.dx < endThumbCenter!.dx; + } + } else { + if (!_isInversed) { + isInactive = center.dx > thumbCenter!.dx; + } else { + isInactive = center.dx < thumbCenter!.dx; + } + } + } else { + // Added this condition to check whether consider single thumb or + // two thumbs for finding inactive range. + if (startThumbCenter != null) { + if (!_isInversed) { + isInactive = + center.dy > startThumbCenter.dy || center.dy < endThumbCenter!.dy; + } else { + isInactive = + center.dy < startThumbCenter.dy || center.dy > endThumbCenter!.dy; + } + } else { + if (!_isInversed) { + isInactive = center.dy < thumbCenter!.dy; + } else { + isInactive = center.dy > thumbCenter!.dy; + } + } } final TextSpan textSpan = TextSpan( @@ -1370,12 +1418,12 @@ class RenderBaseSlider extends RenderProxyBox ); textPainter.text = textSpan; textPainter.layout(); - if (sliderType == SliderType.vertical) { - textPainter.paint(context.canvas, - Offset(center.dx, center.dy - textPainter.height / 2)); - } else { + if (sliderType == SliderType.horizontal) { textPainter.paint( context.canvas, Offset(center.dx - textPainter.width / 2, center.dy)); + } else { + textPainter.paint(context.canvas, + Offset(center.dx, center.dy - textPainter.height / 2)); } } @@ -1435,7 +1483,7 @@ class RenderBaseSlider extends RenderProxyBox return (value as DateTime).millisecondsSinceEpoch.toDouble(); } // ignore: avoid_as - return value as double; + return value.toDouble() as double; } // This method is only applicable for vertical sliders @@ -1500,34 +1548,45 @@ class RenderBaseSlider extends RenderProxyBox updateTextPainter(); } + @override + void setupParentData(RenderObject child) { + if (child.parentData is! BoxParentData) { + child.parentData = BoxParentData(); + } + } + @override void performLayout() { // Here, the required width for the rendering of the // vertical sliders is also considered as actualHeight. - actualHeight = math.max( - 2 * trackOffset.dy, - trackOffset.dy + - maxTrackHeight / 2 + - (sliderType == SliderType.vertical - ? math.max(actualTickWidth, actualMinorTickWidth) + - _maximumLabelWidth() + - actualLabelOffset - : math.max(actualTickHeight, actualMinorTickHeight) + - actualLabelHeight)); - - size = sliderType == SliderType.vertical - ? Size( - constraints.hasBoundedWidth ? constraints.maxWidth : actualHeight, - constraints.hasBoundedHeight - ? constraints.maxHeight - : minTrackWidth + 2 * trackOffset.dx) - : Size( - constraints.hasBoundedWidth - ? constraints.maxWidth - : minTrackWidth + 2 * trackOffset.dx, - constraints.hasBoundedHeight - ? constraints.maxHeight - : actualHeight); + if (sliderType == SliderType.horizontal) { + actualHeight = math.max( + 2 * trackOffset.dy, + trackOffset.dy + + maxTrackHeight / 2 + + math.max(actualTickHeight, actualMinorTickHeight) + + actualLabelHeight); + + size = Size( + constraints.hasBoundedWidth + ? constraints.maxWidth + : minTrackWidth + 2 * trackOffset.dx, + constraints.hasBoundedHeight ? constraints.maxHeight : actualHeight); + } else { + actualHeight = math.max( + 2 * trackOffset.dx, + trackOffset.dx + + maxTrackHeight / 2 + + math.max(actualTickWidth, actualMinorTickWidth) + + _maximumLabelWidth() + + actualLabelOffset); + size = Size( + constraints.hasBoundedWidth ? constraints.maxWidth : actualHeight, + constraints.hasBoundedHeight + ? constraints.maxHeight + : minTrackWidth + 2 * trackOffset.dy); + } + generateLabelsAndMajorTicks(); generateMinorTicks(); } @@ -1536,10 +1595,10 @@ class RenderBaseSlider extends RenderProxyBox void handleEvent(PointerEvent event, HitTestEntry entry) { // Checked [_isInteractionEnd] for avoiding multi touch. if (isInteractionEnd && event.down && event is PointerDownEvent) { - if (sliderType == SliderType.vertical) { - verticalDragGestureRecognizer!.addPointer(event); - } else { + if (sliderType == SliderType.horizontal) { horizontalDragGestureRecognizer!.addPointer(event); + } else { + verticalDragGestureRecognizer!.addPointer(event); } tapGestureRecognizer.addPointer(event); } diff --git a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart index 4a3aeaf6a..0ee0a8f49 100644 --- a/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart +++ b/packages/syncfusion_flutter_sliders/lib/src/slider_shapes.dart @@ -23,12 +23,27 @@ class SfTrackShape { Rect getPreferredRect( RenderBox parentBox, SfSliderThemeData themeData, Offset offset, {bool? isActive}) { - final double maxRadius = math.max(themeData.overlayRadius, - math.max(themeData.thumbRadius, themeData.tickSize!.width / 2)); + final Size overlayPreferredSize = (parentBox as RenderBaseSlider) + .overlayShape + .getPreferredSize(themeData); + final Size thumbPreferredSize = + parentBox.thumbShape.getPreferredSize(themeData); + final Size tickPreferredSize = + parentBox.tickShape.getPreferredSize(themeData); + double maxRadius; + if (_isVertical(parentBox)) { + maxRadius = math.max( + overlayPreferredSize.height / 2, + math.max( + thumbPreferredSize.height / 2, tickPreferredSize.height / 2)); + } else { + maxRadius = math.max(overlayPreferredSize.width / 2, + math.max(thumbPreferredSize.width / 2, tickPreferredSize.width / 2)); + } final double maxTrackHeight = math.max(themeData.activeTrackHeight, themeData.inactiveTrackHeight); // ignore: avoid_as - if (_isVertical(parentBox as RenderBaseSlider)) { + if (_isVertical(parentBox)) { double left = offset.dx; if (isActive != null) { left += isActive @@ -110,28 +125,31 @@ class SfTrackShape { context, activeTrackRect, // ignore: avoid_as - isVertical: _isVertical(parentBox as RenderBaseSlider)); + isVertical: _isVertical(parentBox as RenderBaseSlider), + isInversed: parentBox.isInversed); } void _drawTrackRect( - TextDirection? textDirection, - Offset? thumbCenter, - Offset? startThumbCenter, - Offset? endThumbCenter, - Paint activePaint, - Paint inactivePaint, - Rect inactiveTrackRect, - Radius radius, - PaintingContext context, - Rect activeTrackRect, - {required bool isVertical}) { + TextDirection? textDirection, + Offset? thumbCenter, + Offset? startThumbCenter, + Offset? endThumbCenter, + Paint activePaint, + Paint inactivePaint, + Rect inactiveTrackRect, + Radius radius, + PaintingContext context, + Rect activeTrackRect, { + required bool isVertical, + required bool isInversed, + }) { Offset? leftThumbCenter; Offset? rightThumbCenter; Paint? leftTrackPaint; Paint? rightTrackPaint; Rect? leftTrackRect; Rect? rightTrackRect; - if (textDirection == TextDirection.rtl && !isVertical) { + if (isInversed) { if (startThumbCenter != null) { // For range slider and range selector widget. leftThumbCenter = endThumbCenter; @@ -180,23 +198,7 @@ class SfTrackShape { Paint inactivePaint, {required bool isVertical}) { RRect inactiveTrackRRect; - if (isVertical) { - Rect trackRect = Rect.fromLTRB(activeTrackRect.left, thumbCenter.dy, - activeTrackRect.right, activeTrackRect.bottom); - final RRect activeTrackRRect = RRect.fromRectAndCorners(trackRect, - bottomRight: radius, bottomLeft: radius); - context.canvas.drawRRect(activeTrackRRect, activePaint); - // Drawing inactive track. - trackRect = Rect.fromLTRB(inactiveTrackRect.left, inactiveTrackRect.top, - inactiveTrackRect.right, thumbCenter.dy); - inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, - topLeft: radius, - topRight: radius, - bottomLeft: Radius.zero, - bottomRight: Radius.zero); - - context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); - } else { + if (!isVertical) { // Drawing active track. Rect trackRect = Rect.fromLTRB(activeTrackRect.left, activeTrackRect.top, thumbCenter.dx, activeTrackRect.bottom); @@ -217,46 +219,38 @@ class SfTrackShape { bottomRight: radius); context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); - } - } - - void _drawRangeSliderTrack( - Rect inactiveTrackRect, - Offset startThumbCenter, - Radius radius, - PaintingContext context, - Paint inactivePaint, - Rect activeTrackRect, - Offset endThumbCenter, - Paint activePaint, - {bool isVertical = false}) { - RRect inactiveTrackRRect; - if (isVertical) { - // Drawing inactive track - Rect trackRect = Rect.fromLTRB( - inactiveTrackRect.left, - startThumbCenter.dy, - inactiveTrackRect.right, - inactiveTrackRect.bottom); - inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, - bottomLeft: radius, bottomRight: radius); - context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); - - // Drawing active track. - final Rect activeTrackRRect = Rect.fromLTRB(activeTrackRect.left, - startThumbCenter.dy, activeTrackRect.right, endThumbCenter.dy); - context.canvas.drawRect(activeTrackRRect, activePaint); + } else { + Rect trackRect = Rect.fromLTRB(activeTrackRect.left, thumbCenter.dy, + activeTrackRect.right, activeTrackRect.bottom); + final RRect activeTrackRRect = RRect.fromRectAndCorners(trackRect, + bottomRight: radius, bottomLeft: radius); + context.canvas.drawRRect(activeTrackRRect, activePaint); // Drawing inactive track. trackRect = Rect.fromLTRB(inactiveTrackRect.left, inactiveTrackRect.top, - inactiveTrackRect.right, endThumbCenter.dy); + inactiveTrackRect.right, thumbCenter.dy); inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, topLeft: radius, topRight: radius, bottomLeft: Radius.zero, bottomRight: Radius.zero); context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); - } else { + } + } + + void _drawRangeSliderTrack( + Rect inactiveTrackRect, + Offset startThumbCenter, + Radius radius, + PaintingContext context, + Paint inactivePaint, + Rect activeTrackRect, + Offset endThumbCenter, + Paint activePaint, { + bool isVertical = false, + }) { + RRect inactiveTrackRRect; + if (!isVertical) { // Drawing inactive track. Rect trackRect = Rect.fromLTRB(inactiveTrackRect.left, inactiveTrackRect.top, startThumbCenter.dx, inactiveTrackRect.bottom); @@ -281,6 +275,31 @@ class SfTrackShape { bottomLeft: Radius.zero, bottomRight: radius); context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + } else { + // Drawing inactive track + Rect trackRect = Rect.fromLTRB( + inactiveTrackRect.left, + startThumbCenter.dy, + inactiveTrackRect.right, + inactiveTrackRect.bottom); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + bottomLeft: radius, bottomRight: radius); + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); + + // Drawing active track. + final Rect activeTrackRRect = Rect.fromLTRB(activeTrackRect.left, + startThumbCenter.dy, activeTrackRect.right, endThumbCenter.dy); + context.canvas.drawRect(activeTrackRRect, activePaint); + + // Drawing inactive track. + trackRect = Rect.fromLTRB(inactiveTrackRect.left, inactiveTrackRect.top, + inactiveTrackRect.right, endThumbCenter.dy); + inactiveTrackRRect = RRect.fromRectAndCorners(trackRect, + topLeft: radius, + topRight: radius, + bottomLeft: Radius.zero, + bottomRight: Radius.zero); + context.canvas.drawRRect(inactiveTrackRRect, inactivePaint); } } } @@ -291,7 +310,7 @@ class SfThumbShape { /// Enables subclasses to provide constant constructors. const SfThumbShape(); - bool _isThumbOverlap(RenderBaseSlider parentBox) { + bool _isThumbOverlapping(RenderBaseSlider parentBox) { return parentBox.showOverlappingThumbStroke; } @@ -312,7 +331,7 @@ class SfThumbShape { required TextDirection textDirection, required SfThumb? thumb}) { final double radius = getPreferredSize(themeData).width / 2; - final bool isThumbStroke = themeData.thumbStrokeColor != null && + final bool hasThumbStroke = themeData.thumbStrokeColor != null && themeData.thumbStrokeColor != Colors.transparent && themeData.thumbStrokeWidth != null && themeData.thumbStrokeWidth! > 0; @@ -326,8 +345,7 @@ class SfThumbShape { (parentRenderBox.activeThumb == thumb || thumb == null) && parentRenderBox.currentPointerType != null && parentRenderBox.currentPointerType != PointerType.up; - path.addOval( - Rect.fromCircle(center: center, radius: themeData.thumbRadius)); + path.addOval(Rect.fromCircle(center: center, radius: radius)); final double thumbElevation = isThumbActive ? parentRenderBox.thumbElevationTween.evaluate(enableAnimation) : defaultElevation; @@ -335,22 +353,19 @@ class SfThumbShape { context.canvas.drawShadow(path, shadowColor, thumbElevation, true); } - if (!isThumbStroke && - _isThumbOverlap(parentBox) && - themeData.thumbColor != Colors.transparent) { - final Color? thumbOverlappingStrokeColor = - themeData is SfRangeSliderThemeData - ? themeData.overlappingThumbStrokeColor - : null; - if (thumbOverlappingStrokeColor != null) { - final Paint strokePaint = Paint() - ..color = thumbOverlappingStrokeColor - ..style = PaintingStyle.stroke - ..strokeWidth = 1.0; - - context.canvas.drawCircle( - center, getPreferredSize(themeData).width / 2, strokePaint); - } + if (themeData is SfRangeSliderThemeData && + !hasThumbStroke && + _isThumbOverlapping(parentBox) && + themeData.thumbColor != Colors.transparent && + themeData.overlappingThumbStrokeColor != null) { + context.canvas.drawCircle( + center, + radius, + Paint() + ..color = themeData.overlappingThumbStrokeColor! + ..style = PaintingStyle.stroke + ..isAntiAlias = true + ..strokeWidth = 1.0); } if (paint == null) { @@ -361,8 +376,7 @@ class SfThumbShape { .evaluate(enableAnimation)!; } - context.canvas - .drawCircle(center, getPreferredSize(themeData).width / 2, paint); + context.canvas.drawCircle(center, radius, paint); if (child != null) { context.paintChild( child, @@ -373,17 +387,18 @@ class SfThumbShape { if (themeData.thumbStrokeColor != null && themeData.thumbStrokeWidth != null && themeData.thumbStrokeWidth! > 0) { + final Paint strokePaint = Paint() + ..color = themeData.thumbStrokeColor! + ..style = PaintingStyle.stroke + ..strokeWidth = themeData.thumbStrokeWidth! > radius + ? radius + : themeData.thumbStrokeWidth!; context.canvas.drawCircle( center, themeData.thumbStrokeWidth! > radius ? radius / 2 : radius - themeData.thumbStrokeWidth! / 2, - paint - ..color = themeData.thumbStrokeColor! - ..style = PaintingStyle.stroke - ..strokeWidth = themeData.thumbStrokeWidth! > radius - ? radius - : themeData.thumbStrokeWidth!); + strokePaint); } } } @@ -418,35 +433,44 @@ class SfDividerShape { required Animation enableAnimation, required TextDirection textDirection}) { late bool isActive; - switch (textDirection) { - case TextDirection.ltr: - // Added this condition to check whether consider single thumb or - // two thumbs for finding active range. - isActive = startThumbCenter != null - // ignore: avoid_as - ? (_isVertical(parentBox as RenderBaseSlider) - ? center.dy <= startThumbCenter.dy && - center.dy >= endThumbCenter!.dy - : center.dx >= startThumbCenter.dx && - center.dx <= endThumbCenter!.dx) - // ignore: avoid_as - : (_isVertical(parentBox as RenderBaseSlider) - ? center.dy >= thumbCenter!.dy - : center.dx <= thumbCenter!.dx); - break; - case TextDirection.rtl: - isActive = startThumbCenter != null - // ignore: avoid_as - ? (_isVertical(parentBox as RenderBaseSlider) - ? center.dy <= startThumbCenter.dy && - center.dy >= endThumbCenter!.dy - : center.dx >= endThumbCenter!.dx && - center.dx <= startThumbCenter.dx) - // ignore: avoid_as - : (_isVertical(parentBox as RenderBaseSlider) - ? center.dy >= thumbCenter!.dy - : center.dx >= thumbCenter!.dx); - break; + final bool isVertical = _isVertical(parentBox as RenderBaseSlider); + + if (!isVertical) { + // Added this condition to check whether consider single thumb or + // two thumbs for finding active range. + if (startThumbCenter != null) { + if (!parentBox.isInversed) { + isActive = center.dx >= startThumbCenter.dx && + center.dx <= endThumbCenter!.dx; + } else { + isActive = center.dx >= endThumbCenter!.dx && + center.dx <= startThumbCenter.dx; + } + } else { + if (!parentBox.isInversed) { + isActive = center.dx <= thumbCenter!.dx; + } else { + isActive = center.dx >= thumbCenter!.dx; + } + } + } else { + // Added this condition to check whether consider single thumb or + // two thumbs for finding active range. + if (startThumbCenter != null) { + if (!parentBox.isInversed) { + isActive = center.dy <= startThumbCenter.dy && + center.dy >= endThumbCenter!.dy; + } else { + isActive = center.dy >= startThumbCenter.dy && + center.dy <= endThumbCenter!.dy; + } + } else { + if (!parentBox.isInversed) { + isActive = center.dy >= thumbCenter!.dy; + } else { + isActive = center.dy <= thumbCenter!.dy; + } + } } if (paint == null) { @@ -549,35 +573,44 @@ class SfTickShape { required TextDirection textDirection}) { bool isInactive = false; final Size tickSize = getPreferredSize(themeData); - switch (textDirection) { - case TextDirection.ltr: - // Added this condition to check whether consider single thumb or - // two thumbs for finding inactive range. - isInactive = startThumbCenter != null - // ignore: avoid_as - ? (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy > startThumbCenter.dy || - offset.dy < endThumbCenter!.dy - : offset.dx < startThumbCenter.dx || - offset.dx > endThumbCenter!.dx) - // ignore: avoid_as - : (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy < thumbCenter!.dy - : offset.dx > thumbCenter!.dx); - break; - case TextDirection.rtl: - isInactive = startThumbCenter != null - // ignore: avoid_as - ? (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy > startThumbCenter.dy || - offset.dy < endThumbCenter!.dy - : offset.dx > startThumbCenter.dx || - offset.dx < endThumbCenter!.dx) - // ignore: avoid_as - : (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy < thumbCenter!.dy - : offset.dx < thumbCenter!.dx); - break; + final bool isVertical = _isVertical(parentBox as RenderBaseSlider); + + if (!isVertical) { + // Added this condition to check whether consider single thumb or + // two thumbs for finding active range. + if (startThumbCenter != null) { + if (!parentBox.isInversed) { + isInactive = + offset.dx < startThumbCenter.dx || offset.dx > endThumbCenter!.dx; + } else { + isInactive = + offset.dx > startThumbCenter.dx || offset.dx < endThumbCenter!.dx; + } + } else { + if (!parentBox.isInversed) { + isInactive = offset.dx > thumbCenter!.dx; + } else { + isInactive = offset.dx < thumbCenter!.dx; + } + } + } else { + // Added this condition to check whether consider single thumb or + // two thumbs for finding active range. + if (startThumbCenter != null) { + if (!parentBox.isInversed) { + isInactive = + offset.dy > startThumbCenter.dy || offset.dy < endThumbCenter!.dy; + } else { + isInactive = + offset.dy < startThumbCenter.dy || offset.dy > endThumbCenter!.dy; + } + } else { + if (!parentBox.isInversed) { + isInactive = offset.dy < thumbCenter!.dy; + } else { + isInactive = offset.dy > thumbCenter!.dy; + } + } } final Color begin = isInactive @@ -635,33 +668,44 @@ class SfMinorTickShape extends SfTickShape { required TextDirection textDirection}) { bool isInactive; final Size minorTickSize = getPreferredSize(themeData); - switch (textDirection) { - case TextDirection.ltr: - isInactive = startThumbCenter != null - // ignore: avoid_as - ? (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy > startThumbCenter.dy || - offset.dy < endThumbCenter!.dy - : offset.dx < startThumbCenter.dx || - offset.dx > endThumbCenter!.dx) - // ignore: avoid_as - : (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy < thumbCenter!.dy - : offset.dx > thumbCenter!.dx); - break; - case TextDirection.rtl: - isInactive = startThumbCenter != null - // ignore: avoid_as - ? (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy > startThumbCenter.dy || - offset.dy < endThumbCenter!.dy - : offset.dx > startThumbCenter.dx || - offset.dx < endThumbCenter!.dx) - // ignore: avoid_as - : (_isVertical(parentBox as RenderBaseSlider) - ? offset.dy < thumbCenter!.dy - : offset.dx < thumbCenter!.dx); - break; + final bool isVertical = _isVertical(parentBox as RenderBaseSlider); + + if (!isVertical) { + // Added this condition to check whether consider single thumb or + // two thumbs for finding active range. + if (startThumbCenter != null) { + if (!parentBox.isInversed) { + isInactive = + offset.dx < startThumbCenter.dx || offset.dx > endThumbCenter!.dx; + } else { + isInactive = + offset.dx > startThumbCenter.dx || offset.dx < endThumbCenter!.dx; + } + } else { + if (!parentBox.isInversed) { + isInactive = offset.dx > thumbCenter!.dx; + } else { + isInactive = offset.dx < thumbCenter!.dx; + } + } + } else { + // Added this condition to check whether consider single thumb or + // two thumbs for finding active range. + if (startThumbCenter != null) { + if (!parentBox.isInversed) { + isInactive = + offset.dy > startThumbCenter.dy || offset.dy < endThumbCenter!.dy; + } else { + isInactive = + offset.dy < startThumbCenter.dy || offset.dy > endThumbCenter!.dy; + } + } else { + if (!parentBox.isInversed) { + isInactive = offset.dy < thumbCenter!.dy; + } else { + isInactive = offset.dy > thumbCenter!.dy; + } + } } final Color begin = isInactive @@ -691,7 +735,7 @@ class SfPaddleTooltipShape extends SfTooltipShape { /// /// A class which is used to render paddle shape tooltip. const SfPaddleTooltipShape(); - bool _isTooltipOverlapStroke(RenderBaseSlider parentBox) { + bool _hasTooltipOverlapStroke(RenderBaseSlider parentBox) { return parentBox.showOverlappingTooltipStroke; } @@ -711,15 +755,19 @@ class SfPaddleTooltipShape extends SfTooltipShape { PaintingContext context, Animation animation, Paint? paint) { + final double thumbRadius = (parentBox as RenderBaseSlider) + .thumbShape + .getPreferredSize(sliderThemeData) + .width / + 2; final double paddleTopCircleRadius = textPainter.height > minPaddleTopCircleRadius ? textPainter.height : minPaddleTopCircleRadius; final double topNeckRadius = paddleTopCircleRadius - neckDifference; - final double bottomNeckRadius = - sliderThemeData.thumbRadius > defaultThumbRadius - ? sliderThemeData.thumbRadius - neckDifference * 2 - : minBottomNeckRadius; + final double bottomNeckRadius = thumbRadius > defaultThumbRadius + ? thumbRadius - neckDifference * 2 + : minBottomNeckRadius; final double halfTextWidth = textPainter.width / 2 + textPadding; final double paddleTopCircleX = halfTextWidth > paddleTopCircleRadius ? halfTextWidth - paddleTopCircleRadius @@ -736,8 +784,7 @@ class SfPaddleTooltipShape extends SfTooltipShape { bottomNeckRadius); final Offset bottomNeckCenter = Offset( bottomNeckRadius + neckDifference / 2, - -sliderThemeData.thumbRadius - - bottomNeckRadius * (1.0 - moveNeckValue)); + -thumbRadius - bottomNeckRadius * (1.0 - moveNeckValue)); final double leftShiftWidth = thumbCenter.dx - offset.dx - halfTextWidth; double shiftPaddleWidth = leftShiftWidth < 0 ? leftShiftWidth : 0; final double rightEndPosition = @@ -806,14 +853,14 @@ class SfPaddleTooltipShape extends SfTooltipShape { adjustLeftNeckArcAngle, bottomNeckCenter, bottomNeckRadius, + thumbRadius, sliderThemeData); context.canvas.save(); context.canvas.translate(thumbCenter.dx, thumbCenter.dy); context.canvas.scale(animation.value); final Paint strokePaint = Paint(); - // ignore: avoid_as - if (_isTooltipOverlapStroke(parentBox as RenderBaseSlider) && + if (_hasTooltipOverlapStroke(parentBox) && sliderThemeData is SfRangeSliderThemeData && sliderThemeData.tooltipBackgroundColor != Colors.transparent) { strokePaint @@ -854,6 +901,7 @@ class SfPaddleTooltipShape extends SfTooltipShape { double adjustLeftNeckArcAngle, Offset bottomNeckCenter, double bottomNeckRadius, + double thumbRadius, SfSliderThemeData sliderThemeData) { final Path path = Path(); path.moveTo( @@ -901,16 +949,12 @@ class SfPaddleTooltipShape extends SfTooltipShape { math.pi / 3, false); path.arcTo( - Rect.fromCircle( - center: const Offset(0.0, 0.0), - radius: sliderThemeData.thumbRadius), + Rect.fromCircle(center: const Offset(0.0, 0.0), radius: thumbRadius), 3 * math.pi / 2, -math.pi, false); path.arcTo( - Rect.fromCircle( - center: const Offset(0.0, 0.0), - radius: sliderThemeData.thumbRadius), + Rect.fromCircle(center: const Offset(0.0, 0.0), radius: thumbRadius), math.pi / 2, -math.pi, false); @@ -956,7 +1000,7 @@ class SfRectangularTooltipShape extends SfTooltipShape { /// /// A class which is used to render rectangular shape tooltip. const SfRectangularTooltipShape(); - bool _isTooltipOverlapStroke(RenderBaseSlider parentBox) { + bool _hasTooltipOverlapStroke(RenderBaseSlider parentBox) { return parentBox.showOverlappingTooltipStroke; } @@ -1235,7 +1279,7 @@ class SfRectangularTooltipShape extends SfTooltipShape { context.canvas.translate(thumbCenter.dx, thumbCenter.dy); context.canvas.scale(animation.value); final Paint strokePaint = Paint(); - if (_isTooltipOverlapStroke(parentBox) && + if (_hasTooltipOverlapStroke(parentBox) && sliderThemeData.tooltipBackgroundColor != Colors.transparent) { if (sliderThemeData is SfRangeSliderThemeData) { strokePaint.color = sliderThemeData.overlappingTooltipStrokeColor!; diff --git a/packages/syncfusion_flutter_treemap/README.md b/packages/syncfusion_flutter_treemap/README.md index d045a515a..b29367043 100644 --- a/packages/syncfusion_flutter_treemap/README.md +++ b/packages/syncfusion_flutter_treemap/README.md @@ -259,4 +259,4 @@ The following screenshot illustrates the result of the above code sample. 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_flutter_treemap/example/pubspec.yaml b/packages/syncfusion_flutter_treemap/example/pubspec.yaml index 680f634a8..d2cda59d0 100644 --- a/packages/syncfusion_flutter_treemap/example/pubspec.yaml +++ b/packages/syncfusion_flutter_treemap/example/pubspec.yaml @@ -27,6 +27,9 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 +dev_dependencies: + flutter_test: + sdk: flutter # For information on the generic Dart part of this file, see the diff --git a/packages/syncfusion_flutter_treemap/lib/src/controller.dart b/packages/syncfusion_flutter_treemap/lib/src/controller.dart index 9ec9c6d63..728da0e01 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/controller.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/controller.dart @@ -3,14 +3,11 @@ import 'package:flutter/foundation.dart'; import '../treemap.dart'; import 'layouts.dart'; -typedef _DrillDownCallback = void Function( - TreemapTile tile, bool isForwardDrilldown); +typedef _DrillDownCallback = void Function(TreemapTile tile, bool isDrilledIn); /// Whenever the user modifies a selection, hover or drilldown, the controller /// notifies its listeners. class TreemapController { - /// Which is represents the treemap size is changed or not. - bool isSizeChanged = false; ObserverList? _selectionListeners = ObserverList(); ObserverList? _hoverListeners = ObserverList(); @@ -63,9 +60,9 @@ class TreemapController { } /// This method should be called whenever the drilldown visible index changes. - void notifyDrilldownListeners(TreemapTile tile, bool isForwardDrillDown) { + void notifyDrilldownListeners(TreemapTile tile, bool isDrilledIn) { for (final _DrillDownCallback listener in _drillDownListeners!) { - listener(tile, isForwardDrillDown); + listener(tile, isDrilledIn); } } diff --git a/packages/syncfusion_flutter_treemap/lib/src/layouts.dart b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart index 4b744a3a3..5e952a4ca 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/layouts.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/layouts.dart @@ -51,7 +51,27 @@ Widget _buildAnimatedBuilder( double height; // We have to increase the label/item builder's size while drilling down // to maintain its position so used [OverflowBox]. - if (status == AnimationStatus.forward) { + if (scale.width < 1 || scale.height < 1) { + width = scale.width < 1 + ? tile._size!.width * scale.width - gap.width + : tile._size!.width - gap.width; + height = scale.height < 1 + ? tile._size!.height * scale.height - gap.height + : tile._size!.height - gap.height; + return Align( + alignment: Alignment.topLeft, + child: SizedBox( + // If the tile size is smaller than padding or border, it will + // return a negative value. When we using negative value to the + // [SizedBox], we get box constraints issue. For that, we set the width + // and height is 0.0 to the sized box when the width or height having a + // negative value. + width: width > 0 ? width : 0.0, + height: height > 0 ? height : 0.0, + child: child, + ), + ); + } else { width = scale.width > 1 ? tile._size!.width * scale.width - gap.width : tile._size!.width - gap.width; @@ -64,26 +84,63 @@ Widget _buildAnimatedBuilder( maxWidth: width > 0 ? width : 0.0, child: child); } +} - width = scale.width < 1 - ? tile._size!.width * scale.width - gap.width - : tile._size!.width - gap.width; - height = scale.height < 1 - ? tile._size!.height * scale.height - gap.height - : tile._size!.height - gap.height; - return Align( - alignment: Alignment.topLeft, - child: SizedBox( - // If the tile size is smaller than padding or border, it will - // return a negative value. When we using negative value to the - // [SizedBox], we get box constraints issue. For that, we set the width - // and height is 0.0 to the sized box when the width or height having a - // negative value. - width: width > 0 ? width : 0.0, - height: height > 0 ? height : 0.0, - child: child, - ), - ); +Widget? _buildLabeAndItemBuilder( + Widget current, + TreemapTile tile, + int visibleIndex, + AnimationStatus animationStatus, + Animation scalingAndTranslationAnimation, + Animation opacityAnimation, + Tween currentLevelScale, + Tween nextLevelScale, + Tween tileOpacity, + Tween labelAndItemBuilderOpacity) { + Size? scaleSize; + if (animationStatus == AnimationStatus.forward && + visibleIndex == tile._levelIndex) { + // If we set align property to the label builder the label builder size + // is gets tile size. If we scale tile the label builder size is not + // increased. So the label builder align improper position. For than we + //need to increase the label builder size. + return Opacity( + opacity: tileOpacity.evaluate(opacityAnimation), + child: _buildAnimatedBuilder( + tile, + currentLevelScale.evaluate(scalingAndTranslationAnimation), + animationStatus, + current), + ); + } else if (animationStatus == AnimationStatus.reverse) { + if (visibleIndex == tile._levelIndex) { + scaleSize = nextLevelScale.evaluate(scalingAndTranslationAnimation); + scaleSize = scaleSize.width > scaleSize.height + ? Size(scaleSize.width / scaleSize.width, + scaleSize.height / scaleSize.width) + : Size(scaleSize.width / scaleSize.height, + scaleSize.height / scaleSize.height); + return Opacity( + opacity: + labelAndItemBuilderOpacity.evaluate(scalingAndTranslationAnimation), + child: _buildAnimatedBuilder(tile, scaleSize, animationStatus, current), + ); + } else { + current = _buildAnimatedBuilder( + tile, + currentLevelScale.evaluate(scalingAndTranslationAnimation), + animationStatus, + current); + if (tile._isDrilled) { + current = Opacity( + opacity: tileOpacity.evaluate(opacityAnimation), + child: current, + ); + } + } + } + + return current; } /// Specifies the kind of pointer. @@ -169,10 +226,6 @@ class TreemapTile { /// The mixin methods or fields are reusing to the other different class. mixin _TreemapMixin { - /// Represents the translation offset animation for the tile when the - /// drilldown animation is progress. - Animation? _offsetAnimation; - /// Represents the opacity animation for the tile when the /// drilldown animation is progress. /// @@ -181,234 +234,248 @@ mixin _TreemapMixin { /// /// If we backward drill down 1st level to 0th level, It will be decrease /// opacity 0.0 to 1.0 the 0th level tiles and elements. - late Animation _parentOpacityAnimation; + late Tween _tileOpacity; /// When the drilldown animation is backward, decrease the opacity 1.0 to 0.0 /// for the label and item builder. - late Animation _labelAndItemBuilderOpacityAnimation; + late Tween _labelAndItemBuilderOpacity; - /// Represents the color animation for the tile when the + /// Represents the translation offset animation for the tile when the /// drilldown animation is progress. - late Animation _colorAnimation; + late Tween _visibleTileTranslation; /// Represents the scaling animation for the tile when the /// drilldown animation is progress. - Animation? _scaleAnimation; + late Tween _visibleTileScaleSize; - /// Which represents the size of the treemap. - Size? size; - - /// Current drill down tile details. - TreemapTile? _visibleTile; - - /// Which is represents the home page tile. - TreemapTile? _homeTile; + /// Represents the translation offset animation for the tile when the + /// drilldown animation is progress. + late Tween _nextTileTranslation; - /// Represents the opacity animation for the tile when the + /// Represents the scaling animation for the tile when the /// drilldown animation is progress. - /// - /// If we forward drill down 0th level to 1st level, It will be increase - /// opacity 0.0 to 1.0 in the 1st level tiles and elements. - /// - /// If we backward drill down 1st level to 0th level, It will be decrease - /// opacity 1.0 to 0.0 the 1st level tiles and elements. - late Animation _descendantsOpacityAnimation; - - /// Update the treemap tile size and offset based on the scale value - /// and translate offset. - void _updateTileSizeAndOffset(TreemapTile details, Size scaleSize, - Offset? translation, int visibleIndex) { - // Changes the tile size and offset based on the scale factor value. - details._size = Size(details._size!.width * scaleSize.width, - details._size!.height * scaleSize.height); - details._offset = Offset(details._offset!.dx * scaleSize.width, - details._offset!.dy * scaleSize.height); - - // Translate oth level tile offset alone. Because we have positioned - // descendants inside of the stack. - if (details._levelIndex < 1 && translation != null) { - details._offset = details._offset! + translation; - } - } - - Widget? _buildLabelBuilder(BuildContext context, TreemapTile tile, - int visibleIndex, Widget current, AnimationStatus animationStatus) { - if (animationStatus == AnimationStatus.forward && - visibleIndex == tile._levelIndex) { - // If we set align property to the label builder the label builder size - // is gets tile size. If we scale tile the label builder size is not - // increased. So the label builder align improper position. For than we - //need to increase the label builder size. - return FadeTransition( - opacity: _parentOpacityAnimation, - child: _buildAnimatedBuilder( - tile, _scaleAnimation!.value, animationStatus, current), - ); - } else if (animationStatus == AnimationStatus.reverse) { - final Size scaleSize = _scaleAnimation!.value.width > - _scaleAnimation!.value.height - ? Size(_scaleAnimation!.value.width / _scaleAnimation!.value.width, - _scaleAnimation!.value.height / _scaleAnimation!.value.width) - : Size(_scaleAnimation!.value.width / _scaleAnimation!.value.height, - _scaleAnimation!.value.height / _scaleAnimation!.value.height); - - return visibleIndex == tile._levelIndex - ? FadeTransition( - opacity: _labelAndItemBuilderOpacityAnimation, - child: _buildAnimatedBuilder( - tile, scaleSize, animationStatus, current)) - : _buildAnimatedBuilder( - tile, _scaleAnimation!.value, animationStatus, current); - } + late Tween _nextTileScaleSize; - return current; - } + /// Current drill down tile details. + late TreemapTile _visibleTile; - Animation _getOffsetAnimation( - Curve curve, AnimationController drilldownAnimationController, - {required Offset begin, required Offset end}) { - return Tween(begin: begin, end: end).animate( - CurvedAnimation(parent: drilldownAnimationController, curve: curve), - ); - } + /// If we drilled-in or drilled-out tiles, the scaling and translation + /// only happens for the first half of the duration. + late CurvedAnimation _scaleAndTranslationAnimation; - Animation _getScalingAnimation( - Curve curve, AnimationController drilldownAnimationController, - {required Size begin, required Size end}) { - return Tween( - begin: begin, - end: end, - ).animate( - CurvedAnimation(parent: drilldownAnimationController, curve: curve), - ); - } + /// If we drilled-in or drilled-out tiles, opacity only applies for + /// the second half of the duration. + late CurvedAnimation _opacityAnimation; - Animation _getOpacityAnimation( - Curve curve, AnimationController drilldownAnimationController, - {required double begin, required double end}) { - return Tween(begin: begin, end: end).animate( - CurvedAnimation(parent: drilldownAnimationController, curve: curve), - ); - } + /// Which represents the size of the treemap. + Size? size; /// Calculates the scale and translation value by using the current /// drilldown tile size and offset. - void _initializeDrilldownAnimations( - TreemapTile tappedTile, - TreemapController controller, - AnimationController drilldownAnimationController) { - tappedTile = tappedTile._levelIndex == -1 ? _homeTile! : tappedTile; - final Size scaleSize = _getScale(tappedTile); - final Offset translation = - _getTranslation(tappedTile, scaleSize, drilldownAnimationController); - // The scaling animation is running in the first half duration and the - // opacity animation is running in the second half. - const Curve firstHalfAnimationInterval = - Interval(0.0, 0.5, curve: Curves.easeOut); - const Curve secondHalfAnimationInterval = - Interval(0.5, 1.0, curve: Curves.decelerate); - - if (drilldownAnimationController.status == AnimationStatus.forward) { - _offsetAnimation = _getOffsetAnimation( - firstHalfAnimationInterval, drilldownAnimationController, - begin: Offset.zero, end: translation); - _scaleAnimation = _getScalingAnimation( - firstHalfAnimationInterval, drilldownAnimationController, - begin: const Size(1.0, 1.0), end: scaleSize); - _parentOpacityAnimation = _getOpacityAnimation( - secondHalfAnimationInterval, drilldownAnimationController, - begin: 1.0, end: 0.0); - _descendantsOpacityAnimation = _getOpacityAnimation( - secondHalfAnimationInterval, drilldownAnimationController, - begin: 0.0, end: 1.0); - _colorAnimation = CurvedAnimation( - parent: drilldownAnimationController, - curve: secondHalfAnimationInterval); + void _computeDrilldownTweenValue( + TreemapTile visibleTile, + AnimationController drilldownAnimationController, + GlobalKey breadcrumbKey, + bool isDrilledIn) { + if (isDrilledIn) { + final Size currentScale = Size(size!.width / visibleTile._size!.width, + size!.height / visibleTile._size!.height); + final Offset currentTranslation = + _getTranslation(visibleTile._offset!, currentScale); + _scaleAndTranslationAnimation.curve = + const Interval(0, 0.5, curve: Curves.easeInOut); + _opacityAnimation.curve = + const Interval(0.5, 1.0, curve: Curves.easeInOut); + _visibleTileTranslation + ..begin = Offset.zero + ..end = currentTranslation; + _visibleTileScaleSize + ..begin = const Size(1.0, 1.0) + ..end = currentScale; + _tileOpacity + ..begin = 1.0 + ..end = 0.0; + drilldownAnimationController.forward(from: 0.01); } else { - _offsetAnimation = _getOffsetAnimation( - secondHalfAnimationInterval, drilldownAnimationController, - begin: translation, end: Offset.zero); - _scaleAnimation = _getScalingAnimation( - secondHalfAnimationInterval, drilldownAnimationController, - begin: scaleSize, end: const Size(1.0, 1.0)); - _parentOpacityAnimation = _getOpacityAnimation( - firstHalfAnimationInterval, drilldownAnimationController, - begin: 1.0, end: 0.0); - _descendantsOpacityAnimation = _getOpacityAnimation( - firstHalfAnimationInterval, drilldownAnimationController, - begin: 0.0, end: 1.0); - _colorAnimation = CurvedAnimation( - parent: drilldownAnimationController, - curve: firstHalfAnimationInterval); - _labelAndItemBuilderOpacityAnimation = _getOpacityAnimation( - secondHalfAnimationInterval, drilldownAnimationController, - begin: 0.0, end: 1.0); - } - _visibleTile = tappedTile; - } - - Size _getScale(TreemapTile tile) { - return Size( - size!.width / tile._size!.width, size!.height / tile._size!.height); - } - - Offset _getTranslation(TreemapTile tile, Size scaleSize, - AnimationController drilldownController) { - return drilldownController.status == AnimationStatus.reverse && - tile._levelIndex != -1 - ? Offset(_visibleTile!._offset!.dx * scaleSize.width, - _visibleTile!._offset!.dy * scaleSize.height) - : -Offset(tile._offset!.dx * scaleSize.width, - tile._offset!.dy * scaleSize.height); + _scaleAndTranslationAnimation.curve = + const Interval(0.5, 1.0, curve: Curves.easeInOut); + _opacityAnimation.curve = const Interval(0, 0.5, curve: Curves.easeInOut); + _computeDrilledOutTweenValue(visibleTile, breadcrumbKey); + drilldownAnimationController.reverse(from: 0.99); + } + + _visibleTile = visibleTile; + } + + void _computeDrilledOutTweenValue( + TreemapTile tappedTile, GlobalKey breadcrumbKey) { + late TreemapTile currentDrilledTile; + if (breadcrumbKey.currentState != null) { + final _BreadcrumbsState breadcrumbRenderBox = + breadcrumbKey.currentState! as _BreadcrumbsState; + // if we drilled-out 2nd level to 0th level, the first level tile should + // be widget size and 0th level size should be greater than widget size. + // So calculates the scale factor which we need to increase the size of + // the 0th tiles by using the current drilled tile size. + currentDrilledTile = + breadcrumbRenderBox._tiles[tappedTile._levelIndex + 2]; + } + + _buildTiles(tappedTile._descendants!, tappedTile.weight, size!); + // It represents which size we need to increase the from scale + // value to the current level tiles. + final Size currentLevelScale = Size( + size!.width / currentDrilledTile._size!.width, + size!.height / currentDrilledTile._size!.height); + // It represents the offset we need to translate in the animation begin + // to the current level tiles. + final Offset currentLevelTranslation = + _getTranslation(currentDrilledTile._offset!, currentLevelScale); + // It represents which size we need to increase the from scale + // value to the current visible tiles. + final Size nextLevelScale = + Size(1.0 / currentLevelScale.width, 1.0 / currentLevelScale.height); + // It represents the offset we need to translate in the animation begin + // to the current visible tiles. + final Offset nextLevelTranslation = + _getTranslation(currentLevelTranslation, nextLevelScale); + _nextTileTranslation + ..begin = nextLevelTranslation + ..end = Offset.zero; + _nextTileScaleSize + ..begin = nextLevelScale + ..end = const Size(1.0, 1.0); + _visibleTileScaleSize + ..begin = const Size(1.0, 1.0) + ..end = currentLevelScale; + _visibleTileTranslation + ..begin = Offset.zero + ..end = currentLevelTranslation; + _tileOpacity + ..begin = 1.0 + ..end = 0.0; + _labelAndItemBuilderOpacity + ..begin = 0.0 + ..end = 1.0; + } + + Offset _getTranslation(Offset tileOffset, Size scaleSize) { + return -Offset( + tileOffset.dx * scaleSize.width, tileOffset.dy * scaleSize.height); } /// Scales to the current visible level and load its descendants /// with animation. - Widget _buildAnimatedTreemap(Map dataSource, - double weight, AnimationController controller, + Widget _buildAnimatedTreemap( + AnimationController controller, GlobalKey breadcrumbKey, {TreemapLayoutDirection direction = TreemapLayoutDirection.topLeft}) { + late List breadcrumbTiles; + if (breadcrumbKey.currentState != null) { + final _BreadcrumbsState breadcrumbRenderBox = + breadcrumbKey.currentState! as _BreadcrumbsState; + breadcrumbTiles = breadcrumbRenderBox._tiles; + } + + final TreemapTile tile = controller.status == AnimationStatus.reverse + ? _visibleTile + // Gets the previous tapped tiles. + : breadcrumbTiles[breadcrumbTiles.length - 2]; + + return AnimatedBuilder( + animation: controller, + builder: (BuildContext context, Widget? child) { + final Offset translation = _getLayoutDirectionTranslation(direction, + _visibleTileTranslation.evaluate(_scaleAndTranslationAnimation)); + final Size scaleSize = + _visibleTileScaleSize.evaluate(_scaleAndTranslationAnimation); + if (controller.status != AnimationStatus.reverse) { + return Stack( + children: [ + Transform( + alignment: _getEffectiveAlignment(direction), + transform: Matrix4.identity() + ..scale(scaleSize.width, scaleSize.height) + ..leftTranslate(translation.dx, translation.dy), + child: Stack( + clipBehavior: Clip.none, + children: _buildTiles(tile._descendants!, tile.weight, size!, + canLayoutTiles: false), + ), + ), + if (controller.value > 0.5) + Opacity( + opacity: 1.0 - _tileOpacity.evaluate(_opacityAnimation), + child: Stack( + clipBehavior: Clip.none, + children: _buildTiles( + _visibleTile._descendants!, _visibleTile.weight, size!), + ), + ) + ], + ); + } else { + return _buildDrilledOutAnimatedTiles( + tile, breadcrumbTiles.last, scaleSize, translation, direction); + } + }, + ); + } + + Widget _buildDrilledOutAnimatedTiles( + TreemapTile tile, + TreemapTile lastBreadcrumbTile, + Size scaleSize, + Offset translation, + TreemapLayoutDirection direction) { + final Offset descendantsTranslation = _getLayoutDirectionTranslation( + direction, + _nextTileTranslation.evaluate(_scaleAndTranslationAnimation)); + final Size descendantsScaleSize = + _nextTileScaleSize.evaluate(_scaleAndTranslationAnimation); return Stack( children: [ - AnimatedBuilder( - animation: controller, - builder: (BuildContext context, Widget? child) { - final Offset translation = _getTranslationOffset(direction); - return Transform( - alignment: _getEffectiveAlignment(direction), - transform: Matrix4.identity() - ..scale( - _scaleAnimation!.value.width, _scaleAnimation!.value.height) - ..leftTranslate(translation.dx, translation.dy), - child: Stack( - clipBehavior: Clip.none, - children: _buildTiles(dataSource, weight, size!), - ), - ); - }, + Transform( + alignment: _getEffectiveAlignment(direction), + transform: Matrix4.identity() + ..scale(scaleSize.width, scaleSize.height) + ..leftTranslate(translation.dx, translation.dy), + child: Stack( + clipBehavior: Clip.none, + children: _buildTiles(tile._descendants!, tile.weight, size!, + canLayoutTiles: false), + ), ), - if (controller.status == AnimationStatus.forward) - FadeTransition( - opacity: _descendantsOpacityAnimation, + Transform( + alignment: _getEffectiveAlignment(direction), + transform: Matrix4.identity() + ..scale(descendantsScaleSize.width, descendantsScaleSize.height) + ..leftTranslate( + descendantsTranslation.dx, descendantsTranslation.dy), + child: Opacity( + opacity: 1.0 - _tileOpacity.evaluate(_opacityAnimation), child: Stack( clipBehavior: Clip.none, - children: _buildTiles( - _visibleTile!._descendants!, _visibleTile!.weight, size!), + children: _buildTiles(lastBreadcrumbTile._descendants!, + lastBreadcrumbTile.weight, size!, + canLayoutTiles: false), ), ), + ) ], ); } - Offset _getTranslationOffset(TreemapLayoutDirection layoutDirection) { + Offset _getLayoutDirectionTranslation( + TreemapLayoutDirection layoutDirection, Offset translation) { switch (layoutDirection) { case TreemapLayoutDirection.topLeft: - return _offsetAnimation!.value; + return translation; case TreemapLayoutDirection.topRight: - return -Offset(_offsetAnimation!.value.dx, -_offsetAnimation!.value.dy); + return -Offset(translation.dx, -translation.dy); case TreemapLayoutDirection.bottomLeft: - return Offset(_offsetAnimation!.value.dx, -_offsetAnimation!.value.dy); + return Offset(translation.dx, -translation.dy); case TreemapLayoutDirection.bottomRight: - return -_offsetAnimation!.value; + return -translation; } } @@ -429,72 +496,64 @@ mixin _TreemapMixin { /// Loads label builder and descendants for the tiles. Widget? _getDescendants(BuildContext context, TreemapTile tile, TreemapController controller, bool enableDrilldown, - {Size? sizeFactor, AnimationController? animationController}) { + {AnimationController? animationController}) { Widget? child; // Ignored current tile descendants while enable drilldown. - if (enableDrilldown && !tile._isDrilled) { + if (enableDrilldown) { if (tile.level.labelBuilder != null) { - child = _buildLabelBuilder( - context, - tile, - controller.visibleLevelIndex, - tile.level.labelBuilder!(context, tile)!, - animationController!.status); + child = _buildLabeAndItemBuilder( + tile.level.labelBuilder!(context, tile)!, + tile, + controller.visibleLevelIndex, + animationController!.status, + _scaleAndTranslationAnimation, + _opacityAnimation, + _visibleTileScaleSize, + _nextTileScaleSize, + _tileOpacity, + _labelAndItemBuilderOpacity, + ); } return child; } if (tile.level.labelBuilder != null && tile.hasDescendants) { - if (enableDrilldown) { - // We can't scale to the previous level label builder if we are - // backward drilldown. Subtract the visible level index from 1 to - // get the previous visible level index. - if (animationController!.status == AnimationStatus.reverse && - tile._levelIndex == controller.visibleLevelIndex - 1) { - if (tile._levelIndex == controller.visibleLevelIndex - 1) - child = _buildAnimatedDescendants( - context, tile, sizeFactor, animationController.status); - } else { - child = _buildDescendants(tile, sizeFactor); - } - } else { - final Widget? labelBuilder = tile.level.labelBuilder!(context, tile); - if (labelBuilder == null) { - return Stack( - children: _buildTiles(tile._descendants!, tile.weight, tile._size!), - ); - } - - child = Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - labelBuilder, - Expanded( - // The [TreemapLevel.padding] and [TreemapLevel.border] values has - // been previous applied by using padding widget to this column. - // So we will get constraints size with considering that. - // Therefore we haven't considered [TreemapLevel.padding] and - // [TreemapLevel.border] values while passing size to children. - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - // To get the actual size of the parent tile, we had taken the - // size of the label builder and subtracted from it. - tile._labelBuilderSize = Size( - tile._size!.width - constraints.maxWidth, - tile._size!.height - constraints.maxHeight); - return Stack( - children: _buildTiles( - tile._descendants!, - tile.weight, - Size(tile._size!.width - tile._labelBuilderSize.width, - tile._size!.height - tile._labelBuilderSize.height)), - ); - }), - ), - ], + final Widget? labelBuilder = tile.level.labelBuilder!(context, tile); + if (labelBuilder == null) { + return Stack( + children: _buildTiles(tile._descendants!, tile.weight, tile._size!), ); } + + child = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + labelBuilder, + Expanded( + // The [TreemapLevel.padding] and [TreemapLevel.border] values has + // been previous applied by using padding widget to this column. + // So we will get constraints size with considering that. + // Therefore we haven't considered [TreemapLevel.padding] and + // [TreemapLevel.border] values while passing size to children. + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // To get the actual size of the parent tile, we had taken the + // size of the label builder and subtracted from it. + tile._labelBuilderSize = Size( + tile._size!.width - constraints.maxWidth, + tile._size!.height - constraints.maxHeight); + return Stack( + children: _buildTiles( + tile._descendants!, + tile.weight, + Size(tile._size!.width - tile._labelBuilderSize.width, + tile._size!.height - tile._labelBuilderSize.height)), + ); + }), + ), + ], + ); } // In flat or hierarchical levels[levels.length - 1] level i.e., last level // doesn't have descendants. So, we had included the label builder @@ -504,13 +563,13 @@ mixin _TreemapMixin { else if (tile.level.labelBuilder != null) { child = tile.level.labelBuilder!(context, tile); } else if (tile.hasDescendants) { - child = _buildDescendants(tile, sizeFactor); + child = _buildDescendants(tile); } return child; } - Widget _buildDescendants(TreemapTile tile, Size? sizeFactor) { + Widget _buildDescendants(TreemapTile tile) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return Stack( @@ -518,46 +577,15 @@ mixin _TreemapMixin { tile._descendants!, tile.weight, constraints.biggest, - sizeFactor: sizeFactor, )); }); } - Widget _buildAnimatedDescendants(BuildContext context, TreemapTile tile, - Size? sizeFactor, AnimationStatus animationStatus) { - // Increase the label builder opacity while backward drilldown. - final Widget child = FadeTransition( - opacity: _parentOpacityAnimation, - child: _buildAnimatedBuilder(tile, _scaleAnimation!.value, - animationStatus, tile.level.labelBuilder!(context, tile)!), - ); - return Stack( - children: [ - child, - LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - // Decrease the descendants opacity while backward drilldown. - return FadeTransition( - opacity: _descendantsOpacityAnimation, - child: Stack( - children: _buildTiles( - tile._descendants!, - tile.weight, - constraints.biggest, - sizeFactor: sizeFactor, - ), - ), - ); - }), - ], - ); - } - /// Creates the get tiles methods and overrides to the slice, dice /// and squarified class. List _buildTiles( Map source, double aggregatedWeight, Size size, - {Size? sizeFactor}) { + {bool canLayoutTiles = true}) { return []; } } @@ -644,6 +672,7 @@ class Treemap extends StatefulWidget { class _TreemapState extends State with SingleTickerProviderStateMixin { final Color _baseColor = const Color.fromRGBO(60, 119, 233, 1); late GlobalKey _tooltipKey; + late GlobalKey _breadcrumbKey; late Map _dataSource; late TreemapController _controller; late PointerController _pointerController; @@ -661,30 +690,41 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { late Future _computeDataSource; _SquarifiedTreemap get _squarified => _SquarifiedTreemap( - dataCount: widget.dataCount, - dataSource: _dataSource, - totalWeight: _totalWeight, - layoutDirection: widget.layoutDirection, - tooltipKey: _tooltipKey, - onSelectionChanged: widget.onSelectionChanged, - selectionSettings: widget.selectionSettings, - enableDrillDown: widget.enableDrilldown, - controller: _controller, - drilldownAnimationController: _drilldownAnimationController, - ); + dataCount: widget.dataCount, + layoutDirection: widget.layoutDirection, + tooltipKey: _tooltipKey, + breadcrumbKey: _breadcrumbKey, + onSelectionChanged: widget.onSelectionChanged, + selectionSettings: widget.selectionSettings, + enableDrillDown: widget.enableDrilldown, + controller: _controller, + drilldownAnimationController: _drilldownAnimationController, + initialTile: TreemapTile._() + .._group = 'Home' + .._levelIndex = -1 + .._offset = Offset.zero + .._descendants = _dataSource + .._weight = _totalWeight + .._isDrilled = true); _SliceAndDiceTreemap get _sliceAndDice => _SliceAndDiceTreemap( dataCount: widget.dataCount, - dataSource: _dataSource, sortAscending: widget.sortAscending, type: widget.layoutType, - totalWeight: _totalWeight, tooltipKey: _tooltipKey, + breadcrumbKey: _breadcrumbKey, onSelectionChanged: widget.onSelectionChanged, selectionSettings: widget.selectionSettings, enableDrillDown: widget.enableDrilldown, controller: _controller, drilldownAnimationController: _drilldownAnimationController, + initialTile: TreemapTile._() + .._group = 'Home' + .._levelIndex = -1 + .._offset = Offset.zero + .._descendants = _dataSource + .._weight = _totalWeight + .._isDrilled = true, ); TreemapTooltip get _tooltip => @@ -697,9 +737,19 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { if (widget.enableDrilldown) { final _Breadcrumbs _breadcrumbs = _Breadcrumbs( - settings: widget.breadcrumbs!, - controller: _controller, - animationController: _drilldownAnimationController!); + key: _breadcrumbKey, + settings: widget.breadcrumbs!, + controller: _controller, + animationController: _drilldownAnimationController!, + levels: widget.levels, + initialTile: TreemapTile._() + .._group = 'Home' + .._levelIndex = -1 + .._offset = Offset.zero + .._descendants = _dataSource + .._weight = _totalWeight + .._isDrilled = true, + ); switch (widget.breadcrumbs!.position) { case TreemapBreadcrumbPosition.top: current = Column( @@ -949,7 +999,7 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { return null; } - void _handleDrillDown(TreemapTile tile, bool isForwardDrilldown) { + void _handleDrillDown(TreemapTile tile, bool isDrilledIn) { if (_tooltipKey.currentContext != null) { final RenderTooltip tooltipRenderBox = _tooltipKey.currentContext!.findRenderObject()! @@ -957,16 +1007,6 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { as RenderTooltip; tooltipRenderBox.hide(immediately: true); } - - if (isForwardDrilldown) { - // [Dismissed] status is invoked if we forward the animation from 0.0. - // To avoid, we started animation from 0.01. - _drilldownAnimationController!.forward(from: 0.01); - } else { - // [Completed] status is invoked if we forward the animation from 1.0. - // To avoid, we started animation from 0.99. - _drilldownAnimationController!.reverse(from: 0.99); - } } @override @@ -974,6 +1014,7 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { _levelsLength = widget.levels.length; _dataSource = {}; _tooltipKey = GlobalKey(); + _breadcrumbKey = GlobalKey(); _controller = TreemapController(); if (widget.enableDrilldown) { _drilldownAnimationController = AnimationController( @@ -1036,7 +1077,6 @@ class _TreemapState extends State with SingleTickerProviderStateMixin { // animation, force it to complete. if (widget.enableDrilldown && _drilldownAnimationController!.isAnimating) { - _controller.isSizeChanged = true; _drilldownAnimationController!.value = _drilldownAnimationController!.status == AnimationStatus.forward ? 1.0 @@ -1064,27 +1104,27 @@ class _SquarifiedTreemap extends StatefulWidget { const _SquarifiedTreemap({ Key? key, required this.dataCount, - required this.dataSource, - required this.totalWeight, required this.layoutDirection, required this.tooltipKey, + required this.breadcrumbKey, required this.onSelectionChanged, required this.selectionSettings, required this.enableDrillDown, required this.controller, required this.drilldownAnimationController, + required this.initialTile, }) : super(key: key); final int dataCount; - final Map dataSource; - final double totalWeight; final TreemapLayoutDirection layoutDirection; final GlobalKey tooltipKey; + final GlobalKey breadcrumbKey; final ValueChanged? onSelectionChanged; final TreemapSelectionSettings selectionSettings; final bool enableDrillDown; final TreemapController controller; final AnimationController? drilldownAnimationController; + final TreemapTile initialTile; @override _SquarifiedTreemapState createState() => _SquarifiedTreemapState(); @@ -1100,14 +1140,11 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> // its location and size for its children. @override List _buildTiles( - Map source, - double aggregatedWeight, - Size size, { - Offset offset = Offset.zero, - int start = 0, - int? end, - Size? sizeFactor, - }) { + Map source, double aggregatedWeight, Size size, + {Offset offset = Offset.zero, + int start = 0, + int? end, + bool canLayoutTiles = true}) { final Size widgetSize = size; double groupArea = 0; double referenceArea; @@ -1121,19 +1158,10 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> final List children = []; for (int i = start; i < end; i++) { final TreemapTile tile = tiles[i]; - if (widget.enableDrillDown && - (tile._levelIndex < widget.controller.visibleLevelIndex || - ((widget.drilldownAnimationController!.status == - AnimationStatus.reverse && - tile._levelIndex == - widget.controller.visibleLevelIndex) || - (widget.drilldownAnimationController!.status == - AnimationStatus.dismissed && - _scaleAnimation != null)))) { - children.addAll( - _getTileWidgets(tiles, size, offset, start, end, - sizeFactor: sizeFactor, forceUpdateSize: true), - ); + if (!canLayoutTiles) { + children.addAll(_getTileWidgets( + tiles, size, offset, start, tiles.length, + canLayoutTiles: false)); return children; } else { // Area of rectangle = length * width. @@ -1158,7 +1186,7 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> children.addAll( _getTileWidgets(tiles, Size(groupArea / size.height, size.height), offset, start, i, - axis: Axis.vertical, sizeFactor: sizeFactor), + axis: Axis.vertical), ); offset += Offset(groupArea / size.height, 0); size = @@ -1168,7 +1196,7 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> else { children.addAll(_getTileWidgets(tiles, Size(size.width, groupArea / size.width), offset, start, i, - axis: Axis.horizontal, sizeFactor: sizeFactor)); + axis: Axis.horizontal)); offset += Offset(0, groupArea / size.width); size = Size(size.width, max(0, size.height) - groupArea / size.width); @@ -1189,13 +1217,13 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> children.addAll( _getTileWidgets(tiles, Size(groupArea / size.height, size.height), offset, start, end, - axis: Axis.vertical, sizeFactor: sizeFactor), + axis: Axis.vertical), ); } else { children.addAll( _getTileWidgets(tiles, Size(groupArea / size.height, size.height), offset, start, end, - axis: Axis.horizontal, sizeFactor: sizeFactor), + axis: Axis.horizontal), ); } @@ -1204,16 +1232,11 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> List _getTileWidgets( List source, Size size, Offset offset, int start, int end, - {Axis? axis, Size? sizeFactor, bool forceUpdateSize = false}) { + {Axis? axis, bool canLayoutTiles = true}) { final List<_Tile> tiles = <_Tile>[]; for (int i = start; i < end; i++) { final TreemapTile tileDetails = source[i]; - if (forceUpdateSize) { - if (sizeFactor != null) { - _updateTileSizeAndOffset(tileDetails, sizeFactor, offset, - widget.controller.visibleLevelIndex); - } - } else { + if (canLayoutTiles) { if (axis == Axis.vertical) { tileDetails .._size = Size(size.width, tileDetails._area! / size.width) @@ -1227,12 +1250,6 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> } } - if (sizeFactor != null && - tileDetails._levelIndex == widget.controller.visibleLevelIndex) { - _scaleAnimation = null; - _offsetAnimation = null; - } - tiles.add(_Tile( size: tileDetails._size!, details: tileDetails, @@ -1242,7 +1259,6 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> controller: widget.controller, child: _getDescendants( context, tileDetails, widget.controller, widget.enableDrillDown, - sizeFactor: sizeFactor, animationController: widget.drilldownAnimationController), onSelectionChanged: widget.onSelectionChanged, selectionSettings: widget.selectionSettings, @@ -1257,36 +1273,38 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> return width > height ? width / height : height / width; } - void _handleDrillDown(TreemapTile tile, bool isForwardDrilldown) { + void _handleDrillDown(TreemapTile tile, bool isDrilledIn) { setState(() { - _initializeDrilldownAnimations( - tile, - widget.controller, - widget.drilldownAnimationController!, - ); + _computeDrilldownTweenValue(tile, widget.drilldownAnimationController!, + widget.breadcrumbKey, isDrilledIn); }); } void _handleAnimationStatusChange(AnimationStatus status) { if (status == AnimationStatus.completed) { widget.controller.visibleLevelIndex++; - _visibleTile!._isDrilled = true; + _visibleTile._isDrilled = true; } - if (!widget.controller.isSizeChanged) { - setState(() { - // Refresh treemap tile after drill down animation ends. - }); - } + setState(() { + // Refresh treemap tile after drill down animation ends. + }); } @override void initState() { - _homeTile = TreemapTile._() - .._group = 'Home' - .._levelIndex = -1 - .._offset = Offset.zero; + _visibleTile = widget.initialTile; if (widget.enableDrillDown) { + _scaleAndTranslationAnimation = CurvedAnimation( + parent: widget.drilldownAnimationController!, curve: Curves.linear); + _opacityAnimation = CurvedAnimation( + parent: widget.drilldownAnimationController!, curve: Curves.linear); + _tileOpacity = Tween(); + _labelAndItemBuilderOpacity = Tween(); + _visibleTileTranslation = Tween(); + _visibleTileScaleSize = Tween(); + _nextTileTranslation = Tween(); + _nextTileScaleSize = Tween(); widget.controller.addDrillDownListener(_handleDrillDown); widget.drilldownAnimationController! .addStatusListener(_handleAnimationStatusChange); @@ -1311,58 +1329,24 @@ class _SquarifiedTreemapState extends State<_SquarifiedTreemap> return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final Size newSize = constraints.biggest; - Size? newSizeFactor; - Offset translation = Offset.zero; if (size != newSize) { - if (widget.enableDrillDown) { - _homeTile!._size ??= newSize; - newSizeFactor = size != null - ? Size( - newSize.width / size!.width, newSize.height / size!.height) - : size; - if (widget.controller.isSizeChanged) { - widget.controller.isSizeChanged = false; - translation = Offset( - _offsetAnimation!.value.dx * newSizeFactor!.width, - _offsetAnimation!.value.dy * newSizeFactor.height); - newSizeFactor = Size( - _scaleAnimation!.value.width * newSizeFactor.width, - _scaleAnimation!.value.height * newSizeFactor.height); - } - } size = newSize; } if (widget.enableDrillDown && (widget.drilldownAnimationController!.isAnimating)) { return _buildAnimatedTreemap( - widget.dataSource, - widget.totalWeight, widget.drilldownAnimationController!, + widget.breadcrumbKey, direction: widget.layoutDirection, ); } - if (_scaleAnimation != null || newSizeFactor != null) { - _updateTileSizeAndOffset( - _homeTile!, - newSizeFactor ?? _scaleAnimation!.value, - newSizeFactor == null - ? (_offsetAnimation != null ? _offsetAnimation!.value : null) - : translation, - widget.controller.visibleLevelIndex, - ); - } - return Stack( children: _buildTiles( - widget.dataSource, - widget.totalWeight, + _visibleTile._descendants!, + _visibleTile.weight, size!, - sizeFactor: newSizeFactor ?? _scaleAnimation?.value, - offset: newSizeFactor == null - ? (_offsetAnimation?.value ?? Offset.zero) - : translation, ), ); }, @@ -1375,28 +1359,28 @@ class _SliceAndDiceTreemap extends StatefulWidget { Key? key, required this.dataCount, required this.type, - required this.dataSource, required this.sortAscending, - required this.totalWeight, required this.tooltipKey, + required this.breadcrumbKey, required this.onSelectionChanged, required this.selectionSettings, required this.enableDrillDown, required this.controller, required this.drilldownAnimationController, + required this.initialTile, }) : super(key: key); final int dataCount; final LayoutType type; - final Map dataSource; final bool sortAscending; - final double totalWeight; final GlobalKey tooltipKey; + final GlobalKey breadcrumbKey; final ValueChanged? onSelectionChanged; final TreemapSelectionSettings selectionSettings; final bool enableDrillDown; final TreemapController controller; final AnimationController? drilldownAnimationController; + final TreemapTile initialTile; @override _SliceAndDiceTreemapState createState() => _SliceAndDiceTreemapState(); @@ -1407,7 +1391,7 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> @override List _buildTiles( Map source, double aggregatedWeight, Size size, - {Size? sizeFactor, Offset? translation}) { + {bool canLayoutTiles = true}) { final List children = []; final List tiles = source.values.toList(); Offset offset = Offset.zero; @@ -1421,18 +1405,7 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> src.weight.compareTo(target.weight)); } for (final TreemapTile tile in tiles) { - // Finding a tile's size, based on its weight. - if (widget.controller.visibleLevelIndex > 0 && tile._levelIndex == 0) { - if (sizeFactor != null) { - // Update tile size and offset value based on the size factor. - _updateTileSizeAndOffset( - tile, - sizeFactor, - translation, - widget.controller.visibleLevelIndex, - ); - } - } else { + if (canLayoutTiles) { if (widget.type == LayoutType.slice) { tile .._size = @@ -1448,12 +1421,6 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> } } - if (sizeFactor != null && - tile._levelIndex == widget.controller.visibleLevelIndex) { - _scaleAnimation = null; - _offsetAnimation = null; - } - children.add(_Tile( type: widget.type, size: tile._size!, @@ -1463,9 +1430,12 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> sortAscending: widget.sortAscending, controller: widget.controller, child: _getDescendants( - context, tile, widget.controller, widget.enableDrillDown, - animationController: widget.drilldownAnimationController, - sizeFactor: sizeFactor), + context, + tile, + widget.controller, + widget.enableDrillDown, + animationController: widget.drilldownAnimationController, + ), onSelectionChanged: widget.onSelectionChanged, selectionSettings: widget.selectionSettings, drilldownAnimationController: widget.drilldownAnimationController, @@ -1475,37 +1445,38 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> return children; } - void _handleDrillDown(TreemapTile tile, bool isForwardDrilldown) { + void _handleDrillDown(TreemapTile tile, bool isDrilledIn) { setState(() { - _initializeDrilldownAnimations( - tile, - widget.controller, - widget.drilldownAnimationController!, - ); + _computeDrilldownTweenValue(tile, widget.drilldownAnimationController!, + widget.breadcrumbKey, isDrilledIn); }); } void _handleAnimationStatusChange(AnimationStatus status) { if (status == AnimationStatus.completed) { widget.controller.visibleLevelIndex++; - _visibleTile!._isDrilled = true; + _visibleTile._isDrilled = true; } - if (!widget.controller.isSizeChanged) { - setState(() { - // Refresh treemap tile after drill down animation ends. - }); - } + setState(() { + // Refresh treemap tile after drill down animation ends. + }); } @override void initState() { - _homeTile = TreemapTile._() - .._group = 'Home' - .._levelIndex = -1 - .._offset = Offset.zero; - + _visibleTile = widget.initialTile; if (widget.enableDrillDown) { + _scaleAndTranslationAnimation = CurvedAnimation( + parent: widget.drilldownAnimationController!, curve: Curves.linear); + _opacityAnimation = CurvedAnimation( + parent: widget.drilldownAnimationController!, curve: Curves.linear); + _tileOpacity = Tween(); + _labelAndItemBuilderOpacity = Tween(); + _visibleTileTranslation = Tween(); + _visibleTileScaleSize = Tween(); + _nextTileTranslation = Tween(); + _nextTileScaleSize = Tween(); widget.controller.addDrillDownListener(_handleDrillDown); widget.drilldownAnimationController! .addStatusListener(_handleAnimationStatusChange); @@ -1530,57 +1501,21 @@ class _SliceAndDiceTreemapState extends State<_SliceAndDiceTreemap> return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { final Size newSize = constraints.biggest; - Size? newSizeFactor; - Offset? translation; if (size != newSize) { - if (widget.enableDrillDown) { - _homeTile!._size ??= newSize; - newSizeFactor = size != null - ? Size( - newSize.width / size!.width, newSize.height / size!.height) - : size; - if (widget.controller.isSizeChanged) { - widget.controller.isSizeChanged = false; - translation = Offset( - _offsetAnimation!.value.dx * newSizeFactor!.width, - _offsetAnimation!.value.dy * newSizeFactor.height); - newSizeFactor = Size( - _scaleAnimation!.value.width * newSizeFactor.width, - _scaleAnimation!.value.height * newSizeFactor.height); - } - } size = newSize; } if (widget.drilldownAnimationController != null && widget.drilldownAnimationController!.isAnimating) { return _buildAnimatedTreemap( - widget.dataSource, - widget.totalWeight, - widget.drilldownAnimationController!, - ); - } - - if (_scaleAnimation != null || newSizeFactor != null) { - _updateTileSizeAndOffset( - _homeTile!, - newSizeFactor ?? _scaleAnimation!.value, - newSizeFactor == null - ? (_offsetAnimation?.value ?? Offset.zero) - : translation, - widget.controller.visibleLevelIndex, - ); + widget.drilldownAnimationController!, widget.breadcrumbKey); } return Stack( children: _buildTiles( - widget.dataSource, - widget.totalWeight, + _visibleTile._descendants!, + _visibleTile.weight, size!, - sizeFactor: newSizeFactor ?? _scaleAnimation?.value, - translation: newSizeFactor == null - ? (_offsetAnimation?.value ?? Offset.zero) - : translation, ), ); }, @@ -1647,8 +1582,20 @@ class _Tile extends StatelessWidget { controller.visibleLevelIndex == details._levelIndex) || drilldownAnimationController!.status == AnimationStatus.reverse)) { - // ignore: avoid_as - final Size scaleSize = ancestor._scaleAnimation.value as Size; + Size scaleSize; + // If we driled-out 2 -> 0 level, we have used two scaling for second + // and zeroth level tiles. So We should use the next tile Scale to the + // second level tile and the visible level scale to the zeroth level + // tiles. + if (drilldownAnimationController!.status == AnimationStatus.reverse && + controller.visibleLevelIndex == details._levelIndex) { + scaleSize = ancestor._nextTileScaleSize! + .evaluate(ancestor._scaleAndTranslationAnimation) as Size; + } else { + scaleSize = ancestor._visibleTileScaleSize! + .evaluate(ancestor._scaleAndTranslationAnimation) as Size; + } + padding = EdgeInsets.fromLTRB( padding.left / scaleSize.width, padding.top / scaleSize.height, @@ -1656,12 +1603,7 @@ class _Tile extends StatelessWidget { padding.bottom / scaleSize.height); } - if (!details._isDrilled || - (drilldownAnimationController != null && - drilldownAnimationController!.status == AnimationStatus.reverse && - details._levelIndex == controller.visibleLevelIndex - 1)) { - current = Padding(padding: padding, child: current); - } + current = Padding(padding: padding, child: current); } return _buildPositionedWidget(current); @@ -2008,52 +1950,33 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } void _handleDrilldownTweenColor() { - if (_ancestor.enableDrilldown && widget.details._isDrilled) { - _colorTween.begin = Colors.transparent; - } - if (widget.drilldownAnimationController!.status == AnimationStatus.forward && - widget.controller.visibleLevelIndex >= widget.details._levelIndex) { + widget.controller.visibleLevelIndex == widget.details._levelIndex) { _colorTween.end = Colors.transparent; } else if (widget.drilldownAnimationController!.status == AnimationStatus.reverse) { if (widget.details._isDrilled) { - _colorTween.begin = widget.details._levelIndex == - widget.controller.visibleLevelIndex - 1 + _colorTween.begin = widget.ancestor._visibleTile._levelIndex + 1 == + widget.details._levelIndex ? widget.details.color : Colors.transparent; - - if (widget.details._levelIndex == - widget.controller.visibleLevelIndex - 1) { - _colorTween.end = Colors.transparent; - } else { - _colorTween.end = - widget.controller.visibleLevelIndex == widget.details._levelIndex - ? _colorTween.end - : Colors.transparent; - } + _colorTween.end = Colors.transparent; } } } Color _getTileColor() { - if (_ancestor.enableDrilldown) { - if (widget.drilldownAnimationController!.status == - AnimationStatus.forward && - widget.controller.visibleLevelIndex >= widget.details._levelIndex) { - return _colorTween.evaluate(widget.ancestor._colorAnimation)!; - } else if (widget.drilldownAnimationController!.status == - AnimationStatus.reverse) { - return _colorTween.evaluate(widget.ancestor._colorAnimation)!; - } + if (_ancestor.enableDrilldown && + widget.drilldownAnimationController!.isAnimating) { + return _colorTween.evaluate(widget.ancestor._opacityAnimation)!; } return _colorTween.evaluate(_animation)!; } - Widget _buildTileBorder( - Widget child, EdgeInsets dimensions, RoundedRectangleBorder? border) { + Widget _buildTileBorder(Widget child, EdgeInsets dimensions, + RoundedRectangleBorder? border, Size? scaleSize) { if (border != null) { BorderRadius borderRadius = border.borderRadius.resolve(Directionality.of(context)); @@ -2064,10 +1987,18 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { widget.details._levelIndex) || widget.drilldownAnimationController!.status == AnimationStatus.reverse)) { - // ignore: avoid_as - final Size scaleSize = widget.ancestor._scaleAnimation.value as Size; - borderRadius = _getBorderRadius(borderRadius, scaleSize); + // If we driled-out 2 -> 0 level, we have used two scaling for second + // and zeroth level tiles. So We should use the next tile Scale to the + // second level tile and the visible level scale to the zeroth level + // tiles. + if (widget.drilldownAnimationController!.status == + AnimationStatus.reverse && + widget.controller.visibleLevelIndex == widget.details._levelIndex) { + scaleSize = widget.ancestor._nextTileScaleSize! + .evaluate(widget.ancestor._scaleAndTranslationAnimation) as Size; + } + borderRadius = _getBorderRadius(borderRadius, scaleSize!); border = AnimatedBorder( borderRadius: borderRadius, side: border.side, @@ -2081,25 +2012,13 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { } } - // We can't scale to the previous level border if we are - // backward drilldown. Subtract the visible level index from 1 to - // get the previous visible level index. - if (!widget.details._isDrilled || - (widget.drilldownAnimationController != null && - widget.drilldownAnimationController!.status == - AnimationStatus.reverse && - widget.details._levelIndex == - widget.controller.visibleLevelIndex - 1)) { - return CustomPaint( - size: widget.size, - foregroundPainter: _BorderPainter(border), - child: ClipPath.shape( - shape: border ?? const RoundedRectangleBorder(), - child: Padding(padding: dimensions, child: child)), - ); - } - - return child; + return CustomPaint( + size: widget.size, + foregroundPainter: _BorderPainter(border), + child: ClipPath.shape( + shape: border ?? const RoundedRectangleBorder(), + child: Padding(padding: dimensions, child: child)), + ); } BorderRadius _getBorderRadius(BorderRadius borderRadius, Size scaleSize) { @@ -2123,96 +2042,28 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { return current; } - // if we forward 0 -> 1st level, we need to ignore scaling to the 0th - // level tiles only. - if (widget.drilldownAnimationController != null && - widget.drilldownAnimationController!.status == - AnimationStatus.forward && - widget.controller.visibleLevelIndex == widget.details._levelIndex) { - itemBuilder = FadeTransition( - opacity: widget.ancestor._parentOpacityAnimation, - child: _buildAnimatedBuilder( - widget.details, - widget.ancestor._scaleAnimation!.value, - widget.drilldownAnimationController!.status, - itemBuilder, - )); - - if (current != null) { - current = Stack(children: [ - itemBuilder, - current, - ]); - } else { - current = itemBuilder; - } - // If we backward 1 -> 0th level we need to ignore scaling for 0th - // levels item builders alone. - } else if (widget.drilldownAnimationController != null && - widget.drilldownAnimationController!.status == - AnimationStatus.reverse) { - itemBuilder = _buildAnimatedBuilder( + if (_ancestor.enableDrilldown) { + itemBuilder = _buildLabeAndItemBuilder( + itemBuilder, widget.details, - widget.ancestor._scaleAnimation!.value, + widget.controller.visibleLevelIndex, widget.drilldownAnimationController!.status, - Align( - alignment: Alignment.topLeft, - child: itemBuilder, - ), + widget.ancestor._scaleAndTranslationAnimation, + widget.ancestor._opacityAnimation, + widget.ancestor._visibleTileScaleSize, + widget.ancestor._nextTileScaleSize, + widget.ancestor._tileOpacity, + widget.ancestor._labelAndItemBuilderOpacity, ); - if (widget.details._isDrilled && - widget.details._levelIndex == - widget.controller.visibleLevelIndex - 1) { - itemBuilder = FadeTransition( - opacity: widget.ancestor._parentOpacityAnimation, - child: itemBuilder, - ); - } else if (widget.controller.visibleLevelIndex == - widget.details._levelIndex) { - // ignore: avoid_as - Size scaleSize = widget.ancestor._scaleAnimation!.value as Size; - scaleSize = scaleSize.width > scaleSize.height - ? Size(scaleSize.width / scaleSize.width, - scaleSize.height / scaleSize.width) - : Size(scaleSize.width / scaleSize.height, - scaleSize.height / scaleSize.height); - itemBuilder = FadeTransition( - opacity: widget.ancestor._labelAndItemBuilderOpacityAnimation, - child: _buildAnimatedBuilder( - widget.details, - scaleSize, - widget.drilldownAnimationController!.status, - Align( - alignment: Alignment.topLeft, - child: itemBuilder, - ), - ), - ); - } + } - if (!widget.details._isDrilled || - (widget.details._levelIndex == - widget.controller.visibleLevelIndex - 1 || - widget.details._levelIndex == - widget.controller.visibleLevelIndex)) { - if (current != null) { - current = Stack(children: [ - itemBuilder, - current, - ]); - } else { - current = itemBuilder; - } - } - } else if (!(widget.details._isDrilled && _ancestor.enableDrilldown)) { - if (current != null) { - current = Stack(children: [ - itemBuilder, - current, - ]); - } else { - current = itemBuilder; - } + if (current != null) { + current = Stack(children: [ + itemBuilder!, + current, + ]); + } else { + current = itemBuilder; } return current; @@ -2259,9 +2110,8 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { @override void dispose() { - _controller - ..removeListener(_rebuild) - ..dispose(); + _animation.removeListener(_rebuild); + _controller.dispose(); widget.controller ..removeSelectionListener(_handleSelectionChange) @@ -2277,12 +2127,16 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { Widget? current = // Loads only the current visible tile label builder if the tiles have // descendants and we enable drilldown. - !widget.details.hasDescendants || - (_ancestor.enableDrilldown && - widget.details._levelIndex >= - widget.controller.visibleLevelIndex) + !widget.details.hasDescendants || _ancestor.enableDrilldown ? widget.child : null; + Size? scaleSize; + if (_ancestor.enableDrilldown && + widget.ancestor._visibleTileScaleSize.begin != null) { + scaleSize = widget.ancestor._visibleTileScaleSize! + .evaluate(widget.ancestor._scaleAndTranslationAnimation) as Size; + } + if (widget.details.level.itemBuilder != null) { current = _buildItemBuilder(current); } @@ -2297,14 +2151,11 @@ class _TileDecorState extends State<_TileDecor> with TickerProviderStateMixin { final RoundedRectangleBorder? border = _getBorder(); final EdgeInsetsGeometry dimensions = border?.dimensions ?? EdgeInsets.zero; if (border != null || widget.onSelectionChanged != null) { - current = _buildTileBorder( - current, dimensions.resolve(Directionality.of(context)), border); + current = _buildTileBorder(current, + dimensions.resolve(Directionality.of(context)), border, scaleSize); } - if (!widget.details.hasDescendants || - (_ancestor.enableDrilldown && - widget.details._levelIndex >= - widget.controller.visibleLevelIndex)) { + if (!widget.details.hasDescendants || _ancestor.enableDrilldown) { return _buildTileDecor(current, context); } else { // Added descendant tiles to the siblings of the parent tile instead of @@ -2352,6 +2203,8 @@ class _Breadcrumbs extends StatefulWidget { required this.settings, required this.controller, required this.animationController, + required this.levels, + required this.initialTile, }) : super(key: key); /// Configuration of the [_Breadcrumbs]. @@ -2363,6 +2216,10 @@ class _Breadcrumbs extends StatefulWidget { /// An opacity animation. final AnimationController animationController; + final List levels; + + final TreemapTile initialTile; + @override _BreadcrumbsState createState() => _BreadcrumbsState(); } @@ -2373,36 +2230,55 @@ class _BreadcrumbsState extends State<_Breadcrumbs> late GlobalKey _breadcrumbItemKey; late Animation _opacityAnimation; late TreemapTile _current; + late List _breadcrumbs; Size? _size; void _handleDrilldown(TreemapTile tile, bool isForward) { _current = tile; _size ??= _breadcrumbItemKey.currentContext!.size; + final List fadeOutBreadcrumb = []; if (isForward) { _tiles.add(tile); + } else { + fadeOutBreadcrumb.addAll(_breadcrumbs); + } + + _breadcrumbs.clear(); + final int breadcrumbTilesLength = _tiles.length; + final int nextLevelIndex = _current._levelIndex + 1; + + for (int i = 0; i < breadcrumbTilesLength; i++) { + _breadcrumbs.add(i > nextLevelIndex + ? fadeOutBreadcrumb[i] + : widget.settings.builder(context, _tiles[i], i == nextLevelIndex)!); } } void _handleStatusChange(AnimationStatus status) { if (widget.animationController.isDismissed) { - _tiles.last._isDrilled = false; - widget.controller.visibleLevelIndex--; - _tiles.removeLast(); - } - if (!widget.controller.isSizeChanged) { - setState(() { - // Refresh breadcrumbs after opacity animation ends. - }); + widget.controller.visibleLevelIndex = _current._levelIndex + 1; + final List tiles = []; + for (int i = 0; i < _tiles.length; i++) { + if (_tiles[i]._levelIndex >= widget.controller.visibleLevelIndex) { + _tiles[i]._isDrilled = false; + _breadcrumbs.removeLast(); + } else { + tiles.add(_tiles[i]); + } + } + _tiles = tiles; } + + setState(() { + // Refresh breadcrumbs after opacity animation ends. + }); } @override void initState() { _tiles = []; - _current = TreemapTile._() - .._group = 'Home' - .._levelIndex = -1; + _current = widget.initialTile; _tiles.add(_current); _breadcrumbItemKey = GlobalKey(); @@ -2411,6 +2287,7 @@ class _BreadcrumbsState extends State<_Breadcrumbs> widget.animationController.addStatusListener(_handleStatusChange); widget.controller.addDrillDownListener(_handleDrilldown); + _breadcrumbs = []; super.initState(); } @@ -2425,46 +2302,65 @@ class _BreadcrumbsState extends State<_Breadcrumbs> Widget build(BuildContext context) { final List children = []; final int length = _tiles.length; + if (_breadcrumbs.isEmpty) { + _breadcrumbs.add(widget.settings.builder(context, _current, true)!); + } final Color dividerColor = Theme.of(context).brightness == Brightness.light ? const Color.fromRGBO(0, 0, 0, 0.54) : const Color.fromRGBO(255, 255, 255, 0.54); - final Widget divider = Padding( - padding: const EdgeInsets.only(right: 6.0), - child: widget.settings.divider ?? - Icon(Icons.chevron_right, size: 16.0, color: dividerColor), - ); + final Widget divider = widget.settings.divider ?? + Icon(Icons.chevron_right, size: 16.0, color: dividerColor); + for (int i = 0; i < length; i++) { - final bool isLast = i == length - 1; - final Widget current = Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (i != 0) divider, - const SizedBox(width: 4.0), - widget.settings.builder.call(context, _tiles[i], isLast)!, - if (!isLast) const SizedBox(width: 10.0), - ], - ); + if (_breadcrumbs[i] != null) { + final Widget current = Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (i != 0) + Padding( + padding: const EdgeInsets.only(left: 5.0, right: 5.0), + child: divider, + ), + _breadcrumbs[i]!, + ], + ); - children.add(_Breadcrumb( - tile: _tiles[i], - controller: widget.controller, - animation: _opacityAnimation, - isLast: isLast, - child: current, - )); + final bool canApplyOpacity = + (widget.animationController.status == AnimationStatus.forward && + _current._levelIndex == _tiles[i]._levelIndex) || + widget.animationController.status == AnimationStatus.reverse && + _current._levelIndex < _tiles[i]._levelIndex; + + children.add(_Breadcrumb( + tile: _tiles[i], + controller: widget.controller, + animation: _opacityAnimation, + canApplyOpacity: canApplyOpacity, + child: current, + )); + } + } + + Widget? current; + if (children.isNotEmpty) { + current = Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: children, + ); } - Widget current = Padding( + final EdgeInsets padding = widget.levels[_current._levelIndex + 1].padding! + .resolve(Directionality.of(context)); + current = AnimatedPadding( key: _breadcrumbItemKey, - padding: - const EdgeInsets.only(left: 4.0, top: 11.0, right: 4.0, bottom: 9.0), + duration: const Duration(milliseconds: 1000), + padding: EdgeInsets.only( + left: padding.left, top: 11.0, bottom: 9.0, right: padding.right), child: Stack( alignment: Alignment.centerLeft, children: [ Opacity(opacity: 0.0, child: divider), - Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: children), + if (current != null) current ], ), ); @@ -2484,14 +2380,14 @@ class _Breadcrumb extends StatefulWidget { required this.tile, required this.controller, required this.animation, - required this.isLast, + required this.canApplyOpacity, required this.child, }) : super(key: key); final TreemapTile tile; final TreemapController controller; final Animation animation; - final bool isLast; + final bool canApplyOpacity; final Widget child; @override @@ -2502,9 +2398,14 @@ class _BreadcrumbState extends State<_Breadcrumb> { @override Widget build(BuildContext context) { Widget current = widget.child; - if ((widget.animation.isCompleted || widget.animation.isDismissed) && - widget.tile._levelIndex == widget.controller.visibleLevelIndex - 2) { - current = MouseRegion( + + // State is not preserved, when a widget's visual tree is changed + // dynamically. So that gesture widget wrapped into the ignore pointer. + current = IgnorePointer( + ignoring: !((widget.animation.isCompleted || + widget.animation.isDismissed) && + widget.tile._levelIndex != widget.controller.visibleLevelIndex - 1), + child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () { @@ -2512,10 +2413,10 @@ class _BreadcrumbState extends State<_Breadcrumb> { }, child: current, ), - ); - } + ), + ); - if (widget.isLast && + if (widget.canApplyOpacity && (widget.animation.status == AnimationStatus.forward || widget.animation.status == AnimationStatus.reverse)) { current = FadeTransition(opacity: widget.animation, child: current); diff --git a/packages/syncfusion_flutter_treemap/lib/src/legend.dart b/packages/syncfusion_flutter_treemap/lib/src/legend.dart index 93bc97bb9..3cb4bfdb0 100644 --- a/packages/syncfusion_flutter_treemap/lib/src/legend.dart +++ b/packages/syncfusion_flutter_treemap/lib/src/legend.dart @@ -77,7 +77,7 @@ enum TreemapLabelOverflow { /// Option to place the labels either between the bars or on the bar in bar /// legend. enum TreemapLegendLabelsPlacement { - /// [TreemapLegendLabelsPlacement.Item] places labels in the center + /// [TreemapLegendLabelsPlacement.onItem] places labels in the center /// of the bar. onItem, @@ -678,7 +678,7 @@ class TreemapLegend extends DiagnosticableTree { /// If the [offset] has been set and if the [position] is top, then the legend /// will be placed in top but in the position additional to the /// actual top position. Also, the legend will not take dedicated position for - /// it and will be drawn on the top of map. + /// it and will be drawn on the top of treemap. /// /// ```dart /// late List _source; @@ -1405,11 +1405,10 @@ class TreemapLegend extends DiagnosticableTree { /// Returns a widget for the given value. /// - /// The pointer is used to indicate the exact colour of the hovering - /// shape or bubble on the segment. + /// The pointer is used to indicate the exact color of the hovering tile. /// /// The [pointerBuilder] will be called when the user interacts with the - /// shapes or bubbles i.e., while tapping in touch devices and hovering in + /// tiles i.e., while tapping in touch devices and hovering in /// the mouse enabled devices. final TreemapLegendPointerBuilder? pointerBuilder; diff --git a/packages/syncfusion_flutter_treemap/lib/treemap.dart b/packages/syncfusion_flutter_treemap/lib/treemap.dart index 56af318f7..8d116ee9e 100644 --- a/packages/syncfusion_flutter_treemap/lib/treemap.dart +++ b/packages/syncfusion_flutter_treemap/lib/treemap.dart @@ -1,3 +1,14 @@ +/// Syncfusion Flutter Treemap library for creating interactive treemap to +/// visualize flat and hierarchical data as rectangles that are sized and +/// colored based on quantitative variables using squarified, slice, and dice +/// algorithms. +/// +/// To use, `import package:syncfusion_flutter_treemap/treemap.dart`; +/// +/// See also: +/// * [Syncfusion Flutter Treemap product page](https://www.syncfusion.com/flutter-widgets/flutter-treemap) +/// * [User guide documentation for Treemap](https://help.syncfusion.com/flutter/treemap/overview) +/// * [Knowledge base](https://www.syncfusion.com/kb/flutter/sftreemap) library treemap; import 'package:flutter/foundation.dart'; @@ -46,7 +57,7 @@ typedef TreemapTileWidgetBuilder = Widget? Function( /// Signature to return a widget based on the given tile. /// /// isCurrent - Specifies whether the current tile’s descendants are in visual. -/// For example, if we drilling down into [0] -> [1] -> [2] level, only the +/// For example, if we drilling down into `0` -> `1` -> `2` level, only the /// second level tiles will be visible, and the rest are hidden in the /// background. /// @@ -90,7 +101,7 @@ enum TreemapBreadcrumbPosition { /// the values returned in the [TreemapLevel.groupMapper] callback will form as /// inner tiles of the tile formed in the previous level for which the indices /// match.This hierarchy will go on till the last [TreemapLevel] in the -/// [Treemap.levels] collection. +/// [SfTreemap.levels] collection. /// /// ```dart /// late List _socialMediaUsersData; @@ -149,12 +160,12 @@ class TreemapLevel extends DiagnosticableTree { /// The levels collection which forms either flat or hierarchal treemap. /// /// You can have more than one [TreemapLevel] in this collection to form a - /// hierarchal treemap. The 0th index of the [TreemapLevel.levels] collection + /// hierarchal treemap. The 0th index of the [Treemap.levels] collection /// forms the base level of the treemap or flat treemap. From the 1st index, /// the values returned in the [TreemapLevel.groupMapper] callback will form /// as inner tiles of the tile formed in the previous level for which the /// indices match.This hierarchy will go on till the last [TreemapLevel] in - /// the [TreemapLevel.levels] collection. + /// the [Treemap.levels] collection. /// /// ```dart /// late List _socialMediaUsersData; @@ -2314,16 +2325,16 @@ class SfTreemap extends StatelessWidget { /// Represents the layout direction of the tiles. /// - /// * The `TreemapLayoutDirection.topLeft` will layout the tiles from top-left + /// * The [TreemapLayoutDirection.topLeft] will layout the tiles from top-left /// to bottom-right of the rectangle. - /// * The `TreemapLayoutDirection.topRight` will layout the tiles from + /// * The [TreemapLayoutDirection.topRight] will layout the tiles from /// top-right to bottom-left of the rectangle. - /// * The `TreemapLayoutDirection.bottomLeft` will start layout the tiles + /// * The [TreemapLayoutDirection.bottomLeft] will start layout the tiles /// from bottom-left to top-right of the rectangle. - /// * The `TreemapLayoutDirection.bottomRight` will start layout the tiles + /// * The [TreemapLayoutDirection.bottomRight] will start layout the tiles /// from bottom-right to top-left of the rectangle. /// - /// Defaults to `TreemapLayoutDirection.topLeft`. + /// Defaults to [TreemapLayoutDirection.topLeft]. /// /// ```dart /// late List _socialMediaUsersData; diff --git a/packages/syncfusion_flutter_treemap/pubspec.yaml b/packages/syncfusion_flutter_treemap/pubspec.yaml index 0ff5a7108..78fe54a18 100644 --- a/packages/syncfusion_flutter_treemap/pubspec.yaml +++ b/packages/syncfusion_flutter_treemap/pubspec.yaml @@ -11,4 +11,4 @@ dependencies: sdk: flutter syncfusion_flutter_core: - path: ../syncfusion_flutter_core \ No newline at end of file + path: ../syncfusion_flutter_core diff --git a/packages/syncfusion_flutter_xlsio/CHANGELOG.md b/packages/syncfusion_flutter_xlsio/CHANGELOG.md index ff01a3962..47d3c2065 100644 --- a/packages/syncfusion_flutter_xlsio/CHANGELOG.md +++ b/packages/syncfusion_flutter_xlsio/CHANGELOG.md @@ -1,4 +1,4 @@ -## Unreleased +## [19.2.44-beta] - 06/30/2021 **Features** diff --git a/packages/syncfusion_flutter_xlsio/README.md b/packages/syncfusion_flutter_xlsio/README.md index 29ce4c490..d26f46cd9 100644 --- a/packages/syncfusion_flutter_xlsio/README.md +++ b/packages/syncfusion_flutter_xlsio/README.md @@ -644,4 +644,4 @@ workbook.dispose(); 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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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. +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), and [Blazor](https://www.syncfusion.com/blazor-components)), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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. diff --git a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart index adf470673..12863e435 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_mobile.dart @@ -1,39 +1,36 @@ import 'dart:io'; + +import 'package:open_file/open_file.dart' as open_file; import 'package:path_provider/path_provider.dart' as path_provider; import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; -import 'package:open_file/open_file.dart' as open_file; ///To save the Excel file in the device -class FileSaveHelper { - ///To save the Excel file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - //Get the storage folder location using path_provider package. - String? path; - if (Platform.isAndroid || - Platform.isIOS || - Platform.isLinux || - Platform.isWindows) { - final Directory directory = - await path_provider.getApplicationSupportDirectory(); - path = directory.path; - } else { - path = await PathProviderPlatform.instance.getApplicationSupportPath(); - } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); - await file.writeAsBytes(bytes, flush: true); - if (Platform.isAndroid || Platform.isIOS) { - //Launch the file (used open_file package) - await open_file.OpenFile.open('$path/$fileName'); - } else if (Platform.isWindows) { - await Process.run('start', ['$path\\$fileName'], - runInShell: true); - } else if (Platform.isMacOS) { - await Process.run('open', ['$path/$fileName'], runInShell: true); - } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); - } +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + //Get the storage folder location using path_provider package. + String? path; + if (Platform.isAndroid || + Platform.isIOS || + Platform.isLinux || + Platform.isWindows) { + final Directory directory = + await path_provider.getApplicationSupportDirectory(); + path = directory.path; + } else { + path = await PathProviderPlatform.instance.getApplicationSupportPath(); + } + final File file = + File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); + await file.writeAsBytes(bytes, flush: true); + if (Platform.isAndroid || Platform.isIOS) { + //Launch the file (used open_file package) + await open_file.OpenFile.open('$path/$fileName'); + } else if (Platform.isWindows) { + await Process.run('start', ['$path\\$fileName'], runInShell: true); + } else if (Platform.isMacOS) { + await Process.run('open', ['$path/$fileName'], runInShell: true); + } else if (Platform.isLinux) { + await Process.run('xdg-open', ['$path/$fileName'], + runInShell: true); } } diff --git a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart index 9c8042230..a455ce682 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/helper/save_file_web.dart @@ -5,14 +5,11 @@ import 'dart:convert'; import 'dart:html'; ///To save the Excel file in the device -class FileSaveHelper { - ///To save the Excel file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); - } +///To save the Excel file in the device +Future saveAndLaunchFile(List bytes, String fileName) async { + AnchorElement( + href: + 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') + ..setAttribute('download', fileName) + ..click(); } diff --git a/packages/syncfusion_flutter_xlsio/example/lib/main.dart b/packages/syncfusion_flutter_xlsio/example/lib/main.dart index 4ec36e5a4..e278f3522 100644 --- a/packages/syncfusion_flutter_xlsio/example/lib/main.dart +++ b/packages/syncfusion_flutter_xlsio/example/lib/main.dart @@ -222,6 +222,6 @@ class _CreateExcelState extends State { workbook.dispose(); //Save and launch the file. - await FileSaveHelper.saveAndLaunchFile(bytes, 'Invoice.xlsx'); + await saveAndLaunchFile(bytes, 'Invoice.xlsx'); } } diff --git a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc index d38195aa0..e71a16d23 100644 --- a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.h index 9bf747894..e0f0a47bc 100644 --- a/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_xlsio/example/linux/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.cc b/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.cc index 4bfa0f3a3..8b6d4680a 100644 --- a/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.cc +++ b/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.cc @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #include "generated_plugin_registrant.h" diff --git a/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.h b/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.h index 9846246b4..dc139d85a 100644 --- a/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.h +++ b/packages/syncfusion_flutter_xlsio/example/windows/flutter/generated_plugin_registrant.h @@ -2,6 +2,8 @@ // Generated file. Do not edit. // +// clang-format off + #ifndef GENERATED_PLUGIN_REGISTRANT_ #define GENERATED_PLUGIN_REGISTRANT_ diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart index bd0811ece..463b68584 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/calc_engine.dart @@ -1765,8 +1765,7 @@ class CalcEngine { col = _grid!.getFirstColumn(); } - result = _getValueFromArg( - sheet + RangeInfo._getAlphaLabel(col) + row.toString()); + result = _getValueFromArg(sheet + _getAlphaLabel(col) + row.toString()); if (!_isIndexInteriorFormula && result.isEmpty) { return '0'; } @@ -3510,7 +3509,7 @@ class CalcEngine { final int c2 = _getColIndex(cells[last]!); final int c = _getColIndex(_cell); if (c >= c1 && c <= c2) { - s = RangeInfo._getAlphaLabel(c) + r1.toString(); + s = _getAlphaLabel(c) + r1.toString(); } } return s; @@ -3555,7 +3554,7 @@ class CalcEngine { args = 'A' + args.substring(0, i) + ':' + - RangeInfo._getAlphaLabel(count) + + _getAlphaLabel(count) + args.substring(i + 1); i = args.indexOf(':'); } @@ -3631,8 +3630,7 @@ class CalcEngine { for (i = row1; i <= row2; ++i) { for (j = col1; j <= col2; ++j) { try { - cells[k++] = - book + sheet + RangeInfo._getAlphaLabel(j) + i.toString(); + cells[k++] = book + sheet + _getAlphaLabel(j) + i.toString(); } catch (e) { continue; } @@ -4397,7 +4395,7 @@ class CalcEngine { } sumRange = sumRange.substring(0, i + 1) + - RangeInfo._getAlphaLabel(col) + + _getAlphaLabel(col) + row.toString(); s2 = _getCellsFromArgs(sumRange); } @@ -4723,7 +4721,7 @@ class CalcEngine { col += count - s2.length; } calculateRange = calculateRange.substring(0, i + 1) + - RangeInfo._getAlphaLabel(col) + + _getAlphaLabel(col) + row.toString(); s2 = _getCellsFromArgs(calculateRange); } @@ -5226,7 +5224,7 @@ class CalcEngine { family._parentObjectToToken!.isEmpty) ? '' : family._parentObjectToToken![grd].toString(); - cell1 = cell1 + RangeInfo._getAlphaLabel(col) + row.toString(); + cell1 = cell1 + _getAlphaLabel(col) + row.toString(); final Worksheet? saveGrid = _grid; final String saveCell = _cell; _cell = cell1; @@ -5340,9 +5338,9 @@ class CalcEngine { if (height != criteriaHeight) { row = startRow + criteriaHeight; } - sumRange = RangeInfo._getAlphaLabel(startCol) + + sumRange = _getAlphaLabel(startCol) + sumRange.substring(1, i + 1) + - RangeInfo._getAlphaLabel(col) + + _getAlphaLabel(col) + row.toString(); } else { int resultRow = 0, resultCol = 0; @@ -5351,7 +5349,7 @@ class CalcEngine { resultCol = _getColIndex(sumRange); resultRow += criteriaHeight; resultCol += crietriaWidth; - resultVal = RangeInfo._getAlphaLabel(resultCol); + resultVal = _getAlphaLabel(resultCol); sumRange = sumRange + ':' + resultVal + resultRow.toString(); } s2 = _getCellsFromArgs(sumRange); diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart index bb9160c8c..2d5422e5e 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/calculate/formula_info.dart @@ -12,23 +12,21 @@ class FormulaInfo { /// RangeInfo represents a rectangle array of cells that may contain formulas, strings, or numbers /// that may be referenced by other formulas. -class RangeInfo { - /// GetAlphaLabel is a method that retrieves a String value for the column whose numerical index is passed in. - static String _getAlphaLabel(int col) { - final List cols = List.filled(10, '', growable: false); - int n = 0; - while (col > 0 && n < 9) { - col--; - cols[n] = String.fromCharCode((col % 26) + 'A'.codeUnitAt(0)); - col = col ~/ 26; - n++; - } - - final List chs = List.filled(n, '', growable: false); - for (int i = 0; i < n; i++) { - chs[n - i - 1] = cols[i]; - } +/// GetAlphaLabel is a method that retrieves a String value for the column whose numerical index is passed in. +String _getAlphaLabel(int col) { + final List cols = List.filled(10, '', growable: false); + int n = 0; + while (col > 0 && n < 9) { + col--; + cols[n] = String.fromCharCode((col % 26) + 'A'.codeUnitAt(0)); + col = col ~/ 26; + n++; + } - return chs.join(); + final List chs = List.filled(n, '', growable: false); + for (int i = 0; i < n; i++) { + chs[n - i - 1] = cols[i]; } + + return chs.join(); } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart index 16269858a..60ae703e1 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/autofit_manager.dart @@ -383,7 +383,7 @@ class _AutoFitManager { for (final int key in columnsWidth.keys) { final int num12 = columnsWidth[key]!; if (num12 != 0) { - _worksheet._setColumnWidthInPixels(key, num12); + _worksheet.setColumnWidthInPixels(key, num12); } } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart index d0487547a..5a286c033 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/general/serialize_workbook.dart @@ -448,6 +448,7 @@ class SerializeWorkbook { return; } builder.element('hyperlinks', nest: () { + int id = 1; for (final Hyperlink link in sheet.hyperlinks.innerList) { if (link._attachedType == ExcelHyperlinkAttachedType.range) { builder.element('hyperlink', nest: () { @@ -456,10 +457,10 @@ class SerializeWorkbook { builder.attribute('location', link.address); } else { builder.attribute('ref', link.reference); - final String id = link._rId.toString(); final String rId = 'rId' + id.toString(); builder.attribute('r:id', rId); _relationId.add(rId); + id++; } if (link.screenTip != null) { builder.attribute('tooltip', link.screenTip!); @@ -874,16 +875,18 @@ class SerializeWorkbook { 'http://schemas.openxmlformats.org/package/2006/relationships'); if (sheet.hyperlinks.count > 0) { + int id = 1; for (final Hyperlink link in sheet.hyperlinks.innerList) { if (link._attachedType == ExcelHyperlinkAttachedType.range && link.type != HyperlinkType.workbook) { builder.element('Relationship', nest: () { - builder.attribute('Id', 'rId' + link._rId.toString()); + builder.attribute('Id', 'rId' + id.toString()); builder.attribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink'); builder.attribute('Target', link.address); builder.attribute('TargetMode', 'External'); }); + id++; } } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart index 9efdd1977..c6a8d2d15 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink.dart @@ -17,10 +17,6 @@ class Hyperlink { } } - /// Represents hyperlink id. - // ignore: prefer_final_fields - int _rId = -1; - /// Represents the hyperlink location. // ignore: unused_field String? _location; diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart index 3b176e05f..09fd0be49 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/hyperlinks/hyperlink_collection.dart @@ -97,6 +97,5 @@ class HyperlinkCollection { /// Add hyperlink to the hyperlinks collection. void addHyperlink(Hyperlink hyperlink) { innerList.add(hyperlink); - hyperlink._rId = count; } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart index d4948b44e..a26e1dcf1 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/range/range.dart @@ -92,12 +92,12 @@ class Range { /// ``` String get addressGlobal { final String result = worksheet.name + '!'; - final String cell0 = r'\$' + _getCellNameWithSymbol(row, column); + final String cell0 = r'$' + _getCellNameWithSymbol(row, column); if (isSingleRange) { return result + cell0; } else { - final String cell1 = r'\$' + _getCellNameWithSymbol(lastRow, lastColumn); + final String cell1 = r'$' + _getCellNameWithSymbol(lastRow, lastColumn); return result + cell0 + ':' + cell1; } } @@ -1090,7 +1090,7 @@ class Range { static void _updateCellValue( Worksheet worksheet, int column, int row, bool updateCellVaue) { if (worksheet.calcEngine != null && updateCellVaue) { - final String cellRef = RangeInfo._getAlphaLabel(column) + row.toString(); + final String cellRef = _getAlphaLabel(column) + row.toString(); worksheet.calcEngine!._pullUpdatedValue(cellRef); } } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart index de98a9e86..68a26829b 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/security/security_helper.dart @@ -1,43 +1,39 @@ part of xlsio; -class _SecurityHelper { - /// Represents the SHA1 Hash Algorithm. - // ignore: unused_field - static const String _sha1Algorithm = 'SHA-1'; +/// Represents the SHA1 Hash Algorithm. +// ignore: unused_element +const String _sha1Algorithm = 'SHA-1'; - /// Represents the SHA256 Hash Algorithm. - // ignore: unused_field - static const String _sha256Alogrithm = 'SHA-256'; +/// Represents the SHA256 Hash Algorithm. +// ignore: unused_element +const String _sha256Alogrithm = 'SHA-256'; - /// Represents the SH512 Hash Algorithm. - static const String _sha512Alogrithm = 'SHA-512'; +/// Represents the SH512 Hash Algorithm. +const String _sha512Alogrithm = 'SHA-512'; - /// Gets HashAlgorithm from the Algorithm name - // ignore: unused_element - static Hash _getAlgorithm(String algorithmName) { - switch (algorithmName) { - case 'SHA-512': - return sha512; - default: - return sha1; - } +/// Gets HashAlgorithm from the Algorithm name +// ignore: unused_element +Hash _getAlgorithm(String algorithmName) { + switch (algorithmName) { + case 'SHA-512': + return sha512; + default: + return sha1; } +} - /// Combines two arrays into one. - static List _combineArray(List buffer1, List buffer2) { - final List arrResult = []; - arrResult.addAll(buffer1); - arrResult.addAll(buffer2); +/// Combines two arrays into one. +List _combineArray(List buffer1, List buffer2) { + final List arrResult = []; + arrResult.addAll(buffer1); + arrResult.addAll(buffer2); - return arrResult; - } + return arrResult; } -class _BitConverter { - static List _getBytes(int value) { - final Uint8List int32Bytes = - Uint8List.fromList(List.filled(4, 0, growable: false)) - ..buffer.asByteData().setInt32(0, value, Endian.big); - return int32Bytes.toList().reversed.toList(); - } +List _getBytes(int value) { + final Uint8List int32Bytes = + Uint8List.fromList(List.filled(4, 0, growable: false)) + ..buffer.asByteData().setInt32(0, value, Endian.big); + return int32Bytes.toList().reversed.toList(); } diff --git a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart index a34317a82..14e31b2bb 100644 --- a/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart +++ b/packages/syncfusion_flutter_xlsio/lib/src/xlsio/worksheet/worksheet.dart @@ -1753,17 +1753,17 @@ class Worksheet { /// Protects the worksheet based on the Excel 2013. void _advancedSheetProtection(String password) { - _algorithmName = _SecurityHelper._sha512Alogrithm; + _algorithmName = _sha512Alogrithm; _saltValue = _createSalt(16); - final Hash algorithm = _SecurityHelper._getAlgorithm(_algorithmName!); + final Hash algorithm = _getAlgorithm(_algorithmName!); List arrPassword = utf8.encode(password).toList(); arrPassword = _convertCodeUnitsToUnicodeByteArray(arrPassword); - List temp = _SecurityHelper._combineArray(_saltValue, arrPassword); + List temp = _combineArray(_saltValue, arrPassword); final List h0 = algorithm.convert(temp).bytes.toList(); List h1 = h0; for (int iterator = 0; iterator < _spinCount; iterator++) { - final List arrIterator = _BitConverter._getBytes(iterator); - temp = _SecurityHelper._combineArray(h1, arrIterator); + final List arrIterator = _getBytes(iterator); + temp = _combineArray(h1, arrIterator); temp = Uint8List.fromList(temp); h1 = algorithm.convert(temp).bytes.toList(); } @@ -1950,10 +1950,46 @@ class Worksheet { return _book._pixelsToWidth(pixels); } - /// Sets column width in pixels for the specified column. - void _setColumnWidthInPixels(int iColumn, int value) { - final double dColumnWidth = _pixelsToColumnWidth(value); - _setColumnWidth(iColumn, dColumnWidth); + /// Sets column width in pixels for the specified row. + /// ```dart + /// //Create a new Excel Document. + /// final Workbook workbook = Workbook(); + /// // Accessing sheet via index. + /// final Worksheet sheet = workbook.worksheets[0]; + /// // set columnWidth in pixels. + /// sheet.setColumnWidthInPixels(2, 20); + /// final List bytes = workbook.saveAsStream(); + /// File('ColumnWidth.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + void setColumnWidthInPixels(int iColumnIndex, int columnWidth) { + final double dColumnWidth = _pixelsToColumnWidth(columnWidth); + _setColumnWidth(iColumnIndex, dColumnWidth); + } + + /// Sets Rows Heights in pixels for the specified column. + /// ```dart + /// //Create a new Excel Document. + /// final Workbook workbook = Workbook(); + /// // Accessing sheet via index. + /// final Worksheet sheet = workbook.worksheets[0]; + /// // set row height in pixels. + /// sheet.setRowHeightInPixels(2, 30); + /// final List bytes = workbook.saveAsStream(); + /// File('RowHeight.xlsx').writeAsBytes(bytes); + /// workbook.dispose(); + /// ``` + void setRowHeightInPixels(int iRowIndex, double rowHeight) { + if (iRowIndex < 1 || iRowIndex > _book._maxRowCount) { + throw Exception( + 'iRowIndex ,Value cannot be less 1 and greater than max row index.'); + } + + if (rowHeight < 0) { + throw Exception('value'); + } + + _innerSetRowHeight(iRowIndex, rowHeight, true, 5); } /// Sets column width for the specified column. @@ -2149,7 +2185,7 @@ class Worksheet { final int height = picture.height; if (_innerGetColumnWidth(range.column) < _pixelsToColumnWidth(width)) { - _setColumnWidthInPixels(range.column, width); + setColumnWidthInPixels(range.column, width); } if (range.rowHeight < height) { range._setRowHeight( diff --git a/packages/syncfusion_flutter_xlsio/lib/xlsio.dart b/packages/syncfusion_flutter_xlsio/lib/xlsio.dart index 1b76edb15..a4ab9f3d3 100644 --- a/packages/syncfusion_flutter_xlsio/lib/xlsio.dart +++ b/packages/syncfusion_flutter_xlsio/lib/xlsio.dart @@ -9,44 +9,64 @@ import 'dart:typed_data'; import 'dart:ui'; import 'package:archive/archive.dart'; -import 'package:xml/xml.dart'; -import 'package:image/image.dart' as img; import 'package:crypto/crypto.dart'; -import 'package:syncfusion_officecore/officecore.dart'; -import 'package:intl/intl.dart'; -import 'package:intl/number_symbols_data.dart'; -import 'package:intl/number_symbols.dart'; +import 'package:image/image.dart' as img; import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbols.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/number_symbols.dart'; +import 'package:intl/number_symbols_data.dart'; +import 'package:syncfusion_officecore/officecore.dart'; +import 'package:xml/xml.dart'; part 'src/xlsio/calculate/calc_engine.dart'; -part 'src/xlsio/calculate/stack.dart'; part 'src/xlsio/calculate/formula_info.dart'; part 'src/xlsio/calculate/sheet_family_item.dart'; -part 'src/xlsio/images/picture.dart'; -part 'src/xlsio/images/pictures_collection.dart'; +part 'src/xlsio/calculate/stack.dart'; +part 'src/xlsio/cell_styles/alignment.dart'; +part 'src/xlsio/cell_styles/border.dart'; part 'src/xlsio/cell_styles/borders.dart'; part 'src/xlsio/cell_styles/cell_style.dart'; +part 'src/xlsio/cell_styles/cell_style_wrapper.dart'; part 'src/xlsio/cell_styles/cell_style_xfs.dart'; part 'src/xlsio/cell_styles/cell_xfs.dart'; +part 'src/xlsio/cell_styles/extend_compare_style.dart'; part 'src/xlsio/cell_styles/font.dart'; part 'src/xlsio/cell_styles/global_style.dart'; -part 'src/xlsio/cell_styles/alignment.dart'; -part 'src/xlsio/cell_styles/border.dart'; -part 'src/xlsio/cell_styles/extend_compare_style.dart'; +part 'src/xlsio/cell_styles/style.dart'; part 'src/xlsio/cell_styles/styles_collection.dart'; +part 'src/xlsio/conditional_format/above_below_average/above_below_average.dart'; +part 'src/xlsio/conditional_format/above_below_average/above_below_average_impl.dart'; +part 'src/xlsio/conditional_format/above_below_average/above_below_average_wrapper.dart'; +part 'src/xlsio/conditional_format/color_scale/color_scale.dart'; +part 'src/xlsio/conditional_format/color_scale/color_scale_impl.dart'; +part 'src/xlsio/conditional_format/color_scale/color_scale_wrapper.dart'; +part 'src/xlsio/conditional_format/condformat_collection_wrapper.dart'; +part 'src/xlsio/conditional_format/condformat_wrapper.dart'; +part 'src/xlsio/conditional_format/condition_value.dart'; +part 'src/xlsio/conditional_format/conditionalformat.dart'; +part 'src/xlsio/conditional_format/conditionalformat_collections.dart'; +part 'src/xlsio/conditional_format/conditionalformat_impl.dart'; +part 'src/xlsio/conditional_format/data_bar/data_bar.dart'; +part 'src/xlsio/conditional_format/data_bar/data_bar_impl.dart'; +part 'src/xlsio/conditional_format/data_bar/data_bar_wrapper.dart'; +part 'src/xlsio/conditional_format/icon_set/icon_set.dart'; +part 'src/xlsio/conditional_format/icon_set/icon_set_impl.dart'; +part 'src/xlsio/conditional_format/icon_set/icon_set_wrapper.dart'; +part 'src/xlsio/conditional_format/top_bottom/top_bottom.dart'; +part 'src/xlsio/conditional_format/top_bottom/top_bottom_impl.dart'; +part 'src/xlsio/conditional_format/top_bottom/top_bottom_wrapper.dart'; part 'src/xlsio/formats/format.dart'; -part 'src/xlsio/formats/formats_collection.dart'; part 'src/xlsio/formats/format_parser.dart'; part 'src/xlsio/formats/format_section.dart'; part 'src/xlsio/formats/format_section_collection.dart'; -part 'src/xlsio/formats/format_tokens/constants.dart'; -part 'src/xlsio/formats/format_tokens/enums.dart'; -part 'src/xlsio/formats/format_tokens/format_token_base.dart'; part 'src/xlsio/formats/format_tokens/am_pm_token.dart'; part 'src/xlsio/formats/format_tokens/character_token.dart'; +part 'src/xlsio/formats/format_tokens/constants.dart'; part 'src/xlsio/formats/format_tokens/day_token.dart'; part 'src/xlsio/formats/format_tokens/decimal_point_token.dart'; +part 'src/xlsio/formats/format_tokens/enums.dart'; +part 'src/xlsio/formats/format_tokens/format_token_base.dart'; part 'src/xlsio/formats/format_tokens/fraction_token.dart'; part 'src/xlsio/formats/format_tokens/hour_24_token.dart'; part 'src/xlsio/formats/format_tokens/hour_token.dart'; @@ -57,48 +77,28 @@ part 'src/xlsio/formats/format_tokens/second_token.dart'; part 'src/xlsio/formats/format_tokens/significant_digit_token.dart'; part 'src/xlsio/formats/format_tokens/unknown_token.dart'; part 'src/xlsio/formats/format_tokens/year_token.dart'; +part 'src/xlsio/formats/formats_collection.dart'; part 'src/xlsio/general/autofit_manager.dart'; +part 'src/xlsio/general/chart_helper.dart'; part 'src/xlsio/general/culture_info.dart'; part 'src/xlsio/general/enums.dart'; part 'src/xlsio/general/serialize_workbook.dart'; part 'src/xlsio/general/workbook.dart'; -part 'src/xlsio/general/chart_helper.dart'; +part 'src/xlsio/hyperlinks/hyperlink.dart'; +part 'src/xlsio/hyperlinks/hyperlink_collection.dart'; +part 'src/xlsio/images/picture.dart'; +part 'src/xlsio/images/pictures_collection.dart'; +part 'src/xlsio/merged_cells/extend_style.dart'; part 'src/xlsio/merged_cells/merge_cells.dart'; part 'src/xlsio/merged_cells/merged_cell_collection.dart'; -part 'src/xlsio/merged_cells/extend_style.dart'; -part 'src/xlsio/range/range.dart'; -part 'src/xlsio/range/row.dart'; -part 'src/xlsio/range/row_collection.dart'; part 'src/xlsio/range/column.dart'; part 'src/xlsio/range/column_collection.dart'; +part 'src/xlsio/range/range.dart'; part 'src/xlsio/range/range_collection.dart'; -part 'src/xlsio/worksheet/worksheet.dart'; -part 'src/xlsio/worksheet/worksheet_collection.dart'; -part 'src/xlsio/worksheet/excel_data_row.dart'; -part 'src/xlsio/cell_styles/style.dart'; -part 'src/xlsio/cell_styles/cell_style_wrapper.dart'; -part 'src/xlsio/hyperlinks/hyperlink.dart'; -part 'src/xlsio/hyperlinks/hyperlink_collection.dart'; +part 'src/xlsio/range/row.dart'; +part 'src/xlsio/range/row_collection.dart'; part 'src/xlsio/security/excel_sheet_protection.dart'; part 'src/xlsio/security/security_helper.dart'; -part 'src/xlsio/conditional_format/conditionalformat.dart'; -part 'src/xlsio/conditional_format/conditionalformat_impl.dart'; -part 'src/xlsio/conditional_format/conditionalformat_collections.dart'; -part 'src/xlsio/conditional_format/condformat_collection_wrapper.dart'; -part 'src/xlsio/conditional_format/condformat_wrapper.dart'; -part 'src/xlsio/conditional_format/condition_value.dart'; -part 'src/xlsio/conditional_format/color_scale/color_scale_impl.dart'; -part 'src/xlsio/conditional_format/color_scale/color_scale_wrapper.dart'; -part 'src/xlsio/conditional_format/color_scale/color_scale.dart'; -part 'src/xlsio/conditional_format/data_bar/data_bar.dart'; -part 'src/xlsio/conditional_format/data_bar/data_bar_impl.dart'; -part 'src/xlsio/conditional_format/data_bar/data_bar_wrapper.dart'; -part 'src/xlsio/conditional_format/icon_set/icon_set.dart'; -part 'src/xlsio/conditional_format/icon_set/icon_set_impl.dart'; -part 'src/xlsio/conditional_format/icon_set/icon_set_wrapper.dart'; -part 'src/xlsio/conditional_format/above_below_average/above_below_average.dart'; -part 'src/xlsio/conditional_format/above_below_average/above_below_average_impl.dart'; -part 'src/xlsio/conditional_format/above_below_average/above_below_average_wrapper.dart'; -part 'src/xlsio/conditional_format/top_bottom/top_bottom.dart'; -part 'src/xlsio/conditional_format/top_bottom/top_bottom_impl.dart'; -part 'src/xlsio/conditional_format/top_bottom/top_bottom_wrapper.dart'; \ No newline at end of file +part 'src/xlsio/worksheet/excel_data_row.dart'; +part 'src/xlsio/worksheet/worksheet.dart'; +part 'src/xlsio/worksheet/worksheet_collection.dart'; diff --git a/packages/syncfusion_flutter_xlsio/pubspec.yaml b/packages/syncfusion_flutter_xlsio/pubspec.yaml index e0c4fb49a..7d3fe52a8 100644 --- a/packages/syncfusion_flutter_xlsio/pubspec.yaml +++ b/packages/syncfusion_flutter_xlsio/pubspec.yaml @@ -9,10 +9,10 @@ environment: dependencies: flutter: sdk: flutter - xml: ^5.0.2 + xml: ^5.1.0 archive: ^3.1.2 image: ^3.0.1 intl: ^0.17.0 crypto: ^3.0.0 syncfusion_officecore: - path: ../syncfusion_officecore \ No newline at end of file + path: ../syncfusion_flutter_officecore \ No newline at end of file diff --git a/packages/syncfusion_localizations/README.md b/packages/syncfusion_localizations/README.md index b6e038df9..ac26c42a3 100644 --- a/packages/syncfusion_localizations/README.md +++ b/packages/syncfusion_localizations/README.md @@ -120,4 +120,4 @@ Take a look at the following to learn more about Syncfusion Flutter widgets: 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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), and [UWP](https://www.syncfusion.com/uwp-ui-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 +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), and [Blazor](https://www.syncfusion.com/blazor-components), mobile ([Xamarin](https://www.syncfusion.com/xamarin-ui-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 ([WinForms](https://www.syncfusion.com/winforms-ui-controls), [WPF](https://www.syncfusion.com/wpf-ui-controls), [UWP](https://www.syncfusion.com/uwp-ui-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_localizations/example/pubspec.yaml b/packages/syncfusion_localizations/example/pubspec.yaml index 383b1e8e9..c455fad4e 100644 --- a/packages/syncfusion_localizations/example/pubspec.yaml +++ b/packages/syncfusion_localizations/example/pubspec.yaml @@ -10,8 +10,10 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - syncfusion_localizations: ^19.1.57 - syncfusion_flutter_calendar: ^19.1.57 + syncfusion_localizations: + path: ../../syncfusion_localizations + syncfusion_flutter_calendar: + path: ../../syncfusion_flutter_calendar cupertino_icons: ^0.1.3 flutter: 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 d1d537f7d..4f07971b3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart +++ b/packages/syncfusion_localizations/lib/src/l10n/generated_syncfusion_localizations.dart @@ -32,22 +32,22 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Skedule'; @override - String get allowedViewTimelineDayLabel => r'Tydlyndag'; + String get allowedViewTimelineDayLabel => r'Tydlyn dag'; @override - String get allowedViewTimelineMonthLabel => r'Tydlynmaand'; + String get allowedViewTimelineMonthLabel => r'Tydlyn Maand'; @override String get allowedViewTimelineWeekLabel => r'Tydlynweek'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tydlyn-werkweek'; + String get allowedViewTimelineWorkWeekLabel => r'Tydlynwerkweek'; @override String get allowedViewWeekLabel => r'Week'; @override - String get allowedViewWorkWeekLabel => r'Werksweek'; + String get allowedViewWorkWeekLabel => r'Werkweek'; @override String get daySpanCountLabel => r'Dag'; @@ -59,7 +59,6 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'items'; @override @@ -75,7 +74,7 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Geen gebeure nie'; @override - String get noSelectedDateCalendarLabel => r'Geen geselekteerde datum nie'; + String get noSelectedDateCalendarLabel => r'Geen gekose datum nie'; @override String get ofDataPagerLabel => r'van'; @@ -93,13 +92,14 @@ class SfLocalizationsAf extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Gaan na bladsy'; @override - String get pdfInvalidPageNumberLabel => r'Voer ' "'" r'n geldige nommer in'; + String get pdfInvalidPageNumberLabel => + r'Voer asseblief ' "'" r'n geldige nommer in'; @override String get pdfNoBookmarksLabel => r'Geen boekmerke gevind nie'; @override - String get pdfPaginationDialogCancelLabel => r'KANSELLER'; + String get pdfPaginationDialogCancelLabel => r'KANSELLEER'; @override String get pdfPaginationDialogOkLabel => r'OK'; @@ -166,6 +166,9 @@ class SfLocalizationsAf extends SfGlobalLocalizations { @override String get todayLabel => r'Vandag'; + + @override + String get weeknumberLabel => r'Week'; } /// The translations for Amharic (`am`). @@ -184,19 +187,19 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'ወር'; @override - String get allowedViewScheduleLabel => r'የጊዜ ሰሌዳ'; + String get allowedViewScheduleLabel => r'መርሐግብር'; @override - String get allowedViewTimelineDayLabel => r'የጊዜ ሰሌዳ ቀን'; + String get allowedViewTimelineDayLabel => r'የጊዜ መስመር ቀን'; @override - String get allowedViewTimelineMonthLabel => r'የጊዜ ሰሌዳ ወር'; + String get allowedViewTimelineMonthLabel => r'የጊዜ መስመር ወር'; @override - String get allowedViewTimelineWeekLabel => r'የጊዜ ሰሌዳ ሳምንት'; + String get allowedViewTimelineWeekLabel => r'የጊዜ መስመር ሳምንት'; @override - String get allowedViewTimelineWorkWeekLabel => r'የጊዜ መስመር የስራ ሳምንት'; + String get allowedViewTimelineWorkWeekLabel => r'የጊዜ መስመር የሥራ ሳምንት'; @override String get allowedViewWeekLabel => r'ሳምንት'; @@ -208,17 +211,16 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get daySpanCountLabel => r'ቀን'; @override - String get dhualhiLabel => r'ዱል ሂጃህ'; + String get dhualhiLabel => r'ዱ አል-ሂጃህ'; @override - String get dhualqiLabel => r'ዱ አል-ቂዳህ'; + String get dhualqiLabel => r'ዱ አል-ቂዕዳ'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'ዕቃዎች'; + String get itemsDataPagerLabel => r'ንጥሎች'; @override - String get jumada1Label => r'ጁማዳ አል-አወል'; + String get jumada1Label => r'ጁመዳ አል-አወል'; @override String get jumada2Label => r'ጁማዳ አል-ታኒ'; @@ -227,10 +229,10 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get muharramLabel => r'ሙሃረም'; @override - String get noEventsCalendarLabel => r'ክስተቶች የሉም'; + String get noEventsCalendarLabel => r'ምንም ክስተቶች የሉም'; @override - String get noSelectedDateCalendarLabel => r'ምንም የተመረጠ ቀን የለም'; + String get noSelectedDateCalendarLabel => r'የተመረጠ ቀን የለም'; @override String get ofDataPagerLabel => r'የ'; @@ -248,13 +250,13 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'ወደ ገጽ ይሂዱ'; @override - String get pdfInvalidPageNumberLabel => r'እባክዎ ትክክለኛ ቁጥር ያስገቡ'; + String get pdfInvalidPageNumberLabel => r'እባክዎ የሚሰራ ቁጥር ያስገቡ'; @override String get pdfNoBookmarksLabel => r'ምንም ዕልባቶች አልተገኙም'; @override - String get pdfPaginationDialogCancelLabel => r'መሰረዝ'; + String get pdfPaginationDialogCancelLabel => r'ሰርዝ'; @override String get pdfPaginationDialogOkLabel => r'እሺ'; @@ -263,13 +265,13 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'የ'; @override - String get rabi1Label => r'ራቢዕ አል-አወል'; + String get rabi1Label => r'ረቢዕ አል-አወል'; @override - String get rabi2Label => r'ራቢዕ አል-ታኒ'; + String get rabi2Label => r'ረቢዕ አል-ታኒ'; @override - String get rajabLabel => r'ራጃብ'; + String get rajabLabel => r'ረጀብ'; @override String get ramadanLabel => r'ረመዳን'; @@ -278,49 +280,52 @@ class SfLocalizationsAm extends SfGlobalLocalizations { String get safarLabel => r'ሳፋር'; @override - String get shaabanLabel => r'ሻአባን'; + String get shaabanLabel => r'ሻዕባን'; @override - String get shawwalLabel => r'ሻውል'; + String get shawwalLabel => r'ሸዋል'; @override String get shortDhualhiLabel => r'ዱል-ኤች'; @override - String get shortDhualqiLabel => r'ዱል-ኪ'; + String get shortDhualqiLabel => r'ዙል-ቁ'; @override - String get shortJumada1Label => r'ጃም እኔ'; + String get shortJumada1Label => r'ጁም. እኔ'; @override - String get shortJumada2Label => r'ጃም II'; + String get shortJumada2Label => r'ጁም. II'; @override - String get shortMuharramLabel => r'ሙ.'; + String get shortMuharramLabel => r'ሙህ።'; @override - String get shortRabi1Label => r'ራቢ. እኔ'; + String get shortRabi1Label => r'ረቢ። እኔ'; @override - String get shortRabi2Label => r'ራቢ. II'; + String get shortRabi2Label => r'ረቢ። II'; @override - String get shortRajabLabel => r'ራጅ'; + String get shortRajabLabel => r'ራጅ።'; @override String get shortRamadanLabel => r'ራንደም አክሰስ ሜሞሪ.'; @override - String get shortSafarLabel => r'ሳፍ'; + String get shortSafarLabel => r'ሳፍ.'; @override String get shortShaabanLabel => r'ሻ.'; @override - String get shortShawwalLabel => r'ሻው.'; + String get shortShawwalLabel => r'ሸው።'; @override String get todayLabel => r'ዛሬ'; + + @override + String get weeknumberLabel => r'ሳምንት'; } /// The translations for Arabic (`ar`). @@ -369,7 +374,6 @@ class SfLocalizationsAr extends SfGlobalLocalizations { String get dhualqiLabel => r'ذو القعدة'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'العناصر'; @override @@ -412,7 +416,7 @@ class SfLocalizationsAr extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'إلغاء'; @override - String get pdfPaginationDialogOkLabel => r'حسنا'; + String get pdfPaginationDialogOkLabel => r'نعم'; @override String get pdfScrollStatusOfLabel => r'من'; @@ -477,6 +481,9 @@ class SfLocalizationsAr extends SfGlobalLocalizations { @override String get todayLabel => r'اليوم'; + + @override + String get weeknumberLabel => r'أسبوع'; } /// The translations for Azerbaijani (`az`). @@ -501,50 +508,49 @@ class SfLocalizationsAz extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'Zaman Çizelgesi Günü'; @override - String get allowedViewTimelineMonthLabel => r'Təqvim ayı'; + String get allowedViewTimelineMonthLabel => r'Zaman Çizelgesi Ayı'; @override - String get allowedViewTimelineWeekLabel => r'Həyat həftəsi'; + String get allowedViewTimelineWeekLabel => r'Zaman Çizelgesi Həftəsi'; @override - String get allowedViewTimelineWorkWeekLabel => r'İş qrafiki'; + String get allowedViewTimelineWorkWeekLabel => r'İş Həftəsi Qrafiki'; @override String get allowedViewWeekLabel => r'Həftə'; @override - String get allowedViewWorkWeekLabel => r'İş həftəsi'; + String get allowedViewWorkWeekLabel => r'İş Həftəsi'; @override String get daySpanCountLabel => r'Gün'; @override - String get dhualhiLabel => r'Zül-hiccə'; + String get dhualhiLabel => r'Zilhiccə'; @override - String get dhualqiLabel => r'Zülqüda'; + String get dhualqiLabel => r'Zülqüdah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'maddələr'; @override - String get jumada1Label => r'Jumada al-awal'; + String get jumada1Label => r'Cümədə əl-əvvəl'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'Jumada əl-tani'; @override String get muharramLabel => r'Məhərrəm'; @override - String get noEventsCalendarLabel => r'Tədbir yoxdur'; + String get noEventsCalendarLabel => r'Hadisələr yoxdur'; @override String get noSelectedDateCalendarLabel => r'Seçilmiş tarix yoxdur'; @override - String get ofDataPagerLabel => r'of'; + String get ofDataPagerLabel => r'-dən'; @override String get pagesDataPagerLabel => r'səhifələr'; @@ -560,7 +566,7 @@ class SfLocalizationsAz extends SfGlobalLocalizations { @override String get pdfInvalidPageNumberLabel => - r'Xahiş edirəm etibarlı bir nömrə daxil edin'; + r'Zəhmət olmasa düzgün nömrə daxil edin'; @override String get pdfNoBookmarksLabel => r'Əlfəcin tapılmadı'; @@ -572,13 +578,13 @@ class SfLocalizationsAz extends SfGlobalLocalizations { String get pdfPaginationDialogOkLabel => r'tamam'; @override - String get pdfScrollStatusOfLabel => r'of'; + String get pdfScrollStatusOfLabel => r'-dən'; @override - String get rabi1Label => r'Rəbiul-əvvəl'; + String get rabi1Label => r'Rəbiül-əvvəl'; @override - String get rabi2Label => r'Rabi ' "'" r'əl-thani'; + String get rabi2Label => r'Rəbi əl-Tani'; @override String get rajabLabel => r'Rəcəb'; @@ -590,16 +596,16 @@ class SfLocalizationsAz extends SfGlobalLocalizations { String get safarLabel => r'Səfər'; @override - String get shaabanLabel => r'Şəban'; + String get shaabanLabel => r'Şaban'; @override String get shawwalLabel => r'Şəvval'; @override - String get shortDhualhiLabel => r'Zül-H'; + String get shortDhualhiLabel => r'Zülh'; @override - String get shortDhualqiLabel => r'Zül-Q'; + String get shortDhualqiLabel => r'Zülq Q'; @override String get shortJumada1Label => r'Jum. Mən'; @@ -626,13 +632,16 @@ class SfLocalizationsAz extends SfGlobalLocalizations { String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Şa.'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortShawwalLabel => r'Şou.'; + String get shortShawwalLabel => r'Shaw.'; @override String get todayLabel => r'Bu gün'; + + @override + String get weeknumberLabel => r'Həftə'; } /// The translations for Belarusian (`be`). @@ -657,13 +666,13 @@ class SfLocalizationsBe extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'Дзень часовай шкалы'; @override - String get allowedViewTimelineMonthLabel => r'Месяц тэрмінаў'; + String get allowedViewTimelineMonthLabel => r'Месяц часовай шкалы'; @override - String get allowedViewTimelineWeekLabel => r'Тыдзень тэрмінаў'; + String get allowedViewTimelineWeekLabel => r'Тыдзень часовай шкалы'; @override - String get allowedViewTimelineWorkWeekLabel => r'Графік працоўнага тыдня'; + String get allowedViewTimelineWorkWeekLabel => r'Графік працы тыдзень'; @override String get allowedViewWeekLabel => r'Тыдзень'; @@ -675,26 +684,25 @@ class SfLocalizationsBe extends SfGlobalLocalizations { String get daySpanCountLabel => r'Дзень'; @override - String get dhualhiLabel => r'Джу аль-Хіджа'; + String get dhualhiLabel => r'Дху аль-Хіджа'; @override - String get dhualqiLabel => r'Ду аль-Кіда'; + String get dhualqiLabel => r'Дху аль-Кіда'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'прадметы'; @override String get jumada1Label => r'Джумада аль-аўваль'; @override - String get jumada2Label => r'Джумада аль-Тані'; + String get jumada2Label => r'Джумада аль-тані'; @override String get muharramLabel => r'Мухаррам'; @override - String get noEventsCalendarLabel => r'Ніякіх падзей'; + String get noEventsCalendarLabel => r'Няма падзей'; @override String get noSelectedDateCalendarLabel => r'Няма выбранай даты'; @@ -703,7 +711,7 @@ class SfLocalizationsBe extends SfGlobalLocalizations { String get ofDataPagerLabel => r'з'; @override - String get pagesDataPagerLabel => r'старонкі'; + String get pagesDataPagerLabel => r'старонак'; @override String get pdfBookmarksLabel => r'Закладкі'; @@ -719,10 +727,10 @@ class SfLocalizationsBe extends SfGlobalLocalizations { r'Калі ласка, увядзіце сапраўдны нумар'; @override - String get pdfNoBookmarksLabel => r'Закладак не знойдзена'; + String get pdfNoBookmarksLabel => r'Закладкі не знойдзены'; @override - String get pdfPaginationDialogCancelLabel => r'АДМЕНА'; + String get pdfPaginationDialogCancelLabel => r'СКАСАВАЦЬ'; @override String get pdfPaginationDialogOkLabel => r'добра'; @@ -731,10 +739,10 @@ class SfLocalizationsBe extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'з'; @override - String get rabi1Label => r'Рабі аль-Аўваль'; + String get rabi1Label => r'Рабі аль-аўваль'; @override - String get rabi2Label => r'Рабі аль-Тані'; + String get rabi2Label => r'Рабі-аль-Тані'; @override String get rajabLabel => r'Раджаб'; @@ -752,10 +760,10 @@ class SfLocalizationsBe extends SfGlobalLocalizations { String get shawwalLabel => r'Шаўваль'; @override - String get shortDhualhiLabel => r'Зул-Н'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override - String get shortDhualqiLabel => r'Зул-К'; + String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override String get shortJumada1Label => r'Jum. Я'; @@ -764,7 +772,7 @@ class SfLocalizationsBe extends SfGlobalLocalizations { String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'М-м-м.'; + String get shortMuharramLabel => r'Ага.'; @override String get shortRabi1Label => r'Рабі. Я'; @@ -789,6 +797,9 @@ class SfLocalizationsBe extends SfGlobalLocalizations { @override String get todayLabel => r'Сёння'; + + @override + String get weeknumberLabel => r'Тыдзень'; } /// The translations for Bulgarian (`bg`). @@ -813,14 +824,14 @@ class SfLocalizationsBg extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'Ден на хронологията'; @override - String get allowedViewTimelineMonthLabel => r'Месец на хронологията'; + String get allowedViewTimelineMonthLabel => r'Хронология Месец'; @override - String get allowedViewTimelineWeekLabel => r'Седмица на времевата скала'; + String get allowedViewTimelineWeekLabel => r'Седмица на хронологията'; @override String get allowedViewTimelineWorkWeekLabel => - r'Работна седмица на хронологията'; + r'Работна седмица по времева линия'; @override String get allowedViewWeekLabel => r'Седмица'; @@ -832,13 +843,12 @@ class SfLocalizationsBg extends SfGlobalLocalizations { String get daySpanCountLabel => r'Ден'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get dhualhiLabel => r'Дху ал-Хиджджа'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => r'Дху ал-Кида'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'елементи'; @override @@ -905,7 +915,7 @@ class SfLocalizationsBg extends SfGlobalLocalizations { String get shaabanLabel => r'Шаабан'; @override - String get shawwalLabel => r'Шаввал'; + String get shawwalLabel => r'Шаувал'; @override String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @@ -920,7 +930,7 @@ class SfLocalizationsBg extends SfGlobalLocalizations { String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Ммм.'; + String get shortMuharramLabel => r'Ами'; @override String get shortRabi1Label => r'Раби. Аз'; @@ -945,6 +955,9 @@ class SfLocalizationsBg extends SfGlobalLocalizations { @override String get todayLabel => r'Днес'; + + @override + String get weeknumberLabel => r'Седмица'; } /// The translations for Bengali Bangla (`bn`). @@ -963,19 +976,19 @@ class SfLocalizationsBn extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'মাস'; @override - String get allowedViewScheduleLabel => r'সময়সূচী'; + String get allowedViewScheduleLabel => r'তফসিল'; @override - String get allowedViewTimelineDayLabel => r'সময়রেখার দিন'; + String get allowedViewTimelineDayLabel => r'সময়রেখা দিন'; @override - String get allowedViewTimelineMonthLabel => r'টাইমলাইন মাস'; + String get allowedViewTimelineMonthLabel => r'সময়রেখা মাস'; @override String get allowedViewTimelineWeekLabel => r'টাইমলাইন সপ্তাহ'; @override - String get allowedViewTimelineWorkWeekLabel => r'সময়রেখা কর্ম সপ্তাহ'; + String get allowedViewTimelineWorkWeekLabel => r'টাইমলাইন কর্ম সপ্তাহ'; @override String get allowedViewWeekLabel => r'সপ্তাহ'; @@ -987,38 +1000,37 @@ class SfLocalizationsBn extends SfGlobalLocalizations { String get daySpanCountLabel => r'দিন'; @override - String get dhualhiLabel => r'ধূ আল-হিজাহ'; + String get dhualhiLabel => r'ধু আল-হিজজা'; @override - String get dhualqiLabel => r'ধু আল-কিয়াদাহ'; + String get dhualqiLabel => r'ধু আল-ক্বিদা'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'আইটেম'; @override - String get jumada1Label => r'জুমদা আল-আউয়াল'; + String get jumada1Label => r'জুমাদা আল-আউয়াল'; @override - String get jumada2Label => r'জুমদা আল থানি'; + String get jumada2Label => r'জুমাদা আল-থানি'; @override - String get muharramLabel => r'মোহাররম'; + String get muharramLabel => r'মহররম'; @override String get noEventsCalendarLabel => r'কোন ইভেন্ট নেই'; @override - String get noSelectedDateCalendarLabel => r'কোনও নির্বাচিত তারিখ নেই'; + String get noSelectedDateCalendarLabel => r'কোন নির্বাচিত তারিখ নেই'; @override String get ofDataPagerLabel => r'এর'; @override - String get pagesDataPagerLabel => r'পৃষ্ঠা'; + String get pagesDataPagerLabel => r'পাতা'; @override - String get pdfBookmarksLabel => r'বুকমার্কস'; + String get pdfBookmarksLabel => r'বুকমার্ক'; @override String get pdfEnterPageNumberLabel => r'পৃষ্ঠা নম্বর লিখুন'; @@ -1027,13 +1039,13 @@ class SfLocalizationsBn extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'পৃষ্ঠায় যান'; @override - String get pdfInvalidPageNumberLabel => r'দয়া করে একটি বৈধ সংখ্যা লিখুন'; + String get pdfInvalidPageNumberLabel => r'দয়া করে একটি বৈধ নম্বর লিখুন'; @override - String get pdfNoBookmarksLabel => r'কোনও বুকমার্ক পাওয়া যায় নি'; + String get pdfNoBookmarksLabel => r'কোন বুকমার্ক পাওয়া যায়নি'; @override - String get pdfPaginationDialogCancelLabel => r'বাতিল'; + String get pdfPaginationDialogCancelLabel => r'বাতিল করুন'; @override String get pdfPaginationDialogOkLabel => r'ঠিক আছে'; @@ -1042,10 +1054,10 @@ class SfLocalizationsBn extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'এর'; @override - String get rabi1Label => r'রাবি আল-আউয়াল'; + String get rabi1Label => r'রবিউল আউয়াল'; @override - String get rabi2Label => r'রাবি ' "'" r'আল-থানি'; + String get rabi2Label => r'রবি আল-থানি'; @override String get rajabLabel => r'রজব'; @@ -1069,19 +1081,19 @@ class SfLocalizationsBn extends SfGlobalLocalizations { String get shortDhualqiLabel => r'ধুল-কিউ'; @override - String get shortJumada1Label => r'জম। আমি'; + String get shortJumada1Label => r'জুম। আমি'; @override - String get shortJumada2Label => r'জম। II'; + String get shortJumada2Label => r'জুম। II'; @override String get shortMuharramLabel => r'মুহ।'; @override - String get shortRabi1Label => r'রবি। আমি'; + String get shortRabi1Label => r'রাবি। আমি'; @override - String get shortRabi2Label => r'রবি। II'; + String get shortRabi2Label => r'রাবি। II'; @override String get shortRajabLabel => r'রাজ।'; @@ -1090,16 +1102,19 @@ class SfLocalizationsBn extends SfGlobalLocalizations { String get shortRamadanLabel => r'র্যাম.'; @override - String get shortSafarLabel => r'সাফ'; + String get shortSafarLabel => r'সেফ।'; @override - String get shortShaabanLabel => r'শ।'; + String get shortShaabanLabel => r'শা।'; @override String get shortShawwalLabel => r'শ।'; @override String get todayLabel => r'আজ'; + + @override + String get weeknumberLabel => r'সপ্তাহ'; } /// The translations for Bosnian (`bs`). @@ -1121,16 +1136,17 @@ class SfLocalizationsBs extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Raspored'; @override - String get allowedViewTimelineDayLabel => r'Dan vremenske trake'; + String get allowedViewTimelineDayLabel => r'Dan vremenske linije'; @override - String get allowedViewTimelineMonthLabel => r'Mjesec vremenske trake'; + String get allowedViewTimelineMonthLabel => r'Mjesec vremenske linije'; @override - String get allowedViewTimelineWeekLabel => r'Nedelja vremenske trake'; + String get allowedViewTimelineWeekLabel => r'Sedmica vremenske linije'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; + String get allowedViewTimelineWorkWeekLabel => + r'Radna sedmica vremenske linije'; @override String get allowedViewWeekLabel => r'Sedmica'; @@ -1148,11 +1164,10 @@ class SfLocalizationsBs extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'predmeta'; + String get itemsDataPagerLabel => r'stavke'; @override - String get jumada1Label => r'Jumada al-avval'; + String get jumada1Label => r'Džumada el-avval'; @override String get jumada2Label => r'Jumada al-thani'; @@ -1167,7 +1182,7 @@ class SfLocalizationsBs extends SfGlobalLocalizations { String get noSelectedDateCalendarLabel => r'Nema odabranog datuma'; @override - String get ofDataPagerLabel => r'od'; + String get ofDataPagerLabel => r'of'; @override String get pagesDataPagerLabel => r'stranice'; @@ -1185,25 +1200,25 @@ class SfLocalizationsBs extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'Unesite važeći broj'; @override - String get pdfNoBookmarksLabel => r'Oznake nisu pronađene'; + String get pdfNoBookmarksLabel => r'Nisu pronađene oznake'; @override - String get pdfPaginationDialogCancelLabel => r'OTKAŽI'; + String get pdfPaginationDialogCancelLabel => r'CANCEL'; @override String get pdfPaginationDialogOkLabel => r'uredu'; @override - String get pdfScrollStatusOfLabel => r'od'; + String get pdfScrollStatusOfLabel => r'of'; @override - String get rabi1Label => r'Rabi al-Avval'; + String get rabi1Label => r'Rabi ' "'" r'al-awwal'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @override - String get rajabLabel => r'Rajab'; + String get rajabLabel => r'Redžeb'; @override String get ramadanLabel => r'Ramazan'; @@ -1224,7 +1239,7 @@ class SfLocalizationsBs extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. Ja'; + String get shortJumada1Label => r'Jum. I'; @override String get shortJumada2Label => r'Jum. II'; @@ -1233,7 +1248,7 @@ class SfLocalizationsBs extends SfGlobalLocalizations { String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. Ja'; + String get shortRabi1Label => r'Rabi. I'; @override String get shortRabi2Label => r'Rabi. II'; @@ -1255,6 +1270,9 @@ class SfLocalizationsBs extends SfGlobalLocalizations { @override String get todayLabel => r'Danas'; + + @override + String get weeknumberLabel => r'Sedmica'; } /// The translations for Catalan Valencian (`ca`). @@ -1282,7 +1300,7 @@ class SfLocalizationsCa extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Mes de cronologia'; @override - String get allowedViewTimelineWeekLabel => r'Setmana del cronograma'; + String get allowedViewTimelineWeekLabel => r'Setmana de la cronologia'; @override String get allowedViewTimelineWorkWeekLabel => @@ -1304,7 +1322,6 @@ class SfLocalizationsCa extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'articles'; @override @@ -1412,6 +1429,9 @@ class SfLocalizationsCa extends SfGlobalLocalizations { @override String get todayLabel => r'Avui'; + + @override + String get weeknumberLabel => r'Setmana'; } /// The translations for Czech (`cs`). @@ -1439,7 +1459,7 @@ class SfLocalizationsCs extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Měsíc časové osy'; @override - String get allowedViewTimelineWeekLabel => r'Časová osa týden'; + String get allowedViewTimelineWeekLabel => r'Týden časové osy'; @override String get allowedViewTimelineWorkWeekLabel => @@ -1461,7 +1481,6 @@ class SfLocalizationsCs extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'položky'; @override @@ -1477,13 +1496,13 @@ class SfLocalizationsCs extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Žádné události'; @override - String get noSelectedDateCalendarLabel => r'Není vybráno žádné datum'; + String get noSelectedDateCalendarLabel => r'Žádné vybrané datum'; @override String get ofDataPagerLabel => r'z'; @override - String get pagesDataPagerLabel => r'stránky'; + String get pagesDataPagerLabel => r'stránek'; @override String get pdfBookmarksLabel => r'Záložky'; @@ -1516,13 +1535,13 @@ class SfLocalizationsCs extends SfGlobalLocalizations { String get rabi2Label => r'Rabi ' "'" r'al-thani'; @override - String get rajabLabel => r'Rajab'; + String get rajabLabel => r'Rádžab'; @override String get ramadanLabel => r'Ramadán'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'Šafář'; @override String get shaabanLabel => r'Sha' "'" r'aban'; @@ -1568,6 +1587,9 @@ class SfLocalizationsCs extends SfGlobalLocalizations { @override String get todayLabel => r'Dnes'; + + @override + String get weeknumberLabel => r'Týden'; } /// The translations for Danish (`da`). @@ -1589,13 +1611,13 @@ class SfLocalizationsDa extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Tidsplan'; @override - String get allowedViewTimelineDayLabel => r'Tidslinjedag'; + String get allowedViewTimelineDayLabel => r'Tidslinjens dag'; @override - String get allowedViewTimelineMonthLabel => r'Tidslinjemåned'; + String get allowedViewTimelineMonthLabel => r'Tidslinje Måned'; @override - String get allowedViewTimelineWeekLabel => r'Tidslinjeuge'; + String get allowedViewTimelineWeekLabel => r'Tidslinje uge'; @override String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbejdsuge'; @@ -1616,7 +1638,6 @@ class SfLocalizationsDa extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'genstande'; @override @@ -1650,7 +1671,7 @@ class SfLocalizationsDa extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Gå til side'; @override - String get pdfInvalidPageNumberLabel => r'Indtast et gyldigt nummer'; + String get pdfInvalidPageNumberLabel => r'Angiv et gyldigt nummer'; @override String get pdfNoBookmarksLabel => r'Ingen bogmærker fundet'; @@ -1723,6 +1744,9 @@ class SfLocalizationsDa extends SfGlobalLocalizations { @override String get todayLabel => r'I dag'; + + @override + String get weeknumberLabel => r'Uge'; } /// The translations for German (`de`). @@ -1741,19 +1765,19 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Monat'; @override - String get allowedViewScheduleLabel => r'Zeitplan'; + String get allowedViewScheduleLabel => r'Zeitlicher Ablauf'; @override - String get allowedViewTimelineDayLabel => r'Timeline Day'; + String get allowedViewTimelineDayLabel => r'Zeitleiste Tag'; @override - String get allowedViewTimelineMonthLabel => r'Timeline-Monat'; + String get allowedViewTimelineMonthLabel => r'Zeitachse Monat'; @override - String get allowedViewTimelineWeekLabel => r'Timeline-Woche'; + String get allowedViewTimelineWeekLabel => r'Zeitleiste Woche'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; + String get allowedViewTimelineWorkWeekLabel => r'Zeitleiste Arbeitswoche'; @override String get allowedViewWeekLabel => r'Woche'; @@ -1771,14 +1795,13 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'Artikel'; + String get itemsDataPagerLabel => r'Produkte'; @override - String get jumada1Label => r'Jumada al-Awwal'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'Dschumada al-Thani'; @override String get muharramLabel => r'Muharram'; @@ -1799,10 +1822,10 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'Lesezeichen'; @override - String get pdfEnterPageNumberLabel => r'Geben Sie die Seitenzahl ein'; + String get pdfEnterPageNumberLabel => r'Seitenzahl eingeben'; @override - String get pdfGoToPageLabel => r'Zur Seite gehen'; + String get pdfGoToPageLabel => r'Gehe zu Seite'; @override String get pdfInvalidPageNumberLabel => @@ -1812,7 +1835,7 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Keine Lesezeichen gefunden'; @override - String get pdfPaginationDialogCancelLabel => r'STORNIEREN'; + String get pdfPaginationDialogCancelLabel => r'ABBRECHEN'; @override String get pdfPaginationDialogOkLabel => r'OK'; @@ -1821,10 +1844,10 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'von'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi' "'" r' al-awwal'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'Rabi' "'" r' al-Thani'; @override String get rajabLabel => r'Rajab'; @@ -1833,7 +1856,7 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get ramadanLabel => r'Ramadan'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'Safari'; @override String get shaabanLabel => r'Sha' "'" r'aban'; @@ -1869,7 +1892,7 @@ class SfLocalizationsDe extends SfGlobalLocalizations { String get shortRamadanLabel => r'RAM.'; @override - String get shortSafarLabel => r'Saf.'; + String get shortSafarLabel => r'Sicher.'; @override String get shortShaabanLabel => r'Sha.'; @@ -1879,6 +1902,9 @@ class SfLocalizationsDe extends SfGlobalLocalizations { @override String get todayLabel => r'Heute'; + + @override + String get weeknumberLabel => r'Woche'; } /// The translations for Modern Greek (`el`). @@ -1900,13 +1926,13 @@ class SfLocalizationsEl extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Πρόγραμμα'; @override - String get allowedViewTimelineDayLabel => r'Ημέρα χρονολογίου'; + String get allowedViewTimelineDayLabel => r'Ημερολόγιο'; @override - String get allowedViewTimelineMonthLabel => r'Χρονοδιάγραμμα Μήνας'; + String get allowedViewTimelineMonthLabel => r'Μήνας χρονολογίου'; @override - String get allowedViewTimelineWeekLabel => r'Εβδομάδα χρονοδιαγράμματος'; + String get allowedViewTimelineWeekLabel => r'Εβδομάδα χρονολογίου'; @override String get allowedViewTimelineWorkWeekLabel => @@ -1916,35 +1942,34 @@ class SfLocalizationsEl extends SfGlobalLocalizations { String get allowedViewWeekLabel => r'Εβδομάδα'; @override - String get allowedViewWorkWeekLabel => r'Εβδομάδα εργασίας'; + String get allowedViewWorkWeekLabel => r'Εβδομάδα Εργασίας'; @override String get daySpanCountLabel => r'Ημέρα'; @override - String get dhualhiLabel => r'Ντου αλ Χιτζάτζ'; + String get dhualhiLabel => r'Ντου αλ-Χίτζα'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'αντικείμενα'; @override String get jumada1Label => r'Jumada al-awwal'; @override - String get jumada2Label => r'Τζουμάδα αλ-θάνι'; + String get jumada2Label => r'Jumada al-thani'; @override String get muharramLabel => r'Μουχάραμ'; @override - String get noEventsCalendarLabel => r'Δεν υπάρχουν εκδηλώσεις'; + String get noEventsCalendarLabel => r'Χωρίς εκδηλώσεις'; @override - String get noSelectedDateCalendarLabel => r'Δεν έχει επιλεγεί ημερομηνία'; + String get noSelectedDateCalendarLabel => r'Καμία επιλεγμένη ημερομηνία'; @override String get ofDataPagerLabel => r'του'; @@ -1978,10 +2003,10 @@ class SfLocalizationsEl extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'του'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Ραμπί ' "'" r'αλ-αυβάλ'; @override - String get rabi2Label => r'Ραμπι ' "'" r'αλ-θανι'; + String get rabi2Label => r'Ραμπί ' "'" r'αλ-θάνι'; @override String get rajabLabel => r'Ρατζάμπ'; @@ -1990,34 +2015,34 @@ class SfLocalizationsEl extends SfGlobalLocalizations { String get ramadanLabel => r'Ραμαζάνι'; @override - String get safarLabel => r'Σαφάρι'; + String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'Σααμπάν'; + String get shaabanLabel => r'Sha' "'" r'aban'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Ντουλ-Χ'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Τζαμ. Εγώ'; + String get shortJumada1Label => r'Jum. Εγώ'; @override - String get shortJumada2Label => r'Τζαμ. ΙΙ'; + String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Μουχ.'; + String get shortMuharramLabel => r'Muh.'; @override String get shortRabi1Label => r'Ράμπι. Εγώ'; @override - String get shortRabi2Label => r'Ράμπι. ΙΙ'; + String get shortRabi2Label => r'Ράμπι. II'; @override String get shortRajabLabel => r'Κυριαρχία.'; @@ -2026,16 +2051,19 @@ class SfLocalizationsEl extends SfGlobalLocalizations { String get shortRamadanLabel => r'Εμβολο.'; @override - String get shortSafarLabel => r'Σαφ.'; + String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Σα.'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortShawwalLabel => r'Σω.'; + String get shortShawwalLabel => r'Shaw.'; @override String get todayLabel => r'Σήμερα'; + + @override + String get weeknumberLabel => r'Εβδομάδα'; } /// The translations for English (`en`). @@ -2084,7 +2112,6 @@ class SfLocalizationsEn extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'items'; @override @@ -2191,6 +2218,9 @@ class SfLocalizationsEn extends SfGlobalLocalizations { @override String get todayLabel => r'Today'; + + @override + String get weeknumberLabel => r'Week'; } /// The translations for Spanish Castilian (`es`). @@ -2234,14 +2264,13 @@ class SfLocalizationsEs extends SfGlobalLocalizations { String get daySpanCountLabel => r'Día'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get dhualhiLabel => r'Dhu al-Hiyyah'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'artículos'; + String get itemsDataPagerLabel => r'elementos'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -2283,7 +2312,7 @@ class SfLocalizationsEs extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'CANCELAR'; @override - String get pdfPaginationDialogOkLabel => r'Okay'; + String get pdfPaginationDialogOkLabel => r'OK'; @override String get pdfScrollStatusOfLabel => r'de'; @@ -2316,7 +2345,7 @@ class SfLocalizationsEs extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. yo'; + String get shortJumada1Label => r'Jum. I'; @override String get shortJumada2Label => r'Jum. II'; @@ -2325,7 +2354,7 @@ class SfLocalizationsEs extends SfGlobalLocalizations { String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. yo'; + String get shortRabi1Label => r'Rabi. I'; @override String get shortRabi2Label => r'Rabi. II'; @@ -2346,7 +2375,10 @@ class SfLocalizationsEs extends SfGlobalLocalizations { String get shortShawwalLabel => r'Shaw.'; @override - String get todayLabel => r'Hoy'; + String get todayLabel => r'Hoy dia'; + + @override + String get weeknumberLabel => r'Semana'; } /// The translations for Estonian (`et`). @@ -2395,8 +2427,7 @@ class SfLocalizationsEt extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'esemed'; + String get itemsDataPagerLabel => r'esemeid'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -2408,10 +2439,10 @@ class SfLocalizationsEt extends SfGlobalLocalizations { String get muharramLabel => r'Muharram'; @override - String get noEventsCalendarLabel => r'Ühtegi üritust pole'; + String get noEventsCalendarLabel => r'Üritusi pole'; @override - String get noSelectedDateCalendarLabel => r'Valitud kuupäeva pole'; + String get noSelectedDateCalendarLabel => r'Kuupäeva pole valitud'; @override String get ofDataPagerLabel => r'kohta'; @@ -2423,10 +2454,10 @@ class SfLocalizationsEt extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'Järjehoidjad'; @override - String get pdfEnterPageNumberLabel => r'Sisestage lehenumber'; + String get pdfEnterPageNumberLabel => r'Sisestage lehe number'; @override - String get pdfGoToPageLabel => r'Minge lehele'; + String get pdfGoToPageLabel => r'Mine lehele'; @override String get pdfInvalidPageNumberLabel => r'Sisestage kehtiv number'; @@ -2444,7 +2475,7 @@ class SfLocalizationsEt extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'kohta'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi al-awwal'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @@ -2459,7 +2490,7 @@ class SfLocalizationsEt extends SfGlobalLocalizations { String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'Shaaban'; @override String get shawwalLabel => r'Shawwal'; @@ -2471,10 +2502,10 @@ class SfLocalizationsEt extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. Mina'; + String get shortJumada1Label => r'Jumal. Mina'; @override - String get shortJumada2Label => r'Jum. II'; + String get shortJumada2Label => r'Jumal. II'; @override String get shortMuharramLabel => r'Muh.'; @@ -2502,6 +2533,9 @@ class SfLocalizationsEt extends SfGlobalLocalizations { @override String get todayLabel => r'Täna'; + + @override + String get weeknumberLabel => r'Nädal'; } /// The translations for Basque (`eu`). @@ -2529,7 +2563,7 @@ class SfLocalizationsEu extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Kronograma Hilabetea'; @override - String get allowedViewTimelineWeekLabel => r'Kronologia Astea'; + String get allowedViewTimelineWeekLabel => r'Kronograma Astea'; @override String get allowedViewTimelineWorkWeekLabel => r'Kronogramaren Lan Astea'; @@ -2550,7 +2584,6 @@ class SfLocalizationsEu extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'elementuak'; @override @@ -2658,6 +2691,9 @@ class SfLocalizationsEu extends SfGlobalLocalizations { @override String get todayLabel => r'Gaur'; + + @override + String get weeknumberLabel => r'Astea'; } /// The translations for Persian (`fa`). @@ -2688,7 +2724,7 @@ class SfLocalizationsFa extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'هفته جدول زمانی'; @override - String get allowedViewTimelineWorkWeekLabel => r'هفته کار جدول زمانی'; + String get allowedViewTimelineWorkWeekLabel => r'جدول کار هفته'; @override String get allowedViewWeekLabel => r'هفته'; @@ -2700,20 +2736,19 @@ class SfLocalizationsFa extends SfGlobalLocalizations { String get daySpanCountLabel => r'روز'; @override - String get dhualhiLabel => r'ذو الحجه'; + String get dhualhiLabel => r'ذی الحجه'; @override - String get dhualqiLabel => r'ذو القعده'; + String get dhualqiLabel => r'ذوالقیعده'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'موارد'; @override - String get jumada1Label => r'جماد al الاول'; + String get jumada1Label => r'جمادی الاول'; @override - String get jumada2Label => r'جمادا الثانی'; + String get jumada2Label => r'جمادی الثانی'; @override String get muharramLabel => r'محرم'; @@ -2722,7 +2757,7 @@ class SfLocalizationsFa extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'هیچ رویدادی وجود ندارد'; @override - String get noSelectedDateCalendarLabel => r'تاریخ انتخاب نشده است'; + String get noSelectedDateCalendarLabel => r'تاریخ انتخابی وجود ندارد'; @override String get ofDataPagerLabel => r'از'; @@ -2746,7 +2781,7 @@ class SfLocalizationsFa extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'هیچ نشانکی یافت نشد'; @override - String get pdfPaginationDialogCancelLabel => r'لغو'; + String get pdfPaginationDialogCancelLabel => r'لغو کنید'; @override String get pdfPaginationDialogOkLabel => r'خوب'; @@ -2761,7 +2796,7 @@ class SfLocalizationsFa extends SfGlobalLocalizations { String get rabi2Label => r'ربیع الثانی'; @override - String get rajabLabel => r'راجب'; + String get rajabLabel => r'رجب'; @override String get ramadanLabel => r'ماه رمضان'; @@ -2785,16 +2820,16 @@ class SfLocalizationsFa extends SfGlobalLocalizations { String get shortJumada1Label => r'جوم من'; @override - String get shortJumada2Label => r'جوم دوم'; + String get shortJumada2Label => r'جوم II'; @override - String get shortMuharramLabel => r'مه'; + String get shortMuharramLabel => r'Muh.'; @override String get shortRabi1Label => r'ربیع من'; @override - String get shortRabi2Label => r'ربیع دوم'; + String get shortRabi2Label => r'ربیع II'; @override String get shortRajabLabel => r'راج'; @@ -2803,7 +2838,7 @@ class SfLocalizationsFa extends SfGlobalLocalizations { String get shortRamadanLabel => r'رم.'; @override - String get shortSafarLabel => r'ساف'; + String get shortSafarLabel => r'صف'; @override String get shortShaabanLabel => r'شا'; @@ -2813,6 +2848,9 @@ class SfLocalizationsFa extends SfGlobalLocalizations { @override String get todayLabel => r'امروز'; + + @override + String get weeknumberLabel => r'هفته'; } /// The translations for Finnish (`fi`). @@ -2840,7 +2878,7 @@ class SfLocalizationsFi extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Aikajanan kuukausi'; @override - String get allowedViewTimelineWeekLabel => r'Aikajanan viikko'; + String get allowedViewTimelineWeekLabel => r'Aikajanaviikko'; @override String get allowedViewTimelineWorkWeekLabel => r'Aikajanan työviikko'; @@ -2861,7 +2899,6 @@ class SfLocalizationsFi extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'kohteita'; @override @@ -2889,7 +2926,7 @@ class SfLocalizationsFi extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'Kirjanmerkit'; @override - String get pdfEnterPageNumberLabel => r'Syötä sivunumero'; + String get pdfEnterPageNumberLabel => r'Anna sivunumero'; @override String get pdfGoToPageLabel => r'Mene sivulle'; @@ -2968,6 +3005,9 @@ class SfLocalizationsFi extends SfGlobalLocalizations { @override String get todayLabel => r'Tänään'; + + @override + String get weeknumberLabel => r'Viikko'; } /// The translations for Filipino Pilipino (`fil`). @@ -3017,7 +3057,6 @@ class SfLocalizationsFil extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'mga item'; @override @@ -3125,6 +3164,9 @@ class SfLocalizationsFil extends SfGlobalLocalizations { @override String get todayLabel => r'Ngayon'; + + @override + String get weeknumberLabel => r'Linggo'; } /// The translations for French (`fr`). @@ -3137,13 +3179,13 @@ class SfLocalizationsFr extends SfGlobalLocalizations { ); @override - String get allowedViewDayLabel => r'journée'; + String get allowedViewDayLabel => r'Jour'; @override String get allowedViewMonthLabel => r'Mois'; @override - String get allowedViewScheduleLabel => r'Programme'; + String get allowedViewScheduleLabel => r'Calendrier'; @override String get allowedViewTimelineDayLabel => r'Jour de la chronologie'; @@ -3152,11 +3194,11 @@ class SfLocalizationsFr extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Mois de la chronologie'; @override - String get allowedViewTimelineWeekLabel => r'Semaine chronologique'; + String get allowedViewTimelineWeekLabel => r'Semaine de la chronologie'; @override String get allowedViewTimelineWorkWeekLabel => - r'Calendrier de la semaine de travail'; + r'Calendrier Semaine de travail'; @override String get allowedViewWeekLabel => r'La semaine'; @@ -3174,17 +3216,16 @@ class SfLocalizationsFr extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'articles'; + String get itemsDataPagerLabel => r'éléments'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get jumada1Label => r'Joumada al-awwal'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'Joumada al-thani'; @override - String get muharramLabel => r'Muharram'; + String get muharramLabel => r'Mouharram'; @override String get noEventsCalendarLabel => r'Pas d' "'" r'événements'; @@ -3199,7 +3240,7 @@ class SfLocalizationsFr extends SfGlobalLocalizations { String get pagesDataPagerLabel => r'pages'; @override - String get pdfBookmarksLabel => r'Favoris'; + String get pdfBookmarksLabel => r'Signets'; @override String get pdfEnterPageNumberLabel => r'Entrez le numéro de page'; @@ -3212,19 +3253,19 @@ class SfLocalizationsFr extends SfGlobalLocalizations { r'S' "'" r'il vous plait, entrez un nombre valide'; @override - String get pdfNoBookmarksLabel => r'Aucun favori trouvé'; + String get pdfNoBookmarksLabel => r'Aucun signet trouvé'; @override String get pdfPaginationDialogCancelLabel => r'ANNULER'; @override - String get pdfPaginationDialogOkLabel => r'D' "'" r'accord'; + String get pdfPaginationDialogOkLabel => r'd' "'" r'accord'; @override String get pdfScrollStatusOfLabel => r'de'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi' "'" r' al-awwal'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @@ -3282,6 +3323,9 @@ class SfLocalizationsFr extends SfGlobalLocalizations { @override String get todayLabel => r'Aujourd' "'" r'hui'; + + @override + String get weeknumberLabel => r'La semaine'; } /// The translations for Galician (`gl`). @@ -3331,7 +3375,6 @@ class SfLocalizationsGl extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'elementos'; @override @@ -3439,6 +3482,9 @@ class SfLocalizationsGl extends SfGlobalLocalizations { @override String get todayLabel => r'Hoxe'; + + @override + String get weeknumberLabel => r'Semana'; } /// The translations for Gujarati (`gu`). @@ -3469,7 +3515,7 @@ class SfLocalizationsGu extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'સમયરેખા સપ્તાહ'; @override - String get allowedViewTimelineWorkWeekLabel => r'સમયરેખા વર્ક અઠવાડિયું'; + String get allowedViewTimelineWorkWeekLabel => r'સમયરેખા કાર્ય સપ્તાહ'; @override String get allowedViewWeekLabel => r'અઠવાડિયું'; @@ -3484,29 +3530,28 @@ class SfLocalizationsGu extends SfGlobalLocalizations { String get dhualhiLabel => r'ધુ અલ-હિજ્જા'; @override - String get dhualqiLabel => r'ધુ અલ-કિયાદહ'; + String get dhualqiLabel => r'ધુ અલ-કિદાહ'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'વસ્તુઓ'; @override String get jumada1Label => r'જુમાદા અલ-અવવાલ'; @override - String get jumada2Label => r'જુમાદા અલ-થiની'; + String get jumada2Label => r'જુમાદા અલ-થાની'; @override - String get muharramLabel => r'મુહરમ'; + String get muharramLabel => r'મોહર્રમ'; @override - String get noEventsCalendarLabel => r'કોઈ ઘટનાઓ નથી'; + String get noEventsCalendarLabel => r'કોઈ ઇવેન્ટ નથી'; @override - String get noSelectedDateCalendarLabel => r'કોઈ પસંદ કરેલી તારીખ'; + String get noSelectedDateCalendarLabel => r'કોઈ પસંદ કરેલી તારીખ નથી'; @override - String get ofDataPagerLabel => r'ની'; + String get ofDataPagerLabel => r'નું'; @override String get pagesDataPagerLabel => r'પૃષ્ઠો'; @@ -3515,13 +3560,13 @@ class SfLocalizationsGu extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'બુકમાર્ક્સ'; @override - String get pdfEnterPageNumberLabel => r'પૃષ્ઠ નંબર દાખલ કરો'; + String get pdfEnterPageNumberLabel => r'પેજ નંબર દાખલ કરો'; @override - String get pdfGoToPageLabel => r'પૃષ્ઠ પર જાઓ'; + String get pdfGoToPageLabel => r'પેજ પર જાઓ'; @override - String get pdfInvalidPageNumberLabel => r'કૃપા કરી માન્ય નંબર દાખલ કરો'; + String get pdfInvalidPageNumberLabel => r'કૃપા કરીને માન્ય નંબર દાખલ કરો'; @override String get pdfNoBookmarksLabel => r'કોઈ બુકમાર્ક્સ મળ્યા નથી'; @@ -3533,13 +3578,13 @@ class SfLocalizationsGu extends SfGlobalLocalizations { String get pdfPaginationDialogOkLabel => r'બરાબર'; @override - String get pdfScrollStatusOfLabel => r'ની'; + String get pdfScrollStatusOfLabel => r'નું'; @override - String get rabi1Label => r'રબી ' "'" r'અલ-અવવાલ'; + String get rabi1Label => r'રબી અલ-અવવાલ'; @override - String get rabi2Label => r'રબી ' "'" r'અલ-થેની'; + String get rabi2Label => r'રબી અલ-થાની'; @override String get rajabLabel => r'રજબ'; @@ -3551,16 +3596,16 @@ class SfLocalizationsGu extends SfGlobalLocalizations { String get safarLabel => r'સફર'; @override - String get shaabanLabel => r'શઆબન'; + String get shaabanLabel => r'શાબાન'; @override - String get shawwalLabel => r'શવાલ'; + String get shawwalLabel => r'શવવાલ'; @override - String get shortDhualhiLabel => r'ધૂલ-એચ'; + String get shortDhualhiLabel => r'ધુલ-એચ'; @override - String get shortDhualqiLabel => r'ધૂલ-ક્યૂ'; + String get shortDhualqiLabel => r'ધુલ-ક્યૂ'; @override String get shortJumada1Label => r'જામ. હું'; @@ -3569,13 +3614,13 @@ class SfLocalizationsGu extends SfGlobalLocalizations { String get shortJumada2Label => r'જામ. II'; @override - String get shortMuharramLabel => r'મુહ.'; + String get shortMuharramLabel => r'મહ.'; @override - String get shortRabi1Label => r'રબી. હું'; + String get shortRabi1Label => r'રવિ. હું'; @override - String get shortRabi2Label => r'રબી. II'; + String get shortRabi2Label => r'રવિ. II'; @override String get shortRajabLabel => r'રાજ.'; @@ -3584,7 +3629,7 @@ class SfLocalizationsGu extends SfGlobalLocalizations { String get shortRamadanLabel => r'રામ.'; @override - String get shortSafarLabel => r'સેફ.'; + String get shortSafarLabel => r'સફ.'; @override String get shortShaabanLabel => r'શા.'; @@ -3594,6 +3639,9 @@ class SfLocalizationsGu extends SfGlobalLocalizations { @override String get todayLabel => r'આજે'; + + @override + String get weeknumberLabel => r'અઠવાડિયું'; } /// The translations for Hebrew (`he`). @@ -3624,7 +3672,7 @@ class SfLocalizationsHe extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'שבוע ציר הזמן'; @override - String get allowedViewTimelineWorkWeekLabel => r'שבוע העבודה של ציר הזמן'; + String get allowedViewTimelineWorkWeekLabel => r'שבוע עבודה של ציר הזמן'; @override String get allowedViewWeekLabel => r'שָׁבוּעַ'; @@ -3642,14 +3690,13 @@ class SfLocalizationsHe extends SfGlobalLocalizations { String get dhualqiLabel => r'דהו אל-קי' "'" r'דה'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'פריטים'; @override - String get jumada1Label => r'ג' "'" r'ומדה אל-עוואל'; + String get jumada1Label => r'ג' "'" r'ומאדה אל-אוואל'; @override - String get jumada2Label => r'ג' "'" r'ומדה אל-ת' "'" r'אני'; + String get jumada2Label => r'ג' "'" r'ומאדה אל-תני'; @override String get muharramLabel => r'מוהרם'; @@ -3658,13 +3705,13 @@ class SfLocalizationsHe extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'אין אירועים'; @override - String get noSelectedDateCalendarLabel => r'לא נבחר תאריך'; + String get noSelectedDateCalendarLabel => r'אין תאריך שנבחר'; @override String get ofDataPagerLabel => r'שֶׁל'; @override - String get pagesDataPagerLabel => r'עמודים'; + String get pagesDataPagerLabel => r'דפים'; @override String get pdfBookmarksLabel => r'סימניות'; @@ -3691,10 +3738,10 @@ class SfLocalizationsHe extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'שֶׁל'; @override - String get rabi1Label => r'ראבי אל-עוואל'; + String get rabi1Label => r'רבי אלעוואל'; @override - String get rabi2Label => r'ראבי אל-ת' "'" r'אני'; + String get rabi2Label => r'רבי אל-תני'; @override String get rajabLabel => r'רג' "'" r'אב'; @@ -3703,13 +3750,13 @@ class SfLocalizationsHe extends SfGlobalLocalizations { String get ramadanLabel => r'רמדאן'; @override - String get safarLabel => r'ספאר'; + String get safarLabel => r'סאפר'; @override - String get shaabanLabel => r'שעבאן'; + String get shaabanLabel => r'שאבן'; @override - String get shawwalLabel => r'שאוווול'; + String get shawwalLabel => r'שוואל'; @override String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @@ -3718,19 +3765,19 @@ class SfLocalizationsHe extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'ג' "'" r'אם. אני'; + String get shortJumada1Label => r'ג' "'" r'ום. אני'; @override - String get shortJumada2Label => r'ג' "'" r'אם. II'; + String get shortJumada2Label => r'ג' "'" r'ום. II'; @override - String get shortMuharramLabel => r'מו'; + String get shortMuharramLabel => r'מו.'; @override - String get shortRabi1Label => r'ראבי. אני'; + String get shortRabi1Label => r'רבי. אני'; @override - String get shortRabi2Label => r'ראבי. II'; + String get shortRabi2Label => r'רבי. II'; @override String get shortRajabLabel => r'ראג ' "'" r'.'; @@ -3745,10 +3792,13 @@ class SfLocalizationsHe extends SfGlobalLocalizations { String get shortShaabanLabel => r'שא.'; @override - String get shortShawwalLabel => r'שו.'; + String get shortShawwalLabel => r'שאו.'; @override String get todayLabel => r'היום'; + + @override + String get weeknumberLabel => r'שָׁבוּעַ'; } /// The translations for Hindi (`hi`). @@ -3773,7 +3823,7 @@ class SfLocalizationsHi extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'समयरेखा दिवस'; @override - String get allowedViewTimelineMonthLabel => r'समय महीना'; + String get allowedViewTimelineMonthLabel => r'समयरेखा महीना'; @override String get allowedViewTimelineWeekLabel => r'समयरेखा सप्ताह'; @@ -3791,20 +3841,19 @@ class SfLocalizationsHi extends SfGlobalLocalizations { String get daySpanCountLabel => r'दिन'; @override - String get dhualhiLabel => r'धू अल-हिज्जाह'; + String get dhualhiLabel => r'धू अल-हिज्जाही'; @override - String get dhualqiLabel => r'धू अल-कियूबाह'; + String get dhualqiLabel => r'धू अल क़िदाही'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'आइटम'; @override String get jumada1Label => r'जुमादा अल-अव्वल'; @override - String get jumada2Label => r'जुमादा अल-थानी'; + String get jumada2Label => r'जुमादा अल-थानि'; @override String get muharramLabel => r'मुहर्रम'; @@ -3825,7 +3874,7 @@ class SfLocalizationsHi extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'बुकमार्क'; @override - String get pdfEnterPageNumberLabel => r'पेज नंबर डालें'; + String get pdfEnterPageNumberLabel => r'पेज नंबर दर्ज करें'; @override String get pdfGoToPageLabel => r'पृष्ठ पर जाओ'; @@ -3846,13 +3895,13 @@ class SfLocalizationsHi extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'का'; @override - String get rabi1Label => r'रबी ' "'" r'अल-अव्वल'; + String get rabi1Label => r'रबी अल-अव्वल'; @override - String get rabi2Label => r'रबी ' "'" r'अल-थानी'; + String get rabi2Label => r'रबी अल-थानी'; @override - String get rajabLabel => r'रज्जब'; + String get rajabLabel => r'राजाबी'; @override String get ramadanLabel => r'रमजान'; @@ -3861,25 +3910,25 @@ class SfLocalizationsHi extends SfGlobalLocalizations { String get safarLabel => r'सफ़र'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'शाबानो'; @override String get shawwalLabel => r'शावाल'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-एच'; + String get shortDhualhiLabel => r'धुल-हो'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-क्यू'; + String get shortDhualqiLabel => r'धुल-क्यू'; @override - String get shortJumada1Label => r'Jum। मैं'; + String get shortJumada1Label => r'जम. मैं'; @override - String get shortJumada2Label => r'Jum। द्वितीय'; + String get shortJumada2Label => r'जम. द्वितीय'; @override - String get shortMuharramLabel => r'Muh।'; + String get shortMuharramLabel => r'मुह।'; @override String get shortRabi1Label => r'रबी। मैं'; @@ -3888,22 +3937,25 @@ class SfLocalizationsHi extends SfGlobalLocalizations { String get shortRabi2Label => r'रबी। द्वितीय'; @override - String get shortRajabLabel => r'राज।'; + String get shortRajabLabel => r'राज.'; @override - String get shortRamadanLabel => r'राम।'; + String get shortRamadanLabel => r'टक्कर मारना।'; @override - String get shortSafarLabel => r'सैफ़।'; + String get shortSafarLabel => r'साफ.'; @override - String get shortShaabanLabel => r'शा।'; + String get shortShaabanLabel => r'शा.'; @override String get shortShawwalLabel => r'शॉ।'; @override String get todayLabel => r'आज'; + + @override + String get weeknumberLabel => r'सप्ताह'; } /// The translations for Croatian (`hr`). @@ -3934,7 +3986,8 @@ class SfLocalizationsHr extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'Tjedan vremenske trake'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tjedan radnog vremena'; + String get allowedViewTimelineWorkWeekLabel => + r'Radni tjedan vremenske trake'; @override String get allowedViewWeekLabel => r'Tjedan'; @@ -3946,23 +3999,22 @@ class SfLocalizationsHr extends SfGlobalLocalizations { String get daySpanCountLabel => r'Dan'; @override - String get dhualhiLabel => r'Dhu al-Hidždža'; + String get dhualhiLabel => r'Zul al-Hidždže'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'predmeta'; + String get itemsDataPagerLabel => r'stavke'; @override - String get jumada1Label => r'Džumada al-avval'; + String get jumada1Label => r'Džumada el-evval'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'Džumada al-tani'; @override - String get muharramLabel => r'Muharram'; + String get muharramLabel => r'Muharrem'; @override String get noEventsCalendarLabel => r'Nema događaja'; @@ -3986,10 +4038,10 @@ class SfLocalizationsHr extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Idi na stranicu'; @override - String get pdfInvalidPageNumberLabel => r'Unesite valjani broj'; + String get pdfInvalidPageNumberLabel => r'Unesite važeći broj'; @override - String get pdfNoBookmarksLabel => r'Nisu pronađene oznake'; + String get pdfNoBookmarksLabel => r'Oznake nisu pronađene'; @override String get pdfPaginationDialogCancelLabel => r'OTKAZATI'; @@ -4007,7 +4059,7 @@ class SfLocalizationsHr extends SfGlobalLocalizations { String get rabi2Label => r'Rabi ' "'" r'al-thani'; @override - String get rajabLabel => r'Rajab'; + String get rajabLabel => r'Redžeb'; @override String get ramadanLabel => r'Ramazan'; @@ -4016,16 +4068,16 @@ class SfLocalizationsHr extends SfGlobalLocalizations { String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'Ša' "'" r'aban'; + String get shaabanLabel => r'Ša' "'" r'ban'; @override - String get shawwalLabel => r'Shawwal'; + String get shawwalLabel => r'Ševval'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'Zul-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'Zul-Q'; @override String get shortJumada1Label => r'Jum. Ja'; @@ -4059,6 +4111,9 @@ class SfLocalizationsHr extends SfGlobalLocalizations { @override String get todayLabel => r'Danas'; + + @override + String get weeknumberLabel => r'Tjedan'; } /// The translations for Hungarian (`hu`). @@ -4083,13 +4138,13 @@ class SfLocalizationsHu extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'Idővonal napja'; @override - String get allowedViewTimelineMonthLabel => r'Idővonal Hónap'; + String get allowedViewTimelineMonthLabel => r'Idővonal hónap'; @override String get allowedViewTimelineWeekLabel => r'Idővonal hét'; @override - String get allowedViewTimelineWorkWeekLabel => r'Időrend munkahete'; + String get allowedViewTimelineWorkWeekLabel => r'Idővonal munkahét'; @override String get allowedViewWeekLabel => r'Hét'; @@ -4101,14 +4156,13 @@ class SfLocalizationsHu extends SfGlobalLocalizations { String get daySpanCountLabel => r'Nap'; @override - String get dhualhiLabel => r'Dhu al-Hidzsáh'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'elemeket'; + String get itemsDataPagerLabel => r'tételeket'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -4129,7 +4183,7 @@ class SfLocalizationsHu extends SfGlobalLocalizations { String get ofDataPagerLabel => r'nak,-nek'; @override - String get pagesDataPagerLabel => r'oldalakat'; + String get pagesDataPagerLabel => r'oldalak'; @override String get pdfBookmarksLabel => r'Könyvjelzők'; @@ -4145,7 +4199,7 @@ class SfLocalizationsHu extends SfGlobalLocalizations { r'Kérjük, adjon meg egy érvényes számot'; @override - String get pdfNoBookmarksLabel => r'Nem találhatók könyvjelzők'; + String get pdfNoBookmarksLabel => r'Nem található könyvjelző'; @override String get pdfPaginationDialogCancelLabel => r'MEGSZÜNTETI'; @@ -4157,7 +4211,7 @@ class SfLocalizationsHu extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'nak,-nek'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi al-awwal'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @@ -4169,10 +4223,10 @@ class SfLocalizationsHu extends SfGlobalLocalizations { String get ramadanLabel => r'Ramadán'; @override - String get safarLabel => r'Szafar'; + String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'Shaabán'; @override String get shawwalLabel => r'Shawwal'; @@ -4205,7 +4259,7 @@ class SfLocalizationsHu extends SfGlobalLocalizations { String get shortRamadanLabel => r'Ram.'; @override - String get shortSafarLabel => r'Szaf.'; + String get shortSafarLabel => r'Saf.'; @override String get shortShaabanLabel => r'Sha.'; @@ -4215,6 +4269,9 @@ class SfLocalizationsHu extends SfGlobalLocalizations { @override String get todayLabel => r'Ma'; + + @override + String get weeknumberLabel => r'Hét'; } /// The translations for Armenian (`hy`). @@ -4233,20 +4290,20 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Ամիս'; @override - String get allowedViewScheduleLabel => r'Գրաֆիկ'; + String get allowedViewScheduleLabel => r'Ժամանակացույց'; @override - String get allowedViewTimelineDayLabel => r'Elineամանակացույցի օր'; + String get allowedViewTimelineDayLabel => r'Timամանակացույցի օր'; @override - String get allowedViewTimelineMonthLabel => r'Elineամանակացույցի ամիս'; + String get allowedViewTimelineMonthLabel => r'Timամանակացույցի ամիս'; @override - String get allowedViewTimelineWeekLabel => r'Elineամանակացույցի շաբաթ'; + String get allowedViewTimelineWeekLabel => r'Timամանակացույցի շաբաթ'; @override String get allowedViewTimelineWorkWeekLabel => - r'Elineամանակացույցի աշխատանքային շաբաթ'; + r'Timամանակացույցի աշխատանքային շաբաթ'; @override String get allowedViewWeekLabel => r'Շաբաթ'; @@ -4258,26 +4315,25 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get daySpanCountLabel => r'Օր'; @override - String get dhualhiLabel => r'Huու ալ-Հիջա'; + String get dhualhiLabel => r'Դհու ալ-Հիջա'; @override String get dhualqiLabel => r'Դհու ալ-Քիդա'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'իրեր'; @override - String get jumada1Label => r'Umումադա ալ-վուալ'; + String get jumada1Label => r'Umումադա ալ-ավվալ'; @override String get jumada2Label => r'Umումադա ալ-թանի'; @override - String get muharramLabel => r'Մուհարամ'; + String get muharramLabel => r'Մուհարրամ'; @override - String get noEventsCalendarLabel => r'Ոչ մի իրադարձություն'; + String get noEventsCalendarLabel => r'Միջոցառումներ չկան'; @override String get noSelectedDateCalendarLabel => r'Ընտրված ամսաթիվ չկա'; @@ -4295,7 +4351,7 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get pdfEnterPageNumberLabel => r'Մուտքագրեք էջի համարը'; @override - String get pdfGoToPageLabel => r'Անցեք էջին'; + String get pdfGoToPageLabel => r'Գնալ դեպի էջ'; @override String get pdfInvalidPageNumberLabel => r'Խնդրում ենք մուտքագրել վավեր համար'; @@ -4304,7 +4360,7 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Էջանիշներ չեն գտնվել'; @override - String get pdfPaginationDialogCancelLabel => r'ՉԵՅԱԼ'; + String get pdfPaginationDialogCancelLabel => r'ՉԵՉԵԼ'; @override String get pdfPaginationDialogOkLabel => r'լավ'; @@ -4313,7 +4369,7 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'ի'; @override - String get rabi1Label => r'Ռաբի ալ-Աուվալ'; + String get rabi1Label => r'Ռաբի ալ-ավալ'; @override String get rabi2Label => r'Ռաբի ալ-թանի'; @@ -4337,28 +4393,28 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get shortDhualhiLabel => r'Դհուլ-Հ'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'Դհուլ-Ք'; @override - String get shortJumada1Label => r'Umում Ես'; + String get shortJumada1Label => r'Ցատկել: Ես'; @override - String get shortJumada2Label => r'Umում II'; + String get shortJumada2Label => r'Ցատկել: II'; @override - String get shortMuharramLabel => r'Մուհ'; + String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Ռաբի Ես'; + String get shortRabi1Label => r'Ռաբի. Ես'; @override - String get shortRabi2Label => r'Ռաբի II'; + String get shortRabi2Label => r'Ռաբի. II'; @override - String get shortRajabLabel => r'Ռաջ'; + String get shortRajabLabel => r'Ռաջ.'; @override - String get shortRamadanLabel => r'Խոյ'; + String get shortRamadanLabel => r'Խոյ.'; @override String get shortSafarLabel => r'Սաֆ'; @@ -4367,10 +4423,13 @@ class SfLocalizationsHy extends SfGlobalLocalizations { String get shortShaabanLabel => r'Շա'; @override - String get shortShawwalLabel => r'Շոուն'; + String get shortShawwalLabel => r'Շոու.'; @override String get todayLabel => r'Այսօր'; + + @override + String get weeknumberLabel => r'Շաբաթ'; } /// The translations for Indonesian (`id`). @@ -4389,22 +4448,22 @@ class SfLocalizationsId extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Bulan'; @override - String get allowedViewScheduleLabel => r'Susunan acara'; + String get allowedViewScheduleLabel => r'Jadwal'; @override - String get allowedViewTimelineDayLabel => r'Timeline Day'; + String get allowedViewTimelineDayLabel => r'Hari Garis Waktu'; @override - String get allowedViewTimelineMonthLabel => r'Bulan Garis Waktu'; + String get allowedViewTimelineMonthLabel => r'Garis Waktu Bulan'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Week'; + String get allowedViewTimelineWeekLabel => r'Garis Waktu Minggu'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; + String get allowedViewTimelineWorkWeekLabel => r'Garis Waktu Minggu Kerja'; @override - String get allowedViewWeekLabel => r'Minggu'; + String get allowedViewWeekLabel => r'Pekan'; @override String get allowedViewWorkWeekLabel => r'Minggu Kerja'; @@ -4413,13 +4472,12 @@ class SfLocalizationsId extends SfGlobalLocalizations { String get daySpanCountLabel => r'Hari'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get dhualhiLabel => r'Dzulhijjah'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => r'Dzul Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'item'; @override @@ -4462,16 +4520,16 @@ class SfLocalizationsId extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'MEMBATALKAN'; @override - String get pdfPaginationDialogOkLabel => r'baik'; + String get pdfPaginationDialogOkLabel => r'oke'; @override String get pdfScrollStatusOfLabel => r'dari'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi' "'" r' al-awwal'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'Rabi' "'" r' al-thani'; @override String get rajabLabel => r'Rajab'; @@ -4480,22 +4538,22 @@ class SfLocalizationsId extends SfGlobalLocalizations { String get ramadanLabel => r'Ramadan'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'Safari'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'Sya' "'" r'ban'; @override String get shawwalLabel => r'Syawal'; @override - String get shortDhualhiLabel => r'Dzul-H'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override - String get shortDhualqiLabel => r'Dzul-Q'; + String get shortDhualqiLabel => r'Dzul Q'; @override - String get shortJumada1Label => r'Jum. saya'; + String get shortJumada1Label => r'Jum. Saya'; @override String get shortJumada2Label => r'Jum. II'; @@ -4504,7 +4562,7 @@ class SfLocalizationsId extends SfGlobalLocalizations { String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. saya'; + String get shortRabi1Label => r'Rabi. Saya'; @override String get shortRabi2Label => r'Rabi. II'; @@ -4513,19 +4571,22 @@ class SfLocalizationsId extends SfGlobalLocalizations { String get shortRajabLabel => r'Raj.'; @override - String get shortRamadanLabel => r'Ram.'; + String get shortRamadanLabel => r'Rama.'; @override String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Sha.'; + String get shortShaabanLabel => r'Sha'; @override - String get shortShawwalLabel => r'Shaw.'; + String get shortShawwalLabel => r'Sya.'; @override String get todayLabel => r'Hari ini'; + + @override + String get weeknumberLabel => r'Pekan'; } /// The translations for Icelandic (`is`). @@ -4547,16 +4608,16 @@ class SfLocalizationsIs extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Dagskrá'; @override - String get allowedViewTimelineDayLabel => r'Tímalínudagur'; + String get allowedViewTimelineDayLabel => r'Dagur tímalínu'; @override - String get allowedViewTimelineMonthLabel => r'Tímalínumánuður'; + String get allowedViewTimelineMonthLabel => r'Tímalína mánuður'; @override - String get allowedViewTimelineWeekLabel => r'Tímalínuvika'; + String get allowedViewTimelineWeekLabel => r'Tímalína Vika'; @override - String get allowedViewTimelineWorkWeekLabel => r'Vinnuvika tímalínu'; + String get allowedViewTimelineWorkWeekLabel => r'Vinnutími tímalínu'; @override String get allowedViewWeekLabel => r'Vika'; @@ -4574,7 +4635,6 @@ class SfLocalizationsIs extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'hlutir'; @override @@ -4596,19 +4656,19 @@ class SfLocalizationsIs extends SfGlobalLocalizations { String get ofDataPagerLabel => r'af'; @override - String get pagesDataPagerLabel => r'blaðsíður'; + String get pagesDataPagerLabel => r'síður'; @override String get pdfBookmarksLabel => r'Bókamerki'; @override - String get pdfEnterPageNumberLabel => r'Sláðu inn blaðsíðunúmer'; + String get pdfEnterPageNumberLabel => r'Sláðu inn síðunúmer'; @override String get pdfGoToPageLabel => r'Farðu á síðu'; @override - String get pdfInvalidPageNumberLabel => r'Vinsamlegast sláðu inn gilt númer'; + String get pdfInvalidPageNumberLabel => r'Sláðu inn gilt númer'; @override String get pdfNoBookmarksLabel => r'Engin bókamerki fundust'; @@ -4650,10 +4710,10 @@ class SfLocalizationsIs extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. Ég'; + String get shortJumada1Label => r'Jamm. Ég'; @override - String get shortJumada2Label => r'Jum. II'; + String get shortJumada2Label => r'Jamm. II'; @override String get shortMuharramLabel => r'Muh.'; @@ -4681,6 +4741,9 @@ class SfLocalizationsIs extends SfGlobalLocalizations { @override String get todayLabel => r'Í dag'; + + @override + String get weeknumberLabel => r'Vika'; } /// The translations for Italian (`it`). @@ -4702,16 +4765,17 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Programma'; @override - String get allowedViewTimelineDayLabel => r'Timeline Day'; + String get allowedViewTimelineDayLabel => r'Giorno della cronologia'; @override - String get allowedViewTimelineMonthLabel => r'Mese della sequenza temporale'; + String get allowedViewTimelineMonthLabel => r'Cronologia del mese'; @override - String get allowedViewTimelineWeekLabel => r'Settimana temporale'; + String get allowedViewTimelineWeekLabel => r'Timeline settimana'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; + String get allowedViewTimelineWorkWeekLabel => + r'Cronologia della settimana lavorativa'; @override String get allowedViewWeekLabel => r'Settimana'; @@ -4729,8 +4793,7 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'elementi'; + String get itemsDataPagerLabel => r'Oggetti'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -4754,10 +4817,10 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get pagesDataPagerLabel => r'pagine'; @override - String get pdfBookmarksLabel => r'Segnalibri'; + String get pdfBookmarksLabel => r'segnalibri'; @override - String get pdfEnterPageNumberLabel => r'Immettere il numero di pagina'; + String get pdfEnterPageNumberLabel => r'Inserisci il numero di pagina'; @override String get pdfGoToPageLabel => r'Vai alla pagina'; @@ -4779,10 +4842,10 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'di'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi' "'" r' al-awwal'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'Rabi' "'" r' al-thani'; @override String get rajabLabel => r'Rajab'; @@ -4791,7 +4854,7 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get ramadanLabel => r'Ramadan'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'Safari'; @override String get shaabanLabel => r'Sha' "'" r'aban'; @@ -4812,7 +4875,7 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Muh.'; + String get shortMuharramLabel => r'mah.'; @override String get shortRabi1Label => r'Rabi. io'; @@ -4827,7 +4890,7 @@ class SfLocalizationsIt extends SfGlobalLocalizations { String get shortRamadanLabel => r'Ram.'; @override - String get shortSafarLabel => r'Saf.'; + String get shortSafarLabel => r'sicuro'; @override String get shortShaabanLabel => r'Sha.'; @@ -4837,6 +4900,9 @@ class SfLocalizationsIt extends SfGlobalLocalizations { @override String get todayLabel => r'Oggi'; + + @override + String get weeknumberLabel => r'Settimana'; } /// The translations for Japanese (`ja`). @@ -4870,7 +4936,7 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get allowedViewTimelineWorkWeekLabel => r'タイムラインワークウィーク'; @override - String get allowedViewWeekLabel => r'週間'; + String get allowedViewWeekLabel => r'週'; @override String get allowedViewWorkWeekLabel => r'労働週'; @@ -4885,14 +4951,13 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get dhualqiLabel => r'ズル・カイダ'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'アイテム'; @override String get jumada1Label => r'ジュマーダアルアウワル'; @override - String get jumada2Label => r'ジュマーダッサーニ'; + String get jumada2Label => r'ジュマーダ・アルタニ'; @override String get muharramLabel => r'ムハッラム'; @@ -4928,16 +4993,16 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'キャンセル'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get pdfPaginationDialogOkLabel => r'わかった'; @override String get pdfScrollStatusOfLabel => r'の'; @override - String get rabi1Label => r'Rabi' "'" r'al-awwal'; + String get rabi1Label => r'ラビー・ウル・アウワル'; @override - String get rabi2Label => r'ラビー・アルタニ'; + String get rabi2Label => r'ラビー・アル・タニ'; @override String get rajabLabel => r'ラジャブ'; @@ -4946,16 +5011,16 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get ramadanLabel => r'ラマダン'; @override - String get safarLabel => r'サファル'; + String get safarLabel => r'サファー'; @override String get shaabanLabel => r'シャアバーン'; @override - String get shawwalLabel => r'Shawwal'; + String get shawwalLabel => r'シャウワール'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'ズル・カイダ'; @override String get shortDhualqiLabel => r'ズル・カイダ'; @@ -4979,19 +5044,22 @@ class SfLocalizationsJa extends SfGlobalLocalizations { String get shortRajabLabel => r'ラージ。'; @override - String get shortRamadanLabel => r'羊。'; + String get shortRamadanLabel => r'RAM。'; @override String get shortSafarLabel => r'Saf。'; @override - String get shortShaabanLabel => r'Sha。'; + String get shortShaabanLabel => r'シャ。'; @override String get shortShawwalLabel => r'ショー。'; @override String get todayLabel => r'今日'; + + @override + String get weeknumberLabel => r'週'; } /// The translations for Georgian (`ka`). @@ -5013,13 +5081,13 @@ class SfLocalizationsKa extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'განრიგი'; @override - String get allowedViewTimelineDayLabel => r'ქრონოლოგია დღე'; + String get allowedViewTimelineDayLabel => r'ქრონოლოგიის დღე'; @override - String get allowedViewTimelineMonthLabel => r'ქრონოლოგია თვე'; + String get allowedViewTimelineMonthLabel => r'ქრონოლოგიის თვე'; @override - String get allowedViewTimelineWeekLabel => r'ქრონოლოგია კვირა'; + String get allowedViewTimelineWeekLabel => r'ქრონოლოგიის კვირა'; @override String get allowedViewTimelineWorkWeekLabel => r'ქრონოლოგია სამუშაო კვირა'; @@ -5031,20 +5099,19 @@ class SfLocalizationsKa extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'Სამუშაო კვირა'; @override - String get daySpanCountLabel => r'Დღეს'; + String get daySpanCountLabel => r'Დღის'; @override - String get dhualhiLabel => r'დუ ალ-ჰიჯა'; + String get dhualhiLabel => r'დჰუ-ჰიჯა'; @override - String get dhualqiLabel => r'დუ ალ-ქიდა'; + String get dhualqiLabel => r'დჰუ-კიდა'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'საგნები'; + String get itemsDataPagerLabel => r'ნივთები'; @override - String get jumada1Label => r'ჯუმადა ალ-ავალი'; + String get jumada1Label => r'ჯუმადა ალ-ავვალი'; @override String get jumada2Label => r'ჯუმადა ალ-ტანი'; @@ -5059,7 +5126,7 @@ class SfLocalizationsKa extends SfGlobalLocalizations { String get noSelectedDateCalendarLabel => r'არჩეული თარიღი არ არის'; @override - String get ofDataPagerLabel => r'საქართველოს'; + String get ofDataPagerLabel => r'-ის'; @override String get pagesDataPagerLabel => r'გვერდები'; @@ -5074,10 +5141,10 @@ class SfLocalizationsKa extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Გადადით გვერდზე'; @override - String get pdfInvalidPageNumberLabel => r'გთხოვთ, შეიყვანოთ სწორი ნომერი'; + String get pdfInvalidPageNumberLabel => r'გთხოვთ შეიყვანოთ სწორი ნომერი'; @override - String get pdfNoBookmarksLabel => r'სანიშნე ვერ მოიძებნა'; + String get pdfNoBookmarksLabel => r'სანიშნეები ვერ მოიძებნა'; @override String get pdfPaginationDialogCancelLabel => r'გაუქმება'; @@ -5086,19 +5153,19 @@ class SfLocalizationsKa extends SfGlobalLocalizations { String get pdfPaginationDialogOkLabel => r'კარგი'; @override - String get pdfScrollStatusOfLabel => r'საქართველოს'; + String get pdfScrollStatusOfLabel => r'-ის'; @override - String get rabi1Label => r'რაბი ' "'" r'ალ-ავალი'; + String get rabi1Label => r'რაბი ალ-ავვალი'; @override - String get rabi2Label => r'რაბი ' "'" r'ალ-ტანი'; + String get rabi2Label => r'რაბი ალ-ტანი'; @override String get rajabLabel => r'რაჯაბ'; @override - String get ramadanLabel => r'რამაზანი'; + String get ramadanLabel => r'რამადანი'; @override String get safarLabel => r'საფარი'; @@ -5107,46 +5174,49 @@ class SfLocalizationsKa extends SfGlobalLocalizations { String get shaabanLabel => r'შააბანი'; @override - String get shawwalLabel => r'შავალი'; + String get shawwalLabel => r'შოვალი'; @override - String get shortDhualhiLabel => r'დულ-ჰ'; + String get shortDhualhiLabel => r'დჰულ-ჰ'; @override - String get shortDhualqiLabel => r'დულ-ქ'; + String get shortDhualqiLabel => r'დჰულ-ქ'; @override - String get shortJumada1Label => r'ჯუმი მე'; + String get shortJumada1Label => r'ჯუმ მე'; @override - String get shortJumada2Label => r'ჯუმი II'; + String get shortJumada2Label => r'ჯუმ II'; @override String get shortMuharramLabel => r'მუჰ'; @override - String get shortRabi1Label => r'რაბი მე'; + String get shortRabi1Label => r'რაბი. მე'; @override - String get shortRabi2Label => r'რაბი II'; + String get shortRabi2Label => r'რაბი. II'; @override - String get shortRajabLabel => r'რაჟ'; + String get shortRajabLabel => r'რაჯი.'; @override - String get shortRamadanLabel => r'ვერძი'; + String get shortRamadanLabel => r'რამი.'; @override - String get shortSafarLabel => r'საფ.'; + String get shortSafarLabel => r'სეფი.'; @override - String get shortShaabanLabel => r'შა'; + String get shortShaabanLabel => r'შა.'; @override - String get shortShawwalLabel => r'შოუ'; + String get shortShawwalLabel => r'შოუ.'; @override String get todayLabel => r'დღეს'; + + @override + String get weeknumberLabel => r'კვირა'; } /// The translations for Kazakh (`kk`). @@ -5168,16 +5238,16 @@ class SfLocalizationsKk extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Кесте'; @override - String get allowedViewTimelineDayLabel => r'Хронология күні'; + String get allowedViewTimelineDayLabel => r'Уақыт шкаласы күні'; @override - String get allowedViewTimelineMonthLabel => r'Хронология айы'; + String get allowedViewTimelineMonthLabel => r'Уақыт шкаласы айы'; @override - String get allowedViewTimelineWeekLabel => r'Уақыт кестесі'; + String get allowedViewTimelineWeekLabel => r'Уақыт кестесі аптасы'; @override - String get allowedViewTimelineWorkWeekLabel => r'Жұмыс кестесі'; + String get allowedViewTimelineWorkWeekLabel => r'Жұмыс аптасының кестесі'; @override String get allowedViewWeekLabel => r'Апта'; @@ -5189,32 +5259,31 @@ class SfLocalizationsKk extends SfGlobalLocalizations { String get daySpanCountLabel => r'Күн'; @override - String get dhualhiLabel => r'Зуль-Хиджа'; + String get dhualhiLabel => r'Зулхиджа'; @override - String get dhualqiLabel => r'Зуль-әл-Қида'; + String get dhualqiLabel => r'Зул-аль-Қида'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'заттар'; @override - String get jumada1Label => r'Джумада әл-аввал'; + String get jumada1Label => r'Жұмада әл-аввал'; @override - String get jumada2Label => r'Джумада аль-тени'; + String get jumada2Label => r'Джумада әл-Тани'; @override String get muharramLabel => r'Мухаррам'; @override - String get noEventsCalendarLabel => r'Іс-шаралар жоқ'; + String get noEventsCalendarLabel => r'Іс -шаралар жоқ'; @override String get noSelectedDateCalendarLabel => r'Таңдалған күн жоқ'; @override - String get ofDataPagerLabel => r'туралы'; + String get ofDataPagerLabel => r'ның'; @override String get pagesDataPagerLabel => r'беттер'; @@ -5226,28 +5295,28 @@ class SfLocalizationsKk extends SfGlobalLocalizations { String get pdfEnterPageNumberLabel => r'Бет нөмірін енгізіңіз'; @override - String get pdfGoToPageLabel => r'Параққа өту'; + String get pdfGoToPageLabel => r'Бетке өту'; @override - String get pdfInvalidPageNumberLabel => r'Жарамды нөмірді енгізіңіз'; + String get pdfInvalidPageNumberLabel => r'Жарамды нөмір енгізіңіз'; @override - String get pdfNoBookmarksLabel => r'Бетбелгілер табылған жоқ'; + String get pdfNoBookmarksLabel => r'Бетбелгілер табылмады'; @override - String get pdfPaginationDialogCancelLabel => r'ТОҚТАТУ'; + String get pdfPaginationDialogCancelLabel => r'ЖОЮ'; @override String get pdfPaginationDialogOkLabel => r'ЖАРАЙДЫ МА'; @override - String get pdfScrollStatusOfLabel => r'туралы'; + String get pdfScrollStatusOfLabel => r'ның'; @override - String get rabi1Label => r'Раби ' "'" r'әл-әууал'; + String get rabi1Label => r'Рабиъул-әууәл'; @override - String get rabi2Label => r'Раби ' "'" r'аль-тени'; + String get rabi2Label => r'Раби ал-Тани'; @override String get rajabLabel => r'Раджаб'; @@ -5271,13 +5340,13 @@ class SfLocalizationsKk extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Зуль-Q'; @override - String get shortJumada1Label => r'Джум. Мен'; + String get shortJumada1Label => r'Jum. Мен'; @override - String get shortJumada2Label => r'Джум. II'; + String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Мұх.'; + String get shortMuharramLabel => r'Мух.'; @override String get shortRabi1Label => r'Раби. Мен'; @@ -5292,7 +5361,7 @@ class SfLocalizationsKk extends SfGlobalLocalizations { String get shortRamadanLabel => r'Жедел Жадтау Құрылғысы.'; @override - String get shortSafarLabel => r'Қауіпсіз.'; + String get shortSafarLabel => r'Saf.'; @override String get shortShaabanLabel => r'Ша.'; @@ -5302,6 +5371,9 @@ class SfLocalizationsKk extends SfGlobalLocalizations { @override String get todayLabel => r'Бүгін'; + + @override + String get weeknumberLabel => r'Апта'; } /// The translations for Khmer Central Khmer (`km`). @@ -5323,16 +5395,16 @@ class SfLocalizationsKm extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'កាលវិភាគ'; @override - String get allowedViewTimelineDayLabel => r'ថ្ងៃកំណត់ពេលវេលា'; + String get allowedViewTimelineDayLabel => r'ទិវាកំណត់ពេលវេលា'; @override - String get allowedViewTimelineMonthLabel => r'ខែពេលវេលា'; + String get allowedViewTimelineMonthLabel => r'ខែកំណត់ពេលវេលា'; @override - String get allowedViewTimelineWeekLabel => r'សប្តាហ៍កំណត់ពេល'; + String get allowedViewTimelineWeekLabel => r'សប្តាហ៍កំណត់ពេលវេលា'; @override - String get allowedViewTimelineWorkWeekLabel => r'សប្តាហ៍ការងារកំណត់ពេល'; + String get allowedViewTimelineWorkWeekLabel => r'សប្តាហ៍ការងារកំណត់ពេលវេលា'; @override String get allowedViewWeekLabel => r'សប្តាហ៍'; @@ -5344,23 +5416,22 @@ class SfLocalizationsKm extends SfGlobalLocalizations { String get daySpanCountLabel => r'ថ្ងៃ'; @override - String get dhualhiLabel => r'ឌូអាល់ហីចា'; + String get dhualhiLabel => r'ឌូអាល់ហ៊ីចា'; @override - String get dhualqiLabel => r'ឌូអាល់ឈីឌា'; + String get dhualqiLabel => r'ឌូអាល់កៃដា'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'របស់របរ'; + String get itemsDataPagerLabel => r'ធាតុ'; @override - String get jumada1Label => r'ជូម៉ាដាអាល់អាល់វ៉ាល'; + String get jumada1Label => r'ជូម៉ាដាអាល់អាវ៉ាល់'; @override - String get jumada2Label => r'ចាមដាអាល់ - ថាវី'; + String get jumada2Label => r'ជូម៉ាដាអាល់ថានី'; @override - String get muharramLabel => r'Muharram'; + String get muharramLabel => r'មូហារ៉ាម'; @override String get noEventsCalendarLabel => r'គ្មានព្រឹត្តិការណ៍'; @@ -5387,10 +5458,10 @@ class SfLocalizationsKm extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'សូមបញ្ចូលលេខដែលត្រឹមត្រូវ'; @override - String get pdfNoBookmarksLabel => r'រកមិនឃើញចំណាំទេ'; + String get pdfNoBookmarksLabel => r'រកមិនឃើញចំណាំ'; @override - String get pdfPaginationDialogCancelLabel => r'CANCEL'; + String get pdfPaginationDialogCancelLabel => r'បោះបង់'; @override String get pdfPaginationDialogOkLabel => r'យល់ព្រម'; @@ -5399,40 +5470,40 @@ class SfLocalizationsKm extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'នៃ'; @override - String get rabi1Label => r'រ៉ាប៊ី ' "'" r'អាល់ - ដាស់'; + String get rabi1Label => r'រ៉ាប៊ីអាល់អាវ៉ាល់'; @override - String get rabi2Label => r'រ៉ាប៊ីអាល់ - ថាវី'; + String get rabi2Label => r'រ៉ាប៊ីអាល់ថានី'; @override - String get rajabLabel => r'រ៉ាបាប់'; + String get rajabLabel => r'រ៉ាចាប'; @override - String get ramadanLabel => r'រ៉ាម៉ាដាន'; + String get ramadanLabel => r'បុណ្យរ៉ាម៉ាដាន'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'សាហ្វារ៉ា'; @override - String get shaabanLabel => r'សាអាបាន'; + String get shaabanLabel => r'សាបាអាន'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'ឌូអេល - អេ'; + String get shortDhualhiLabel => r'ឌូ-អិល'; @override - String get shortDhualqiLabel => r'ឌូហល'; + String get shortDhualqiLabel => r'ឌូល-ឃ'; @override - String get shortJumada1Label => r'ចាម។ ខ្ញុំ'; + String get shortJumada1Label => r'ជុម។ ខ្ញុំ'; @override - String get shortJumada2Label => r'ចាម។ II'; + String get shortJumada2Label => r'ជុម។ II'; @override - String get shortMuharramLabel => r'Muh ។'; + String get shortMuharramLabel => r'ម។'; @override String get shortRabi1Label => r'រ៉ាប៊ី។ ខ្ញុំ'; @@ -5441,22 +5512,25 @@ class SfLocalizationsKm extends SfGlobalLocalizations { String get shortRabi2Label => r'រ៉ាប៊ី។ II'; @override - String get shortRajabLabel => r'រ៉ាក់។'; + String get shortRajabLabel => r'រាជ'; @override String get shortRamadanLabel => r'អង្គ​ចងចាំ។'; @override - String get shortSafarLabel => r'សុវត្ថិភាព។'; + String get shortSafarLabel => r'សាហ្វ'; @override String get shortShaabanLabel => r'សា។'; @override - String get shortShawwalLabel => r'ទឹករលក។'; + String get shortShawwalLabel => r'សៅ។'; @override String get todayLabel => r'ថ្ងៃនេះ'; + + @override + String get weeknumberLabel => r'សប្តាហ៍'; } /// The translations for Kannada (`kn`). @@ -5511,20 +5585,19 @@ class SfLocalizationsKn extends SfGlobalLocalizations { @override String get dhualqiLabel => - '\u{ca7}\u{cc1}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c96}\u{cbf}\u{ca6}\u{cbe}'; + '\u{ca7}\u{cc1}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c95}\u{cbf}\u{caf}\u{cbe}\u{ca1}\u{cbe}'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => '\u{cb5}\u{cb8}\u{ccd}\u{ca4}\u{cc1}\u{c97}\u{cb3}\u{cc1}'; @override String get jumada1Label => - '\u{c9c}\u{cc1}\u{cae}\u{ca1}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cbe}\u{cb2}\u{ccd}'; + '\u{c9c}\u{cc1}\u{cae}\u{ca6}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cb2}\u{ccd}'; @override String get jumada2Label => - '\u{c9c}\u{cc1}\u{cae}\u{ca1}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; + '\u{c9c}\u{cc1}\u{cae}\u{ca6}\u{cbe}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; @override String get muharramLabel => '\u{cae}\u{cca}\u{cb9}\u{cb0}\u{c82}'; @@ -5566,7 +5639,7 @@ class SfLocalizationsKn extends SfGlobalLocalizations { @override String get pdfPaginationDialogCancelLabel => - '\u{cb0}\u{ca6}\u{ccd}\u{ca6}\u{cc1}\u{c97}\u{cca}\u{cb3}\u{cbf}\u{cb8}\u{cbf}'; + '\u{c95}\u{ccd}\u{caf}\u{cbe}\u{ca8}\u{ccd}\u{cb8}\u{cb2}\u{ccd}'; @override String get pdfPaginationDialogOkLabel => '\u{cb8}\u{cb0}\u{cbf}'; @@ -5576,14 +5649,14 @@ class SfLocalizationsKn extends SfGlobalLocalizations { @override String get rabi1Label => - '\u{cb0}\u{cac}\u{cbf}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cbe}\u{cb2}\u{ccd}'; + '\u{cb0}\u{cac}\u{cbf}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{c85}\u{cb5}\u{ccd}\u{cb5}\u{cb2}\u{ccd}'; @override String get rabi2Label => - '\u{cb0}\u{cac}\u{cbf}\u{20}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; + '\u{cb0}\u{cac}\u{cbf}\u{20}\u{27}\u{c85}\u{cb2}\u{ccd}\u{2d}\u{ca5}\u{cbe}\u{ca8}\u{cbf}'; @override - String get rajabLabel => '\u{cb0}\u{cbe}\u{c9c}\u{cbe}\u{cac}\u{ccd}'; + String get rajabLabel => '\u{cb0}\u{c9c}\u{cac}\u{ccd}'; @override String get ramadanLabel => '\u{cb0}\u{c82}\u{c9c}\u{cbe}\u{ca8}\u{ccd}'; @@ -5592,7 +5665,7 @@ class SfLocalizationsKn extends SfGlobalLocalizations { String get safarLabel => '\u{cb8}\u{cab}\u{cb0}\u{ccd}'; @override - String get shaabanLabel => '\u{cb6}\u{cbe}\u{cac}\u{cbe}\u{ca8}\u{ccd}'; + String get shaabanLabel => '\u{cb6}\u{cac}\u{cbe}\u{ca8}\u{ccd}'; @override String get shawwalLabel => @@ -5600,48 +5673,50 @@ class SfLocalizationsKn extends SfGlobalLocalizations { @override String get shortDhualhiLabel => - '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{cb9}\u{cc6}\u{c9a}\u{ccd}'; + '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{c8e}\u{c9a}\u{ccd}'; @override String get shortDhualqiLabel => - '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{c95}\u{ccd}\u{caf}\u{cc2}'; + '\u{ca7}\u{cc1}\u{cb2}\u{ccd}\u{2d}\u{caa}\u{ccd}\u{cb0}'; @override String get shortJumada1Label => - '\u{c9c}\u{cae}\u{ccd}\u{2e}\u{20}\u{ca8}\u{cbe}\u{ca8}\u{cc1}'; + '\u{c9c}\u{cc1}\u{c82}\u{2e}\u{20}\u{ca8}\u{cbe}\u{ca8}\u{cc1}'; @override String get shortJumada2Label => - '\u{c9c}\u{cae}\u{ccd}\u{2e}\u{20}\u{49}\u{49}'; + '\u{c9c}\u{cc1}\u{c82}\u{2e}\u{20}\u{49}\u{49}'; @override String get shortMuharramLabel => '\u{cae}\u{cc1}\u{cb9}\u{ccd}\u{2e}'; @override String get shortRabi1Label => - '\u{cb0}\u{cac}\u{cbf}\u{2e}\u{20}\u{ca8}\u{cbe}\u{ca8}\u{cc1}'; + '\u{cb0}\u{cac}\u{cbf}\u{20}\u{ca8}\u{cbe}\u{ca8}\u{cc1}'; @override - String get shortRabi2Label => '\u{cb0}\u{cac}\u{cbf}\u{2e}\u{20}\u{49}\u{49}'; + String get shortRabi2Label => '\u{cb0}\u{cac}\u{cbf}\u{20}\u{49}\u{49}'; @override - String get shortRajabLabel => '\u{cb0}\u{cbe}\u{c9c}\u{ccd}\u{2e}'; + String get shortRajabLabel => '\u{cb0}\u{cbe}\u{c9c}\u{ccd}'; @override String get shortRamadanLabel => '\u{cb0}\u{cbe}\u{cae}\u{ccd}\u{2e}'; @override - String get shortSafarLabel => - '\u{cb8}\u{cc1}\u{cb0}\u{c95}\u{ccd}\u{cb7}\u{cbf}\u{ca4}\u{2e}'; + String get shortSafarLabel => '\u{cb8}\u{cc7}\u{cab}\u{ccd}'; @override - String get shortShaabanLabel => '\u{cb6}\u{cbe}\u{2e}'; + String get shortShaabanLabel => '\u{cb6}\u{cbe}'; @override - String get shortShawwalLabel => '\u{cb6}\u{cbe}\u{2e}'; + String get shortShawwalLabel => '\u{cb6}\u{cbe}'; @override String get todayLabel => '\u{c87}\u{c82}\u{ca6}\u{cc1}'; + + @override + String get weeknumberLabel => '\u{cb5}\u{cbe}\u{cb0}'; } /// The translations for Korean (`ko`). @@ -5657,28 +5732,28 @@ class SfLocalizationsKo extends SfGlobalLocalizations { String get allowedViewDayLabel => r'일'; @override - String get allowedViewMonthLabel => r'달'; + String get allowedViewMonthLabel => r'월'; @override - String get allowedViewScheduleLabel => r'시간표'; + String get allowedViewScheduleLabel => r'일정'; @override - String get allowedViewTimelineDayLabel => r'타임 라인 데이'; + String get allowedViewTimelineDayLabel => r'타임라인의 날'; @override - String get allowedViewTimelineMonthLabel => r'타임 라인 월'; + String get allowedViewTimelineMonthLabel => r'타임라인 월'; @override - String get allowedViewTimelineWeekLabel => r'타임 라인 주'; + String get allowedViewTimelineWeekLabel => r'타임라인 주간'; @override - String get allowedViewTimelineWorkWeekLabel => r'타임 라인 작업 주'; + String get allowedViewTimelineWorkWeekLabel => r'타임라인 작업 주간'; @override String get allowedViewWeekLabel => r'주'; @override - String get allowedViewWorkWeekLabel => r'작업 주'; + String get allowedViewWorkWeekLabel => r'작업 주간'; @override String get daySpanCountLabel => r'일'; @@ -5687,35 +5762,34 @@ class SfLocalizationsKo extends SfGlobalLocalizations { String get dhualhiLabel => r'두 알 히자'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => r'두 알 키다'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'항목'; + String get itemsDataPagerLabel => r'아이템'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get jumada1Label => r'주마다 알-아왈'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'주마다 알타니'; @override - String get muharramLabel => r'무 하람'; + String get muharramLabel => r'무하람'; @override String get noEventsCalendarLabel => r'이벤트 없음'; @override - String get noSelectedDateCalendarLabel => r'선택한 날짜가 없습니다.'; + String get noSelectedDateCalendarLabel => r'선택한 날짜가 없습니다'; @override - String get ofDataPagerLabel => r'의'; + String get ofDataPagerLabel => r'NS'; @override String get pagesDataPagerLabel => r'페이지'; @override - String get pdfBookmarksLabel => r'북마크'; + String get pdfBookmarksLabel => r'책갈피'; @override String get pdfEnterPageNumberLabel => r'페이지 번호 입력'; @@ -5724,58 +5798,58 @@ class SfLocalizationsKo extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'페이지로 이동'; @override - String get pdfInvalidPageNumberLabel => r'유효한 번호를 입력하십시오'; + String get pdfInvalidPageNumberLabel => r'유효한 숫자를 입력하세요'; @override - String get pdfNoBookmarksLabel => r'북마크가 없습니다.'; + String get pdfNoBookmarksLabel => r'북마크를 찾을 수 없습니다.'; @override String get pdfPaginationDialogCancelLabel => r'취소'; @override - String get pdfPaginationDialogOkLabel => r'확인'; + String get pdfPaginationDialogOkLabel => r'좋아요'; @override - String get pdfScrollStatusOfLabel => r'의'; + String get pdfScrollStatusOfLabel => r'NS'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'라비 알-아왈'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'라비 알타니'; @override - String get rajabLabel => r'라잡'; + String get rajabLabel => r'라자브'; @override String get ramadanLabel => r'라마단'; @override - String get safarLabel => r'사 파르'; + String get safarLabel => r'사파르'; @override - String get shaabanLabel => r'샤아 반'; + String get shaabanLabel => r'샤아반'; @override - String get shawwalLabel => r'Shawwal'; + String get shawwalLabel => r'샤왈'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'둘-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'둘큐'; @override - String get shortJumada1Label => r'Jum. 나는'; + String get shortJumada1Label => r'줌. NS'; @override - String get shortJumada2Label => r'Jum. II'; + String get shortJumada2Label => r'줌. II'; @override String get shortMuharramLabel => r'음.'; @override - String get shortRabi1Label => r'라비. 나는'; + String get shortRabi1Label => r'라비. NS'; @override String get shortRabi2Label => r'라비. II'; @@ -5787,16 +5861,19 @@ class SfLocalizationsKo extends SfGlobalLocalizations { String get shortRamadanLabel => r'램.'; @override - String get shortSafarLabel => r'Saf.'; + String get shortSafarLabel => r'사프.'; @override - String get shortShaabanLabel => r'Sha.'; + String get shortShaabanLabel => r'샤.'; @override String get shortShawwalLabel => r'쇼.'; @override String get todayLabel => r'오늘'; + + @override + String get weeknumberLabel => r'주'; } /// The translations for Kirghiz Kyrgyz (`ky`). @@ -5815,22 +5892,22 @@ class SfLocalizationsKy extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Ай'; @override - String get allowedViewScheduleLabel => r'Расписание'; + String get allowedViewScheduleLabel => r'График'; @override String get allowedViewTimelineDayLabel => r'Убакыт тилкеси күнү'; @override - String get allowedViewTimelineMonthLabel => r'Убакыт айы'; + String get allowedViewTimelineMonthLabel => r'Убакыт тилкеси айы'; @override - String get allowedViewTimelineWeekLabel => r'Timeline Week'; + String get allowedViewTimelineWeekLabel => r'Убакыт тилкеси жумасы'; @override - String get allowedViewTimelineWorkWeekLabel => r'Убакыт тилкесиндеги Жума'; + String get allowedViewTimelineWorkWeekLabel => r'Убакыт тилкеси Жумалык'; @override - String get allowedViewWeekLabel => r'Апта'; + String get allowedViewWeekLabel => r'Жума'; @override String get allowedViewWorkWeekLabel => r'Жумуш аптасы'; @@ -5839,32 +5916,31 @@ class SfLocalizationsKy extends SfGlobalLocalizations { String get daySpanCountLabel => r'Күн'; @override - String get dhualhiLabel => r'Зуль-Хиджа'; + String get dhualhiLabel => r'Зул-Хижжа'; @override - String get dhualqiLabel => r'Zhu al-Qi' "'" r'dah'; + String get dhualqiLabel => r'Зулкийда'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'буюмдар'; @override - String get jumada1Label => r'Жумада ал-аввал'; + String get jumada1Label => r'Жумада аль-аввал'; @override - String get jumada2Label => r'Жумада ал-тени'; + String get jumada2Label => r'Жумада ал-тани'; @override String get muharramLabel => r'Мухаррам'; @override - String get noEventsCalendarLabel => r'Иш-чара жок'; + String get noEventsCalendarLabel => r'Иш -чаралар жок'; @override String get noSelectedDateCalendarLabel => r'Тандалган күн жок'; @override - String get ofDataPagerLabel => r'боюнча'; + String get ofDataPagerLabel => r'дын'; @override String get pagesDataPagerLabel => r'барактар'; @@ -5876,38 +5952,37 @@ class SfLocalizationsKy extends SfGlobalLocalizations { String get pdfEnterPageNumberLabel => r'Барактын номерин киргизиңиз'; @override - String get pdfGoToPageLabel => r'Баракчага өтүңүз'; + String get pdfGoToPageLabel => r'Баракка өтүү'; @override - String get pdfInvalidPageNumberLabel => - r'Сураныч, жарактуу номерди киргизиңиз'; + String get pdfInvalidPageNumberLabel => r'Жарактуу номерди киргизиңиз'; @override String get pdfNoBookmarksLabel => r'Кыстармалар табылган жок'; @override - String get pdfPaginationDialogCancelLabel => r'ЖОК'; + String get pdfPaginationDialogCancelLabel => r'ЖОК КЫЛУУ'; @override String get pdfPaginationDialogOkLabel => r'Макул'; @override - String get pdfScrollStatusOfLabel => r'боюнча'; + String get pdfScrollStatusOfLabel => r'дын'; @override - String get rabi1Label => r'Раби ' "'" r'ал-аввал'; + String get rabi1Label => r'Рабиъул-аввал'; @override - String get rabi2Label => r'Раби ' "'" r'аль-тени'; + String get rabi2Label => r'Раби ал-Тани'; @override String get rajabLabel => r'Ражаб'; @override - String get ramadanLabel => r'Рамазан'; + String get ramadanLabel => r'Орозо айт'; @override - String get safarLabel => r'Сафар'; + String get safarLabel => r'Safar'; @override String get shaabanLabel => r'Шаабан'; @@ -5919,25 +5994,25 @@ class SfLocalizationsKy extends SfGlobalLocalizations { String get shortDhualhiLabel => r'Зуль-Х'; @override - String get shortDhualqiLabel => r'Zhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'Зуль-Q'; @override - String get shortJumada1Label => r'Жум. I'; + String get shortJumada1Label => r'Jum. Мен'; @override - String get shortJumada2Label => r'Жум. II'; + String get shortJumada2Label => r'Jum. II'; @override String get shortMuharramLabel => r'Мух.'; @override - String get shortRabi1Label => r'Раби. I'; + String get shortRabi1Label => r'Раби. Мен'; @override String get shortRabi2Label => r'Раби. II'; @override - String get shortRajabLabel => r'Raj.'; + String get shortRajabLabel => r'Радж.'; @override String get shortRamadanLabel => r'RAM.'; @@ -5953,6 +6028,9 @@ class SfLocalizationsKy extends SfGlobalLocalizations { @override String get todayLabel => r'Бүгүн'; + + @override + String get weeknumberLabel => r'Жума'; } /// The translations for Lao (`lo`). @@ -5974,16 +6052,16 @@ class SfLocalizationsLo extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'ຕາຕະລາງ'; @override - String get allowedViewTimelineDayLabel => r'ວັນ ກຳ ນົດເວລາ'; + String get allowedViewTimelineDayLabel => r'ທາມລາຍວັນ'; @override - String get allowedViewTimelineMonthLabel => r'ເດືອນ ກຳ ນົດເວລາ'; + String get allowedViewTimelineMonthLabel => r'ໄລຍະເວລາເດືອນ'; @override - String get allowedViewTimelineWeekLabel => r'ອາທິດ ກຳ ນົດເວລາ'; + String get allowedViewTimelineWeekLabel => r'ລຳ ດັບອາທິດ'; @override - String get allowedViewTimelineWorkWeekLabel => r'ຕາຕະລາງເວລາເຮັດວຽກ'; + String get allowedViewTimelineWorkWeekLabel => r'ກໍານົດເວລາເຮັດວຽກອາທິດ'; @override String get allowedViewWeekLabel => r'ອາທິດ'; @@ -6001,11 +6079,10 @@ class SfLocalizationsLo extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'ລາຍການ'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get jumada1Label => r'Jumada al-awal'; @override String get jumada2Label => r'Jumada al-thani'; @@ -6017,31 +6094,31 @@ class SfLocalizationsLo extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'ບໍ່ມີເຫດການ'; @override - String get noSelectedDateCalendarLabel => r'ບໍ່ມີວັນທີ່ເລືອກ'; + String get noSelectedDateCalendarLabel => r'ບໍ່ມີວັນທີເລືອກ'; @override String get ofDataPagerLabel => r'ຂອງ'; @override - String get pagesDataPagerLabel => r'ໜ້າ'; + String get pagesDataPagerLabel => r'ຫນ້າ'; @override - String get pdfBookmarksLabel => r'ຫມາຍ'; + String get pdfBookmarksLabel => r'ບຸກມາກ'; @override - String get pdfEnterPageNumberLabel => r'ໃສ່ ໝາຍ ເລກ ໜ້າ'; + String get pdfEnterPageNumberLabel => r'ໃສ່ເລກ ໜ້າ'; @override - String get pdfGoToPageLabel => r'ໄປທີ່ ໜ້າ'; + String get pdfGoToPageLabel => r'ໄປຫາ ໜ້າ'; @override - String get pdfInvalidPageNumberLabel => r'ກະລຸນາໃສ່ເບີທີ່ຖືກຕ້ອງ'; + String get pdfInvalidPageNumberLabel => r'ກະລຸນາໃສ່ຕົວເລກທີ່ຖືກຕ້ອງ'; @override - String get pdfNoBookmarksLabel => r'ບໍ່ພົບເຄື່ອງ ໝາຍ'; + String get pdfNoBookmarksLabel => r'ບໍ່ພົບບຸກມາກ'; @override - String get pdfPaginationDialogCancelLabel => r'CANCEL'; + String get pdfPaginationDialogCancelLabel => r'ຍົກເລີກ'; @override String get pdfPaginationDialogOkLabel => r'ຕົກ​ລົງ'; @@ -6050,13 +6127,13 @@ class SfLocalizationsLo extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'ຂອງ'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi ' "'" r'al-awal'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @override - String get rajabLabel => r'ລາດ'; + String get rajabLabel => r'Rajab'; @override String get ramadanLabel => r'Ramadan'; @@ -6065,49 +6142,52 @@ class SfLocalizationsLo extends SfGlobalLocalizations { String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'ຊາ' "'" r'aban'; + String get shaabanLabel => r'Sha' "'" r'aban'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'Dhu' "'" r'l -H'; @override String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'ຈູມ. ຂ້ອຍ'; + String get shortJumada1Label => r'ຈຸ່ມ. ຂ້ອຍ'; @override - String get shortJumada2Label => r'ຈູມ. II'; + String get shortJumada2Label => r'ຈຸ່ມ. II'; @override - String get shortMuharramLabel => r'Muh.'; + String get shortMuharramLabel => r'ມ.'; @override - String get shortRabi1Label => r'ຣາເບ. ຂ້ອຍ'; + String get shortRabi1Label => r'Rabi. ຂ້ອຍ'; @override - String get shortRabi2Label => r'ຣາເບ. II'; + String get shortRabi2Label => r'Rabi. II'; @override - String get shortRajabLabel => r'ລາດ.'; + String get shortRajabLabel => r'Raj.'; @override String get shortRamadanLabel => r'Ram.'; @override - String get shortSafarLabel => r'ປອດໄພ.'; + String get shortSafarLabel => r'ປອດໄພ'; @override - String get shortShaabanLabel => r'ຊາ.'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortShawwalLabel => r'ໂກລ.'; + String get shortShawwalLabel => r'Shaw.'; @override String get todayLabel => r'ມື້​ນີ້'; + + @override + String get weeknumberLabel => r'ອາທິດ'; } /// The translations for Lithuanian (`lt`). @@ -6129,16 +6209,16 @@ class SfLocalizationsLt extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Tvarkaraštis'; @override - String get allowedViewTimelineDayLabel => r'Laiko juostos diena'; + String get allowedViewTimelineDayLabel => r'Laiko skalės diena'; @override - String get allowedViewTimelineMonthLabel => r'Laiko juostos mėnuo'; + String get allowedViewTimelineMonthLabel => r'Laiko skalės mėnuo'; @override String get allowedViewTimelineWeekLabel => r'Laiko juostos savaitė'; @override - String get allowedViewTimelineWorkWeekLabel => r'Laiko juostos darbo savaitė'; + String get allowedViewTimelineWorkWeekLabel => r'Darbo savaitės laiko juosta'; @override String get allowedViewWeekLabel => r'Savaitė'; @@ -6156,8 +6236,7 @@ class SfLocalizationsLt extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'daiktų'; + String get itemsDataPagerLabel => r'elementus'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -6166,19 +6245,19 @@ class SfLocalizationsLt extends SfGlobalLocalizations { String get jumada2Label => r'Jumada al-thani'; @override - String get muharramLabel => r'Muharram'; + String get muharramLabel => r'Muharramas'; @override String get noEventsCalendarLabel => r'Jokių įvykių'; @override - String get noSelectedDateCalendarLabel => r'Nepasirinkta data'; + String get noSelectedDateCalendarLabel => r'Nėra pasirinktos datos'; @override String get ofDataPagerLabel => r'apie'; @override - String get pagesDataPagerLabel => r'puslapių'; + String get pagesDataPagerLabel => r'puslapius'; @override String get pdfBookmarksLabel => r'Žymės'; @@ -6190,7 +6269,7 @@ class SfLocalizationsLt extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Eiti į puslapį'; @override - String get pdfInvalidPageNumberLabel => r'Įveskite galiojantį numerį'; + String get pdfInvalidPageNumberLabel => r'Įveskite tinkamą numerį'; @override String get pdfNoBookmarksLabel => r'Nerasta jokių žymių'; @@ -6205,7 +6284,7 @@ class SfLocalizationsLt extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'apie'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi al-awwal'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @@ -6217,10 +6296,10 @@ class SfLocalizationsLt extends SfGlobalLocalizations { String get ramadanLabel => r'Ramadanas'; @override - String get safarLabel => r'Safaras'; + String get safarLabel => r'„Safar“'; @override - String get shaabanLabel => r'Šaabanas'; + String get shaabanLabel => r'Shaabanas'; @override String get shawwalLabel => r'Shawwal'; @@ -6247,22 +6326,25 @@ class SfLocalizationsLt extends SfGlobalLocalizations { String get shortRabi2Label => r'Rabi. II'; @override - String get shortRajabLabel => r'Radž.'; + String get shortRajabLabel => r'Radžis.'; @override - String get shortRamadanLabel => r'Avinas.'; + String get shortRamadanLabel => r'Aunas.'; @override String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Ša.'; + String get shortShaabanLabel => r'Sha.'; @override String get shortShawwalLabel => r'Shaw.'; @override String get todayLabel => r'Šiandien'; + + @override + String get weeknumberLabel => r'Savaitė'; } /// The translations for Latvian (`lv`). @@ -6287,7 +6369,7 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get allowedViewTimelineDayLabel => r'Laika skalas diena'; @override - String get allowedViewTimelineMonthLabel => r'Laika skala mēnesis'; + String get allowedViewTimelineMonthLabel => r'Laika skalas mēnesis'; @override String get allowedViewTimelineWeekLabel => r'Laika skalas nedēļa'; @@ -6305,13 +6387,12 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get daySpanCountLabel => r'Diena'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get dhualhiLabel => r'Dhu al-Hidža'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'preces'; @override @@ -6327,10 +6408,10 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Nav notikumu'; @override - String get noSelectedDateCalendarLabel => r'Nav atlasīts datums'; + String get noSelectedDateCalendarLabel => r'Nav izvēlēts datums'; @override - String get ofDataPagerLabel => r'gada'; + String get ofDataPagerLabel => r'no'; @override String get pagesDataPagerLabel => r'lapas'; @@ -6348,7 +6429,7 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'Lūdzu, ievadiet derīgu numuru'; @override - String get pdfNoBookmarksLabel => r'Nav atrasta neviena grāmatzīme'; + String get pdfNoBookmarksLabel => r'Grāmatzīmes nav atrastas'; @override String get pdfPaginationDialogCancelLabel => r'ATCELT'; @@ -6357,16 +6438,16 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get pdfPaginationDialogOkLabel => r'labi'; @override - String get pdfScrollStatusOfLabel => r'gada'; + String get pdfScrollStatusOfLabel => r'no'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi al-awwal'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @override - String get rajabLabel => r'Radžabs'; + String get rajabLabel => r'Radžaba'; @override String get ramadanLabel => r'Ramadāns'; @@ -6375,7 +6456,7 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get safarLabel => r'Safārs'; @override - String get shaabanLabel => r'Šaabans'; + String get shaabanLabel => r'Šabāns'; @override String get shawwalLabel => r'Shawwal'; @@ -6402,7 +6483,7 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get shortRabi2Label => r'Rabi. II'; @override - String get shortRajabLabel => r'Raj.'; + String get shortRajabLabel => r'Radž.'; @override String get shortRamadanLabel => r'Auns.'; @@ -6411,13 +6492,16 @@ class SfLocalizationsLv extends SfGlobalLocalizations { String get shortSafarLabel => r'Saf.'; @override - String get shortShaabanLabel => r'Ša.'; + String get shortShaabanLabel => r'Sha.'; @override String get shortShawwalLabel => r'Šovs.'; @override String get todayLabel => r'Šodien'; + + @override + String get weeknumberLabel => r'Nedēļa'; } /// The translations for Macedonian (`mk`). @@ -6448,7 +6532,7 @@ class SfLocalizationsMk extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'Недела на времеплов'; @override - String get allowedViewTimelineWorkWeekLabel => r'Времепловна работна недела'; + String get allowedViewTimelineWorkWeekLabel => r'Временска работна недела'; @override String get allowedViewWeekLabel => r'Недела'; @@ -6460,13 +6544,12 @@ class SfLocalizationsMk extends SfGlobalLocalizations { String get daySpanCountLabel => r'Ден'; @override - String get dhualhiLabel => r'Huу ал-хиџа'; + String get dhualhiLabel => r'Huу ал-Хиџа'; @override String get dhualqiLabel => r'Huу ал-Кида'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'предмети'; @override @@ -6476,7 +6559,7 @@ class SfLocalizationsMk extends SfGlobalLocalizations { String get jumada2Label => r'Umумада ал-тани'; @override - String get muharramLabel => r'Мухарем'; + String get muharramLabel => r'Мухарам'; @override String get noEventsCalendarLabel => r'Нема настани'; @@ -6500,7 +6583,7 @@ class SfLocalizationsMk extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'Оди на страна'; @override - String get pdfInvalidPageNumberLabel => r'Внесете важечки број'; + String get pdfInvalidPageNumberLabel => r'Внесете валиден број'; @override String get pdfNoBookmarksLabel => r'Не се пронајдени обележувачи'; @@ -6515,10 +6598,10 @@ class SfLocalizationsMk extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'на'; @override - String get rabi1Label => r'Раби ал-аввал'; + String get rabi1Label => r'Раби ' "'" r'ел-аввал'; @override - String get rabi2Label => r'Раби ал-тани'; + String get rabi2Label => r'Раби ' "'" r'ал-тани'; @override String get rajabLabel => r'Раџаб'; @@ -6542,37 +6625,40 @@ class SfLocalizationsMk extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Дул-П'; @override - String get shortJumada1Label => r'Umум Јас'; + String get shortJumada1Label => r'Џум Јас'; @override - String get shortJumada2Label => r'Umум II'; + String get shortJumada2Label => r'Џум II'; @override - String get shortMuharramLabel => r'Мух'; + String get shortMuharramLabel => r'Мух.'; @override - String get shortRabi1Label => r'Раби Јас'; + String get shortRabi1Label => r'Раби. Јас'; @override - String get shortRabi2Label => r'Раби II'; + String get shortRabi2Label => r'Раби. II'; @override - String get shortRajabLabel => r'Рај'; + String get shortRajabLabel => r'Рај.'; @override - String get shortRamadanLabel => r'Овен'; + String get shortRamadanLabel => r'Рам.'; @override - String get shortSafarLabel => r'Саф.'; + String get shortSafarLabel => r'Сеф.'; @override - String get shortShaabanLabel => r'Ша'; + String get shortShaabanLabel => r'Ша.'; @override - String get shortShawwalLabel => r'Шоу'; + String get shortShawwalLabel => r'Шо.'; @override String get todayLabel => r'Денес'; + + @override + String get weeknumberLabel => r'Недела'; } /// The translations for Malayalam (`ml`). @@ -6594,16 +6680,16 @@ class SfLocalizationsMl extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'പട്ടിക'; @override - String get allowedViewTimelineDayLabel => r'ടൈംലൈൻ ദിനം'; + String get allowedViewTimelineDayLabel => r'ടൈംലൈൻ ദിവസം'; @override String get allowedViewTimelineMonthLabel => r'ടൈംലൈൻ മാസം'; @override - String get allowedViewTimelineWeekLabel => r'ടൈംലൈൻ ആഴ്ച'; + String get allowedViewTimelineWeekLabel => r'ടൈംലൈൻ വാരം'; @override - String get allowedViewTimelineWorkWeekLabel => r'ടൈംലൈൻ വർക്ക് വീക്ക്'; + String get allowedViewTimelineWorkWeekLabel => r'ടൈംലൈൻ പ്രവൃത്തി ആഴ്ച'; @override String get allowedViewWeekLabel => r'ആഴ്ച'; @@ -6615,17 +6701,16 @@ class SfLocalizationsMl extends SfGlobalLocalizations { String get daySpanCountLabel => r'ദിവസം'; @override - String get dhualhiLabel => r'ധു അൽ ഹിജ'; + String get dhualhiLabel => r'ധു അൽ ഹിജ്ജ'; @override - String get dhualqiLabel => r'ധു അൽ ക്വിദ'; + String get dhualqiLabel => r'ധു അൽ-ഖിദ'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'ഇനങ്ങൾ'; @override - String get jumada1Label => r'ജുമാദ അൽ അവ്വാൾ'; + String get jumada1Label => r'ജുമാദ അൽ അവ്വൽ'; @override String get jumada2Label => r'ജുമാദ അൽ താനി'; @@ -6640,7 +6725,7 @@ class SfLocalizationsMl extends SfGlobalLocalizations { String get noSelectedDateCalendarLabel => r'തിരഞ്ഞെടുത്ത തീയതിയില്ല'; @override - String get ofDataPagerLabel => r'ന്റെ'; + String get ofDataPagerLabel => r'യുടെ'; @override String get pagesDataPagerLabel => r'പേജുകൾ'; @@ -6655,28 +6740,28 @@ class SfLocalizationsMl extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'പേജിലേക്ക് പോകുക'; @override - String get pdfInvalidPageNumberLabel => r'സാധുവായ ഒരു നമ്പർ നൽകുക'; + String get pdfInvalidPageNumberLabel => r'ദയവായി ഒരു സാധുവായ നമ്പർ നൽകുക'; @override - String get pdfNoBookmarksLabel => r'ബുക്ക്മാർക്കുകളൊന്നും കണ്ടെത്തിയില്ല'; + String get pdfNoBookmarksLabel => r'ബുക്ക്മാർക്കുകൾ കണ്ടെത്തിയില്ല'; @override - String get pdfPaginationDialogCancelLabel => r'റദ്ദാക്കുക'; + String get pdfPaginationDialogCancelLabel => r'കാൻസൽ'; @override String get pdfPaginationDialogOkLabel => r'ശരി'; @override - String get pdfScrollStatusOfLabel => r'ന്റെ'; + String get pdfScrollStatusOfLabel => r'യുടെ'; @override - String get rabi1Label => r'റാബി അൽ അവൽ'; + String get rabi1Label => r'റാബി അൽ-അവ്വൽ'; @override - String get rabi2Label => r'റാബി അൽ താനി'; + String get rabi2Label => r'റാബി അൽ-താനി'; @override - String get rajabLabel => r'രാജാബ്'; + String get rajabLabel => r'റജബ്'; @override String get ramadanLabel => r'റമദാൻ'; @@ -6685,19 +6770,19 @@ class SfLocalizationsMl extends SfGlobalLocalizations { String get safarLabel => r'സഫർ'; @override - String get shaabanLabel => r'ഷാബാൻ'; + String get shaabanLabel => r'ശഅബാൻ'; @override - String get shawwalLabel => r'ഷാവാൽ'; + String get shawwalLabel => r'ശവ്വാൽ'; @override String get shortDhualhiLabel => r'ദുൽ-എച്ച്'; @override - String get shortDhualqiLabel => r'ദുൽ-ക്യു'; + String get shortDhualqiLabel => r'ദുൽ-ക്യൂ'; @override - String get shortJumada1Label => r'ജം. ഞാൻ'; + String get shortJumada1Label => r'ജം. ഐ'; @override String get shortJumada2Label => r'ജം. II'; @@ -6706,13 +6791,13 @@ class SfLocalizationsMl extends SfGlobalLocalizations { String get shortMuharramLabel => r'മുഹ്.'; @override - String get shortRabi1Label => r'റാബി. ഞാൻ'; + String get shortRabi1Label => r'റാബി ഐ'; @override - String get shortRabi2Label => r'റാബി. II'; + String get shortRabi2Label => r'റാബി II'; @override - String get shortRajabLabel => r'രാജ്.'; + String get shortRajabLabel => r'രാജ്'; @override String get shortRamadanLabel => r'RAM.'; @@ -6721,13 +6806,16 @@ class SfLocalizationsMl extends SfGlobalLocalizations { String get shortSafarLabel => r'സേഫ്.'; @override - String get shortShaabanLabel => r'ഷാ.'; + String get shortShaabanLabel => r'ഷാ'; @override String get shortShawwalLabel => r'ഷാ.'; @override String get todayLabel => r'ഇന്ന്'; + + @override + String get weeknumberLabel => r'ആഴ്ച'; } /// The translations for Mongolian (`mn`). @@ -6749,16 +6837,16 @@ class SfLocalizationsMn extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Хуваарь'; @override - String get allowedViewTimelineDayLabel => r'Цаг хугацааны өдөр'; + String get allowedViewTimelineDayLabel => r'Цагийн хуваарийн өдөр'; @override - String get allowedViewTimelineMonthLabel => r'Цаг хугацааны сар'; + String get allowedViewTimelineMonthLabel => r'Хугацааны сар'; @override - String get allowedViewTimelineWeekLabel => r'Он цагийн хэлхээс'; + String get allowedViewTimelineWeekLabel => r'Хугацааны долоо хоног'; @override - String get allowedViewTimelineWorkWeekLabel => r'Хугацааны ажлын долоо хоног'; + String get allowedViewTimelineWorkWeekLabel => r'Цагийн ажлын долоо хоног'; @override String get allowedViewWeekLabel => r'Долоо хоног'; @@ -6770,13 +6858,12 @@ class SfLocalizationsMn extends SfGlobalLocalizations { String get daySpanCountLabel => r'Өдөр'; @override - String get dhualhiLabel => r'Зу аль-Хижжа'; + String get dhualhiLabel => r'Зул аль-Хижжа'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => r'Зул аль-Кида'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'зүйлс'; @override @@ -6789,10 +6876,10 @@ class SfLocalizationsMn extends SfGlobalLocalizations { String get muharramLabel => r'Мухаррам'; @override - String get noEventsCalendarLabel => r'Арга хэмжээ алга'; + String get noEventsCalendarLabel => r'Үйл явдал байхгүй'; @override - String get noSelectedDateCalendarLabel => r'Сонгосон огноо алга'; + String get noSelectedDateCalendarLabel => r'Сонгосон огноо байхгүй'; @override String get ofDataPagerLabel => r'-ийн'; @@ -6804,7 +6891,7 @@ class SfLocalizationsMn extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'Хавчуурга'; @override - String get pdfEnterPageNumberLabel => r'Хуудасны дугаар оруулна уу'; + String get pdfEnterPageNumberLabel => r'Хуудасны дугаарыг оруулна уу'; @override String get pdfGoToPageLabel => r'Хуудас руу очих'; @@ -6816,7 +6903,7 @@ class SfLocalizationsMn extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Хавчуурга олдсонгүй'; @override - String get pdfPaginationDialogCancelLabel => r'Цуцлах'; + String get pdfPaginationDialogCancelLabel => r'ЦУЦЛАХ'; @override String get pdfPaginationDialogOkLabel => r'БОЛЖ БАЙНА УУ'; @@ -6828,7 +6915,7 @@ class SfLocalizationsMn extends SfGlobalLocalizations { String get rabi1Label => r'Раби ' "'" r'аль-аввал'; @override - String get rabi2Label => r'Раби ' "'" r'аль-тени'; + String get rabi2Label => r'Раби ' "'" r'аль-Тани'; @override String get rajabLabel => r'Ражаб'; @@ -6873,7 +6960,7 @@ class SfLocalizationsMn extends SfGlobalLocalizations { String get shortRamadanLabel => r'Рам.'; @override - String get shortSafarLabel => r'Аюулгүй.'; + String get shortSafarLabel => r'Саф.'; @override String get shortShaabanLabel => r'Ша.'; @@ -6883,6 +6970,9 @@ class SfLocalizationsMn extends SfGlobalLocalizations { @override String get todayLabel => r'Өнөөдөр'; + + @override + String get weeknumberLabel => r'Долоо хоног'; } /// The translations for Marathi (`mr`). @@ -6913,7 +7003,7 @@ class SfLocalizationsMr extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'टाइमलाइन आठवडा'; @override - String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कार्य आठवडा'; + String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन वर्क वीक'; @override String get allowedViewWeekLabel => r'आठवडा'; @@ -6928,23 +7018,22 @@ class SfLocalizationsMr extends SfGlobalLocalizations { String get dhualhiLabel => r'धु अल-हिज्जा'; @override - String get dhualqiLabel => r'धु अल-कायदा'; + String get dhualqiLabel => r'धु अल- Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'आयटम'; + String get itemsDataPagerLabel => r'वस्तू'; @override - String get jumada1Label => r'जुमादा अल-आव्वल'; + String get jumada1Label => r'जुमादा अल-अव्वल'; @override - String get jumada2Label => r'जुमादा अल-थॅनी'; + String get jumada2Label => r'जुमादा अल-थानी'; @override - String get muharramLabel => r'मुहर्रम'; + String get muharramLabel => r'मोहरम'; @override - String get noEventsCalendarLabel => r'कार्यक्रम नाहीत'; + String get noEventsCalendarLabel => r'कोणतेही कार्यक्रम नाहीत'; @override String get noSelectedDateCalendarLabel => r'निवडलेली तारीख नाही'; @@ -6980,10 +7069,10 @@ class SfLocalizationsMr extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'च्या'; @override - String get rabi1Label => r'रबी अल अलवाल'; + String get rabi1Label => r'रबी अल-अवाल'; @override - String get rabi2Label => r'रबी ' "'" r'अल-थॅनी'; + String get rabi2Label => r'रबी अल-थानी'; @override String get rajabLabel => r'रजब'; @@ -6992,13 +7081,13 @@ class SfLocalizationsMr extends SfGlobalLocalizations { String get ramadanLabel => r'रमजान'; @override - String get safarLabel => r'सफार'; + String get safarLabel => r'सफर'; @override String get shaabanLabel => r'शाबान'; @override - String get shawwalLabel => r'शावल'; + String get shawwalLabel => r'शवाल'; @override String get shortDhualhiLabel => r'धुळ-एच'; @@ -7007,10 +7096,10 @@ class SfLocalizationsMr extends SfGlobalLocalizations { String get shortDhualqiLabel => r'धुळ-क्यू'; @override - String get shortJumada1Label => r'जम्. मी'; + String get shortJumada1Label => r'जम. मी'; @override - String get shortJumada2Label => r'जम्. II'; + String get shortJumada2Label => r'जम. II'; @override String get shortMuharramLabel => r'मुह.'; @@ -7028,7 +7117,7 @@ class SfLocalizationsMr extends SfGlobalLocalizations { String get shortRamadanLabel => r'रॅम.'; @override - String get shortSafarLabel => r'सेफ.'; + String get shortSafarLabel => r'सुरक्षित.'; @override String get shortShaabanLabel => r'शा.'; @@ -7038,6 +7127,9 @@ class SfLocalizationsMr extends SfGlobalLocalizations { @override String get todayLabel => r'आज'; + + @override + String get weeknumberLabel => r'आठवडा'; } /// The translations for Malay (`ms`). @@ -7071,7 +7163,7 @@ class SfLocalizationsMs extends SfGlobalLocalizations { String get allowedViewTimelineWorkWeekLabel => r'Minggu Kerja Garis Masa'; @override - String get allowedViewWeekLabel => r'Minggu'; + String get allowedViewWeekLabel => r'Seminggu'; @override String get allowedViewWorkWeekLabel => r'Minggu Kerja'; @@ -7086,7 +7178,6 @@ class SfLocalizationsMs extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'barang'; @override @@ -7193,6 +7284,9 @@ class SfLocalizationsMs extends SfGlobalLocalizations { @override String get todayLabel => r'Hari ini'; + + @override + String get weeknumberLabel => r'Seminggu'; } /// The translations for Burmese (`my`). @@ -7208,56 +7302,55 @@ class SfLocalizationsMy extends SfGlobalLocalizations { String get allowedViewDayLabel => r'နေ့'; @override - String get allowedViewMonthLabel => r'လ'; + String get allowedViewMonthLabel => r'တစ်လ'; @override - String get allowedViewScheduleLabel => r'ဇယား'; + String get allowedViewScheduleLabel => r'အချိန်ဇယား'; @override String get allowedViewTimelineDayLabel => r'အချိန်ဇယားနေ့'; @override - String get allowedViewTimelineMonthLabel => r'အချိန်ဇယားလ'; + String get allowedViewTimelineMonthLabel => r'အချိန်စာရင်းလ'; @override String get allowedViewTimelineWeekLabel => r'Timeline အပတ်'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline အလုပ်ရက်သတ္တပတ်'; + String get allowedViewTimelineWorkWeekLabel => r'အလုပ်ချိန်ရက်သတ္တပတ်'; @override - String get allowedViewWeekLabel => r'အပတ်'; + String get allowedViewWeekLabel => r'အေးလေ'; @override - String get allowedViewWorkWeekLabel => r'အလုပ်ရက်သတ္တပတ်'; + String get allowedViewWorkWeekLabel => r'အလုပ်အပတ်'; @override String get daySpanCountLabel => r'နေ့'; @override - String get dhualhiLabel => r'Dhu Al-Hijjah'; + String get dhualhiLabel => r'Dhu al-Hijjah'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'ပစ္စည်းတွေ'; + String get itemsDataPagerLabel => r'ပစ္စည်းများ'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get jumada1Label => r'Jumada al-awal'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'Jumada al-Thani'; @override String get muharramLabel => r'Muharram'; @override - String get noEventsCalendarLabel => r'ဖြစ်ရပ်များမရှိပါ'; + String get noEventsCalendarLabel => r'အဖြစ်အပျက်များမရှိပါ'; @override - String get noSelectedDateCalendarLabel => r'ရွေးချယ်ထားသည့်နေ့စွဲမရှိပါ'; + String get noSelectedDateCalendarLabel => r'ရွေးထားသောရက်မရှိပါ'; @override String get ofDataPagerLabel => r'၏'; @@ -7266,70 +7359,71 @@ class SfLocalizationsMy extends SfGlobalLocalizations { String get pagesDataPagerLabel => r'စာမျက်နှာများ'; @override - String get pdfBookmarksLabel => r'mark ။'; + String get pdfBookmarksLabel => r'စာညှပ်များ'; @override - String get pdfEnterPageNumberLabel => r'စာမျက်နှာနံပါတ်ကိုရိုက်ထည့်ပါ'; + String get pdfEnterPageNumberLabel => r'စာမျက်နှာနံပါတ်ထည့်ပါ'; @override String get pdfGoToPageLabel => r'စာမျက်နှာသို့သွားပါ'; @override - String get pdfInvalidPageNumberLabel => r'ကျေးဇူးပြု၍ မှန်ကန်နံပါတ်ထည့်ပါ'; + String get pdfInvalidPageNumberLabel => + r'ကျေးဇူးပြု၍ မှန်ကန်သောနံပါတ်ကိုထည့်ပါ'; @override - String get pdfNoBookmarksLabel => r'Bookmarks မရှိပါ'; + String get pdfNoBookmarksLabel => r'စာညှပ်များမတွေ့ပါ'; @override - String get pdfPaginationDialogCancelLabel => r'ဖျက်သိမ်းခြင်း'; + String get pdfPaginationDialogCancelLabel => r'ပယ်ဖျက်ပါ'; @override - String get pdfPaginationDialogOkLabel => r'ရလား'; + String get pdfPaginationDialogOkLabel => r'အိုကေ'; @override String get pdfScrollStatusOfLabel => r'၏'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi ' "'" r'al-awal'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'Rabi ' "'" r'al-Thani'; @override - String get rajabLabel => r'ရာဂျ'; + String get rajabLabel => r'Rajab'; @override - String get ramadanLabel => r'Ramadan'; + String get ramadanLabel => r'ဥပုသ်လ'; @override String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'ရှာဖန်'; @override String get shawwalLabel => r'Shawwal'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H ကို'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum ။ ငါ'; + String get shortJumada1Label => r'ဂျမ်။ ငါ'; @override - String get shortJumada2Label => r'Jum ။ ၂'; + String get shortJumada2Label => r'ဂျမ်။ II'; @override String get shortMuharramLabel => r'Muh ။'; @override - String get shortRabi1Label => r'ရာဘီ။ ငါ'; + String get shortRabi1Label => r'Rabi ။ ငါ'; @override - String get shortRabi2Label => r'ရာဘီ။ ၂'; + String get shortRabi2Label => r'Rabi ။ II'; @override String get shortRajabLabel => r'Raj ။'; @@ -7338,16 +7432,19 @@ class SfLocalizationsMy extends SfGlobalLocalizations { String get shortRamadanLabel => r'ရမ်။'; @override - String get shortSafarLabel => r'Saf ။'; + String get shortSafarLabel => r'Saf'; @override String get shortShaabanLabel => r'Sha ။'; @override - String get shortShawwalLabel => r'Shaw ။'; + String get shortShawwalLabel => r'ရှော။'; @override String get todayLabel => r'ဒီနေ့'; + + @override + String get weeknumberLabel => r'အေးလေ'; } /// The translations for Norwegian Bokmål (`nb`). @@ -7369,13 +7466,13 @@ class SfLocalizationsNb extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Rute'; @override - String get allowedViewTimelineDayLabel => r'Tidslinjedag'; + String get allowedViewTimelineDayLabel => r'Tidslinjens dag'; @override - String get allowedViewTimelineMonthLabel => r'Tidslinjemåned'; + String get allowedViewTimelineMonthLabel => r'Tidslinje måned'; @override - String get allowedViewTimelineWeekLabel => r'Tidslinjeuke'; + String get allowedViewTimelineWeekLabel => r'Tidslinje uke'; @override String get allowedViewTimelineWorkWeekLabel => r'Tidslinje arbeidsuke'; @@ -7396,8 +7493,7 @@ class SfLocalizationsNb extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'gjenstander'; + String get itemsDataPagerLabel => r'elementer'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -7433,7 +7529,7 @@ class SfLocalizationsNb extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'Vennligst oppgi et gyldig nummer'; @override - String get pdfNoBookmarksLabel => r'Ingen bokmerker funnet'; + String get pdfNoBookmarksLabel => r'Fant ingen bokmerker'; @override String get pdfPaginationDialogCancelLabel => r'AVBRYT'; @@ -7503,6 +7599,9 @@ class SfLocalizationsNb extends SfGlobalLocalizations { @override String get todayLabel => r'I dag'; + + @override + String get weeknumberLabel => r'Uke'; } /// The translations for Nepali (`ne`). @@ -7521,38 +7620,37 @@ class SfLocalizationsNe extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'महिना'; @override - String get allowedViewScheduleLabel => r'तालिका'; + String get allowedViewScheduleLabel => r'अनुसूची'; @override - String get allowedViewTimelineDayLabel => r'समयरेखा दिन'; + String get allowedViewTimelineDayLabel => r'टाइमलाइन दिन'; @override - String get allowedViewTimelineMonthLabel => r'टाइमलाइन महीना'; + String get allowedViewTimelineMonthLabel => r'टाइमलाइन महिना'; @override String get allowedViewTimelineWeekLabel => r'टाइमलाइन हप्ता'; @override - String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कार्य सप्ताह'; + String get allowedViewTimelineWorkWeekLabel => r'टाइमलाइन कार्य हप्ता'; @override String get allowedViewWeekLabel => r'हप्ता'; @override - String get allowedViewWorkWeekLabel => r'कार्य सप्ताह'; + String get allowedViewWorkWeekLabel => r'काम हप्ता'; @override String get daySpanCountLabel => r'दिन'; @override - String get dhualhiLabel => r'धु अल-हिज्जा'; + String get dhualhiLabel => r'धु अल Hijjah'; @override String get dhualqiLabel => r'धु अल Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'वस्तुहरू'; + String get itemsDataPagerLabel => r'वस्तुहरु'; @override String get jumada1Label => r'जुमादा अल-अवाल'; @@ -7564,32 +7662,32 @@ class SfLocalizationsNe extends SfGlobalLocalizations { String get muharramLabel => r'मुहर्रम'; @override - String get noEventsCalendarLabel => r'घटनाहरू छैनन्'; + String get noEventsCalendarLabel => r'कुनै घटना छैन'; @override - String get noSelectedDateCalendarLabel => r'कुनै चयन गरिएको मिति छैन'; + String get noSelectedDateCalendarLabel => r'कुनै चयनित मिति छैन'; @override String get ofDataPagerLabel => r'को'; @override - String get pagesDataPagerLabel => r'पृष्ठहरु'; + String get pagesDataPagerLabel => r'पानाहरु'; @override - String get pdfBookmarksLabel => r'बुकमार्कहरू'; + String get pdfBookmarksLabel => r'बुकमार्क'; @override - String get pdfEnterPageNumberLabel => r'पृष्ठ संख्या प्रविष्ट गर्नुहोस्'; + String get pdfEnterPageNumberLabel => r'पृष्ठ नम्बर प्रविष्ट गर्नुहोस्'; @override String get pdfGoToPageLabel => r'पृष्ठमा जानुहोस्'; @override String get pdfInvalidPageNumberLabel => - r'कृपया मान्य संख्या प्रविष्ट गर्नुहोस्'; + r'कृपया एक मान्य नम्बर प्रविष्ट गर्नुहोस्'; @override - String get pdfNoBookmarksLabel => r'कुनै बुकमार्कहरू फेला परेनन्'; + String get pdfNoBookmarksLabel => r'कुनै बुकमार्क भेटिएन'; @override String get pdfPaginationDialogCancelLabel => r'रद्द गर्नुहोस्'; @@ -7601,10 +7699,10 @@ class SfLocalizationsNe extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'को'; @override - String get rabi1Label => r'रबी अल अलवाल'; + String get rabi1Label => r'रबी अल-अवाल'; @override - String get rabi2Label => r'रबी ' "'" r'अल-थानी'; + String get rabi2Label => r'रबी अल थानी'; @override String get rajabLabel => r'रजब'; @@ -7616,28 +7714,28 @@ class SfLocalizationsNe extends SfGlobalLocalizations { String get safarLabel => r'सफार'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'शाबान'; @override - String get shawwalLabel => r'शोवल'; + String get shawwalLabel => r'शावल'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'धुल-एच'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'धुल-क्यू'; @override - String get shortJumada1Label => r'Jum। I'; + String get shortJumada1Label => r'जुम। म'; @override - String get shortJumada2Label => r'Jum। II'; + String get shortJumada2Label => r'जुम। II'; @override - String get shortMuharramLabel => r'मुह।'; + String get shortMuharramLabel => r'Muh।'; @override - String get shortRabi1Label => r'रबी। I'; + String get shortRabi1Label => r'रबी। म'; @override String get shortRabi2Label => r'रबी। II'; @@ -7649,16 +7747,19 @@ class SfLocalizationsNe extends SfGlobalLocalizations { String get shortRamadanLabel => r'राम।'; @override - String get shortSafarLabel => r'सफ।'; + String get shortSafarLabel => r'सुरक्षित'; @override - String get shortShaabanLabel => r'Sha।'; + String get shortShaabanLabel => r'शा।'; @override - String get shortShawwalLabel => r'श'; + String get shortShawwalLabel => r'शा।'; @override String get todayLabel => r'आज'; + + @override + String get weeknumberLabel => r'हप्ता'; } /// The translations for Dutch Flemish (`nl`). @@ -7680,16 +7781,16 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Schema'; @override - String get allowedViewTimelineDayLabel => r'Tijdlijn dag'; + String get allowedViewTimelineDayLabel => r'Tijdlijn Dag'; @override String get allowedViewTimelineMonthLabel => r'Tijdlijn maand'; @override - String get allowedViewTimelineWeekLabel => r'Tijdlijn Week'; + String get allowedViewTimelineWeekLabel => r'Tijdlijnweek'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tijdlijn werkweek'; + String get allowedViewTimelineWorkWeekLabel => r'Tijdlijn Werkweek'; @override String get allowedViewWeekLabel => r'Week'; @@ -7707,14 +7808,13 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'artikelen'; @override String get jumada1Label => r'Jumada al-awwal'; @override - String get jumada2Label => r'Jumada al-Thani'; + String get jumada2Label => r'Jumada al-thani'; @override String get muharramLabel => r'Muharram'; @@ -7735,7 +7835,7 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'Bladwijzers'; @override - String get pdfEnterPageNumberLabel => r'Voer het paginanummer in'; + String get pdfEnterPageNumberLabel => r'Voer paginanummer in'; @override String get pdfGoToPageLabel => r'Ga naar pagina'; @@ -7747,19 +7847,19 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Geen bladwijzers gevonden'; @override - String get pdfPaginationDialogCancelLabel => r'ANNULEREN'; + String get pdfPaginationDialogCancelLabel => r'ANNULEER'; @override - String get pdfPaginationDialogOkLabel => r'OK'; + String get pdfPaginationDialogOkLabel => r'Oke'; @override String get pdfScrollStatusOfLabel => r'van'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi' "'" r' al-awwali'; @override - String get rabi2Label => r'Rabi ' "'" r'al-Thani'; + String get rabi2Label => r'Rabi' "'" r' al Thani'; @override String get rajabLabel => r'Rajab'; @@ -7768,7 +7868,7 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get ramadanLabel => r'Ramadan'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'Safari'; @override String get shaabanLabel => r'Sha' "'" r'aban'; @@ -7783,16 +7883,16 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. ik'; + String get shortJumada1Label => r'jum. l'; @override - String get shortJumada2Label => r'Jum. II'; + String get shortJumada2Label => r'jum. II'; @override - String get shortMuharramLabel => r'Muh.'; + String get shortMuharramLabel => r'mwah.'; @override - String get shortRabi1Label => r'Rabi. ik'; + String get shortRabi1Label => r'Rabi. l'; @override String get shortRabi2Label => r'Rabi. II'; @@ -7804,16 +7904,19 @@ class SfLocalizationsNl extends SfGlobalLocalizations { String get shortRamadanLabel => r'RAM.'; @override - String get shortSafarLabel => r'Saf.'; + String get shortSafarLabel => r'saf.'; @override String get shortShaabanLabel => r'Sha.'; @override - String get shortShawwalLabel => r'Shaw.'; + String get shortShawwalLabel => r'Sjaa.'; @override String get todayLabel => r'Vandaag'; + + @override + String get weeknumberLabel => r'Week'; } /// The translations for Panjabi Punjabi (`pa`). @@ -7835,13 +7938,13 @@ class SfLocalizationsPa extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'ਸਮਾਸੂਚੀ, ਕਾਰਜ - ਕ੍ਰਮ'; @override - String get allowedViewTimelineDayLabel => r'ਟਾਈਮਲਾਈਨ ਦਿਵਸ'; + String get allowedViewTimelineDayLabel => r'ਸਮਾਂਰੇਖਾ ਦਿਵਸ'; @override - String get allowedViewTimelineMonthLabel => r'ਟਾਈਮਲਾਈਨ ਮਹੀਨਾ'; + String get allowedViewTimelineMonthLabel => r'ਸਮਾਂਰੇਖਾ ਮਹੀਨਾ'; @override - String get allowedViewTimelineWeekLabel => r'ਟਾਈਮਲਾਈਨ ਹਫ਼ਤਾ'; + String get allowedViewTimelineWeekLabel => r'ਸਮਾਂਰੇਖਾ ਹਫ਼ਤਾ'; @override String get allowedViewTimelineWorkWeekLabel => r'ਟਾਈਮਲਾਈਨ ਵਰਕ ਵੀਕ'; @@ -7850,38 +7953,37 @@ class SfLocalizationsPa extends SfGlobalLocalizations { String get allowedViewWeekLabel => r'ਹਫ਼ਤਾ'; @override - String get allowedViewWorkWeekLabel => r'ਕੰਮ ਦਾ ਹਫ਼ਤਾ'; + String get allowedViewWorkWeekLabel => r'ਵਰਕ ਵੀਕ'; @override String get daySpanCountLabel => r'ਦਿਨ'; @override - String get dhualhiLabel => r'ਧੂ ਅਲ-ਹਿਜਾਜਾ'; + String get dhualhiLabel => r'ਧੂ ਅਲ-ਹਿਜਾਹ'; @override - String get dhualqiLabel => r'ਧੂ ਅਲ ਕਿਆਦਾਹ'; + String get dhualqiLabel => r'ਧੂ ਅਲ-ਕਿਆਦਾਹ'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'ਇਕਾਈ'; @override - String get jumada1Label => r'ਜੁਮਾਦਾ ਅਲ-ਅਵਾਲ'; + String get jumada1Label => r'ਜੁਮਾਦਾ ਅਲ-ਅਵਲ'; @override - String get jumada2Label => r'ਜੁਮਦਾ ਅਲ-ਥਾਨੀ'; + String get jumada2Label => r'ਜੁਮਾਦਾ ਅਲ-ਥਾਨੀ'; @override - String get muharramLabel => r'ਮੁਹਰਰਾਮ'; + String get muharramLabel => r'ਮੁਹਰਮ'; @override String get noEventsCalendarLabel => r'ਕੋਈ ਇਵੈਂਟ ਨਹੀਂ'; @override - String get noSelectedDateCalendarLabel => r'ਕੋਈ ਚੁਣੀ ਤਾਰੀਖ ਨਹੀਂ'; + String get noSelectedDateCalendarLabel => r'ਕੋਈ ਚੁਣੀ ਹੋਈ ਤਾਰੀਖ ਨਹੀਂ'; @override - String get ofDataPagerLabel => r'ਦੇ'; + String get ofDataPagerLabel => r'ਦੀ'; @override String get pagesDataPagerLabel => r'ਪੰਨੇ'; @@ -7890,28 +7992,28 @@ class SfLocalizationsPa extends SfGlobalLocalizations { String get pdfBookmarksLabel => r'ਬੁੱਕਮਾਰਕ'; @override - String get pdfEnterPageNumberLabel => r'ਪੇਜ ਨੰਬਰ ਦਰਜ ਕਰੋ'; + String get pdfEnterPageNumberLabel => r'ਪੰਨਾ ਨੰਬਰ ਦਾਖਲ ਕਰੋ'; @override - String get pdfGoToPageLabel => r'ਪੇਜ ਤੇ ਜਾਓ'; + String get pdfGoToPageLabel => r'ਪੰਨੇ ਤੇ ਜਾਓ'; @override - String get pdfInvalidPageNumberLabel => r'ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਯੋਗ ਨੰਬਰ ਦਾਖਲ ਕਰੋ'; + String get pdfInvalidPageNumberLabel => r'ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਵੈਧ ਨੰਬਰ ਦਾਖਲ ਕਰੋ'; @override - String get pdfNoBookmarksLabel => r'ਕੋਈ ਬੁੱਕਮਾਰਕ ਨਹੀਂ ਮਿਲਿਆ'; + String get pdfNoBookmarksLabel => r'ਕੋਈ ਬੁੱਕਮਾਰਕ ਨਹੀਂ ਮਿਲੇ'; @override - String get pdfPaginationDialogCancelLabel => r'ਰੱਦ'; + String get pdfPaginationDialogCancelLabel => r'ਰੱਦ ਕਰੋ'; @override String get pdfPaginationDialogOkLabel => r'ਠੀਕ ਹੈ'; @override - String get pdfScrollStatusOfLabel => r'ਦੇ'; + String get pdfScrollStatusOfLabel => r'ਦੀ'; @override - String get rabi1Label => r'ਰਬੀ ' "'" r'ਅਲ-ਅਵਾਲ'; + String get rabi1Label => r'ਰਬੀ ਅਲ-ਅਵਲ'; @override String get rabi2Label => r'ਰਬੀ ' "'" r'ਅਲ-ਥਾਨੀ'; @@ -7923,19 +8025,19 @@ class SfLocalizationsPa extends SfGlobalLocalizations { String get ramadanLabel => r'ਰਮਜ਼ਾਨ'; @override - String get safarLabel => r'ਸਫਾਰ'; + String get safarLabel => r'ਸਫਰ'; @override - String get shaabanLabel => r'ਸ਼ਾਅਾਨ'; + String get shaabanLabel => r'ਸ਼ਾਅਬਾਨ'; @override String get shawwalLabel => r'ਸ਼ੋਵਾਲ'; @override - String get shortDhualhiLabel => r'ਧੂਲ-ਐਚ'; + String get shortDhualhiLabel => r'ਧੂਅਲ-ਐਚ'; @override - String get shortDhualqiLabel => r'ਧੂਅਲ-ਕਿ Q'; + String get shortDhualqiLabel => r'ਧੂਅਲ-ਕਿ.'; @override String get shortJumada1Label => r'ਜਮ. ਆਈ'; @@ -7944,22 +8046,22 @@ class SfLocalizationsPa extends SfGlobalLocalizations { String get shortJumada2Label => r'ਜਮ. II'; @override - String get shortMuharramLabel => r'ਮੁਹ.'; + String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'ਰਬੀ. ਆਈ'; + String get shortRabi1Label => r'ਹਾੜੀ. ਆਈ'; @override - String get shortRabi2Label => r'ਰਬੀ. II'; + String get shortRabi2Label => r'ਹਾੜੀ. II'; @override - String get shortRajabLabel => r'ਰਾਜ'; + String get shortRajabLabel => r'ਰਾਜ.'; @override String get shortRamadanLabel => r'ਰਾਮ.'; @override - String get shortSafarLabel => r'ਸਫ.'; + String get shortSafarLabel => r'ਸੇਫ.'; @override String get shortShaabanLabel => r'ਸ਼ਾ.'; @@ -7969,6 +8071,9 @@ class SfLocalizationsPa extends SfGlobalLocalizations { @override String get todayLabel => r'ਅੱਜ'; + + @override + String get weeknumberLabel => r'ਹਫ਼ਤਾ'; } /// The translations for Polish (`pl`). @@ -7990,13 +8095,13 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Harmonogram'; @override - String get allowedViewTimelineDayLabel => r'Dzień na osi czasu'; + String get allowedViewTimelineDayLabel => r'Dzień osi czasu'; @override String get allowedViewTimelineMonthLabel => r'Miesiąc osi czasu'; @override - String get allowedViewTimelineWeekLabel => r'Tydzień osi czasu'; + String get allowedViewTimelineWeekLabel => r'Tydzień na osi czasu'; @override String get allowedViewTimelineWorkWeekLabel => r'Tydzień pracy na osi czasu'; @@ -8011,14 +8116,13 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get daySpanCountLabel => r'Dzień'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get dhualhiLabel => r'Zu al-Hidżdża'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => r'Zu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'przedmiotów'; + String get itemsDataPagerLabel => r'rzeczy'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -8033,19 +8137,19 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Brak wydarzeń'; @override - String get noSelectedDateCalendarLabel => r'Brak wybranej daty'; + String get noSelectedDateCalendarLabel => r'Nie wybrano daty'; @override String get ofDataPagerLabel => r'z'; @override - String get pagesDataPagerLabel => r'stron'; + String get pagesDataPagerLabel => r'strony'; @override String get pdfBookmarksLabel => r'Zakładki'; @override - String get pdfEnterPageNumberLabel => r'Wprowadź numer strony'; + String get pdfEnterPageNumberLabel => r'Wpisz numer strony'; @override String get pdfGoToPageLabel => r'Idź do strony'; @@ -8066,10 +8170,10 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'z'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi' "'" r' al-awwal'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'Rabi’ al-thani'; @override String get rajabLabel => r'Rajab'; @@ -8081,7 +8185,7 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'Szaaban'; @override String get shawwalLabel => r'Shawwal'; @@ -8093,19 +8197,19 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. ja'; + String get shortJumada1Label => r'Skok. i'; @override - String get shortJumada2Label => r'Jum. II'; + String get shortJumada2Label => r'Skok. II'; @override String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. ja'; + String get shortRabi1Label => r'Rabiego. i'; @override - String get shortRabi2Label => r'Rabi. II'; + String get shortRabi2Label => r'Rabiego. II'; @override String get shortRajabLabel => r'Raj.'; @@ -8114,7 +8218,7 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get shortRamadanLabel => r'Baran.'; @override - String get shortSafarLabel => r'Saf.'; + String get shortSafarLabel => r'Bezp.'; @override String get shortShaabanLabel => r'Sha.'; @@ -8123,7 +8227,10 @@ class SfLocalizationsPl extends SfGlobalLocalizations { String get shortShawwalLabel => r'Shaw.'; @override - String get todayLabel => r'Dzisiaj'; + String get todayLabel => r'Dziś'; + + @override + String get weeknumberLabel => r'Tydzień'; } /// The translations for Pushto Pashto (`ps`). @@ -8151,7 +8258,7 @@ class SfLocalizationsPs extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'د مهال ویش میاشت'; @override - String get allowedViewTimelineWeekLabel => r'د مهال ویش'; + String get allowedViewTimelineWeekLabel => r'د مهال ویش اونۍ'; @override String get allowedViewTimelineWorkWeekLabel => r'د مهال ویش کاري اونۍ'; @@ -8160,50 +8267,49 @@ class SfLocalizationsPs extends SfGlobalLocalizations { String get allowedViewWeekLabel => r'اونۍ'; @override - String get allowedViewWorkWeekLabel => r'کاري اونۍ'; + String get allowedViewWorkWeekLabel => r'د کار اونۍ'; @override String get daySpanCountLabel => r'ورځ'; @override - String get dhualhiLabel => r'ذو الحجjah'; + String get dhualhiLabel => r'ذی الحجه'; @override - String get dhualqiLabel => r'ذي القده'; + String get dhualqiLabel => r'ذی القعده'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'توکي'; @override String get jumada1Label => r'جمعه الاول'; @override - String get jumada2Label => r'جمعه الثاني'; + String get jumada2Label => r'جماد al الثاني'; @override String get muharramLabel => r'محرم'; @override - String get noEventsCalendarLabel => r'هیڅ پیښه نده'; + String get noEventsCalendarLabel => r'هیڅ پیښې نشته'; @override - String get noSelectedDateCalendarLabel => r'نه ټاکل شوې نیټه'; + String get noSelectedDateCalendarLabel => r'هیڅ ټاکل شوې نیټه نده'; @override String get ofDataPagerLabel => r'د'; @override - String get pagesDataPagerLabel => r'مخونه'; + String get pagesDataPagerLabel => r'پاې'; @override - String get pdfBookmarksLabel => r'یادنښې'; + String get pdfBookmarksLabel => r'بک مارک'; @override - String get pdfEnterPageNumberLabel => r'د پا numberې شمیره دننه کړئ'; + String get pdfEnterPageNumberLabel => r'د پا pageې شمیره دننه کړئ'; @override - String get pdfGoToPageLabel => r'پا .ې ته لاړ شه'; + String get pdfGoToPageLabel => r'پا pageې ته لاړ شئ'; @override String get pdfInvalidPageNumberLabel => @@ -8213,7 +8319,7 @@ class SfLocalizationsPs extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'هیڅ بک مارک ونه موندل شو'; @override - String get pdfPaginationDialogCancelLabel => r'کینسل'; + String get pdfPaginationDialogCancelLabel => r'لغوه کول'; @override String get pdfPaginationDialogOkLabel => r'سمه ده'; @@ -8234,7 +8340,7 @@ class SfLocalizationsPs extends SfGlobalLocalizations { String get ramadanLabel => r'رمضان'; @override - String get safarLabel => r'صفر'; + String get safarLabel => r'صفار'; @override String get shaabanLabel => r'شعبان'; @@ -8243,7 +8349,7 @@ class SfLocalizationsPs extends SfGlobalLocalizations { String get shawwalLabel => r'شوال'; @override - String get shortDhualhiLabel => r'ذوالح'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override String get shortDhualqiLabel => r'ذوالق'; @@ -8255,7 +8361,7 @@ class SfLocalizationsPs extends SfGlobalLocalizations { String get shortJumada2Label => r'جم. II'; @override - String get shortMuharramLabel => r'م.'; + String get shortMuharramLabel => r'Muh.'; @override String get shortRabi1Label => r'ربي. زه'; @@ -8270,7 +8376,7 @@ class SfLocalizationsPs extends SfGlobalLocalizations { String get shortRamadanLabel => r'رام.'; @override - String get shortSafarLabel => r'صف.'; + String get shortSafarLabel => r'سف.'; @override String get shortShaabanLabel => r'شا.'; @@ -8280,6 +8386,9 @@ class SfLocalizationsPs extends SfGlobalLocalizations { @override String get todayLabel => r'نن'; + + @override + String get weeknumberLabel => r'اونۍ'; } /// The translations for Portuguese (`pt`). @@ -8307,7 +8416,7 @@ class SfLocalizationsPt extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Mês da linha do tempo'; @override - String get allowedViewTimelineWeekLabel => r'Semana do cronograma'; + String get allowedViewTimelineWeekLabel => r'Semana da Linha do Tempo'; @override String get allowedViewTimelineWorkWeekLabel => @@ -8329,7 +8438,6 @@ class SfLocalizationsPt extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'Itens'; @override @@ -8372,7 +8480,7 @@ class SfLocalizationsPt extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'CANCELAR'; @override - String get pdfPaginationDialogOkLabel => r'Está bem'; + String get pdfPaginationDialogOkLabel => r'OK'; @override String get pdfScrollStatusOfLabel => r'do'; @@ -8405,7 +8513,7 @@ class SfLocalizationsPt extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. Eu'; + String get shortJumada1Label => r'Jum. eu'; @override String get shortJumada2Label => r'Jum. II'; @@ -8414,7 +8522,7 @@ class SfLocalizationsPt extends SfGlobalLocalizations { String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. Eu'; + String get shortRabi1Label => r'Rabi. eu'; @override String get shortRabi2Label => r'Rabi. II'; @@ -8436,6 +8544,9 @@ class SfLocalizationsPt extends SfGlobalLocalizations { @override String get todayLabel => r'Hoje'; + + @override + String get weeknumberLabel => r'Semana'; } /// The translations for Portuguese, as used in Portugal (`pt_PT`). @@ -8468,6 +8579,12 @@ class SfLocalizationsPtPt extends SfLocalizationsPt { @override String get pdfPaginationDialogOkLabel => r'Ok'; + @override + String get shortRabi1Label => r'Rabi. Eu'; + + @override + String get shortJumada1Label => r'Jum. Eu'; + @override String get shortRamadanLabel => r'Ram.'; } @@ -8519,7 +8636,6 @@ class SfLocalizationsRo extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'obiecte'; @override @@ -8563,7 +8679,7 @@ class SfLocalizationsRo extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'ANULARE'; @override - String get pdfPaginationDialogOkLabel => r'O.K'; + String get pdfPaginationDialogOkLabel => r'Bine'; @override String get pdfScrollStatusOfLabel => r'de'; @@ -8614,7 +8730,7 @@ class SfLocalizationsRo extends SfGlobalLocalizations { String get shortRajabLabel => r'Raj.'; @override - String get shortRamadanLabel => r'Berbec.'; + String get shortRamadanLabel => r'RAM.'; @override String get shortSafarLabel => r'Saf.'; @@ -8626,7 +8742,10 @@ class SfLocalizationsRo extends SfGlobalLocalizations { String get shortShawwalLabel => r'Shaw.'; @override - String get todayLabel => r'Astăzi'; + String get todayLabel => r'Azi'; + + @override + String get weeknumberLabel => r'Săptămână'; } /// The translations for Russian (`ru`). @@ -8645,7 +8764,7 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Месяц'; @override - String get allowedViewScheduleLabel => r'График'; + String get allowedViewScheduleLabel => r'Расписание'; @override String get allowedViewTimelineDayLabel => r'Хронология День'; @@ -8657,7 +8776,7 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'Хронология недели'; @override - String get allowedViewTimelineWorkWeekLabel => r'График работы неделя'; + String get allowedViewTimelineWorkWeekLabel => r'График работы за неделю'; @override String get allowedViewWeekLabel => r'Неделя'; @@ -8675,7 +8794,6 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get dhualqiLabel => r'Зу аль-Киада'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'Предметы'; @override @@ -8719,7 +8837,7 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'ОТМЕНА'; @override - String get pdfPaginationDialogOkLabel => r'ОК'; + String get pdfPaginationDialogOkLabel => r'Ok'; @override String get pdfScrollStatusOfLabel => r'из'; @@ -8761,16 +8879,16 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get shortMuharramLabel => r'Мух.'; @override - String get shortRabi1Label => r'Лави. я'; + String get shortRabi1Label => r'Раби. я'; @override - String get shortRabi2Label => r'Лави. II'; + String get shortRabi2Label => r'Раби. II'; @override String get shortRajabLabel => r'Радж.'; @override - String get shortRamadanLabel => r'ОЗУ.'; + String get shortRamadanLabel => r'Баран.'; @override String get shortSafarLabel => r'Saf.'; @@ -8782,7 +8900,10 @@ class SfLocalizationsRu extends SfGlobalLocalizations { String get shortShawwalLabel => r'Шоу.'; @override - String get todayLabel => r'Cегодня'; + String get todayLabel => r'Сегодня'; + + @override + String get weeknumberLabel => r'Неделя'; } /// The translations for Sinhala Sinhalese (`si`). @@ -8801,19 +8922,19 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'මාසික'; @override - String get allowedViewScheduleLabel => r'උපලේඛනය'; + String get allowedViewScheduleLabel => r'කාල සටහන'; @override - String get allowedViewTimelineDayLabel => r'කාල නියමය'; + String get allowedViewTimelineDayLabel => r'කාලරාමු දිනය'; @override - String get allowedViewTimelineMonthLabel => r'කාල නියමය'; + String get allowedViewTimelineMonthLabel => r'කාලරාමුව මාසය'; @override - String get allowedViewTimelineWeekLabel => r'කාල නියමය'; + String get allowedViewTimelineWeekLabel => r'කාලරාමුව සතිය'; @override - String get allowedViewTimelineWorkWeekLabel => r'කාල නියමය වැඩ සතිය'; + String get allowedViewTimelineWorkWeekLabel => r'කාලරේඛා වැඩ සතිය'; @override String get allowedViewWeekLabel => r'සතිය'; @@ -8822,29 +8943,28 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'වැඩ සතිය'; @override - String get daySpanCountLabel => r'දින'; + String get daySpanCountLabel => r'දිනය'; @override - String get dhualhiLabel => r'ධු අල් හිජ්ජා'; + String get dhualhiLabel => r'ධූ අල්-හිජ්ජා'; @override - String get dhualqiLabel => r'ධු අල්-කයිඩා'; + String get dhualqiLabel => r'ධූ අල් කයිඩා'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'අයිතම'; @override - String get jumada1Label => r'ජුමාඩා අල් අව්වාල්'; + String get jumada1Label => r'ජුමාඩා අල්-අව්වාල්'; @override String get jumada2Label => r'ජුමාඩා අල් තානි'; @override - String get muharramLabel => r'මුහාරම්'; + String get muharramLabel => r'මුහර්රාම්'; @override - String get noEventsCalendarLabel => r'සිදුවීම් නොමැත'; + String get noEventsCalendarLabel => r'සිදුවීම් නැත'; @override String get noSelectedDateCalendarLabel => r'තෝරාගත් දිනයක් නොමැත'; @@ -8865,13 +8985,13 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'පිටුවට යන්න'; @override - String get pdfInvalidPageNumberLabel => r'කරුණාකර වලංගු අංකයක් ඇතුළත් කරන්න'; + String get pdfInvalidPageNumberLabel => r'වලංගු අංකයක් ඇතුළත් කරන්න'; @override String get pdfNoBookmarksLabel => r'පිටු සලකුණු හමු නොවීය'; @override - String get pdfPaginationDialogCancelLabel => r'අවලංගු කරන්න'; + String get pdfPaginationDialogCancelLabel => r'කැන්සල්'; @override String get pdfPaginationDialogOkLabel => r'හරි'; @@ -8880,13 +9000,13 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'වල'; @override - String get rabi1Label => r'රබී අල් අව්වාල්'; + String get rabi1Label => r'රබී අල්-අව්වාල්'; @override - String get rabi2Label => r'රබී අල් තානි'; + String get rabi2Label => r'රබී අල්-තානි'; @override - String get rajabLabel => r'රාජාබ්'; + String get rajabLabel => r'රජබ්'; @override String get ramadanLabel => r'රාමදාන්'; @@ -8904,7 +9024,7 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get shortDhualhiLabel => r'දුල්-එච්'; @override - String get shortDhualqiLabel => r'දුල්-කියු'; + String get shortDhualqiLabel => r'දුල්- Q'; @override String get shortJumada1Label => r'ජුම්. මම'; @@ -8913,7 +9033,7 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get shortJumada2Label => r'ජුම්. II'; @override - String get shortMuharramLabel => r'මුහ්.'; + String get shortMuharramLabel => r'ම්හ්.'; @override String get shortRabi1Label => r'රබී. මම'; @@ -8922,7 +9042,7 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get shortRabi2Label => r'රබී. II'; @override - String get shortRajabLabel => r'රාජ්.'; + String get shortRajabLabel => r'රාජ්'; @override String get shortRamadanLabel => r'RAM.'; @@ -8931,13 +9051,16 @@ class SfLocalizationsSi extends SfGlobalLocalizations { String get shortSafarLabel => r'සේෆ්.'; @override - String get shortShaabanLabel => r'ෂා.'; + String get shortShaabanLabel => r'ශා.'; @override String get shortShawwalLabel => r'ෂෝ.'; @override String get todayLabel => r'අද'; + + @override + String get weeknumberLabel => r'සතිය'; } /// The translations for Slovak (`sk`). @@ -8956,13 +9079,13 @@ class SfLocalizationsSk extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Mesiac'; @override - String get allowedViewScheduleLabel => r'Časový plán'; + String get allowedViewScheduleLabel => r'Rozvrh'; @override String get allowedViewTimelineDayLabel => r'Deň časovej osi'; @override - String get allowedViewTimelineMonthLabel => r'Časová os Mesiac'; + String get allowedViewTimelineMonthLabel => r'Mesiac časovej osi'; @override String get allowedViewTimelineWeekLabel => r'Týždeň časovej osi'; @@ -8987,7 +9110,6 @@ class SfLocalizationsSk extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'položky'; @override @@ -9003,7 +9125,7 @@ class SfLocalizationsSk extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Žiadne udalosti'; @override - String get noSelectedDateCalendarLabel => r'Nie je vybratý dátum'; + String get noSelectedDateCalendarLabel => r'Žiadny vybratý dátum'; @override String get ofDataPagerLabel => r'z'; @@ -9030,7 +9152,7 @@ class SfLocalizationsSk extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'ZRUŠIŤ'; @override - String get pdfPaginationDialogOkLabel => r'Ok'; + String get pdfPaginationDialogOkLabel => r'OK'; @override String get pdfScrollStatusOfLabel => r'z'; @@ -9048,7 +9170,7 @@ class SfLocalizationsSk extends SfGlobalLocalizations { String get ramadanLabel => r'Ramadán'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'Šafár'; @override String get shaabanLabel => r'Sha' "'" r'aban'; @@ -9094,6 +9216,9 @@ class SfLocalizationsSk extends SfGlobalLocalizations { @override String get todayLabel => r'Dnes'; + + @override + String get weeknumberLabel => r'Týždeň'; } /// The translations for Slovenian (`sl`). @@ -9112,19 +9237,19 @@ class SfLocalizationsSl extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Mesec'; @override - String get allowedViewScheduleLabel => r'Razpored'; + String get allowedViewScheduleLabel => r'Urnik'; @override - String get allowedViewTimelineDayLabel => r'Dan časovne osi'; + String get allowedViewTimelineDayLabel => r'Dan časovnice'; @override - String get allowedViewTimelineMonthLabel => r'Mesec časovne osi'; + String get allowedViewTimelineMonthLabel => r'Mesec časovnice'; @override - String get allowedViewTimelineWeekLabel => r'Teden časovne osi'; + String get allowedViewTimelineWeekLabel => r'Teden časovnice'; @override - String get allowedViewTimelineWorkWeekLabel => r'Časovni delovni teden'; + String get allowedViewTimelineWorkWeekLabel => r'Delovni teden časovnice'; @override String get allowedViewWeekLabel => r'Teden'; @@ -9142,7 +9267,6 @@ class SfLocalizationsSl extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'predmetov'; @override @@ -9179,10 +9303,10 @@ class SfLocalizationsSl extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'Vnesite veljavno številko'; @override - String get pdfNoBookmarksLabel => r'Zaznamkov ni bilo mogoče najti'; + String get pdfNoBookmarksLabel => r'Ni zaznamkov'; @override - String get pdfPaginationDialogCancelLabel => r'ODPOVED'; + String get pdfPaginationDialogCancelLabel => r'PREKLICI'; @override String get pdfPaginationDialogOkLabel => r'v redu'; @@ -9200,7 +9324,7 @@ class SfLocalizationsSl extends SfGlobalLocalizations { String get rajabLabel => r'Rajab'; @override - String get ramadanLabel => r'Ramazan'; + String get ramadanLabel => r'Ramadan'; @override String get safarLabel => r'Safar'; @@ -9249,6 +9373,9 @@ class SfLocalizationsSl extends SfGlobalLocalizations { @override String get todayLabel => r'Danes'; + + @override + String get weeknumberLabel => r'Teden'; } /// The translations for Albanian (`sq`). @@ -9270,17 +9397,16 @@ class SfLocalizationsSq extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Programi'; @override - String get allowedViewTimelineDayLabel => r'Dita e kronologjisë'; + String get allowedViewTimelineDayLabel => r'Dita e Kronologjisë'; @override - String get allowedViewTimelineMonthLabel => r'Muaji i kronologjisë'; + String get allowedViewTimelineMonthLabel => r'Muaji i Kronologjisë'; @override - String get allowedViewTimelineWeekLabel => r'Java e kronologjisë'; + String get allowedViewTimelineWeekLabel => r'Java e Kronologjisë'; @override - String get allowedViewTimelineWorkWeekLabel => - r'Java e punës me kronologjinë'; + String get allowedViewTimelineWorkWeekLabel => r'Afati kohor Java e Punës'; @override String get allowedViewWeekLabel => r'Javë'; @@ -9292,20 +9418,19 @@ class SfLocalizationsSq extends SfGlobalLocalizations { String get daySpanCountLabel => r'Dita'; @override - String get dhualhiLabel => r'Dhu al-Hixhah'; + String get dhualhiLabel => r'Dhul-Hixhxhe'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'sende'; + String get itemsDataPagerLabel => r'artikuj'; @override - String get jumada1Label => r'Xhumada el-auval'; + String get jumada1Label => r'Xhumada el-evual'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'Xhumada al-thani'; @override String get muharramLabel => r'Muharrem'; @@ -9314,19 +9439,19 @@ class SfLocalizationsSq extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Asnjë ngjarje'; @override - String get noSelectedDateCalendarLabel => r'Nuk ka datë të zgjedhur'; + String get noSelectedDateCalendarLabel => r'Asnjë datë e zgjedhur'; @override - String get ofDataPagerLabel => r'e'; + String get ofDataPagerLabel => r'të'; @override - String get pagesDataPagerLabel => r'faqet'; + String get pagesDataPagerLabel => r'faqe'; @override String get pdfBookmarksLabel => r'Faqeshënuesit'; @override - String get pdfEnterPageNumberLabel => r'Vendosni numrin e faqes'; + String get pdfEnterPageNumberLabel => r'Fut numrin e faqes'; @override String get pdfGoToPageLabel => r'Shko te faqja'; @@ -9339,22 +9464,22 @@ class SfLocalizationsSq extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Nuk u gjetën faqeshënues'; @override - String get pdfPaginationDialogCancelLabel => r'ANULOJ'; + String get pdfPaginationDialogCancelLabel => r'ANULO'; @override String get pdfPaginationDialogOkLabel => r'Ne rregull'; @override - String get pdfScrollStatusOfLabel => r'e'; + String get pdfScrollStatusOfLabel => r'të'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'Rabi' "'" r'el-evvel'; @override String get rabi2Label => r'Rabi ' "'" r'al-thani'; @override - String get rajabLabel => r'Rajab'; + String get rajabLabel => r'Rexheb'; @override String get ramadanLabel => r'Ramazani'; @@ -9366,13 +9491,13 @@ class SfLocalizationsSq extends SfGlobalLocalizations { String get shaabanLabel => r'Sha' "'" r'aban'; @override - String get shawwalLabel => r'Shaval'; + String get shawwalLabel => r'Sheval'; @override String get shortDhualhiLabel => r'Dhul-H'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'Dhul-Q'; @override String get shortJumada1Label => r'Jum Une'; @@ -9381,7 +9506,7 @@ class SfLocalizationsSq extends SfGlobalLocalizations { String get shortJumada2Label => r'Jum II'; @override - String get shortMuharramLabel => r'Muh'; + String get shortMuharramLabel => r'Muh.'; @override String get shortRabi1Label => r'Rabi. Une'; @@ -9390,22 +9515,25 @@ class SfLocalizationsSq extends SfGlobalLocalizations { String get shortRabi2Label => r'Rabi. II'; @override - String get shortRajabLabel => r'Raj'; + String get shortRajabLabel => r'Raj.'; @override - String get shortRamadanLabel => r'Ram'; + String get shortRamadanLabel => r'Ramë.'; @override - String get shortSafarLabel => r'Saf.'; + String get shortSafarLabel => r'Saf'; @override - String get shortShaabanLabel => r'Sha'; + String get shortShaabanLabel => r'Sha.'; @override - String get shortShawwalLabel => r'Shaw'; + String get shortShawwalLabel => r'Shaw.'; @override String get todayLabel => r'Sot'; + + @override + String get weeknumberLabel => r'Javë'; } /// The translations for Serbian (`sr`). @@ -9427,17 +9555,17 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Распоред'; @override - String get allowedViewTimelineDayLabel => r'Дан временске оријентације'; + String get allowedViewTimelineDayLabel => r'Дан временске линије'; @override - String get allowedViewTimelineMonthLabel => r'Месец хронолошког следа'; + String get allowedViewTimelineMonthLabel => r'Месец временске линије'; @override - String get allowedViewTimelineWeekLabel => r'Недеља временске оријентације'; + String get allowedViewTimelineWeekLabel => r'Седмица временске линије'; @override String get allowedViewTimelineWorkWeekLabel => - r'Радна недеља временског следа'; + r'Радна недеља временске линије'; @override String get allowedViewWeekLabel => r'Недеља'; @@ -9449,20 +9577,19 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get daySpanCountLabel => r'Дан'; @override - String get dhualhiLabel => r'Дху ал-Хиџа'; + String get dhualhiLabel => r'Дху ал-Хијјах'; @override String get dhualqiLabel => r'Дху ал-Ки' "'" r'дах'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'предмета'; + String get itemsDataPagerLabel => r'ставке'; @override - String get jumada1Label => r'Јумада ал-аввал'; + String get jumada1Label => r'Џумада ал-аввал'; @override - String get jumada2Label => r'Јумада ал-тхани'; + String get jumada2Label => r'Џумада ал-тани'; @override String get muharramLabel => r'Мухаррам'; @@ -9474,7 +9601,7 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get noSelectedDateCalendarLabel => r'Нема изабраног датума'; @override - String get ofDataPagerLabel => r'од'; + String get ofDataPagerLabel => r'оф'; @override String get pagesDataPagerLabel => r'странице'; @@ -9501,7 +9628,7 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get pdfPaginationDialogOkLabel => r'У реду'; @override - String get pdfScrollStatusOfLabel => r'од'; + String get pdfScrollStatusOfLabel => r'оф'; @override String get rabi1Label => r'Раби ' "'" r'ал-аввал'; @@ -9531,7 +9658,7 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Дху' "'" r'л-К'; @override - String get shortJumada1Label => r'Јум. Ја'; + String get shortJumada1Label => r'Јум. И'; @override String get shortJumada2Label => r'Јум. ИИ'; @@ -9540,7 +9667,7 @@ class SfLocalizationsSr extends SfGlobalLocalizations { String get shortMuharramLabel => r'Мух.'; @override - String get shortRabi1Label => r'Раби. Ја'; + String get shortRabi1Label => r'Раби. И'; @override String get shortRabi2Label => r'Раби. ИИ'; @@ -9562,6 +9689,9 @@ class SfLocalizationsSr extends SfGlobalLocalizations { @override String get todayLabel => r'Данас'; + + @override + String get weeknumberLabel => r'Недеља'; } /// The translations for Serbian, using the Cyrillic script (`sr_Cyrl`). @@ -9602,7 +9732,7 @@ class SfLocalizationsSv extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'Tidslinje vecka'; @override - String get allowedViewTimelineWorkWeekLabel => r'Tidslinje arbetsvecka'; + String get allowedViewTimelineWorkWeekLabel => r'Tidslinje Arbetsvecka'; @override String get allowedViewWeekLabel => r'Vecka'; @@ -9620,8 +9750,7 @@ class SfLocalizationsSv extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'föremål'; + String get itemsDataPagerLabel => r'objekt'; @override String get jumada1Label => r'Jumada al-awwal'; @@ -9633,7 +9762,7 @@ class SfLocalizationsSv extends SfGlobalLocalizations { String get muharramLabel => r'Muharram'; @override - String get noEventsCalendarLabel => r'Inga händelser'; + String get noEventsCalendarLabel => r'Inga evenemang'; @override String get noSelectedDateCalendarLabel => r'Inget valt datum'; @@ -9661,7 +9790,7 @@ class SfLocalizationsSv extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Inga bokmärken hittades'; @override - String get pdfPaginationDialogCancelLabel => r'ANNULLERA'; + String get pdfPaginationDialogCancelLabel => r'AVBRYT'; @override String get pdfPaginationDialogOkLabel => r'OK'; @@ -9697,7 +9826,7 @@ class SfLocalizationsSv extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. Jag'; + String get shortJumada1Label => r'Jum. I'; @override String get shortJumada2Label => r'Jum. II'; @@ -9706,7 +9835,7 @@ class SfLocalizationsSv extends SfGlobalLocalizations { String get shortMuharramLabel => r'Muh.'; @override - String get shortRabi1Label => r'Rabi. Jag'; + String get shortRabi1Label => r'Rabi. I'; @override String get shortRabi2Label => r'Rabi. II'; @@ -9728,6 +9857,9 @@ class SfLocalizationsSv extends SfGlobalLocalizations { @override String get todayLabel => r'I dag'; + + @override + String get weeknumberLabel => r'Vecka'; } /// The translations for Swahili (`sw`). @@ -9776,7 +9908,6 @@ class SfLocalizationsSw extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'vitu'; @override @@ -9816,7 +9947,7 @@ class SfLocalizationsSw extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Hakuna alamisho zilizopatikana'; @override - String get pdfPaginationDialogCancelLabel => r'FUTA'; + String get pdfPaginationDialogCancelLabel => r'GHAFU'; @override String get pdfPaginationDialogOkLabel => r'sawa'; @@ -9883,6 +10014,9 @@ class SfLocalizationsSw extends SfGlobalLocalizations { @override String get todayLabel => r'Leo'; + + @override + String get weeknumberLabel => r'Wiki'; } /// The translations for Tamil (`ta`). @@ -9904,10 +10038,10 @@ class SfLocalizationsTa extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'அட்டவணை'; @override - String get allowedViewTimelineDayLabel => r'காலக்கெடு நாள்'; + String get allowedViewTimelineDayLabel => r'காலவரிசை நாள்'; @override - String get allowedViewTimelineMonthLabel => r'காலக்கெடு மாதம்'; + String get allowedViewTimelineMonthLabel => r'காலவரிசை மாதம்'; @override String get allowedViewTimelineWeekLabel => r'காலவரிசை வாரம்'; @@ -9925,20 +10059,19 @@ class SfLocalizationsTa extends SfGlobalLocalizations { String get daySpanCountLabel => r'நாள்'; @override - String get dhualhiLabel => r'து அல்-ஹிஜ்ஜா'; + String get dhualhiLabel => r'தூ அல்-ஹிஜ்ஜா'; @override - String get dhualqiLabel => r'து அல்-கிதா'; + String get dhualqiLabel => r'து அல்-குய்தா'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'பொருட்களை'; @override String get jumada1Label => r'ஜுமதா அல்-அவ்வால்'; @override - String get jumada2Label => r'ஜுமதா அல்-தானி'; + String get jumada2Label => r'ஜுமடா அல்-தானி'; @override String get muharramLabel => r'முஹர்ரம்'; @@ -9950,7 +10083,7 @@ class SfLocalizationsTa extends SfGlobalLocalizations { String get noSelectedDateCalendarLabel => r'தேர்ந்தெடுக்கப்பட்ட தேதி இல்லை'; @override - String get ofDataPagerLabel => r'of'; + String get ofDataPagerLabel => r'இன்'; @override String get pagesDataPagerLabel => r'பக்கங்கள்'; @@ -9968,25 +10101,25 @@ class SfLocalizationsTa extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'சரியான எண்ணை உள்ளிடவும்'; @override - String get pdfNoBookmarksLabel => r'புக்மார்க்குகள் எதுவும் கிடைக்கவில்லை'; + String get pdfNoBookmarksLabel => r'புக்மார்க்குகள் இல்லை'; @override - String get pdfPaginationDialogCancelLabel => r'ரத்துசெய்'; + String get pdfPaginationDialogCancelLabel => r'கேன்சல்'; @override String get pdfPaginationDialogOkLabel => r'சரி'; @override - String get pdfScrollStatusOfLabel => r'of'; + String get pdfScrollStatusOfLabel => r'இன்'; @override - String get rabi1Label => r'ரபி ' "'" r'அல்-அவ்வால்'; + String get rabi1Label => r'ரபி அல்-அவ்வல்'; @override - String get rabi2Label => r'ரபி ' "'" r'அல்-தானி'; + String get rabi2Label => r'ரபி அல்-தானி'; @override - String get rajabLabel => r'ராஜாப்'; + String get rajabLabel => r'ரஜப்'; @override String get ramadanLabel => r'ரமலான்'; @@ -9995,7 +10128,7 @@ class SfLocalizationsTa extends SfGlobalLocalizations { String get safarLabel => r'சஃபர்'; @override - String get shaabanLabel => r'ஷாஅபன்'; + String get shaabanLabel => r'ஷாபான்'; @override String get shawwalLabel => r'ஷவ்வால்'; @@ -10013,31 +10146,34 @@ class SfLocalizationsTa extends SfGlobalLocalizations { String get shortJumada2Label => r'ஜம். II'; @override - String get shortMuharramLabel => r'மு.'; + String get shortMuharramLabel => r'முஹ்.'; @override - String get shortRabi1Label => r'ரபி. நான்'; + String get shortRabi1Label => r'ரபி நான்'; @override - String get shortRabi2Label => r'ரபி. II'; + String get shortRabi2Label => r'ரபி II'; @override - String get shortRajabLabel => r'ராஜ்.'; + String get shortRajabLabel => r'ராஜ்'; @override String get shortRamadanLabel => r'ரேம்.'; @override - String get shortSafarLabel => r'பாதுகாப்பான.'; + String get shortSafarLabel => r'சேஃப்.'; @override - String get shortShaabanLabel => r'ஷா.'; + String get shortShaabanLabel => r'ஷா'; @override String get shortShawwalLabel => r'ஷா.'; @override String get todayLabel => r'இன்று'; + + @override + String get weeknumberLabel => r'வாரம்'; } /// The translations for Telugu (`te`). @@ -10068,7 +10204,7 @@ class SfLocalizationsTe extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'కాలక్రమం వారం'; @override - String get allowedViewTimelineWorkWeekLabel => r'కాలక్రమం పని వారం'; + String get allowedViewTimelineWorkWeekLabel => r'టైమ్‌లైన్ పని వారం'; @override String get allowedViewWeekLabel => r'వారం'; @@ -10086,20 +10222,19 @@ class SfLocalizationsTe extends SfGlobalLocalizations { String get dhualqiLabel => r'ధు అల్-ఖిదా'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'అంశాలు'; + String get itemsDataPagerLabel => r'వస్తువులు'; @override - String get jumada1Label => r'జుమాడా అల్-అవ్వాల్'; + String get jumada1Label => r'జుమాదా అల్ అవ్వాల్'; @override - String get jumada2Label => r'జుమాడా అల్-తాని'; + String get jumada2Label => r'జుమాదా అల్-థాని'; @override - String get muharramLabel => r'మొహర్రం'; + String get muharramLabel => r'ముహర్రం'; @override - String get noEventsCalendarLabel => r'సంఘటనలు లేవు'; + String get noEventsCalendarLabel => r'ఈవెంట్‌లు లేవు'; @override String get noSelectedDateCalendarLabel => r'ఎంచుకున్న తేదీ లేదు'; @@ -10121,7 +10256,7 @@ class SfLocalizationsTe extends SfGlobalLocalizations { @override String get pdfInvalidPageNumberLabel => - r'దయచేసి చెల్లుబాటు అయ్యే సంఖ్యను నమోదు చేయండి'; + r'దయచేసి చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి'; @override String get pdfNoBookmarksLabel => r'బుక్‌మార్క్‌లు కనుగొనబడలేదు'; @@ -10139,10 +10274,10 @@ class SfLocalizationsTe extends SfGlobalLocalizations { String get rabi1Label => r'రబీ అల్-అవ్వాల్'; @override - String get rabi2Label => r'రబీ అల్-తాని'; + String get rabi2Label => r'రబీ ' "'" r'అల్-థాని'; @override - String get rajabLabel => r'రాజాబ్'; + String get rajabLabel => r'రజబ్'; @override String get ramadanLabel => r'రంజాన్'; @@ -10154,19 +10289,19 @@ class SfLocalizationsTe extends SfGlobalLocalizations { String get shaabanLabel => r'షాబాన్'; @override - String get shawwalLabel => r'షావ్వాల్'; + String get shawwalLabel => r'షవ్వాల్'; @override String get shortDhualhiLabel => r'ధుల్-హెచ్'; @override - String get shortDhualqiLabel => r'ధుల్-క్యూ'; + String get shortDhualqiLabel => r'ధూల్-ప్ర'; @override - String get shortJumada1Label => r'జం. నేను'; + String get shortJumada1Label => r'జమ్. నేను'; @override - String get shortJumada2Label => r'జం. II'; + String get shortJumada2Label => r'జమ్. II'; @override String get shortMuharramLabel => r'ముహ్.'; @@ -10178,22 +10313,25 @@ class SfLocalizationsTe extends SfGlobalLocalizations { String get shortRabi2Label => r'రబీ. II'; @override - String get shortRajabLabel => r'రాజ్.'; + String get shortRajabLabel => r'రాజ్'; @override - String get shortRamadanLabel => r'రామ్.'; + String get shortRamadanLabel => r'రామ్'; @override String get shortSafarLabel => r'సేఫ్.'; @override - String get shortShaabanLabel => r'షా.'; + String get shortShaabanLabel => r'షా'; @override String get shortShawwalLabel => r'షా.'; @override - String get todayLabel => r'ఈ రోజు'; + String get todayLabel => r'నేడు'; + + @override + String get weeknumberLabel => r'వారం'; } /// The translations for Thai (`th`). @@ -10215,7 +10353,7 @@ class SfLocalizationsTh extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'กำหนดการ'; @override - String get allowedViewTimelineDayLabel => r'ไทม์ไลน์วัน'; + String get allowedViewTimelineDayLabel => r'วันไทม์ไลน์'; @override String get allowedViewTimelineMonthLabel => r'ไทม์ไลน์เดือน'; @@ -10224,7 +10362,7 @@ class SfLocalizationsTh extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'ไทม์ไลน์สัปดาห์'; @override - String get allowedViewTimelineWorkWeekLabel => r'Timeline Work Week'; + String get allowedViewTimelineWorkWeekLabel => r'ไทม์ไลน์งานสัปดาห์'; @override String get allowedViewWeekLabel => r'สัปดาห์'; @@ -10236,29 +10374,28 @@ class SfLocalizationsTh extends SfGlobalLocalizations { String get daySpanCountLabel => r'วัน'; @override - String get dhualhiLabel => r'Dhu al-Hijjah'; + String get dhualhiLabel => r'ดูอัลฮิจญะฮ์'; @override String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'รายการ'; @override - String get jumada1Label => r'Jumada al-awwal'; + String get jumada1Label => r'ญุมาดา อัลเอาวัล'; @override String get jumada2Label => r'Jumada al-thani'; @override - String get muharramLabel => r'มูฮาร์ราม'; + String get muharramLabel => r'มูฮัรรอม'; @override - String get noEventsCalendarLabel => r'ไม่มีเหตุการณ์'; + String get noEventsCalendarLabel => r'ไม่มีกิจกรรม'; @override - String get noSelectedDateCalendarLabel => r'ไม่มีวันที่เลือก'; + String get noSelectedDateCalendarLabel => r'ไม่ได้เลือกวันที่'; @override String get ofDataPagerLabel => r'ของ'; @@ -10267,19 +10404,19 @@ class SfLocalizationsTh extends SfGlobalLocalizations { String get pagesDataPagerLabel => r'หน้า'; @override - String get pdfBookmarksLabel => r'บุ๊กมาร์ก'; + String get pdfBookmarksLabel => r'ที่คั่นหนังสือ'; @override - String get pdfEnterPageNumberLabel => r'ป้อนหมายเลขหน้า'; + String get pdfEnterPageNumberLabel => r'ใส่เลขหน้า'; @override String get pdfGoToPageLabel => r'ไปที่หน้า'; @override - String get pdfInvalidPageNumberLabel => r'กรุณากรอกหมายเลขที่ถูกต้อง'; + String get pdfInvalidPageNumberLabel => r'โปรดป้อนหมายเลขที่ถูกต้อง'; @override - String get pdfNoBookmarksLabel => r'ไม่พบบุ๊กมาร์ก'; + String get pdfNoBookmarksLabel => r'ไม่พบบุ๊คมาร์ค'; @override String get pdfPaginationDialogCancelLabel => r'ยกเลิก'; @@ -10291,46 +10428,46 @@ class SfLocalizationsTh extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'ของ'; @override - String get rabi1Label => r'Rabi ' "'" r'al-awwal'; + String get rabi1Label => r'เราะบี อัลเอาวัล'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'รอบีอัล-ธานี'; @override - String get rajabLabel => r'จ'; + String get rajabLabel => r'ราชภัฏ'; @override - String get ramadanLabel => r'เดือนรอมฎอน'; + String get ramadanLabel => r'รอมฎอน'; @override - String get safarLabel => r'Safar'; + String get safarLabel => r'ซาฟาร์'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'ชะอฺบาน'; @override - String get shawwalLabel => r'Shawwal'; + String get shawwalLabel => r'เชาวาล'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'ดุลฮัก'; @override - String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; + String get shortDhualqiLabel => r'ดุล-คิว'; @override - String get shortJumada1Label => r'จุ๋ม. ผม'; + String get shortJumada1Label => r'จัม. ผม'; @override - String get shortJumada2Label => r'จุ๋ม. II'; + String get shortJumada2Label => r'จัม. II'; @override - String get shortMuharramLabel => r'Muh.'; + String get shortMuharramLabel => r'มุ้ย.'; @override - String get shortRabi1Label => r'ราบี. ผม'; + String get shortRabi1Label => r'รบี. ผม'; @override - String get shortRabi2Label => r'ราบี. II'; + String get shortRabi2Label => r'รบี. II'; @override String get shortRajabLabel => r'ราช.'; @@ -10339,7 +10476,7 @@ class SfLocalizationsTh extends SfGlobalLocalizations { String get shortRamadanLabel => r'แกะ.'; @override - String get shortSafarLabel => r'ปลอดภัย.'; + String get shortSafarLabel => r'เซฟ.'; @override String get shortShaabanLabel => r'ชา.'; @@ -10349,6 +10486,9 @@ class SfLocalizationsTh extends SfGlobalLocalizations { @override String get todayLabel => r'วันนี้'; + + @override + String get weeknumberLabel => r'สัปดาห์'; } /// The translations for Tagalog (`tl`). @@ -10398,7 +10538,6 @@ class SfLocalizationsTl extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'mga item'; @override @@ -10506,6 +10645,9 @@ class SfLocalizationsTl extends SfGlobalLocalizations { @override String get todayLabel => r'Ngayon'; + + @override + String get weeknumberLabel => r'Linggo'; } /// The translations for Turkish (`tr`). @@ -10524,7 +10666,7 @@ class SfLocalizationsTr extends SfGlobalLocalizations { String get allowedViewMonthLabel => r'Ay'; @override - String get allowedViewScheduleLabel => r'Program'; + String get allowedViewScheduleLabel => r'Takvim'; @override String get allowedViewTimelineDayLabel => r'Zaman Çizelgesi Günü'; @@ -10552,14 +10694,13 @@ class SfLocalizationsTr extends SfGlobalLocalizations { String get dhualhiLabel => r'Zilhicce'; @override - String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; + String get dhualqiLabel => r'Zil Kaide'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'öğeler'; @override - String get jumada1Label => r'Jumada al-evvel'; + String get jumada1Label => r'Cumade el-evvel'; @override String get jumada2Label => r'Jumada al-thani'; @@ -10571,13 +10712,13 @@ class SfLocalizationsTr extends SfGlobalLocalizations { String get noEventsCalendarLabel => r'Olay yok'; @override - String get noSelectedDateCalendarLabel => r'Tarih seçilmedi'; + String get noSelectedDateCalendarLabel => r'Seçili tarih yok'; @override - String get ofDataPagerLabel => r'nın-nin'; + String get ofDataPagerLabel => r'ile ilgili'; @override - String get pagesDataPagerLabel => r'sayfaları'; + String get pagesDataPagerLabel => r'sayfalar'; @override String get pdfBookmarksLabel => r'Yer imleri'; @@ -10598,19 +10739,19 @@ class SfLocalizationsTr extends SfGlobalLocalizations { String get pdfPaginationDialogCancelLabel => r'İPTAL ETMEK'; @override - String get pdfPaginationDialogOkLabel => r'tamam'; + String get pdfPaginationDialogOkLabel => r'Tamam'; @override - String get pdfScrollStatusOfLabel => r'nın-nin'; + String get pdfScrollStatusOfLabel => r'ile ilgili'; @override String get rabi1Label => r'Rebiülevvel'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'Rabi' "'" r' al-thani'; @override - String get rajabLabel => r'Receb'; + String get rajabLabel => r'Recep'; @override String get ramadanLabel => r'Ramazan'; @@ -10619,13 +10760,13 @@ class SfLocalizationsTr extends SfGlobalLocalizations { String get safarLabel => r'Safar'; @override - String get shaabanLabel => r'Sha' "'" r'aban'; + String get shaabanLabel => r'Şaban'; @override String get shawwalLabel => r'Şevval'; @override - String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; + String get shortDhualhiLabel => r'Zül-H'; @override String get shortDhualqiLabel => r'Zil-Q'; @@ -10637,7 +10778,7 @@ class SfLocalizationsTr extends SfGlobalLocalizations { String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Muh.'; + String get shortMuharramLabel => r'Müh.'; @override String get shortRabi1Label => r'Rabi. ben'; @@ -10662,6 +10803,9 @@ class SfLocalizationsTr extends SfGlobalLocalizations { @override String get todayLabel => r'Bugün'; + + @override + String get weeknumberLabel => r'Hafta'; } /// The translations for Ukrainian (`uk`). @@ -10683,16 +10827,16 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get allowedViewScheduleLabel => r'Розклад'; @override - String get allowedViewTimelineDayLabel => r'День часової шкали'; + String get allowedViewTimelineDayLabel => r'День хронології'; @override - String get allowedViewTimelineMonthLabel => r'Місяць часової шкали'; + String get allowedViewTimelineMonthLabel => r'Місяць хронології'; @override - String get allowedViewTimelineWeekLabel => r'Тиждень шкали часу'; + String get allowedViewTimelineWeekLabel => r'Тиждень хронології'; @override - String get allowedViewTimelineWorkWeekLabel => r'Хронологія робочого тижня'; + String get allowedViewTimelineWorkWeekLabel => r'Тиждень робочого тижня'; @override String get allowedViewWeekLabel => r'Тиждень'; @@ -10707,17 +10851,16 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get dhualhiLabel => r'Дху аль-Хіджа'; @override - String get dhualqiLabel => r'Дху аль-Кіда'; + String get dhualqiLabel => r'Ду аль-Кіда'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'предметів'; @override String get jumada1Label => r'Джумада аль-авваль'; @override - String get jumada2Label => r'Джумада аль-Тані'; + String get jumada2Label => r'Джумада аль-тані'; @override String get muharramLabel => r'Мухаррам'; @@ -10732,7 +10875,7 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get ofDataPagerLabel => r'з'; @override - String get pagesDataPagerLabel => r'сторінки'; + String get pagesDataPagerLabel => r'сторінок'; @override String get pdfBookmarksLabel => r'Закладки'; @@ -10747,7 +10890,7 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get pdfInvalidPageNumberLabel => r'Введіть дійсний номер'; @override - String get pdfNoBookmarksLabel => r'Закладки не знайдено'; + String get pdfNoBookmarksLabel => r'Закладок не знайдено'; @override String get pdfPaginationDialogCancelLabel => r'СКАСУВАТИ'; @@ -10759,10 +10902,10 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'з'; @override - String get rabi1Label => r'Рабі аль-Ауваль'; + String get rabi1Label => r'Рабі аль-аввал'; @override - String get rabi2Label => r'Рабі аль-Тані'; + String get rabi2Label => r'Рабі аль-тані'; @override String get rajabLabel => r'Раджаб'; @@ -10780,10 +10923,10 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get shawwalLabel => r'Шавваль'; @override - String get shortDhualhiLabel => r'Зул-Н'; + String get shortDhualhiLabel => r'Dhu' "'" r'l-H'; @override - String get shortDhualqiLabel => r'Зул-Q'; + String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override String get shortJumada1Label => r'Jum. Я'; @@ -10792,7 +10935,7 @@ class SfLocalizationsUk extends SfGlobalLocalizations { String get shortJumada2Label => r'Jum. II'; @override - String get shortMuharramLabel => r'Мм'; + String get shortMuharramLabel => r'Ага.'; @override String get shortRabi1Label => r'Рабі. Я'; @@ -10817,6 +10960,9 @@ class SfLocalizationsUk extends SfGlobalLocalizations { @override String get todayLabel => r'Сьогодні'; + + @override + String get weeknumberLabel => r'Тиждень'; } /// The translations for Urdu (`ur`). @@ -10829,25 +10975,25 @@ class SfLocalizationsUr extends SfGlobalLocalizations { ); @override - String get allowedViewDayLabel => r'دن'; + String get allowedViewDayLabel => r'دن۔'; @override String get allowedViewMonthLabel => r'مہینہ'; @override - String get allowedViewScheduleLabel => r'نظام الاوقات'; + String get allowedViewScheduleLabel => r'شیڈول'; @override - String get allowedViewTimelineDayLabel => r'ٹائم لائن ڈے'; + String get allowedViewTimelineDayLabel => r'ٹائم لائن ڈے۔'; @override - String get allowedViewTimelineMonthLabel => r'ٹائم لائن مہینہ'; + String get allowedViewTimelineMonthLabel => r'ٹائم لائن مہینہ۔'; @override - String get allowedViewTimelineWeekLabel => r'ٹائم لائن ہفتہ'; + String get allowedViewTimelineWeekLabel => r'ٹائم لائن ہفتہ۔'; @override - String get allowedViewTimelineWorkWeekLabel => r'ٹائم لائن ورک ویک'; + String get allowedViewTimelineWorkWeekLabel => r'ٹائم لائن ورک ویک۔'; @override String get allowedViewWeekLabel => r'ہفتہ'; @@ -10856,44 +11002,43 @@ class SfLocalizationsUr extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'کام کا ہفتہ'; @override - String get daySpanCountLabel => r'دن'; + String get daySpanCountLabel => r'دن۔'; @override String get dhualhiLabel => r'ذو الحجہ'; @override - String get dhualqiLabel => r'ذو الکعدہ'; + String get dhualqiLabel => r'ذو القعدہ۔'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'اشیاء'; @override - String get jumada1Label => r'جمعہ الاول'; + String get jumada1Label => r'جمعہ الاول۔'; @override - String get jumada2Label => r'جمعہ الثانی'; + String get jumada2Label => r'جماد al الثانی۔'; @override - String get muharramLabel => r'محرم'; + String get muharramLabel => r'محرم۔'; @override - String get noEventsCalendarLabel => r'کوئی واقعات نہیں'; + String get noEventsCalendarLabel => r'کوئی ایونٹس نہیں۔'; @override - String get noSelectedDateCalendarLabel => r'کوئی منتخب تاریخ'; + String get noSelectedDateCalendarLabel => r'کوئی منتخب تاریخ نہیں۔'; @override - String get ofDataPagerLabel => r'کے'; + String get ofDataPagerLabel => r'کی'; @override String get pagesDataPagerLabel => r'صفحات'; @override - String get pdfBookmarksLabel => r'بُک مارکس'; + String get pdfBookmarksLabel => r'بُک مارکس۔'; @override - String get pdfEnterPageNumberLabel => r'صفحہ نمبر درج کریں'; + String get pdfEnterPageNumberLabel => r'صفحہ نمبر درج کریں۔'; @override String get pdfGoToPageLabel => r'صفحے پر جائیں'; @@ -10903,76 +11048,79 @@ class SfLocalizationsUr extends SfGlobalLocalizations { r'براہ مہربانی ایک درست نمبر درج کریں'; @override - String get pdfNoBookmarksLabel => r'کوئی بُک مارکس نہیں ملا'; + String get pdfNoBookmarksLabel => r'کوئی بُک مارکس نہیں ملا۔'; @override - String get pdfPaginationDialogCancelLabel => r'کینسل'; + String get pdfPaginationDialogCancelLabel => r'منسوخ کریں۔'; @override String get pdfPaginationDialogOkLabel => r'ٹھیک ہے'; @override - String get pdfScrollStatusOfLabel => r'کے'; + String get pdfScrollStatusOfLabel => r'کی'; @override String get rabi1Label => r'ربیع الاول'; @override - String get rabi2Label => r'ربیع الثانی'; + String get rabi2Label => r'ربیع الثانی۔'; @override - String get rajabLabel => r'رجب'; + String get rajabLabel => r'رجب۔'; @override String get ramadanLabel => r'رمضان'; @override - String get safarLabel => r'صفر'; + String get safarLabel => r'صفر۔'; @override - String get shaabanLabel => r'شعبان'; + String get shaabanLabel => r'شعبان۔'; @override - String get shawwalLabel => r'شوال'; + String get shawwalLabel => r'شوال۔'; @override - String get shortDhualhiLabel => r'ذوالحل'; + String get shortDhualhiLabel => r'ذوال ایچ۔'; @override - String get shortDhualqiLabel => r'ذوالق'; + String get shortDhualqiLabel => r'ذوالق۔'; @override String get shortJumada1Label => r'جم۔ میں'; @override - String get shortJumada2Label => r'جم۔ II'; + String get shortJumada2Label => r'جم۔ دوم'; @override - String get shortMuharramLabel => r'مح۔'; + String get shortMuharramLabel => r'مہ۔'; @override - String get shortRabi1Label => r'ربیع۔ میں'; + String get shortRabi1Label => r'رابی میں'; @override - String get shortRabi2Label => r'ربیع۔ II'; + String get shortRabi2Label => r'رابی دوم'; @override - String get shortRajabLabel => r'راج'; + String get shortRajabLabel => r'راج۔'; @override - String get shortRamadanLabel => r'رام۔'; + String get shortRamadanLabel => r'رام'; @override - String get shortSafarLabel => r'صف۔'; + String get shortSafarLabel => r'سیف'; @override - String get shortShaabanLabel => r'شا۔'; + String get shortShaabanLabel => r'شا'; @override - String get shortShawwalLabel => r'شا۔'; + String get shortShawwalLabel => r'شا'; @override String get todayLabel => r'آج'; + + @override + String get weeknumberLabel => r'ہفتہ'; } /// The translations for Uzbek (`uz`). @@ -11000,11 +11148,11 @@ class SfLocalizationsUz extends SfGlobalLocalizations { String get allowedViewTimelineMonthLabel => r'Vaqt chizig' "'" r'i oyi'; @override - String get allowedViewTimelineWeekLabel => r'Vaqt chizig' "'" r'i haftasi'; + String get allowedViewTimelineWeekLabel => r'Vaqt jadvalining haftaligi'; @override String get allowedViewTimelineWorkWeekLabel => - r'Vaqt jadvalidagi ish haftasi'; + r'Vaqt jadvalining ish haftasi'; @override String get allowedViewWeekLabel => r'Hafta'; @@ -11016,20 +11164,19 @@ class SfLocalizationsUz extends SfGlobalLocalizations { String get daySpanCountLabel => r'Kun'; @override - String get dhualhiLabel => r'Zul al-Hijja'; + String get dhualhiLabel => r'Zulhijja'; @override String get dhualqiLabel => r'Zul al-Qida'; @override - // ignore: override_on_non_overriding_member - String get itemsDataPagerLabel => r'buyumlar'; + String get itemsDataPagerLabel => r'narsalar'; @override String get jumada1Label => r'Jumada al-avval'; @override - String get jumada2Label => r'Jumada al-thani'; + String get jumada2Label => r'Jumada al-taniy'; @override String get muharramLabel => r'Muharram'; @@ -11063,7 +11210,7 @@ class SfLocalizationsUz extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Xatcho' "'" r'plar topilmadi'; @override - String get pdfPaginationDialogCancelLabel => r'Bekor qilish'; + String get pdfPaginationDialogCancelLabel => r'BOSHLASH'; @override String get pdfPaginationDialogOkLabel => r'OK'; @@ -11072,10 +11219,10 @@ class SfLocalizationsUz extends SfGlobalLocalizations { String get pdfScrollStatusOfLabel => r'ning'; @override - String get rabi1Label => r'Rabi ' "'" r'al-avval'; + String get rabi1Label => r'Rabiul-avval'; @override - String get rabi2Label => r'Rabi ' "'" r'al-thani'; + String get rabi2Label => r'Rabi al-taniy'; @override String get rajabLabel => r'Rajab'; @@ -11096,7 +11243,7 @@ class SfLocalizationsUz extends SfGlobalLocalizations { String get shortDhualhiLabel => r'Zulh'; @override - String get shortDhualqiLabel => r'Zul-Q'; + String get shortDhualqiLabel => r'Zulq'; @override String get shortJumada1Label => r'Jum. Men'; @@ -11120,7 +11267,7 @@ class SfLocalizationsUz extends SfGlobalLocalizations { String get shortRamadanLabel => r'Ram.'; @override - String get shortSafarLabel => r'Xavfsiz'; + String get shortSafarLabel => r'Saf.'; @override String get shortShaabanLabel => r'Sha.'; @@ -11130,6 +11277,9 @@ class SfLocalizationsUz extends SfGlobalLocalizations { @override String get todayLabel => r'Bugun'; + + @override + String get weeknumberLabel => r'Hafta'; } /// The translations for Vietnamese (`vi`). @@ -11142,25 +11292,26 @@ class SfLocalizationsVi extends SfGlobalLocalizations { ); @override - String get allowedViewDayLabel => r'ngày'; + String get allowedViewDayLabel => r'Ngày'; @override - String get allowedViewMonthLabel => r'tháng'; + String get allowedViewMonthLabel => r'Tháng'; @override - String get allowedViewScheduleLabel => r'Lên lịch'; + String get allowedViewScheduleLabel => r'Lịch trình'; @override String get allowedViewTimelineDayLabel => r'Ngày dòng thời gian'; @override - String get allowedViewTimelineMonthLabel => r'Thời gian tháng'; + String get allowedViewTimelineMonthLabel => r'Dòng thời gian Tháng'; @override - String get allowedViewTimelineWeekLabel => r'Dòng thời gian tuần'; + String get allowedViewTimelineWeekLabel => r'Dòng thời gian trong tuần'; @override - String get allowedViewTimelineWorkWeekLabel => r'Thời gian làm việc tuần'; + String get allowedViewTimelineWorkWeekLabel => + r'Dòng thời gian làm việc trong tuần'; @override String get allowedViewWeekLabel => r'Tuần'; @@ -11169,7 +11320,7 @@ class SfLocalizationsVi extends SfGlobalLocalizations { String get allowedViewWorkWeekLabel => r'Tuần làm việc'; @override - String get daySpanCountLabel => r'ngày'; + String get daySpanCountLabel => r'Ngày'; @override String get dhualhiLabel => r'Dhu al-Hijjah'; @@ -11178,7 +11329,6 @@ class SfLocalizationsVi extends SfGlobalLocalizations { String get dhualqiLabel => r'Dhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'mặt hàng'; @override @@ -11218,10 +11368,10 @@ class SfLocalizationsVi extends SfGlobalLocalizations { String get pdfNoBookmarksLabel => r'Không tìm thấy dấu trang'; @override - String get pdfPaginationDialogCancelLabel => r'HỦY'; + String get pdfPaginationDialogCancelLabel => r'SỰ HỦY BỎ'; @override - String get pdfPaginationDialogOkLabel => r'đồng ý'; + String get pdfPaginationDialogOkLabel => r'VÂNG'; @override String get pdfScrollStatusOfLabel => r'của'; @@ -11254,7 +11404,7 @@ class SfLocalizationsVi extends SfGlobalLocalizations { String get shortDhualqiLabel => r'Dhu' "'" r'l-Q'; @override - String get shortJumada1Label => r'Jum. Tôi'; + String get shortJumada1Label => r'Jum. tôi'; @override String get shortJumada2Label => r'Jum. II'; @@ -11263,7 +11413,7 @@ class SfLocalizationsVi extends SfGlobalLocalizations { String get shortMuharramLabel => r'Ờ.'; @override - String get shortRabi1Label => r'Rabi. Tôi'; + String get shortRabi1Label => r'Rabi. tôi'; @override String get shortRabi2Label => r'Rabi. II'; @@ -11285,6 +11435,9 @@ class SfLocalizationsVi extends SfGlobalLocalizations { @override String get todayLabel => r'Hôm nay'; + + @override + String get weeknumberLabel => r'Tuần'; } /// The translations for Chinese (`zh`). @@ -11297,16 +11450,16 @@ class SfLocalizationsZh extends SfGlobalLocalizations { ); @override - String get allowedViewDayLabel => r'天'; + String get allowedViewDayLabel => r'日'; @override String get allowedViewMonthLabel => r'月'; @override - String get allowedViewScheduleLabel => r'时间表'; + String get allowedViewScheduleLabel => r'日程'; @override - String get allowedViewTimelineDayLabel => r'时间轴日'; + String get allowedViewTimelineDayLabel => r'时间表日'; @override String get allowedViewTimelineMonthLabel => r'时间轴月'; @@ -11315,47 +11468,46 @@ class SfLocalizationsZh extends SfGlobalLocalizations { String get allowedViewTimelineWeekLabel => r'时间轴周'; @override - String get allowedViewTimelineWorkWeekLabel => r'时间轴工作周'; + String get allowedViewTimelineWorkWeekLabel => r'时间表工作周'; @override - String get allowedViewWeekLabel => r'周'; + String get allowedViewWeekLabel => r'星期'; @override String get allowedViewWorkWeekLabel => r'工作周'; @override - String get daySpanCountLabel => r'天'; + String get daySpanCountLabel => r'日'; @override - String get dhualhiLabel => r'杜·希贾'; + String get dhualhiLabel => r'杜哈杰'; @override - String get dhualqiLabel => r'齐达'; + String get dhualqiLabel => r'杜基达'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'项目'; @override - String get jumada1Label => r'朱马达·阿瓦尔'; + String get jumada1Label => r'Jumada al-awwal'; @override - String get jumada2Label => r'朱马达·萨塔尼(Jumada al-thani)'; + String get jumada2Label => r'Jumada al-thani'; @override - String get muharramLabel => r'穆哈拉姆'; + String get muharramLabel => r'穆哈兰姆'; @override String get noEventsCalendarLabel => r'没有活动'; @override - String get noSelectedDateCalendarLabel => r'没有选定的日期'; + String get noSelectedDateCalendarLabel => r'未选择日期'; @override String get ofDataPagerLabel => r'的'; @override - String get pagesDataPagerLabel => r'页数'; + String get pagesDataPagerLabel => r'页'; @override String get pdfBookmarksLabel => r'书签'; @@ -11367,28 +11519,28 @@ class SfLocalizationsZh extends SfGlobalLocalizations { String get pdfGoToPageLabel => r'转到页面'; @override - String get pdfInvalidPageNumberLabel => r'请输入一个有效的数字'; + String get pdfInvalidPageNumberLabel => r'请输入有效号码'; @override - String get pdfNoBookmarksLabel => r'找不到书签'; + String get pdfNoBookmarksLabel => r'未找到书签'; @override String get pdfPaginationDialogCancelLabel => r'取消'; @override - String get pdfPaginationDialogOkLabel => r'好'; + String get pdfPaginationDialogOkLabel => r'好的'; @override String get pdfScrollStatusOfLabel => r'的'; @override - String get rabi1Label => r'拉比·阿瓦尔'; + String get rabi1Label => r'拉比奥瓦尔'; @override - String get rabi2Label => r'拉比阿尔塔尼'; + String get rabi2Label => r'拉比阿勒萨尼'; @override - String get rajabLabel => r'拉贾卜'; + String get rajabLabel => r'拉贾布'; @override String get ramadanLabel => r'斋月'; @@ -11397,49 +11549,52 @@ class SfLocalizationsZh extends SfGlobalLocalizations { String get safarLabel => r'萨法尔'; @override - String get shaabanLabel => r'沙阿班'; + String get shaabanLabel => r'沙班'; @override String get shawwalLabel => r'肖瓦尔'; @override - String get shortDhualhiLabel => r'杜赫'; + String get shortDhualhiLabel => r'杜尔-H'; @override - String get shortDhualqiLabel => r'杜克'; + String get shortDhualqiLabel => r'杜尔-Q'; @override - String get shortJumada1Label => r'um一世'; + String get shortJumada1Label => r'赞。一世'; @override - String get shortJumada2Label => r'um II'; + String get shortJumada2Label => r'赞。二'; @override - String get shortMuharramLabel => r'嗯'; + String get shortMuharramLabel => r'嗯。'; @override - String get shortRabi1Label => r'拉比一世'; + String get shortRabi1Label => r'拉比。一世'; @override - String get shortRabi2Label => r'拉比II'; + String get shortRabi2Label => r'拉比。二'; @override - String get shortRajabLabel => r'拉吉'; + String get shortRajabLabel => r'拉杰。'; @override String get shortRamadanLabel => r'内存。'; @override - String get shortSafarLabel => r'Saf。'; + String get shortSafarLabel => r'安全。'; @override String get shortShaabanLabel => r'沙。'; @override - String get shortShawwalLabel => r'肖'; + String get shortShawwalLabel => r'肖。'; @override String get todayLabel => r'今天'; + + @override + String get weeknumberLabel => r'星期'; } /// The translations for Chinese, using the Han script (`zh_Hans`). @@ -11462,10 +11617,7 @@ class SfLocalizationsZhHant extends SfLocalizationsZh { ); @override - String get allowedViewScheduleLabel => r'時間表'; - - @override - String get allowedViewTimelineDayLabel => r'時間軸日'; + String get allowedViewTimelineDayLabel => r'時間表日'; @override String get allowedViewTimelineMonthLabel => r'時間軸月'; @@ -11474,37 +11626,31 @@ class SfLocalizationsZhHant extends SfLocalizationsZh { String get allowedViewTimelineWeekLabel => r'時間軸週'; @override - String get allowedViewTimelineWorkWeekLabel => r'時間軸工作週'; - - @override - String get allowedViewWeekLabel => r'週'; + String get allowedViewTimelineWorkWeekLabel => r'時間表工作週'; @override String get allowedViewWorkWeekLabel => r'工作週'; @override - String get dhualhiLabel => r'杜·希賈'; + String get dhualhiLabel => r'杜哈傑'; @override - String get dhualqiLabel => r'齊達'; + String get dhualqiLabel => r'杜基達'; @override String get itemsDataPagerLabel => r'項目'; @override - String get jumada1Label => r'朱馬達·阿瓦爾'; - - @override - String get jumada2Label => r'朱馬達·薩塔尼(Jumada al-thani)'; + String get muharramLabel => r'穆哈蘭姆'; @override String get noEventsCalendarLabel => r'沒有活動'; @override - String get noSelectedDateCalendarLabel => r'沒有選定的日期'; + String get noSelectedDateCalendarLabel => r'未選擇日期'; @override - String get pagesDataPagerLabel => r'頁數'; + String get pagesDataPagerLabel => r'頁'; @override String get pdfBookmarksLabel => r'書籤'; @@ -11516,19 +11662,19 @@ class SfLocalizationsZhHant extends SfLocalizationsZh { String get pdfGoToPageLabel => r'轉到頁面'; @override - String get pdfInvalidPageNumberLabel => r'請輸入一個有效的數字'; + String get pdfInvalidPageNumberLabel => r'請輸入有效號碼'; @override - String get pdfNoBookmarksLabel => r'找不到書籤'; + String get pdfNoBookmarksLabel => r'未找到書籤'; @override - String get rabi1Label => r'拉比·阿瓦爾'; + String get rabi1Label => r'拉比奧瓦爾'; @override - String get rabi2Label => r'拉比阿爾塔尼'; + String get rabi2Label => r'拉比阿勒薩尼'; @override - String get rajabLabel => r'拉賈卜'; + String get rajabLabel => r'拉賈布'; @override String get ramadanLabel => r'齋月'; @@ -11539,6 +11685,21 @@ class SfLocalizationsZhHant extends SfLocalizationsZh { @override String get shawwalLabel => r'肖瓦爾'; + @override + String get shortDhualhiLabel => r'杜爾-H'; + + @override + String get shortDhualqiLabel => r'杜爾-Q'; + + @override + String get shortJumada1Label => r'贊。一世'; + + @override + String get shortJumada2Label => r'贊。二'; + + @override + String get shortRajabLabel => r'拉傑。'; + @override String get shortRamadanLabel => r'內存。'; } @@ -11609,7 +11770,6 @@ class SfLocalizationsZu extends SfGlobalLocalizations { String get dhualqiLabel => r'UDhu al-Qi' "'" r'dah'; @override - // ignore: override_on_non_overriding_member String get itemsDataPagerLabel => r'izinto'; @override @@ -11716,6 +11876,9 @@ class SfLocalizationsZu extends SfGlobalLocalizations { @override String get todayLabel => r'Namuhla'; + + @override + String get weeknumberLabel => r'Isonto'; } /// The set of supported languages, as language code strings. diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb index dac105c22..09a59f631 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_af.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Geen geselekteerde datum nie", +"noSelectedDateCalendarLabel" : "Geen gekose datum nie", "noEventsCalendarLabel" : "Geen gebeure nie", "ofDataPagerLabel" : "van", "pagesDataPagerLabel" : "bladsye", @@ -9,19 +9,20 @@ "pdfScrollStatusOfLabel" : "van", "pdfGoToPageLabel" : "Gaan na bladsy", "pdfEnterPageNumberLabel" : "Voer bladsynommer in", -"pdfInvalidPageNumberLabel" : "Voer 'n geldige nommer in", +"pdfInvalidPageNumberLabel" : "Voer asseblief 'n geldige nommer in", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "KANSELLER", +"pdfPaginationDialogCancelLabel" : "KANSELLEER", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Week", -"allowedViewWorkWeekLabel" : "Werksweek", +"allowedViewWorkWeekLabel" : "Werkweek", "allowedViewMonthLabel" : "Maand", "allowedViewScheduleLabel" : "Skedule", -"allowedViewTimelineDayLabel" : "Tydlyndag", +"allowedViewTimelineDayLabel" : "Tydlyn dag", "allowedViewTimelineWeekLabel" : "Tydlynweek", -"allowedViewTimelineWorkWeekLabel" : "Tydlyn-werkweek", -"allowedViewTimelineMonthLabel" : "Tydlynmaand", +"allowedViewTimelineWorkWeekLabel" : "Tydlynwerkweek", +"allowedViewTimelineMonthLabel" : "Tydlyn Maand", "todayLabel" : "Vandag", +"weeknumberLabel" : "Week", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb index d2815d1b9..136c92c71 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_am.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "ምንም የተመረጠ ቀን የለም", -"noEventsCalendarLabel" : "ክስተቶች የሉም", +"noSelectedDateCalendarLabel" : "የተመረጠ ቀን የለም", +"noEventsCalendarLabel" : "ምንም ክስተቶች የሉም", "ofDataPagerLabel" : "የ", "pagesDataPagerLabel" : "ገጾች", -"itemsDataPagerLabel" : "ዕቃዎች", +"itemsDataPagerLabel" : "ንጥሎች", "pdfBookmarksLabel" : "ዕልባቶች", "pdfNoBookmarksLabel" : "ምንም ዕልባቶች አልተገኙም", "pdfScrollStatusOfLabel" : "የ", "pdfGoToPageLabel" : "ወደ ገጽ ይሂዱ", "pdfEnterPageNumberLabel" : "የገጽ ቁጥር ያስገቡ", -"pdfInvalidPageNumberLabel" : "እባክዎ ትክክለኛ ቁጥር ያስገቡ", +"pdfInvalidPageNumberLabel" : "እባክዎ የሚሰራ ቁጥር ያስገቡ", "pdfPaginationDialogOkLabel" : "እሺ", -"pdfPaginationDialogCancelLabel" : "መሰረዝ", +"pdfPaginationDialogCancelLabel" : "ሰርዝ", "allowedViewDayLabel" : "ቀን", "allowedViewWeekLabel" : "ሳምንት", "allowedViewWorkWeekLabel" : "የሥራ ሳምንት", "allowedViewMonthLabel" : "ወር", -"allowedViewScheduleLabel" : "የጊዜ ሰሌዳ", -"allowedViewTimelineDayLabel" : "የጊዜ ሰሌዳ ቀን", -"allowedViewTimelineWeekLabel" : "የጊዜ ሰሌዳ ሳምንት", -"allowedViewTimelineWorkWeekLabel" : "የጊዜ መስመር የስራ ሳምንት", -"allowedViewTimelineMonthLabel" : "የጊዜ ሰሌዳ ወር", +"allowedViewScheduleLabel" : "መርሐግብር", +"allowedViewTimelineDayLabel" : "የጊዜ መስመር ቀን", +"allowedViewTimelineWeekLabel" : "የጊዜ መስመር ሳምንት", +"allowedViewTimelineWorkWeekLabel" : "የጊዜ መስመር የሥራ ሳምንት", +"allowedViewTimelineMonthLabel" : "የጊዜ መስመር ወር", "todayLabel" : "ዛሬ", +"weeknumberLabel" : "ሳምንት", "muharramLabel" : "ሙሃረም", "safarLabel" : "ሳፋር", -"rabi1Label" : "ራቢዕ አል-አወል", -"rabi2Label" : "ራቢዕ አል-ታኒ", -"jumada1Label" : "ጁማዳ አል-አወል", +"rabi1Label" : "ረቢዕ አል-አወል", +"rabi2Label" : "ረቢዕ አል-ታኒ", +"jumada1Label" : "ጁመዳ አል-አወል", "jumada2Label" : "ጁማዳ አል-ታኒ", -"rajabLabel" : "ራጃብ", -"shaabanLabel" : "ሻአባን", +"rajabLabel" : "ረጀብ", +"shaabanLabel" : "ሻዕባን", "ramadanLabel" : "ረመዳን", -"shawwalLabel" : "ሻውል", -"dhualqiLabel" : "ዱ አል-ቂዳህ", -"dhualhiLabel" : "ዱል ሂጃህ", -"shortMuharramLabel" : "ሙ.", -"shortSafarLabel" : "ሳፍ", -"shortRabi1Label" : "ራቢ. እኔ", -"shortRabi2Label" : "ራቢ. II", -"shortJumada1Label" : "ጃም እኔ", -"shortJumada2Label" : "ጃም II", -"shortRajabLabel" : "ራጅ", +"shawwalLabel" : "ሸዋል", +"dhualqiLabel" : "ዱ አል-ቂዕዳ", +"dhualhiLabel" : "ዱ አል-ሂጃህ", +"shortMuharramLabel" : "ሙህ።", +"shortSafarLabel" : "ሳፍ.", +"shortRabi1Label" : "ረቢ። እኔ", +"shortRabi2Label" : "ረቢ። II", +"shortJumada1Label" : "ጁም. እኔ", +"shortJumada2Label" : "ጁም. II", +"shortRajabLabel" : "ራጅ።", "shortShaabanLabel" : "ሻ.", "shortRamadanLabel" : "ራንደም አክሰስ ሜሞሪ.", -"shortShawwalLabel" : "ሻው.", -"shortDhualqiLabel" : "ዱል-ኪ", +"shortShawwalLabel" : "ሸው።", +"shortDhualqiLabel" : "ዙል-ቁ", "shortDhualhiLabel" : "ዱል-ኤች", "daySpanCountLabel" : "ቀን" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb index a61ecae19..46b2d8aae 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ar.arb @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "انتقل إلى صفحة", "pdfEnterPageNumberLabel" : "أدخل رقم الصفحة", "pdfInvalidPageNumberLabel" : "من فضلك أدخل رقما صالحا", -"pdfPaginationDialogOkLabel" : "حسنا", +"pdfPaginationDialogOkLabel" : "نعم", "pdfPaginationDialogCancelLabel" : "إلغاء", "allowedViewDayLabel" : "يوم", "allowedViewWeekLabel" : "أسبوع", @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel" : "أسبوع العمل المخطط الزمني", "allowedViewTimelineMonthLabel" : "شهر المخطط الزمني", "todayLabel" : "اليوم", +"weeknumberLabel" : "أسبوع", "muharramLabel" : "شهر محرم", "safarLabel" : "سفر", "rabi1Label" : "ربيع الأول", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb index 3727ba08d..222946c57 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_az.arb @@ -1,39 +1,40 @@ { "noSelectedDateCalendarLabel" : "Seçilmiş tarix yoxdur", -"noEventsCalendarLabel" : "Tədbir yoxdur", -"ofDataPagerLabel" : "of", +"noEventsCalendarLabel" : "Hadisələr yoxdur", +"ofDataPagerLabel" : "-dən", "pagesDataPagerLabel" : "səhifələr", "itemsDataPagerLabel" : "maddələr", "pdfBookmarksLabel" : "Əlfəcinlər", "pdfNoBookmarksLabel" : "Əlfəcin tapılmadı", -"pdfScrollStatusOfLabel" : "of", +"pdfScrollStatusOfLabel" : "-dən", "pdfGoToPageLabel" : "Səhifəyə daxil ol", "pdfEnterPageNumberLabel" : "Səhifə nömrəsini daxil edin", -"pdfInvalidPageNumberLabel" : "Xahiş edirəm etibarlı bir nömrə daxil edin", +"pdfInvalidPageNumberLabel" : "Zəhmət olmasa düzgün nömrə daxil edin", "pdfPaginationDialogOkLabel" : "tamam", "pdfPaginationDialogCancelLabel" : "Ləğv et", "allowedViewDayLabel" : "Gün", "allowedViewWeekLabel" : "Həftə", -"allowedViewWorkWeekLabel" : "İş həftəsi", +"allowedViewWorkWeekLabel" : "İş Həftəsi", "allowedViewMonthLabel" : "Ay", "allowedViewScheduleLabel" : "Cədvəl", "allowedViewTimelineDayLabel" : "Zaman Çizelgesi Günü", -"allowedViewTimelineWeekLabel" : "Həyat həftəsi", -"allowedViewTimelineWorkWeekLabel" : "İş qrafiki", -"allowedViewTimelineMonthLabel" : "Təqvim ayı", +"allowedViewTimelineWeekLabel" : "Zaman Çizelgesi Həftəsi", +"allowedViewTimelineWorkWeekLabel" : "İş Həftəsi Qrafiki", +"allowedViewTimelineMonthLabel" : "Zaman Çizelgesi Ayı", "todayLabel" : "Bu gün", +"weeknumberLabel" : "Həftə", "muharramLabel" : "Məhərrəm", "safarLabel" : "Səfər", -"rabi1Label" : "Rəbiul-əvvəl", -"rabi2Label" : "Rabi 'əl-thani", -"jumada1Label" : "Jumada al-awal", -"jumada2Label" : "Jumada al-thani", +"rabi1Label" : "Rəbiül-əvvəl", +"rabi2Label" : "Rəbi əl-Tani", +"jumada1Label" : "Cümədə əl-əvvəl", +"jumada2Label" : "Jumada əl-tani", "rajabLabel" : "Rəcəb", -"shaabanLabel" : "Şəban", +"shaabanLabel" : "Şaban", "ramadanLabel" : "Ramazan", "shawwalLabel" : "Şəvval", -"dhualqiLabel" : "Zülqüda", -"dhualhiLabel" : "Zül-hiccə", +"dhualqiLabel" : "Zülqüdah", +"dhualhiLabel" : "Zilhiccə", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. Mən", @@ -41,10 +42,10 @@ "shortJumada1Label" : "Jum. Mən", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", -"shortShaabanLabel" : "Şa.", +"shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Ram.", -"shortShawwalLabel" : "Şou.", -"shortDhualqiLabel" : "Zül-Q", -"shortDhualhiLabel" : "Zül-H", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Zülq Q", +"shortDhualhiLabel" : "Zülh", "daySpanCountLabel" : "Gün" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb index 9841f75c3..dece95423 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_be.arb @@ -1,40 +1,41 @@ { "noSelectedDateCalendarLabel" : "Няма выбранай даты", -"noEventsCalendarLabel" : "Ніякіх падзей", +"noEventsCalendarLabel" : "Няма падзей", "ofDataPagerLabel" : "з", -"pagesDataPagerLabel" : "старонкі", +"pagesDataPagerLabel" : "старонак", "itemsDataPagerLabel" : "прадметы", "pdfBookmarksLabel" : "Закладкі", -"pdfNoBookmarksLabel" : "Закладак не знойдзена", +"pdfNoBookmarksLabel" : "Закладкі не знойдзены", "pdfScrollStatusOfLabel" : "з", "pdfGoToPageLabel" : "Перайсці на старонку", "pdfEnterPageNumberLabel" : "Увядзіце нумар старонкі", "pdfInvalidPageNumberLabel" : "Калі ласка, увядзіце сапраўдны нумар", "pdfPaginationDialogOkLabel" : "добра", -"pdfPaginationDialogCancelLabel" : "АДМЕНА", +"pdfPaginationDialogCancelLabel" : "СКАСАВАЦЬ", "allowedViewDayLabel" : "Дзень", "allowedViewWeekLabel" : "Тыдзень", "allowedViewWorkWeekLabel" : "Працоўны тыдзень", "allowedViewMonthLabel" : "Месяц", "allowedViewScheduleLabel" : "Расклад", "allowedViewTimelineDayLabel" : "Дзень часовай шкалы", -"allowedViewTimelineWeekLabel" : "Тыдзень тэрмінаў", -"allowedViewTimelineWorkWeekLabel" : "Графік працоўнага тыдня", -"allowedViewTimelineMonthLabel" : "Месяц тэрмінаў", +"allowedViewTimelineWeekLabel" : "Тыдзень часовай шкалы", +"allowedViewTimelineWorkWeekLabel" : "Графік працы тыдзень", +"allowedViewTimelineMonthLabel" : "Месяц часовай шкалы", "todayLabel" : "Сёння", +"weeknumberLabel" : "Тыдзень", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", -"rabi1Label" : "Рабі аль-Аўваль", -"rabi2Label" : "Рабі аль-Тані", +"rabi1Label" : "Рабі аль-аўваль", +"rabi2Label" : "Рабі-аль-Тані", "jumada1Label" : "Джумада аль-аўваль", -"jumada2Label" : "Джумада аль-Тані", +"jumada2Label" : "Джумада аль-тані", "rajabLabel" : "Раджаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамадан", "shawwalLabel" : "Шаўваль", -"dhualqiLabel" : "Ду аль-Кіда", -"dhualhiLabel" : "Джу аль-Хіджа", -"shortMuharramLabel" : "М-м-м.", +"dhualqiLabel" : "Дху аль-Кіда", +"dhualhiLabel" : "Дху аль-Хіджа", +"shortMuharramLabel" : "Ага.", "shortSafarLabel" : "Саф.", "shortRabi1Label" : "Рабі. Я", "shortRabi2Label" : "Рабі. II", @@ -44,7 +45,7 @@ "shortShaabanLabel" : "Ша.", "shortRamadanLabel" : "Баран.", "shortShawwalLabel" : "Шоу.", -"shortDhualqiLabel" : "Зул-К", -"shortDhualhiLabel" : "Зул-Н", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Дзень" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb index ef7753a6e..6fbbe5f6c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bg.arb @@ -18,10 +18,11 @@ "allowedViewMonthLabel" : "Месец", "allowedViewScheduleLabel" : "График", "allowedViewTimelineDayLabel" : "Ден на хронологията", -"allowedViewTimelineWeekLabel" : "Седмица на времевата скала", -"allowedViewTimelineWorkWeekLabel" : "Работна седмица на хронологията", -"allowedViewTimelineMonthLabel" : "Месец на хронологията", +"allowedViewTimelineWeekLabel" : "Седмица на хронологията", +"allowedViewTimelineWorkWeekLabel" : "Работна седмица по времева линия", +"allowedViewTimelineMonthLabel" : "Хронология Месец", "todayLabel" : "Днес", +"weeknumberLabel" : "Седмица", "muharramLabel" : "Мухарам", "safarLabel" : "Сафар", "rabi1Label" : "Раби ал-аввал", @@ -31,10 +32,10 @@ "rajabLabel" : "Раджаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамадан", -"shawwalLabel" : "Шаввал", -"dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "Ммм.", +"shawwalLabel" : "Шаувал", +"dhualqiLabel" : "Дху ал-Кида", +"dhualhiLabel" : "Дху ал-Хиджджа", +"shortMuharramLabel" : "Ами", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Раби. Аз", "shortRabi2Label" : "Раби. II", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb index a847add6b..dc1e7225c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bn.arb @@ -1,47 +1,48 @@ { -"noSelectedDateCalendarLabel" : "কোনও নির্বাচিত তারিখ নেই", +"noSelectedDateCalendarLabel" : "কোন নির্বাচিত তারিখ নেই", "noEventsCalendarLabel" : "কোন ইভেন্ট নেই", "ofDataPagerLabel" : "এর", -"pagesDataPagerLabel" : "পৃষ্ঠা", +"pagesDataPagerLabel" : "পাতা", "itemsDataPagerLabel" : "আইটেম", -"pdfBookmarksLabel" : "বুকমার্কস", -"pdfNoBookmarksLabel" : "কোনও বুকমার্ক পাওয়া যায় নি", +"pdfBookmarksLabel" : "বুকমার্ক", +"pdfNoBookmarksLabel" : "কোন বুকমার্ক পাওয়া যায়নি", "pdfScrollStatusOfLabel" : "এর", "pdfGoToPageLabel" : "পৃষ্ঠায় যান", "pdfEnterPageNumberLabel" : "পৃষ্ঠা নম্বর লিখুন", -"pdfInvalidPageNumberLabel" : "দয়া করে একটি বৈধ সংখ্যা লিখুন", +"pdfInvalidPageNumberLabel" : "দয়া করে একটি বৈধ নম্বর লিখুন", "pdfPaginationDialogOkLabel" : "ঠিক আছে", -"pdfPaginationDialogCancelLabel" : "বাতিল", +"pdfPaginationDialogCancelLabel" : "বাতিল করুন", "allowedViewDayLabel" : "দিন", "allowedViewWeekLabel" : "সপ্তাহ", "allowedViewWorkWeekLabel" : "কর্ম সপ্তাহ", "allowedViewMonthLabel" : "মাস", -"allowedViewScheduleLabel" : "সময়সূচী", -"allowedViewTimelineDayLabel" : "সময়রেখার দিন", +"allowedViewScheduleLabel" : "তফসিল", +"allowedViewTimelineDayLabel" : "সময়রেখা দিন", "allowedViewTimelineWeekLabel" : "টাইমলাইন সপ্তাহ", -"allowedViewTimelineWorkWeekLabel" : "সময়রেখা কর্ম সপ্তাহ", -"allowedViewTimelineMonthLabel" : "টাইমলাইন মাস", +"allowedViewTimelineWorkWeekLabel" : "টাইমলাইন কর্ম সপ্তাহ", +"allowedViewTimelineMonthLabel" : "সময়রেখা মাস", "todayLabel" : "আজ", -"muharramLabel" : "মোহাররম", +"weeknumberLabel" : "সপ্তাহ", +"muharramLabel" : "মহররম", "safarLabel" : "সাফার", -"rabi1Label" : "রাবি আল-আউয়াল", -"rabi2Label" : "রাবি 'আল-থানি", -"jumada1Label" : "জুমদা আল-আউয়াল", -"jumada2Label" : "জুমদা আল থানি", +"rabi1Label" : "রবিউল আউয়াল", +"rabi2Label" : "রবি আল-থানি", +"jumada1Label" : "জুমাদা আল-আউয়াল", +"jumada2Label" : "জুমাদা আল-থানি", "rajabLabel" : "রজব", "shaabanLabel" : "শাবান", "ramadanLabel" : "রমজান", "shawwalLabel" : "শাওয়াল", -"dhualqiLabel" : "ধু আল-কিয়াদাহ", -"dhualhiLabel" : "ধূ আল-হিজাহ", +"dhualqiLabel" : "ধু আল-ক্বিদা", +"dhualhiLabel" : "ধু আল-হিজজা", "shortMuharramLabel" : "মুহ।", -"shortSafarLabel" : "সাফ", -"shortRabi1Label" : "রবি। আমি", -"shortRabi2Label" : "রবি। II", -"shortJumada1Label" : "জম। আমি", -"shortJumada2Label" : "জম। II", +"shortSafarLabel" : "সেফ।", +"shortRabi1Label" : "রাবি। আমি", +"shortRabi2Label" : "রাবি। II", +"shortJumada1Label" : "জুম। আমি", +"shortJumada2Label" : "জুম। II", "shortRajabLabel" : "রাজ।", -"shortShaabanLabel" : "শ।", +"shortShaabanLabel" : "শা।", "shortRamadanLabel" : "র্যাম.", "shortShawwalLabel" : "শ।", "shortDhualqiLabel" : "ধুল-কিউ", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb index 3a38f69ab..ad719f129 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_bs.arb @@ -1,34 +1,35 @@ { "noSelectedDateCalendarLabel" : "Nema odabranog datuma", "noEventsCalendarLabel" : "Nema događaja", -"ofDataPagerLabel" : "od", +"ofDataPagerLabel" : "of", "pagesDataPagerLabel" : "stranice", -"itemsDataPagerLabel" : "predmeta", +"itemsDataPagerLabel" : "stavke", "pdfBookmarksLabel" : "Oznake", -"pdfNoBookmarksLabel" : "Oznake nisu pronađene", -"pdfScrollStatusOfLabel" : "od", +"pdfNoBookmarksLabel" : "Nisu pronađene oznake", +"pdfScrollStatusOfLabel" : "of", "pdfGoToPageLabel" : "Idi na stranicu", "pdfEnterPageNumberLabel" : "Unesite broj stranice", "pdfInvalidPageNumberLabel" : "Unesite važeći broj", "pdfPaginationDialogOkLabel" : "uredu", -"pdfPaginationDialogCancelLabel" : "OTKAŽI", +"pdfPaginationDialogCancelLabel" : "CANCEL", "allowedViewDayLabel" : "Dan", "allowedViewWeekLabel" : "Sedmica", "allowedViewWorkWeekLabel" : "Radna sedmica", "allowedViewMonthLabel" : "Mjesec", "allowedViewScheduleLabel" : "Raspored", -"allowedViewTimelineDayLabel" : "Dan vremenske trake", -"allowedViewTimelineWeekLabel" : "Nedelja vremenske trake", -"allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", -"allowedViewTimelineMonthLabel" : "Mjesec vremenske trake", +"allowedViewTimelineDayLabel" : "Dan vremenske linije", +"allowedViewTimelineWeekLabel" : "Sedmica vremenske linije", +"allowedViewTimelineWorkWeekLabel" : "Radna sedmica vremenske linije", +"allowedViewTimelineMonthLabel" : "Mjesec vremenske linije", "todayLabel" : "Danas", +"weeknumberLabel" : "Sedmica", "muharramLabel" : "Muharram", "safarLabel" : "Safar", -"rabi1Label" : "Rabi al-Avval", +"rabi1Label" : "Rabi 'al-awwal", "rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-avval", +"jumada1Label" : "Džumada el-avval", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Rajab", +"rajabLabel" : "Redžeb", "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramazan", "shawwalLabel" : "Shawwal", @@ -36,9 +37,9 @@ "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. Ja", +"shortRabi1Label" : "Rabi. I", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. Ja", +"shortJumada1Label" : "Jum. I", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb index 23d91a3a2..76baa49cf 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ca.arb @@ -18,10 +18,11 @@ "allowedViewMonthLabel" : "Mes", "allowedViewScheduleLabel" : "Horari", "allowedViewTimelineDayLabel" : "Dia de la cronologia", -"allowedViewTimelineWeekLabel" : "Setmana del cronograma", +"allowedViewTimelineWeekLabel" : "Setmana de la cronologia", "allowedViewTimelineWorkWeekLabel" : "Setmana del treball de cronologia", "allowedViewTimelineMonthLabel" : "Mes de cronologia", "todayLabel" : "Avui", +"weeknumberLabel" : "Setmana", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb index 6480b2669..2dbfc1941 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_cs.arb @@ -1,8 +1,8 @@ { -"noSelectedDateCalendarLabel" : "Není vybráno žádné datum", +"noSelectedDateCalendarLabel" : "Žádné vybrané datum", "noEventsCalendarLabel" : "Žádné události", "ofDataPagerLabel" : "z", -"pagesDataPagerLabel" : "stránky", +"pagesDataPagerLabel" : "stránek", "itemsDataPagerLabel" : "položky", "pdfBookmarksLabel" : "Záložky", "pdfNoBookmarksLabel" : "Nebyly nalezeny žádné záložky", @@ -18,17 +18,18 @@ "allowedViewMonthLabel" : "Měsíc", "allowedViewScheduleLabel" : "Plán", "allowedViewTimelineDayLabel" : "Den časové osy", -"allowedViewTimelineWeekLabel" : "Časová osa týden", +"allowedViewTimelineWeekLabel" : "Týden časové osy", "allowedViewTimelineWorkWeekLabel" : "Pracovní týden na časové ose", "allowedViewTimelineMonthLabel" : "Měsíc časové osy", "todayLabel" : "Dnes", +"weeknumberLabel" : "Týden", "muharramLabel" : "Muharram", -"safarLabel" : "Safar", +"safarLabel" : "Šafář", "rabi1Label" : "Rabi 'al-awwal", "rabi2Label" : "Rabi 'al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Rajab", +"rajabLabel" : "Rádžab", "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadán", "shawwalLabel" : "Shawwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb index 821e2fe04..c26498577 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_da.arb @@ -9,7 +9,7 @@ "pdfScrollStatusOfLabel" : "af", "pdfGoToPageLabel" : "Gå til side", "pdfEnterPageNumberLabel" : "Indtast sidenummer", -"pdfInvalidPageNumberLabel" : "Indtast et gyldigt nummer", +"pdfInvalidPageNumberLabel" : "Angiv et gyldigt nummer", "pdfPaginationDialogOkLabel" : "Okay", "pdfPaginationDialogCancelLabel" : "AFBESTILLE", "allowedViewDayLabel" : "Dag", @@ -17,11 +17,12 @@ "allowedViewWorkWeekLabel" : "Arbejdsuge", "allowedViewMonthLabel" : "Måned", "allowedViewScheduleLabel" : "Tidsplan", -"allowedViewTimelineDayLabel" : "Tidslinjedag", -"allowedViewTimelineWeekLabel" : "Tidslinjeuge", +"allowedViewTimelineDayLabel" : "Tidslinjens dag", +"allowedViewTimelineWeekLabel" : "Tidslinje uge", "allowedViewTimelineWorkWeekLabel" : "Tidslinje Arbejdsuge", -"allowedViewTimelineMonthLabel" : "Tidslinjemåned", +"allowedViewTimelineMonthLabel" : "Tidslinje Måned", "todayLabel" : "I dag", +"weeknumberLabel" : "Uge", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb index e52f706bf..43fecbe2a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_de.arb @@ -3,31 +3,32 @@ "noEventsCalendarLabel" : "Keine Ereignisse", "ofDataPagerLabel" : "von", "pagesDataPagerLabel" : "Seiten", -"itemsDataPagerLabel" : "Artikel", +"itemsDataPagerLabel" : "Produkte", "pdfBookmarksLabel" : "Lesezeichen", "pdfNoBookmarksLabel" : "Keine Lesezeichen gefunden", "pdfScrollStatusOfLabel" : "von", -"pdfGoToPageLabel" : "Zur Seite gehen", -"pdfEnterPageNumberLabel" : "Geben Sie die Seitenzahl ein", +"pdfGoToPageLabel" : "Gehe zu Seite", +"pdfEnterPageNumberLabel" : "Seitenzahl eingeben", "pdfInvalidPageNumberLabel" : "Bitte geben Sie eine gültige Nummer ein", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "STORNIEREN", +"pdfPaginationDialogCancelLabel" : "ABBRECHEN", "allowedViewDayLabel" : "Tag", "allowedViewWeekLabel" : "Woche", "allowedViewWorkWeekLabel" : "Arbeitswoche", "allowedViewMonthLabel" : "Monat", -"allowedViewScheduleLabel" : "Zeitplan", -"allowedViewTimelineDayLabel" : "Timeline Day", -"allowedViewTimelineWeekLabel" : "Timeline-Woche", -"allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", -"allowedViewTimelineMonthLabel" : "Timeline-Monat", +"allowedViewScheduleLabel" : "Zeitlicher Ablauf", +"allowedViewTimelineDayLabel" : "Zeitleiste Tag", +"allowedViewTimelineWeekLabel" : "Zeitleiste Woche", +"allowedViewTimelineWorkWeekLabel" : "Zeitleiste Arbeitswoche", +"allowedViewTimelineMonthLabel" : "Zeitachse Monat", "todayLabel" : "Heute", +"weeknumberLabel" : "Woche", "muharramLabel" : "Muharram", -"safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-Awwal", -"jumada2Label" : "Jumada al-thani", +"safarLabel" : "Safari", +"rabi1Label" : "Rabi' al-awwal", +"rabi2Label" : "Rabi' al-Thani", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Dschumada al-Thani", "rajabLabel" : "Rajab", "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadan", @@ -35,7 +36,7 @@ "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Saf.", +"shortSafarLabel" : "Sicher.", "shortRabi1Label" : "Rabi. ich", "shortRabi2Label" : "Rabi. II", "shortJumada1Label" : "Jum. ich", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb index 0b7aab6f2..c88ef8684 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_el.arb @@ -1,6 +1,6 @@ { -"noSelectedDateCalendarLabel" : "Δεν έχει επιλεγεί ημερομηνία", -"noEventsCalendarLabel" : "Δεν υπάρχουν εκδηλώσεις", +"noSelectedDateCalendarLabel" : "Καμία επιλεγμένη ημερομηνία", +"noEventsCalendarLabel" : "Χωρίς εκδηλώσεις", "ofDataPagerLabel" : "του", "pagesDataPagerLabel" : "σελίδες", "itemsDataPagerLabel" : "αντικείμενα", @@ -14,37 +14,38 @@ "pdfPaginationDialogCancelLabel" : "ΜΑΤΑΙΩΣΗ", "allowedViewDayLabel" : "Ημέρα", "allowedViewWeekLabel" : "Εβδομάδα", -"allowedViewWorkWeekLabel" : "Εβδομάδα εργασίας", +"allowedViewWorkWeekLabel" : "Εβδομάδα Εργασίας", "allowedViewMonthLabel" : "Μήνας", "allowedViewScheduleLabel" : "Πρόγραμμα", -"allowedViewTimelineDayLabel" : "Ημέρα χρονολογίου", -"allowedViewTimelineWeekLabel" : "Εβδομάδα χρονοδιαγράμματος", +"allowedViewTimelineDayLabel" : "Ημερολόγιο", +"allowedViewTimelineWeekLabel" : "Εβδομάδα χρονολογίου", "allowedViewTimelineWorkWeekLabel" : "Εβδομάδα εργασίας χρονοδιαγράμματος", -"allowedViewTimelineMonthLabel" : "Χρονοδιάγραμμα Μήνας", +"allowedViewTimelineMonthLabel" : "Μήνας χρονολογίου", "todayLabel" : "Σήμερα", +"weeknumberLabel" : "Εβδομάδα", "muharramLabel" : "Μουχάραμ", -"safarLabel" : "Σαφάρι", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Ραμπι 'αλ-θανι", +"safarLabel" : "Safar", +"rabi1Label" : "Ραμπί 'αλ-αυβάλ", +"rabi2Label" : "Ραμπί 'αλ-θάνι", "jumada1Label" : "Jumada al-awwal", -"jumada2Label" : "Τζουμάδα αλ-θάνι", +"jumada2Label" : "Jumada al-thani", "rajabLabel" : "Ρατζάμπ", -"shaabanLabel" : "Σααμπάν", +"shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ραμαζάνι", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Ντου αλ Χιτζάτζ", -"shortMuharramLabel" : "Μουχ.", -"shortSafarLabel" : "Σαφ.", +"dhualhiLabel" : "Ντου αλ-Χίτζα", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf.", "shortRabi1Label" : "Ράμπι. Εγώ", -"shortRabi2Label" : "Ράμπι. ΙΙ", -"shortJumada1Label" : "Τζαμ. Εγώ", -"shortJumada2Label" : "Τζαμ. ΙΙ", +"shortRabi2Label" : "Ράμπι. II", +"shortJumada1Label" : "Jum. Εγώ", +"shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Κυριαρχία.", -"shortShaabanLabel" : "Σα.", +"shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Εμβολο.", -"shortShawwalLabel" : "Σω.", +"shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Ντουλ-Χ", +"shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Ημέρα" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb index b90effce5..86d6ec2cf 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.arb @@ -137,6 +137,12 @@ "type": "text" }, + "weeknumberLabel": "Week", + "@weeknumberLabel": { + "description": "Label that is displayed prefix to week number.", + "type": "text" + }, + "muharramLabel": "Muharram", "@muharramLabel": { "description": "The header string for the first month of hirji calendar.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json index 4827267e8..9a668602a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_en.json @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel": "Timeline Work Week", "allowedViewTimelineMonthLabel": "Timeline Month", "todayLabel": "Today", + "weeknumberLabel": "Week", "muharramLabel": "Muharram", "safarLabel": "Safar", "rabi1Label": "Rabi' al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb index d4bdc0101..d5d9a8502 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_es.arb @@ -3,14 +3,14 @@ "noEventsCalendarLabel" : "No hay eventos", "ofDataPagerLabel" : "de", "pagesDataPagerLabel" : "paginas", -"itemsDataPagerLabel" : "artículos", +"itemsDataPagerLabel" : "elementos", "pdfBookmarksLabel" : "Marcadores", "pdfNoBookmarksLabel" : "No se encontraron marcadores", "pdfScrollStatusOfLabel" : "de", "pdfGoToPageLabel" : "Ir a la pagina", "pdfEnterPageNumberLabel" : "Ingrese el número de página", "pdfInvalidPageNumberLabel" : "por favor ingrese un número valido", -"pdfPaginationDialogOkLabel" : "Okay", +"pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "CANCELAR", "allowedViewDayLabel" : "Día", "allowedViewWeekLabel" : "Semana", @@ -21,7 +21,8 @@ "allowedViewTimelineWeekLabel" : "Semana de la cronología", "allowedViewTimelineWorkWeekLabel" : "Semana laboral de la línea de tiempo", "allowedViewTimelineMonthLabel" : "Mes de la cronología", -"todayLabel" : "Hoy", +"todayLabel" : "Hoy dia", +"weeknumberLabel" : "Semana", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", @@ -33,12 +34,12 @@ "ramadanLabel" : "Ramadán", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", +"dhualhiLabel" : "Dhu al-Hiyyah", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. yo", +"shortRabi1Label" : "Rabi. I", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. yo", +"shortJumada1Label" : "Jum. I", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb index bd214880d..d4cd3eb9d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_et.arb @@ -1,14 +1,14 @@ { -"noSelectedDateCalendarLabel" : "Valitud kuupäeva pole", -"noEventsCalendarLabel" : "Ühtegi üritust pole", +"noSelectedDateCalendarLabel" : "Kuupäeva pole valitud", +"noEventsCalendarLabel" : "Üritusi pole", "ofDataPagerLabel" : "kohta", "pagesDataPagerLabel" : "lehekülgi", -"itemsDataPagerLabel" : "esemed", +"itemsDataPagerLabel" : "esemeid", "pdfBookmarksLabel" : "Järjehoidjad", "pdfNoBookmarksLabel" : "Järjehoidjaid ei leitud", "pdfScrollStatusOfLabel" : "kohta", -"pdfGoToPageLabel" : "Minge lehele", -"pdfEnterPageNumberLabel" : "Sisestage lehenumber", +"pdfGoToPageLabel" : "Mine lehele", +"pdfEnterPageNumberLabel" : "Sisestage lehe number", "pdfInvalidPageNumberLabel" : "Sisestage kehtiv number", "pdfPaginationDialogOkLabel" : "Okei", "pdfPaginationDialogCancelLabel" : "TÜHISTA", @@ -22,14 +22,15 @@ "allowedViewTimelineWorkWeekLabel" : "Ajaskaala töönädal", "allowedViewTimelineMonthLabel" : "Ajaskaala kuu", "todayLabel" : "Täna", +"weeknumberLabel" : "Nädal", "muharramLabel" : "Muharram", "safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", +"rabi1Label" : "Rabi al-awwal", "rabi2Label" : "Rabi 'al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", -"shaabanLabel" : "Sha'aban", +"shaabanLabel" : "Shaaban", "ramadanLabel" : "Ramadaan", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", @@ -38,8 +39,8 @@ "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. Mina", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. Mina", -"shortJumada2Label" : "Jum. II", +"shortJumada1Label" : "Jumal. Mina", +"shortJumada2Label" : "Jumal. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Ram.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb index bffa735e2..171975172 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_eu.arb @@ -18,10 +18,11 @@ "allowedViewMonthLabel" : "Hilabetea", "allowedViewScheduleLabel" : "Ordutegia", "allowedViewTimelineDayLabel" : "Kronologia Eguna", -"allowedViewTimelineWeekLabel" : "Kronologia Astea", +"allowedViewTimelineWeekLabel" : "Kronograma Astea", "allowedViewTimelineWorkWeekLabel" : "Kronogramaren Lan Astea", "allowedViewTimelineMonthLabel" : "Kronograma Hilabetea", "todayLabel" : "Gaur", +"weeknumberLabel" : "Astea", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb index 4feeb4de2..7d7417de5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fa.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "تاریخ انتخاب نشده است", +"noSelectedDateCalendarLabel" : "تاریخ انتخابی وجود ندارد", "noEventsCalendarLabel" : "هیچ رویدادی وجود ندارد", "ofDataPagerLabel" : "از", "pagesDataPagerLabel" : "صفحات", @@ -11,7 +11,7 @@ "pdfEnterPageNumberLabel" : "شماره صفحه را وارد کنید", "pdfInvalidPageNumberLabel" : "لطفا یک شماره معتبر وارد کنید", "pdfPaginationDialogOkLabel" : "خوب", -"pdfPaginationDialogCancelLabel" : "لغو", +"pdfPaginationDialogCancelLabel" : "لغو کنید", "allowedViewDayLabel" : "روز", "allowedViewWeekLabel" : "هفته", "allowedViewWorkWeekLabel" : "هفته کاری", @@ -19,27 +19,28 @@ "allowedViewScheduleLabel" : "برنامه", "allowedViewTimelineDayLabel" : "روز جدول زمانی", "allowedViewTimelineWeekLabel" : "هفته جدول زمانی", -"allowedViewTimelineWorkWeekLabel" : "هفته کار جدول زمانی", +"allowedViewTimelineWorkWeekLabel" : "جدول کار هفته", "allowedViewTimelineMonthLabel" : "ماه جدول زمانی", "todayLabel" : "امروز", +"weeknumberLabel" : "هفته", "muharramLabel" : "محرم", "safarLabel" : "صفر", "rabi1Label" : "ربیع الاول", "rabi2Label" : "ربیع الثانی", -"jumada1Label" : "جماد al الاول", -"jumada2Label" : "جمادا الثانی", -"rajabLabel" : "راجب", +"jumada1Label" : "جمادی الاول", +"jumada2Label" : "جمادی الثانی", +"rajabLabel" : "رجب", "shaabanLabel" : "شعبان", "ramadanLabel" : "ماه رمضان", "shawwalLabel" : "شوال", -"dhualqiLabel" : "ذو القعده", -"dhualhiLabel" : "ذو الحجه", -"shortMuharramLabel" : "مه", -"shortSafarLabel" : "ساف", +"dhualqiLabel" : "ذوالقیعده", +"dhualhiLabel" : "ذی الحجه", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "صف", "shortRabi1Label" : "ربیع من", -"shortRabi2Label" : "ربیع دوم", +"shortRabi2Label" : "ربیع II", "shortJumada1Label" : "جوم من", -"shortJumada2Label" : "جوم دوم", +"shortJumada2Label" : "جوم II", "shortRajabLabel" : "راج", "shortShaabanLabel" : "شا", "shortRamadanLabel" : "رم.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb index 8fe2e56e0..6119a21e6 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fi.arb @@ -8,7 +8,7 @@ "pdfNoBookmarksLabel" : "Kirjanmerkkejä ei löytynyt", "pdfScrollStatusOfLabel" : "/", "pdfGoToPageLabel" : "Mene sivulle", -"pdfEnterPageNumberLabel" : "Syötä sivunumero", +"pdfEnterPageNumberLabel" : "Anna sivunumero", "pdfInvalidPageNumberLabel" : "Anna kelvollinen numero", "pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "PERUUTTAA", @@ -18,10 +18,11 @@ "allowedViewMonthLabel" : "Kuukausi", "allowedViewScheduleLabel" : "Ajoittaa", "allowedViewTimelineDayLabel" : "Aikajanan päivä", -"allowedViewTimelineWeekLabel" : "Aikajanan viikko", +"allowedViewTimelineWeekLabel" : "Aikajanaviikko", "allowedViewTimelineWorkWeekLabel" : "Aikajanan työviikko", "allowedViewTimelineMonthLabel" : "Aikajanan kuukausi", "todayLabel" : "Tänään", +"weeknumberLabel" : "Viikko", "muharramLabel" : "Muharram", "safarLabel" : "Safari", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb index 3ac571278..5b2e0090a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fil.arb @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel" : "Linggo ng Trabaho ng Timeline", "allowedViewTimelineMonthLabel" : "Buwan ng Timeline", "todayLabel" : "Ngayon", +"weeknumberLabel" : "Linggo", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb index bdbb4f872..73e8990e8 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_fr.arb @@ -3,31 +3,32 @@ "noEventsCalendarLabel" : "Pas d'événements", "ofDataPagerLabel" : "de", "pagesDataPagerLabel" : "pages", -"itemsDataPagerLabel" : "articles", -"pdfBookmarksLabel" : "Favoris", -"pdfNoBookmarksLabel" : "Aucun favori trouvé", +"itemsDataPagerLabel" : "éléments", +"pdfBookmarksLabel" : "Signets", +"pdfNoBookmarksLabel" : "Aucun signet trouvé", "pdfScrollStatusOfLabel" : "de", "pdfGoToPageLabel" : "Aller à la page", "pdfEnterPageNumberLabel" : "Entrez le numéro de page", "pdfInvalidPageNumberLabel" : "S'il vous plait, entrez un nombre valide", -"pdfPaginationDialogOkLabel" : "D'accord", +"pdfPaginationDialogOkLabel" : "d'accord", "pdfPaginationDialogCancelLabel" : "ANNULER", -"allowedViewDayLabel" : "journée", +"allowedViewDayLabel" : "Jour", "allowedViewWeekLabel" : "La semaine", "allowedViewWorkWeekLabel" : "Semaine de travail", "allowedViewMonthLabel" : "Mois", -"allowedViewScheduleLabel" : "Programme", +"allowedViewScheduleLabel" : "Calendrier", "allowedViewTimelineDayLabel" : "Jour de la chronologie", -"allowedViewTimelineWeekLabel" : "Semaine chronologique", -"allowedViewTimelineWorkWeekLabel" : "Calendrier de la semaine de travail", +"allowedViewTimelineWeekLabel" : "Semaine de la chronologie", +"allowedViewTimelineWorkWeekLabel" : "Calendrier Semaine de travail", "allowedViewTimelineMonthLabel" : "Mois de la chronologie", "todayLabel" : "Aujourd'hui", -"muharramLabel" : "Muharram", +"weeknumberLabel" : "La semaine", +"muharramLabel" : "Mouharram", "safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", +"rabi1Label" : "Rabi' al-awwal", "rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-awwal", -"jumada2Label" : "Jumada al-thani", +"jumada1Label" : "Joumada al-awwal", +"jumada2Label" : "Joumada al-thani", "rajabLabel" : "Rajab", "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadan", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb index 0cb883f77..01c8245da 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gl.arb @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel" : "Semana de traballo da cronoloxía", "allowedViewTimelineMonthLabel" : "Mes do cronograma", "todayLabel" : "Hoxe", +"weeknumberLabel" : "Semana", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb index b7c16d30d..a4da8736b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_gu.arb @@ -1,15 +1,15 @@ { -"noSelectedDateCalendarLabel" : "કોઈ પસંદ કરેલી તારીખ", -"noEventsCalendarLabel" : "કોઈ ઘટનાઓ નથી", -"ofDataPagerLabel" : "ની", +"noSelectedDateCalendarLabel" : "કોઈ પસંદ કરેલી તારીખ નથી", +"noEventsCalendarLabel" : "કોઈ ઇવેન્ટ નથી", +"ofDataPagerLabel" : "નું", "pagesDataPagerLabel" : "પૃષ્ઠો", "itemsDataPagerLabel" : "વસ્તુઓ", "pdfBookmarksLabel" : "બુકમાર્ક્સ", "pdfNoBookmarksLabel" : "કોઈ બુકમાર્ક્સ મળ્યા નથી", -"pdfScrollStatusOfLabel" : "ની", -"pdfGoToPageLabel" : "પૃષ્ઠ પર જાઓ", -"pdfEnterPageNumberLabel" : "પૃષ્ઠ નંબર દાખલ કરો", -"pdfInvalidPageNumberLabel" : "કૃપા કરી માન્ય નંબર દાખલ કરો", +"pdfScrollStatusOfLabel" : "નું", +"pdfGoToPageLabel" : "પેજ પર જાઓ", +"pdfEnterPageNumberLabel" : "પેજ નંબર દાખલ કરો", +"pdfInvalidPageNumberLabel" : "કૃપા કરીને માન્ય નંબર દાખલ કરો", "pdfPaginationDialogOkLabel" : "બરાબર", "pdfPaginationDialogCancelLabel" : "રદ કરો", "allowedViewDayLabel" : "દિવસ", @@ -19,32 +19,33 @@ "allowedViewScheduleLabel" : "અનુસૂચિ", "allowedViewTimelineDayLabel" : "સમયરેખા દિવસ", "allowedViewTimelineWeekLabel" : "સમયરેખા સપ્તાહ", -"allowedViewTimelineWorkWeekLabel" : "સમયરેખા વર્ક અઠવાડિયું", +"allowedViewTimelineWorkWeekLabel" : "સમયરેખા કાર્ય સપ્તાહ", "allowedViewTimelineMonthLabel" : "સમયરેખા મહિનો", "todayLabel" : "આજે", -"muharramLabel" : "મુહરમ", +"weeknumberLabel" : "અઠવાડિયું", +"muharramLabel" : "મોહર્રમ", "safarLabel" : "સફર", -"rabi1Label" : "રબી 'અલ-અવવાલ", -"rabi2Label" : "રબી 'અલ-થેની", +"rabi1Label" : "રબી અલ-અવવાલ", +"rabi2Label" : "રબી અલ-થાની", "jumada1Label" : "જુમાદા અલ-અવવાલ", -"jumada2Label" : "જુમાદા અલ-થiની", +"jumada2Label" : "જુમાદા અલ-થાની", "rajabLabel" : "રજબ", -"shaabanLabel" : "શઆબન", +"shaabanLabel" : "શાબાન", "ramadanLabel" : "રમઝાન", -"shawwalLabel" : "શવાલ", -"dhualqiLabel" : "ધુ અલ-કિયાદહ", +"shawwalLabel" : "શવવાલ", +"dhualqiLabel" : "ધુ અલ-કિદાહ", "dhualhiLabel" : "ધુ અલ-હિજ્જા", -"shortMuharramLabel" : "મુહ.", -"shortSafarLabel" : "સેફ.", -"shortRabi1Label" : "રબી. હું", -"shortRabi2Label" : "રબી. II", +"shortMuharramLabel" : "મહ.", +"shortSafarLabel" : "સફ.", +"shortRabi1Label" : "રવિ. હું", +"shortRabi2Label" : "રવિ. II", "shortJumada1Label" : "જામ. હું", "shortJumada2Label" : "જામ. II", "shortRajabLabel" : "રાજ.", "shortShaabanLabel" : "શા.", "shortRamadanLabel" : "રામ.", "shortShawwalLabel" : "શો.", -"shortDhualqiLabel" : "ધૂલ-ક્યૂ", -"shortDhualhiLabel" : "ધૂલ-એચ", +"shortDhualqiLabel" : "ધુલ-ક્યૂ", +"shortDhualhiLabel" : "ધુલ-એચ", "daySpanCountLabel" : "દિવસ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb index 156dd0196..53fd5ab55 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_he.arb @@ -1,8 +1,8 @@ { -"noSelectedDateCalendarLabel" : "לא נבחר תאריך", +"noSelectedDateCalendarLabel" : "אין תאריך שנבחר", "noEventsCalendarLabel" : "אין אירועים", "ofDataPagerLabel" : "שֶׁל", -"pagesDataPagerLabel" : "עמודים", +"pagesDataPagerLabel" : "דפים", "itemsDataPagerLabel" : "פריטים", "pdfBookmarksLabel" : "סימניות", "pdfNoBookmarksLabel" : "לא נמצאו סימניות", @@ -19,31 +19,32 @@ "allowedViewScheduleLabel" : "לוח זמנים", "allowedViewTimelineDayLabel" : "יום ציר הזמן", "allowedViewTimelineWeekLabel" : "שבוע ציר הזמן", -"allowedViewTimelineWorkWeekLabel" : "שבוע העבודה של ציר הזמן", +"allowedViewTimelineWorkWeekLabel" : "שבוע עבודה של ציר הזמן", "allowedViewTimelineMonthLabel" : "חודש ציר הזמן", "todayLabel" : "היום", +"weeknumberLabel" : "שָׁבוּעַ", "muharramLabel" : "מוהרם", -"safarLabel" : "ספאר", -"rabi1Label" : "ראבי אל-עוואל", -"rabi2Label" : "ראבי אל-ת'אני", -"jumada1Label" : "ג'ומדה אל-עוואל", -"jumada2Label" : "ג'ומדה אל-ת'אני", +"safarLabel" : "סאפר", +"rabi1Label" : "רבי אלעוואל", +"rabi2Label" : "רבי אל-תני", +"jumada1Label" : "ג'ומאדה אל-אוואל", +"jumada2Label" : "ג'ומאדה אל-תני", "rajabLabel" : "רג'אב", -"shaabanLabel" : "שעבאן", +"shaabanLabel" : "שאבן", "ramadanLabel" : "רמדאן", -"shawwalLabel" : "שאוווול", +"shawwalLabel" : "שוואל", "dhualqiLabel" : "דהו אל-קי'דה", "dhualhiLabel" : "דהו אל-חיג'ה", -"shortMuharramLabel" : "מו", +"shortMuharramLabel" : "מו.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "ראבי. אני", -"shortRabi2Label" : "ראבי. II", -"shortJumada1Label" : "ג'אם. אני", -"shortJumada2Label" : "ג'אם. II", +"shortRabi1Label" : "רבי. אני", +"shortRabi2Label" : "רבי. II", +"shortJumada1Label" : "ג'ום. אני", +"shortJumada2Label" : "ג'ום. II", "shortRajabLabel" : "ראג '.", "shortShaabanLabel" : "שא.", "shortRamadanLabel" : "RAM.", -"shortShawwalLabel" : "שו.", +"shortShawwalLabel" : "שאו.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "יְוֹם" diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb index fc07e4366..afc82cfb7 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hi.arb @@ -8,7 +8,7 @@ "pdfNoBookmarksLabel" : "कोई बुकमार्क नहीं मिला", "pdfScrollStatusOfLabel" : "का", "pdfGoToPageLabel" : "पृष्ठ पर जाओ", -"pdfEnterPageNumberLabel" : "पेज नंबर डालें", +"pdfEnterPageNumberLabel" : "पेज नंबर दर्ज करें", "pdfInvalidPageNumberLabel" : "कृपया एक सही संख्या डालिये", "pdfPaginationDialogOkLabel" : "ठीक है", "pdfPaginationDialogCancelLabel" : "रद्द करना", @@ -20,31 +20,32 @@ "allowedViewTimelineDayLabel" : "समयरेखा दिवस", "allowedViewTimelineWeekLabel" : "समयरेखा सप्ताह", "allowedViewTimelineWorkWeekLabel" : "समयरेखा कार्य सप्ताह", -"allowedViewTimelineMonthLabel" : "समय महीना", +"allowedViewTimelineMonthLabel" : "समयरेखा महीना", "todayLabel" : "आज", +"weeknumberLabel" : "सप्ताह", "muharramLabel" : "मुहर्रम", "safarLabel" : "सफ़र", -"rabi1Label" : "रबी 'अल-अव्वल", -"rabi2Label" : "रबी 'अल-थानी", +"rabi1Label" : "रबी अल-अव्वल", +"rabi2Label" : "रबी अल-थानी", "jumada1Label" : "जुमादा अल-अव्वल", -"jumada2Label" : "जुमादा अल-थानी", -"rajabLabel" : "रज्जब", -"shaabanLabel" : "Sha'aban", +"jumada2Label" : "जुमादा अल-थानि", +"rajabLabel" : "राजाबी", +"shaabanLabel" : "शाबानो", "ramadanLabel" : "रमजान", "shawwalLabel" : "शावाल", -"dhualqiLabel" : "धू अल-कियूबाह", -"dhualhiLabel" : "धू अल-हिज्जाह", -"shortMuharramLabel" : "Muh।", -"shortSafarLabel" : "सैफ़।", +"dhualqiLabel" : "धू अल क़िदाही", +"dhualhiLabel" : "धू अल-हिज्जाही", +"shortMuharramLabel" : "मुह।", +"shortSafarLabel" : "साफ.", "shortRabi1Label" : "रबी। मैं", "shortRabi2Label" : "रबी। द्वितीय", -"shortJumada1Label" : "Jum। मैं", -"shortJumada2Label" : "Jum। द्वितीय", -"shortRajabLabel" : "राज।", -"shortShaabanLabel" : "शा।", -"shortRamadanLabel" : "राम।", +"shortJumada1Label" : "जम. मैं", +"shortJumada2Label" : "जम. द्वितीय", +"shortRajabLabel" : "राज.", +"shortShaabanLabel" : "शा.", +"shortRamadanLabel" : "टक्कर मारना।", "shortShawwalLabel" : "शॉ।", -"shortDhualqiLabel" : "Dhu'l-क्यू", -"shortDhualhiLabel" : "Dhu'l-एच", +"shortDhualqiLabel" : "धुल-क्यू", +"shortDhualhiLabel" : "धुल-हो", "daySpanCountLabel" : "दिन" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb index e8766c81e..b16751f6f 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hr.arb @@ -3,13 +3,13 @@ "noEventsCalendarLabel" : "Nema događaja", "ofDataPagerLabel" : "od", "pagesDataPagerLabel" : "stranice", -"itemsDataPagerLabel" : "predmeta", +"itemsDataPagerLabel" : "stavke", "pdfBookmarksLabel" : "Oznake", -"pdfNoBookmarksLabel" : "Nisu pronađene oznake", +"pdfNoBookmarksLabel" : "Oznake nisu pronađene", "pdfScrollStatusOfLabel" : "od", "pdfGoToPageLabel" : "Idi na stranicu", "pdfEnterPageNumberLabel" : "Unesite broj stranice", -"pdfInvalidPageNumberLabel" : "Unesite valjani broj", +"pdfInvalidPageNumberLabel" : "Unesite važeći broj", "pdfPaginationDialogOkLabel" : "u redu", "pdfPaginationDialogCancelLabel" : "OTKAZATI", "allowedViewDayLabel" : "Dan", @@ -19,21 +19,22 @@ "allowedViewScheduleLabel" : "Raspored", "allowedViewTimelineDayLabel" : "Dan vremenske trake", "allowedViewTimelineWeekLabel" : "Tjedan vremenske trake", -"allowedViewTimelineWorkWeekLabel" : "Tjedan radnog vremena", +"allowedViewTimelineWorkWeekLabel" : "Radni tjedan vremenske trake", "allowedViewTimelineMonthLabel" : "Mjesec vremenske trake", "todayLabel" : "Danas", -"muharramLabel" : "Muharram", +"weeknumberLabel" : "Tjedan", +"muharramLabel" : "Muharrem", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", "rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Džumada al-avval", -"jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Rajab", -"shaabanLabel" : "Ša'aban", +"jumada1Label" : "Džumada el-evval", +"jumada2Label" : "Džumada al-tani", +"rajabLabel" : "Redžeb", +"shaabanLabel" : "Ša'ban", "ramadanLabel" : "Ramazan", -"shawwalLabel" : "Shawwal", +"shawwalLabel" : "Ševval", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hidždža", +"dhualhiLabel" : "Zul al-Hidždže", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. Ja", @@ -44,7 +45,7 @@ "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Radna memorija.", "shortShawwalLabel" : "Shaw.", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualqiLabel" : "Zul-Q", +"shortDhualhiLabel" : "Zul-H", "daySpanCountLabel" : "Dan" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb index 94ce54a29..66a98f4e6 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hu.arb @@ -2,10 +2,10 @@ "noSelectedDateCalendarLabel" : "Nincs kiválasztott dátum", "noEventsCalendarLabel" : "Nincs esemény", "ofDataPagerLabel" : "nak,-nek", -"pagesDataPagerLabel" : "oldalakat", -"itemsDataPagerLabel" : "elemeket", +"pagesDataPagerLabel" : "oldalak", +"itemsDataPagerLabel" : "tételeket", "pdfBookmarksLabel" : "Könyvjelzők", -"pdfNoBookmarksLabel" : "Nem találhatók könyvjelzők", +"pdfNoBookmarksLabel" : "Nem található könyvjelző", "pdfScrollStatusOfLabel" : "nak,-nek", "pdfGoToPageLabel" : "Menj az oldalra", "pdfEnterPageNumberLabel" : "Adja meg az oldalszámot", @@ -19,23 +19,24 @@ "allowedViewScheduleLabel" : "Menetrend", "allowedViewTimelineDayLabel" : "Idővonal napja", "allowedViewTimelineWeekLabel" : "Idővonal hét", -"allowedViewTimelineWorkWeekLabel" : "Időrend munkahete", -"allowedViewTimelineMonthLabel" : "Idővonal Hónap", +"allowedViewTimelineWorkWeekLabel" : "Idővonal munkahét", +"allowedViewTimelineMonthLabel" : "Idővonal hónap", "todayLabel" : "Ma", +"weeknumberLabel" : "Hét", "muharramLabel" : "Muharram", -"safarLabel" : "Szafar", -"rabi1Label" : "Rabi 'al-awwal", +"safarLabel" : "Safar", +"rabi1Label" : "Rabi al-awwal", "rabi2Label" : "Rabi 'al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", -"shaabanLabel" : "Sha'aban", +"shaabanLabel" : "Shaabán", "ramadanLabel" : "Ramadán", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hidzsáh", +"dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Szaf.", +"shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. én", "shortRabi2Label" : "Rabi. II", "shortJumada1Label" : "Jum. én", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb index bdd165067..f0d91cc27 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_hy.arb @@ -1,50 +1,51 @@ { "noSelectedDateCalendarLabel" : "Ընտրված ամսաթիվ չկա", -"noEventsCalendarLabel" : "Ոչ մի իրադարձություն", +"noEventsCalendarLabel" : "Միջոցառումներ չկան", "ofDataPagerLabel" : "ի", "pagesDataPagerLabel" : "էջեր", "itemsDataPagerLabel" : "իրեր", "pdfBookmarksLabel" : "Էջանիշեր", "pdfNoBookmarksLabel" : "Էջանիշներ չեն գտնվել", "pdfScrollStatusOfLabel" : "ի", -"pdfGoToPageLabel" : "Անցեք էջին", +"pdfGoToPageLabel" : "Գնալ դեպի էջ", "pdfEnterPageNumberLabel" : "Մուտքագրեք էջի համարը", "pdfInvalidPageNumberLabel" : "Խնդրում ենք մուտքագրել վավեր համար", "pdfPaginationDialogOkLabel" : "լավ", -"pdfPaginationDialogCancelLabel" : "ՉԵՅԱԼ", +"pdfPaginationDialogCancelLabel" : "ՉԵՉԵԼ", "allowedViewDayLabel" : "Օր", "allowedViewWeekLabel" : "Շաբաթ", "allowedViewWorkWeekLabel" : "Աշխատանքային շաբաթ", "allowedViewMonthLabel" : "Ամիս", -"allowedViewScheduleLabel" : "Գրաֆիկ", -"allowedViewTimelineDayLabel" : "Elineամանակացույցի օր", -"allowedViewTimelineWeekLabel" : "Elineամանակացույցի շաբաթ", -"allowedViewTimelineWorkWeekLabel" : "Elineամանակացույցի աշխատանքային շաբաթ", -"allowedViewTimelineMonthLabel" : "Elineամանակացույցի ամիս", +"allowedViewScheduleLabel" : "Ժամանակացույց", +"allowedViewTimelineDayLabel" : "Timամանակացույցի օր", +"allowedViewTimelineWeekLabel" : "Timամանակացույցի շաբաթ", +"allowedViewTimelineWorkWeekLabel" : "Timամանակացույցի աշխատանքային շաբաթ", +"allowedViewTimelineMonthLabel" : "Timամանակացույցի ամիս", "todayLabel" : "Այսօր", -"muharramLabel" : "Մուհարամ", +"weeknumberLabel" : "Շաբաթ", +"muharramLabel" : "Մուհարրամ", "safarLabel" : "Սաֆար", -"rabi1Label" : "Ռաբի ալ-Աուվալ", +"rabi1Label" : "Ռաբի ալ-ավալ", "rabi2Label" : "Ռաբի ալ-թանի", -"jumada1Label" : "Umումադա ալ-վուալ", +"jumada1Label" : "Umումադա ալ-ավվալ", "jumada2Label" : "Umումադա ալ-թանի", "rajabLabel" : "Ռաջաբ", "shaabanLabel" : "Շաաբան", "ramadanLabel" : "Ռամադան", "shawwalLabel" : "Շավալ", "dhualqiLabel" : "Դհու ալ-Քիդա", -"dhualhiLabel" : "Huու ալ-Հիջա", -"shortMuharramLabel" : "Մուհ", +"dhualhiLabel" : "Դհու ալ-Հիջա", +"shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Սաֆ", -"shortRabi1Label" : "Ռաբի Ես", -"shortRabi2Label" : "Ռաբի II", -"shortJumada1Label" : "Umում Ես", -"shortJumada2Label" : "Umում II", -"shortRajabLabel" : "Ռաջ", +"shortRabi1Label" : "Ռաբի. Ես", +"shortRabi2Label" : "Ռաբի. II", +"shortJumada1Label" : "Ցատկել: Ես", +"shortJumada2Label" : "Ցատկել: II", +"shortRajabLabel" : "Ռաջ.", "shortShaabanLabel" : "Շա", -"shortRamadanLabel" : "Խոյ", -"shortShawwalLabel" : "Շոուն", -"shortDhualqiLabel" : "Dhu'l-Q", +"shortRamadanLabel" : "Խոյ.", +"shortShawwalLabel" : "Շոու.", +"shortDhualqiLabel" : "Դհուլ-Ք", "shortDhualhiLabel" : "Դհուլ-Հ", "daySpanCountLabel" : "Օր" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb index 75d449838..5f1e76c91 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_id.arb @@ -10,41 +10,42 @@ "pdfGoToPageLabel" : "Buka halaman", "pdfEnterPageNumberLabel" : "Masukkan nomor halaman", "pdfInvalidPageNumberLabel" : "Harap masukkan nomor yang valid", -"pdfPaginationDialogOkLabel" : "baik", +"pdfPaginationDialogOkLabel" : "oke", "pdfPaginationDialogCancelLabel" : "MEMBATALKAN", "allowedViewDayLabel" : "Hari", -"allowedViewWeekLabel" : "Minggu", +"allowedViewWeekLabel" : "Pekan", "allowedViewWorkWeekLabel" : "Minggu Kerja", "allowedViewMonthLabel" : "Bulan", -"allowedViewScheduleLabel" : "Susunan acara", -"allowedViewTimelineDayLabel" : "Timeline Day", -"allowedViewTimelineWeekLabel" : "Timeline Week", -"allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", -"allowedViewTimelineMonthLabel" : "Bulan Garis Waktu", +"allowedViewScheduleLabel" : "Jadwal", +"allowedViewTimelineDayLabel" : "Hari Garis Waktu", +"allowedViewTimelineWeekLabel" : "Garis Waktu Minggu", +"allowedViewTimelineWorkWeekLabel" : "Garis Waktu Minggu Kerja", +"allowedViewTimelineMonthLabel" : "Garis Waktu Bulan", "todayLabel" : "Hari ini", +"weeknumberLabel" : "Pekan", "muharramLabel" : "Muharram", -"safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", +"safarLabel" : "Safari", +"rabi1Label" : "Rabi' al-awwal", +"rabi2Label" : "Rabi' al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", -"shaabanLabel" : "Sha'aban", +"shaabanLabel" : "Sya'ban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Syawal", -"dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", +"dhualqiLabel" : "Dzul Qi'dah", +"dhualhiLabel" : "Dzulhijjah", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. saya", +"shortRabi1Label" : "Rabi. Saya", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. saya", +"shortJumada1Label" : "Jum. Saya", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", -"shortShaabanLabel" : "Sha.", -"shortRamadanLabel" : "Ram.", -"shortShawwalLabel" : "Shaw.", -"shortDhualqiLabel" : "Dzul-Q", -"shortDhualhiLabel" : "Dzul-H", +"shortShaabanLabel" : "Sha", +"shortRamadanLabel" : "Rama.", +"shortShawwalLabel" : "Sya.", +"shortDhualqiLabel" : "Dzul Q", +"shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Hari" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb index 3b655f353..8a31e5391 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_is.arb @@ -2,14 +2,14 @@ "noSelectedDateCalendarLabel" : "Engin valin dagsetning", "noEventsCalendarLabel" : "Engir viðburðir", "ofDataPagerLabel" : "af", -"pagesDataPagerLabel" : "blaðsíður", +"pagesDataPagerLabel" : "síður", "itemsDataPagerLabel" : "hlutir", "pdfBookmarksLabel" : "Bókamerki", "pdfNoBookmarksLabel" : "Engin bókamerki fundust", "pdfScrollStatusOfLabel" : "af", "pdfGoToPageLabel" : "Farðu á síðu", -"pdfEnterPageNumberLabel" : "Sláðu inn blaðsíðunúmer", -"pdfInvalidPageNumberLabel" : "Vinsamlegast sláðu inn gilt númer", +"pdfEnterPageNumberLabel" : "Sláðu inn síðunúmer", +"pdfInvalidPageNumberLabel" : "Sláðu inn gilt númer", "pdfPaginationDialogOkLabel" : "Allt í lagi", "pdfPaginationDialogCancelLabel" : "HÆTTA", "allowedViewDayLabel" : "Dagur", @@ -17,11 +17,12 @@ "allowedViewWorkWeekLabel" : "Vinnuvika", "allowedViewMonthLabel" : "Mánuður", "allowedViewScheduleLabel" : "Dagskrá", -"allowedViewTimelineDayLabel" : "Tímalínudagur", -"allowedViewTimelineWeekLabel" : "Tímalínuvika", -"allowedViewTimelineWorkWeekLabel" : "Vinnuvika tímalínu", -"allowedViewTimelineMonthLabel" : "Tímalínumánuður", +"allowedViewTimelineDayLabel" : "Dagur tímalínu", +"allowedViewTimelineWeekLabel" : "Tímalína Vika", +"allowedViewTimelineWorkWeekLabel" : "Vinnutími tímalínu", +"allowedViewTimelineMonthLabel" : "Tímalína mánuður", "todayLabel" : "Í dag", +"weeknumberLabel" : "Vika", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", @@ -38,8 +39,8 @@ "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. Ég", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. Ég", -"shortJumada2Label" : "Jum. II", +"shortJumada1Label" : "Jamm. Ég", +"shortJumada2Label" : "Jamm. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Vinnsluminni.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb index f843813eb..72e3fb194 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_it.arb @@ -3,12 +3,12 @@ "noEventsCalendarLabel" : "Nessun evento", "ofDataPagerLabel" : "di", "pagesDataPagerLabel" : "pagine", -"itemsDataPagerLabel" : "elementi", -"pdfBookmarksLabel" : "Segnalibri", +"itemsDataPagerLabel" : "Oggetti", +"pdfBookmarksLabel" : "segnalibri", "pdfNoBookmarksLabel" : "Nessun segnalibro trovato", "pdfScrollStatusOfLabel" : "di", "pdfGoToPageLabel" : "Vai alla pagina", -"pdfEnterPageNumberLabel" : "Immettere il numero di pagina", +"pdfEnterPageNumberLabel" : "Inserisci il numero di pagina", "pdfInvalidPageNumberLabel" : "Per favore, inserire un numero valido", "pdfPaginationDialogOkLabel" : "ok", "pdfPaginationDialogCancelLabel" : "ANNULLA", @@ -17,15 +17,16 @@ "allowedViewWorkWeekLabel" : "Settimana di lavoro", "allowedViewMonthLabel" : "Mese", "allowedViewScheduleLabel" : "Programma", -"allowedViewTimelineDayLabel" : "Timeline Day", -"allowedViewTimelineWeekLabel" : "Settimana temporale", -"allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", -"allowedViewTimelineMonthLabel" : "Mese della sequenza temporale", +"allowedViewTimelineDayLabel" : "Giorno della cronologia", +"allowedViewTimelineWeekLabel" : "Timeline settimana", +"allowedViewTimelineWorkWeekLabel" : "Cronologia della settimana lavorativa", +"allowedViewTimelineMonthLabel" : "Cronologia del mese", "todayLabel" : "Oggi", +"weeknumberLabel" : "Settimana", "muharramLabel" : "Muharram", -"safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", +"safarLabel" : "Safari", +"rabi1Label" : "Rabi' al-awwal", +"rabi2Label" : "Rabi' al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", @@ -34,8 +35,8 @@ "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Saf.", +"shortMuharramLabel" : "mah.", +"shortSafarLabel" : "sicuro", "shortRabi1Label" : "Rabi. io", "shortRabi2Label" : "Rabi. II", "shortJumada1Label" : "Jum. io", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb index 3031fe9ee..f850a4477 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ja.arb @@ -10,10 +10,10 @@ "pdfGoToPageLabel" : "ページに移動", "pdfEnterPageNumberLabel" : "ページ番号を入力してください", "pdfInvalidPageNumberLabel" : "有効な数値を入力してください", -"pdfPaginationDialogOkLabel" : "OK", +"pdfPaginationDialogOkLabel" : "わかった", "pdfPaginationDialogCancelLabel" : "キャンセル", "allowedViewDayLabel" : "日", -"allowedViewWeekLabel" : "週間", +"allowedViewWeekLabel" : "週", "allowedViewWorkWeekLabel" : "労働週", "allowedViewMonthLabel" : "月", "allowedViewScheduleLabel" : "スケジュール", @@ -22,16 +22,17 @@ "allowedViewTimelineWorkWeekLabel" : "タイムラインワークウィーク", "allowedViewTimelineMonthLabel" : "タイムライン月", "todayLabel" : "今日", +"weeknumberLabel" : "週", "muharramLabel" : "ムハッラム", -"safarLabel" : "サファル", -"rabi1Label" : "Rabi'al-awwal", -"rabi2Label" : "ラビー・アルタニ", +"safarLabel" : "サファー", +"rabi1Label" : "ラビー・ウル・アウワル", +"rabi2Label" : "ラビー・アル・タニ", "jumada1Label" : "ジュマーダアルアウワル", -"jumada2Label" : "ジュマーダッサーニ", +"jumada2Label" : "ジュマーダ・アルタニ", "rajabLabel" : "ラジャブ", "shaabanLabel" : "シャアバーン", "ramadanLabel" : "ラマダン", -"shawwalLabel" : "Shawwal", +"shawwalLabel" : "シャウワール", "dhualqiLabel" : "ズル・カイダ", "dhualhiLabel" : "ズル・ヒッジャ", "shortMuharramLabel" : "ムー。", @@ -41,10 +42,10 @@ "shortJumada1Label" : "ジャム。私", "shortJumada2Label" : "ジャム。 II", "shortRajabLabel" : "ラージ。", -"shortShaabanLabel" : "Sha。", -"shortRamadanLabel" : "羊。", +"shortShaabanLabel" : "シャ。", +"shortRamadanLabel" : "RAM。", "shortShawwalLabel" : "ショー。", "shortDhualqiLabel" : "ズル・カイダ", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualhiLabel" : "ズル・カイダ", "daySpanCountLabel" : "日" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb index 8144bf848..8571f3894 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ka.arb @@ -1,15 +1,15 @@ { "noSelectedDateCalendarLabel" : "არჩეული თარიღი არ არის", "noEventsCalendarLabel" : "არანაირი მოვლენა", -"ofDataPagerLabel" : "საქართველოს", +"ofDataPagerLabel" : "-ის", "pagesDataPagerLabel" : "გვერდები", -"itemsDataPagerLabel" : "საგნები", +"itemsDataPagerLabel" : "ნივთები", "pdfBookmarksLabel" : "სანიშნეები", -"pdfNoBookmarksLabel" : "სანიშნე ვერ მოიძებნა", -"pdfScrollStatusOfLabel" : "საქართველოს", +"pdfNoBookmarksLabel" : "სანიშნეები ვერ მოიძებნა", +"pdfScrollStatusOfLabel" : "-ის", "pdfGoToPageLabel" : "Გადადით გვერდზე", "pdfEnterPageNumberLabel" : "შეიყვანეთ გვერდის ნომერი", -"pdfInvalidPageNumberLabel" : "გთხოვთ, შეიყვანოთ სწორი ნომერი", +"pdfInvalidPageNumberLabel" : "გთხოვთ შეიყვანოთ სწორი ნომერი", "pdfPaginationDialogOkLabel" : "კარგი", "pdfPaginationDialogCancelLabel" : "გაუქმება", "allowedViewDayLabel" : "Დღის", @@ -17,34 +17,35 @@ "allowedViewWorkWeekLabel" : "Სამუშაო კვირა", "allowedViewMonthLabel" : "თვე", "allowedViewScheduleLabel" : "განრიგი", -"allowedViewTimelineDayLabel" : "ქრონოლოგია დღე", -"allowedViewTimelineWeekLabel" : "ქრონოლოგია კვირა", +"allowedViewTimelineDayLabel" : "ქრონოლოგიის დღე", +"allowedViewTimelineWeekLabel" : "ქრონოლოგიის კვირა", "allowedViewTimelineWorkWeekLabel" : "ქრონოლოგია სამუშაო კვირა", -"allowedViewTimelineMonthLabel" : "ქრონოლოგია თვე", +"allowedViewTimelineMonthLabel" : "ქრონოლოგიის თვე", "todayLabel" : "დღეს", +"weeknumberLabel" : "კვირა", "muharramLabel" : "მუჰარამი", "safarLabel" : "საფარი", -"rabi1Label" : "რაბი 'ალ-ავალი", -"rabi2Label" : "რაბი 'ალ-ტანი", -"jumada1Label" : "ჯუმადა ალ-ავალი", +"rabi1Label" : "რაბი ალ-ავვალი", +"rabi2Label" : "რაბი ალ-ტანი", +"jumada1Label" : "ჯუმადა ალ-ავვალი", "jumada2Label" : "ჯუმადა ალ-ტანი", "rajabLabel" : "რაჯაბ", "shaabanLabel" : "შააბანი", -"ramadanLabel" : "რამაზანი", -"shawwalLabel" : "შავალი", -"dhualqiLabel" : "დუ ალ-ქიდა", -"dhualhiLabel" : "დუ ალ-ჰიჯა", +"ramadanLabel" : "რამადანი", +"shawwalLabel" : "შოვალი", +"dhualqiLabel" : "დჰუ-კიდა", +"dhualhiLabel" : "დჰუ-ჰიჯა", "shortMuharramLabel" : "მუჰ", -"shortSafarLabel" : "საფ.", -"shortRabi1Label" : "რაბი მე", -"shortRabi2Label" : "რაბი II", -"shortJumada1Label" : "ჯუმი მე", -"shortJumada2Label" : "ჯუმი II", -"shortRajabLabel" : "რაჟ", -"shortShaabanLabel" : "შა", -"shortRamadanLabel" : "ვერძი", -"shortShawwalLabel" : "შოუ", -"shortDhualqiLabel" : "დულ-ქ", -"shortDhualhiLabel" : "დულ-ჰ", -"daySpanCountLabel" : "Დღეს" +"shortSafarLabel" : "სეფი.", +"shortRabi1Label" : "რაბი. მე", +"shortRabi2Label" : "რაბი. II", +"shortJumada1Label" : "ჯუმ მე", +"shortJumada2Label" : "ჯუმ II", +"shortRajabLabel" : "რაჯი.", +"shortShaabanLabel" : "შა.", +"shortRamadanLabel" : "რამი.", +"shortShawwalLabel" : "შოუ.", +"shortDhualqiLabel" : "დჰულ-ქ", +"shortDhualhiLabel" : "დჰულ-ჰ", +"daySpanCountLabel" : "Დღის" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb index dd4fb92cd..cc0dd9785 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kk.arb @@ -1,45 +1,46 @@ { "noSelectedDateCalendarLabel" : "Таңдалған күн жоқ", -"noEventsCalendarLabel" : "Іс-шаралар жоқ", -"ofDataPagerLabel" : "туралы", +"noEventsCalendarLabel" : "Іс -шаралар жоқ", +"ofDataPagerLabel" : "ның", "pagesDataPagerLabel" : "беттер", "itemsDataPagerLabel" : "заттар", "pdfBookmarksLabel" : "Бетбелгілер", -"pdfNoBookmarksLabel" : "Бетбелгілер табылған жоқ", -"pdfScrollStatusOfLabel" : "туралы", -"pdfGoToPageLabel" : "Параққа өту", +"pdfNoBookmarksLabel" : "Бетбелгілер табылмады", +"pdfScrollStatusOfLabel" : "ның", +"pdfGoToPageLabel" : "Бетке өту", "pdfEnterPageNumberLabel" : "Бет нөмірін енгізіңіз", -"pdfInvalidPageNumberLabel" : "Жарамды нөмірді енгізіңіз", +"pdfInvalidPageNumberLabel" : "Жарамды нөмір енгізіңіз", "pdfPaginationDialogOkLabel" : "ЖАРАЙДЫ МА", -"pdfPaginationDialogCancelLabel" : "ТОҚТАТУ", +"pdfPaginationDialogCancelLabel" : "ЖОЮ", "allowedViewDayLabel" : "Күн", "allowedViewWeekLabel" : "Апта", "allowedViewWorkWeekLabel" : "Жұмыс аптасы", "allowedViewMonthLabel" : "Ай", "allowedViewScheduleLabel" : "Кесте", -"allowedViewTimelineDayLabel" : "Хронология күні", -"allowedViewTimelineWeekLabel" : "Уақыт кестесі", -"allowedViewTimelineWorkWeekLabel" : "Жұмыс кестесі", -"allowedViewTimelineMonthLabel" : "Хронология айы", +"allowedViewTimelineDayLabel" : "Уақыт шкаласы күні", +"allowedViewTimelineWeekLabel" : "Уақыт кестесі аптасы", +"allowedViewTimelineWorkWeekLabel" : "Жұмыс аптасының кестесі", +"allowedViewTimelineMonthLabel" : "Уақыт шкаласы айы", "todayLabel" : "Бүгін", +"weeknumberLabel" : "Апта", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", -"rabi1Label" : "Раби 'әл-әууал", -"rabi2Label" : "Раби 'аль-тени", -"jumada1Label" : "Джумада әл-аввал", -"jumada2Label" : "Джумада аль-тени", +"rabi1Label" : "Рабиъул-әууәл", +"rabi2Label" : "Раби ал-Тани", +"jumada1Label" : "Жұмада әл-аввал", +"jumada2Label" : "Джумада әл-Тани", "rajabLabel" : "Раджаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамазан", "shawwalLabel" : "Шаввал", -"dhualqiLabel" : "Зуль-әл-Қида", -"dhualhiLabel" : "Зуль-Хиджа", -"shortMuharramLabel" : "Мұх.", -"shortSafarLabel" : "Қауіпсіз.", +"dhualqiLabel" : "Зул-аль-Қида", +"dhualhiLabel" : "Зулхиджа", +"shortMuharramLabel" : "Мух.", +"shortSafarLabel" : "Saf.", "shortRabi1Label" : "Раби. Мен", "shortRabi2Label" : "Раби. II", -"shortJumada1Label" : "Джум. Мен", -"shortJumada2Label" : "Джум. II", +"shortJumada1Label" : "Jum. Мен", +"shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Радж.", "shortShaabanLabel" : "Ша.", "shortRamadanLabel" : "Жедел Жадтау Құрылғысы.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb index 65f4fc026..f5928f072 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_km.arb @@ -3,48 +3,49 @@ "noEventsCalendarLabel" : "គ្មានព្រឹត្តិការណ៍", "ofDataPagerLabel" : "នៃ", "pagesDataPagerLabel" : "ទំព័រ", -"itemsDataPagerLabel" : "របស់របរ", +"itemsDataPagerLabel" : "ធាតុ", "pdfBookmarksLabel" : "ចំណាំ", -"pdfNoBookmarksLabel" : "រកមិនឃើញចំណាំទេ", +"pdfNoBookmarksLabel" : "រកមិនឃើញចំណាំ", "pdfScrollStatusOfLabel" : "នៃ", "pdfGoToPageLabel" : "ទៅកាន់​ទំព័រ", "pdfEnterPageNumberLabel" : "បញ្ចូលលេខទំព័រ", "pdfInvalidPageNumberLabel" : "សូមបញ្ចូលលេខដែលត្រឹមត្រូវ", "pdfPaginationDialogOkLabel" : "យល់ព្រម", -"pdfPaginationDialogCancelLabel" : "CANCEL", +"pdfPaginationDialogCancelLabel" : "បោះបង់", "allowedViewDayLabel" : "ថ្ងៃ", "allowedViewWeekLabel" : "សប្តាហ៍", "allowedViewWorkWeekLabel" : "សប្តាហ៍ការងារ", "allowedViewMonthLabel" : "ខែ", "allowedViewScheduleLabel" : "កាលវិភាគ", -"allowedViewTimelineDayLabel" : "ថ្ងៃកំណត់ពេលវេលា", -"allowedViewTimelineWeekLabel" : "សប្តាហ៍កំណត់ពេល", -"allowedViewTimelineWorkWeekLabel" : "សប្តាហ៍ការងារកំណត់ពេល", -"allowedViewTimelineMonthLabel" : "ខែពេលវេលា", +"allowedViewTimelineDayLabel" : "ទិវាកំណត់ពេលវេលា", +"allowedViewTimelineWeekLabel" : "សប្តាហ៍កំណត់ពេលវេលា", +"allowedViewTimelineWorkWeekLabel" : "សប្តាហ៍ការងារកំណត់ពេលវេលា", +"allowedViewTimelineMonthLabel" : "ខែកំណត់ពេលវេលា", "todayLabel" : "ថ្ងៃនេះ", -"muharramLabel" : "Muharram", -"safarLabel" : "Safar", -"rabi1Label" : "រ៉ាប៊ី 'អាល់ - ដាស់", -"rabi2Label" : "រ៉ាប៊ីអាល់ - ថាវី", -"jumada1Label" : "ជូម៉ាដាអាល់អាល់វ៉ាល", -"jumada2Label" : "ចាមដាអាល់ - ថាវី", -"rajabLabel" : "រ៉ាបាប់", -"shaabanLabel" : "សាអាបាន", -"ramadanLabel" : "រ៉ាម៉ាដាន", +"weeknumberLabel" : "សប្តាហ៍", +"muharramLabel" : "មូហារ៉ាម", +"safarLabel" : "សាហ្វារ៉ា", +"rabi1Label" : "រ៉ាប៊ីអាល់អាវ៉ាល់", +"rabi2Label" : "រ៉ាប៊ីអាល់ថានី", +"jumada1Label" : "ជូម៉ាដាអាល់អាវ៉ាល់", +"jumada2Label" : "ជូម៉ាដាអាល់ថានី", +"rajabLabel" : "រ៉ាចាប", +"shaabanLabel" : "សាបាអាន", +"ramadanLabel" : "បុណ្យរ៉ាម៉ាដាន", "shawwalLabel" : "Shawwal", -"dhualqiLabel" : "ឌូអាល់ឈីឌា", -"dhualhiLabel" : "ឌូអាល់ហីចា", -"shortMuharramLabel" : "Muh ។", -"shortSafarLabel" : "សុវត្ថិភាព។", +"dhualqiLabel" : "ឌូអាល់កៃដា", +"dhualhiLabel" : "ឌូអាល់ហ៊ីចា", +"shortMuharramLabel" : "ម។", +"shortSafarLabel" : "សាហ្វ", "shortRabi1Label" : "រ៉ាប៊ី។ ខ្ញុំ", "shortRabi2Label" : "រ៉ាប៊ី។ II", -"shortJumada1Label" : "ចាម។ ខ្ញុំ", -"shortJumada2Label" : "ចាម។ II", -"shortRajabLabel" : "រ៉ាក់។", +"shortJumada1Label" : "ជុម។ ខ្ញុំ", +"shortJumada2Label" : "ជុម។ II", +"shortRajabLabel" : "រាជ", "shortShaabanLabel" : "សា។", "shortRamadanLabel" : "អង្គ​ចងចាំ។", -"shortShawwalLabel" : "ទឹករលក។", -"shortDhualqiLabel" : "ឌូហល", -"shortDhualhiLabel" : "ឌូអេល - អេ", +"shortShawwalLabel" : "សៅ។", +"shortDhualqiLabel" : "ឌូល-ឃ", +"shortDhualhiLabel" : "ឌូ-អិល", "daySpanCountLabel" : "ថ្ងៃ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb index 3d7bb38f8..fdff31e2b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_kn.arb @@ -11,7 +11,7 @@ "pdfEnterPageNumberLabel" : "ಪುಟ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ", "pdfInvalidPageNumberLabel" : "ದಯವಿಟ್ಟು ಮಾನ್ಯವಾದ ಸಂಖ್ಯೆಯನ್ನು ನಮೂದಿಸಿ", "pdfPaginationDialogOkLabel" : "ಸರಿ", -"pdfPaginationDialogCancelLabel" : "ರದ್ದುಗೊಳಿಸಿ", +"pdfPaginationDialogCancelLabel" : "ಕ್ಯಾನ್ಸಲ್", "allowedViewDayLabel" : "ದಿನ", "allowedViewWeekLabel" : "ವಾರ", "allowedViewWorkWeekLabel" : "ಕೆಲಸದ ವಾರ", @@ -22,29 +22,30 @@ "allowedViewTimelineWorkWeekLabel" : "ಟೈಮ್‌ಲೈನ್ ಕೆಲಸದ ವಾರ", "allowedViewTimelineMonthLabel" : "ಟೈಮ್‌ಲೈನ್ ತಿಂಗಳು", "todayLabel" : "ಇಂದು", +"weeknumberLabel" : "ವಾರ", "muharramLabel" : "ಮೊಹರಂ", "safarLabel" : "ಸಫರ್", -"rabi1Label" : "ರಬಿ ಅಲ್-ಅವ್ವಾಲ್", -"rabi2Label" : "ರಬಿ ಅಲ್-ಥಾನಿ", -"jumada1Label" : "ಜುಮಡಾ ಅಲ್-ಅವ್ವಾಲ್", -"jumada2Label" : "ಜುಮಡಾ ಅಲ್-ಥಾನಿ", -"rajabLabel" : "ರಾಜಾಬ್", -"shaabanLabel" : "ಶಾಬಾನ್", +"rabi1Label" : "ರಬಿ ಅಲ್-ಅವ್ವಲ್", +"rabi2Label" : "ರಬಿ 'ಅಲ್-ಥಾನಿ", +"jumada1Label" : "ಜುಮದಾ ಅಲ್-ಅವ್ವಲ್", +"jumada2Label" : "ಜುಮದಾ ಅಲ್-ಥಾನಿ", +"rajabLabel" : "ರಜಬ್", +"shaabanLabel" : "ಶಬಾನ್", "ramadanLabel" : "ರಂಜಾನ್", "shawwalLabel" : "ಶವ್ವಾಲ್", -"dhualqiLabel" : "ಧು ಅಲ್-ಖಿದಾ", +"dhualqiLabel" : "ಧು ಅಲ್-ಕಿಯಾಡಾ", "dhualhiLabel" : "ಧು ಅಲ್-ಹಿಜ್ಜಾ", "shortMuharramLabel" : "ಮುಹ್.", -"shortSafarLabel" : "ಸುರಕ್ಷಿತ.", -"shortRabi1Label" : "ರಬಿ. ನಾನು", -"shortRabi2Label" : "ರಬಿ. II", -"shortJumada1Label" : "ಜಮ್. ನಾನು", -"shortJumada2Label" : "ಜಮ್. II", -"shortRajabLabel" : "ರಾಜ್.", -"shortShaabanLabel" : "ಶಾ.", +"shortSafarLabel" : "ಸೇಫ್", +"shortRabi1Label" : "ರಬಿ ನಾನು", +"shortRabi2Label" : "ರಬಿ II", +"shortJumada1Label" : "ಜುಂ. ನಾನು", +"shortJumada2Label" : "ಜುಂ. II", +"shortRajabLabel" : "ರಾಜ್", +"shortShaabanLabel" : "ಶಾ", "shortRamadanLabel" : "ರಾಮ್.", -"shortShawwalLabel" : "ಶಾ.", -"shortDhualqiLabel" : "ಧುಲ್-ಕ್ಯೂ", -"shortDhualhiLabel" : "ಧುಲ್-ಹೆಚ್", +"shortShawwalLabel" : "ಶಾ", +"shortDhualqiLabel" : "ಧುಲ್-ಪ್ರ", +"shortDhualhiLabel" : "ಧುಲ್-ಎಚ್", "daySpanCountLabel" : "ದಿನ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb index 80f62741b..bb269909a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ko.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "선택한 날짜가 없습니다.", +"noSelectedDateCalendarLabel" : "선택한 날짜가 없습니다", "noEventsCalendarLabel" : "이벤트 없음", -"ofDataPagerLabel" : "의", +"ofDataPagerLabel" : "NS", "pagesDataPagerLabel" : "페이지", -"itemsDataPagerLabel" : "항목", -"pdfBookmarksLabel" : "북마크", -"pdfNoBookmarksLabel" : "북마크가 없습니다.", -"pdfScrollStatusOfLabel" : "의", +"itemsDataPagerLabel" : "아이템", +"pdfBookmarksLabel" : "책갈피", +"pdfNoBookmarksLabel" : "북마크를 찾을 수 없습니다.", +"pdfScrollStatusOfLabel" : "NS", "pdfGoToPageLabel" : "페이지로 이동", "pdfEnterPageNumberLabel" : "페이지 번호 입력", -"pdfInvalidPageNumberLabel" : "유효한 번호를 입력하십시오", -"pdfPaginationDialogOkLabel" : "확인", +"pdfInvalidPageNumberLabel" : "유효한 숫자를 입력하세요", +"pdfPaginationDialogOkLabel" : "좋아요", "pdfPaginationDialogCancelLabel" : "취소", "allowedViewDayLabel" : "일", "allowedViewWeekLabel" : "주", -"allowedViewWorkWeekLabel" : "작업 주", -"allowedViewMonthLabel" : "달", -"allowedViewScheduleLabel" : "시간표", -"allowedViewTimelineDayLabel" : "타임 라인 데이", -"allowedViewTimelineWeekLabel" : "타임 라인 주", -"allowedViewTimelineWorkWeekLabel" : "타임 라인 작업 주", -"allowedViewTimelineMonthLabel" : "타임 라인 월", +"allowedViewWorkWeekLabel" : "작업 주간", +"allowedViewMonthLabel" : "월", +"allowedViewScheduleLabel" : "일정", +"allowedViewTimelineDayLabel" : "타임라인의 날", +"allowedViewTimelineWeekLabel" : "타임라인 주간", +"allowedViewTimelineWorkWeekLabel" : "타임라인 작업 주간", +"allowedViewTimelineMonthLabel" : "타임라인 월", "todayLabel" : "오늘", -"muharramLabel" : "무 하람", -"safarLabel" : "사 파르", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-awwal", -"jumada2Label" : "Jumada al-thani", -"rajabLabel" : "라잡", -"shaabanLabel" : "샤아 반", +"weeknumberLabel" : "주", +"muharramLabel" : "무하람", +"safarLabel" : "사파르", +"rabi1Label" : "라비 알-아왈", +"rabi2Label" : "라비 알타니", +"jumada1Label" : "주마다 알-아왈", +"jumada2Label" : "주마다 알타니", +"rajabLabel" : "라자브", +"shaabanLabel" : "샤아반", "ramadanLabel" : "라마단", -"shawwalLabel" : "Shawwal", -"dhualqiLabel" : "Dhu al-Qi'dah", +"shawwalLabel" : "샤왈", +"dhualqiLabel" : "두 알 키다", "dhualhiLabel" : "두 알 히자", "shortMuharramLabel" : "음.", -"shortSafarLabel" : "Saf.", -"shortRabi1Label" : "라비. 나는", +"shortSafarLabel" : "사프.", +"shortRabi1Label" : "라비. NS", "shortRabi2Label" : "라비. II", -"shortJumada1Label" : "Jum. 나는", -"shortJumada2Label" : "Jum. II", +"shortJumada1Label" : "줌. NS", +"shortJumada2Label" : "줌. II", "shortRajabLabel" : "주권.", -"shortShaabanLabel" : "Sha.", +"shortShaabanLabel" : "샤.", "shortRamadanLabel" : "램.", "shortShawwalLabel" : "쇼.", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualqiLabel" : "둘큐", +"shortDhualhiLabel" : "둘-H", "daySpanCountLabel" : "일" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb index 4fffb67e6..7a6a624c8 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ky.arb @@ -1,50 +1,51 @@ { "noSelectedDateCalendarLabel" : "Тандалган күн жок", -"noEventsCalendarLabel" : "Иш-чара жок", -"ofDataPagerLabel" : "боюнча", +"noEventsCalendarLabel" : "Иш -чаралар жок", +"ofDataPagerLabel" : "дын", "pagesDataPagerLabel" : "барактар", "itemsDataPagerLabel" : "буюмдар", "pdfBookmarksLabel" : "Кыстармалар", "pdfNoBookmarksLabel" : "Кыстармалар табылган жок", -"pdfScrollStatusOfLabel" : "боюнча", -"pdfGoToPageLabel" : "Баракчага өтүңүз", +"pdfScrollStatusOfLabel" : "дын", +"pdfGoToPageLabel" : "Баракка өтүү", "pdfEnterPageNumberLabel" : "Барактын номерин киргизиңиз", -"pdfInvalidPageNumberLabel" : "Сураныч, жарактуу номерди киргизиңиз", +"pdfInvalidPageNumberLabel" : "Жарактуу номерди киргизиңиз", "pdfPaginationDialogOkLabel" : "Макул", -"pdfPaginationDialogCancelLabel" : "ЖОК", +"pdfPaginationDialogCancelLabel" : "ЖОК КЫЛУУ", "allowedViewDayLabel" : "Күн", -"allowedViewWeekLabel" : "Апта", +"allowedViewWeekLabel" : "Жума", "allowedViewWorkWeekLabel" : "Жумуш аптасы", "allowedViewMonthLabel" : "Ай", -"allowedViewScheduleLabel" : "Расписание", +"allowedViewScheduleLabel" : "График", "allowedViewTimelineDayLabel" : "Убакыт тилкеси күнү", -"allowedViewTimelineWeekLabel" : "Timeline Week", -"allowedViewTimelineWorkWeekLabel" : "Убакыт тилкесиндеги Жума", -"allowedViewTimelineMonthLabel" : "Убакыт айы", +"allowedViewTimelineWeekLabel" : "Убакыт тилкеси жумасы", +"allowedViewTimelineWorkWeekLabel" : "Убакыт тилкеси Жумалык", +"allowedViewTimelineMonthLabel" : "Убакыт тилкеси айы", "todayLabel" : "Бүгүн", +"weeknumberLabel" : "Жума", "muharramLabel" : "Мухаррам", -"safarLabel" : "Сафар", -"rabi1Label" : "Раби 'ал-аввал", -"rabi2Label" : "Раби 'аль-тени", -"jumada1Label" : "Жумада ал-аввал", -"jumada2Label" : "Жумада ал-тени", +"safarLabel" : "Safar", +"rabi1Label" : "Рабиъул-аввал", +"rabi2Label" : "Раби ал-Тани", +"jumada1Label" : "Жумада аль-аввал", +"jumada2Label" : "Жумада ал-тани", "rajabLabel" : "Ражаб", "shaabanLabel" : "Шаабан", -"ramadanLabel" : "Рамазан", +"ramadanLabel" : "Орозо айт", "shawwalLabel" : "Шаввал", -"dhualqiLabel" : "Zhu al-Qi'dah", -"dhualhiLabel" : "Зуль-Хиджа", +"dhualqiLabel" : "Зулкийда", +"dhualhiLabel" : "Зул-Хижжа", "shortMuharramLabel" : "Мух.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Раби. I", +"shortRabi1Label" : "Раби. Мен", "shortRabi2Label" : "Раби. II", -"shortJumada1Label" : "Жум. I", -"shortJumada2Label" : "Жум. II", -"shortRajabLabel" : "Raj.", +"shortJumada1Label" : "Jum. Мен", +"shortJumada2Label" : "Jum. II", +"shortRajabLabel" : "Радж.", "shortShaabanLabel" : "Ша.", "shortRamadanLabel" : "RAM.", "shortShawwalLabel" : "Шоу.", -"shortDhualqiLabel" : "Zhu'l-Q", +"shortDhualqiLabel" : "Зуль-Q", "shortDhualhiLabel" : "Зуль-Х", "daySpanCountLabel" : "Күн" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb index 7559ff1a7..79dd948ff 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lo.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "ບໍ່ມີວັນທີ່ເລືອກ", +"noSelectedDateCalendarLabel" : "ບໍ່ມີວັນທີເລືອກ", "noEventsCalendarLabel" : "ບໍ່ມີເຫດການ", "ofDataPagerLabel" : "ຂອງ", -"pagesDataPagerLabel" : "ໜ້າ", +"pagesDataPagerLabel" : "ຫນ້າ", "itemsDataPagerLabel" : "ລາຍການ", -"pdfBookmarksLabel" : "ຫມາຍ", -"pdfNoBookmarksLabel" : "ບໍ່ພົບເຄື່ອງ ໝາຍ", +"pdfBookmarksLabel" : "ບຸກມາກ", +"pdfNoBookmarksLabel" : "ບໍ່ພົບບຸກມາກ", "pdfScrollStatusOfLabel" : "ຂອງ", -"pdfGoToPageLabel" : "ໄປທີ່ ໜ້າ", -"pdfEnterPageNumberLabel" : "ໃສ່ ໝາຍ ເລກ ໜ້າ", -"pdfInvalidPageNumberLabel" : "ກະລຸນາໃສ່ເບີທີ່ຖືກຕ້ອງ", +"pdfGoToPageLabel" : "ໄປຫາ ໜ້າ", +"pdfEnterPageNumberLabel" : "ໃສ່ເລກ ໜ້າ", +"pdfInvalidPageNumberLabel" : "ກະລຸນາໃສ່ຕົວເລກທີ່ຖືກຕ້ອງ", "pdfPaginationDialogOkLabel" : "ຕົກ​ລົງ", -"pdfPaginationDialogCancelLabel" : "CANCEL", +"pdfPaginationDialogCancelLabel" : "ຍົກເລີກ", "allowedViewDayLabel" : "ມື້", "allowedViewWeekLabel" : "ອາທິດ", "allowedViewWorkWeekLabel" : "ອາທິດເຮັດວຽກ", "allowedViewMonthLabel" : "ເດືອນ", "allowedViewScheduleLabel" : "ຕາຕະລາງ", -"allowedViewTimelineDayLabel" : "ວັນ ກຳ ນົດເວລາ", -"allowedViewTimelineWeekLabel" : "ອາທິດ ກຳ ນົດເວລາ", -"allowedViewTimelineWorkWeekLabel" : "ຕາຕະລາງເວລາເຮັດວຽກ", -"allowedViewTimelineMonthLabel" : "ເດືອນ ກຳ ນົດເວລາ", +"allowedViewTimelineDayLabel" : "ທາມລາຍວັນ", +"allowedViewTimelineWeekLabel" : "ລຳ ດັບອາທິດ", +"allowedViewTimelineWorkWeekLabel" : "ກໍານົດເວລາເຮັດວຽກອາທິດ", +"allowedViewTimelineMonthLabel" : "ໄລຍະເວລາເດືອນ", "todayLabel" : "ມື້​ນີ້", +"weeknumberLabel" : "ອາທິດ", "muharramLabel" : "Muharram", "safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", +"rabi1Label" : "Rabi 'al-awal", "rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-awwal", +"jumada1Label" : "Jumada al-awal", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "ລາດ", -"shaabanLabel" : "ຊາ'aban", +"rajabLabel" : "Rajab", +"shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "ປອດໄພ.", -"shortRabi1Label" : "ຣາເບ. ຂ້ອຍ", -"shortRabi2Label" : "ຣາເບ. II", -"shortJumada1Label" : "ຈູມ. ຂ້ອຍ", -"shortJumada2Label" : "ຈູມ. II", -"shortRajabLabel" : "ລາດ.", -"shortShaabanLabel" : "ຊາ.", +"shortMuharramLabel" : "ມ.", +"shortSafarLabel" : "ປອດໄພ", +"shortRabi1Label" : "Rabi. ຂ້ອຍ", +"shortRabi2Label" : "Rabi. II", +"shortJumada1Label" : "ຈຸ່ມ. ຂ້ອຍ", +"shortJumada2Label" : "ຈຸ່ມ. II", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Ram.", -"shortShawwalLabel" : "ໂກລ.", +"shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualhiLabel" : "Dhu'l -H", "daySpanCountLabel" : "ມື້" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb index 5c4169f89..ceaf8b87b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lt.arb @@ -1,15 +1,15 @@ { -"noSelectedDateCalendarLabel" : "Nepasirinkta data", +"noSelectedDateCalendarLabel" : "Nėra pasirinktos datos", "noEventsCalendarLabel" : "Jokių įvykių", "ofDataPagerLabel" : "apie", -"pagesDataPagerLabel" : "puslapių", -"itemsDataPagerLabel" : "daiktų", +"pagesDataPagerLabel" : "puslapius", +"itemsDataPagerLabel" : "elementus", "pdfBookmarksLabel" : "Žymės", "pdfNoBookmarksLabel" : "Nerasta jokių žymių", "pdfScrollStatusOfLabel" : "apie", "pdfGoToPageLabel" : "Eiti į puslapį", "pdfEnterPageNumberLabel" : "Įveskite puslapio numerį", -"pdfInvalidPageNumberLabel" : "Įveskite galiojantį numerį", +"pdfInvalidPageNumberLabel" : "Įveskite tinkamą numerį", "pdfPaginationDialogOkLabel" : "Gerai", "pdfPaginationDialogCancelLabel" : "ATŠAUKTI", "allowedViewDayLabel" : "Diena", @@ -17,19 +17,20 @@ "allowedViewWorkWeekLabel" : "Darbo savaitė", "allowedViewMonthLabel" : "Mėnuo", "allowedViewScheduleLabel" : "Tvarkaraštis", -"allowedViewTimelineDayLabel" : "Laiko juostos diena", +"allowedViewTimelineDayLabel" : "Laiko skalės diena", "allowedViewTimelineWeekLabel" : "Laiko juostos savaitė", -"allowedViewTimelineWorkWeekLabel" : "Laiko juostos darbo savaitė", -"allowedViewTimelineMonthLabel" : "Laiko juostos mėnuo", +"allowedViewTimelineWorkWeekLabel" : "Darbo savaitės laiko juosta", +"allowedViewTimelineMonthLabel" : "Laiko skalės mėnuo", "todayLabel" : "Šiandien", -"muharramLabel" : "Muharram", -"safarLabel" : "Safaras", -"rabi1Label" : "Rabi 'al-awwal", +"weeknumberLabel" : "Savaitė", +"muharramLabel" : "Muharramas", +"safarLabel" : "„Safar“", +"rabi1Label" : "Rabi al-awwal", "rabi2Label" : "Rabi 'al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Radžabas", -"shaabanLabel" : "Šaabanas", +"shaabanLabel" : "Shaabanas", "ramadanLabel" : "Ramadanas", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", @@ -40,9 +41,9 @@ "shortRabi2Label" : "Rabi. II", "shortJumada1Label" : "Jum. Aš", "shortJumada2Label" : "Jum. II", -"shortRajabLabel" : "Radž.", -"shortShaabanLabel" : "Ša.", -"shortRamadanLabel" : "Avinas.", +"shortRajabLabel" : "Radžis.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Aunas.", "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb index 185088152..132a045f3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_lv.arb @@ -1,12 +1,12 @@ { -"noSelectedDateCalendarLabel" : "Nav atlasīts datums", +"noSelectedDateCalendarLabel" : "Nav izvēlēts datums", "noEventsCalendarLabel" : "Nav notikumu", -"ofDataPagerLabel" : "gada", +"ofDataPagerLabel" : "no", "pagesDataPagerLabel" : "lapas", "itemsDataPagerLabel" : "preces", "pdfBookmarksLabel" : "Grāmatzīmes", -"pdfNoBookmarksLabel" : "Nav atrasta neviena grāmatzīme", -"pdfScrollStatusOfLabel" : "gada", +"pdfNoBookmarksLabel" : "Grāmatzīmes nav atrastas", +"pdfScrollStatusOfLabel" : "no", "pdfGoToPageLabel" : "Iet uz lapu", "pdfEnterPageNumberLabel" : "Ievadiet lapas numuru", "pdfInvalidPageNumberLabel" : "Lūdzu, ievadiet derīgu numuru", @@ -20,28 +20,29 @@ "allowedViewTimelineDayLabel" : "Laika skalas diena", "allowedViewTimelineWeekLabel" : "Laika skalas nedēļa", "allowedViewTimelineWorkWeekLabel" : "Laika skalas darba nedēļa", -"allowedViewTimelineMonthLabel" : "Laika skala mēnesis", +"allowedViewTimelineMonthLabel" : "Laika skalas mēnesis", "todayLabel" : "Šodien", +"weeknumberLabel" : "Nedēļa", "muharramLabel" : "Muharram", "safarLabel" : "Safārs", -"rabi1Label" : "Rabi 'al-awwal", +"rabi1Label" : "Rabi al-awwal", "rabi2Label" : "Rabi 'al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Radžabs", -"shaabanLabel" : "Šaabans", +"rajabLabel" : "Radžaba", +"shaabanLabel" : "Šabāns", "ramadanLabel" : "Ramadāns", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", +"dhualhiLabel" : "Dhu al-Hidža", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. Es", "shortRabi2Label" : "Rabi. II", "shortJumada1Label" : "Jum. Es", "shortJumada2Label" : "Jum. II", -"shortRajabLabel" : "Raj.", -"shortShaabanLabel" : "Ša.", +"shortRajabLabel" : "Radž.", +"shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Auns.", "shortShawwalLabel" : "Šovs.", "shortDhualqiLabel" : "Dhu'l-Q", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb index bebc87412..7c9f97874 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mk.arb @@ -9,7 +9,7 @@ "pdfScrollStatusOfLabel" : "на", "pdfGoToPageLabel" : "Оди на страна", "pdfEnterPageNumberLabel" : "Внесете број на страница", -"pdfInvalidPageNumberLabel" : "Внесете важечки број", +"pdfInvalidPageNumberLabel" : "Внесете валиден број", "pdfPaginationDialogOkLabel" : "добро", "pdfPaginationDialogCancelLabel" : "ОТКАИ", "allowedViewDayLabel" : "Ден", @@ -19,13 +19,14 @@ "allowedViewScheduleLabel" : "Распоред", "allowedViewTimelineDayLabel" : "Ден на времеплов", "allowedViewTimelineWeekLabel" : "Недела на времеплов", -"allowedViewTimelineWorkWeekLabel" : "Времепловна работна недела", +"allowedViewTimelineWorkWeekLabel" : "Временска работна недела", "allowedViewTimelineMonthLabel" : "Месец на времеплов", "todayLabel" : "Денес", -"muharramLabel" : "Мухарем", +"weeknumberLabel" : "Недела", +"muharramLabel" : "Мухарам", "safarLabel" : "Сафар", -"rabi1Label" : "Раби ал-аввал", -"rabi2Label" : "Раби ал-тани", +"rabi1Label" : "Раби 'ел-аввал", +"rabi2Label" : "Раби 'ал-тани", "jumada1Label" : "Umумада ал-аввал", "jumada2Label" : "Umумада ал-тани", "rajabLabel" : "Раџаб", @@ -33,17 +34,17 @@ "ramadanLabel" : "Рамазан", "shawwalLabel" : "Шавал", "dhualqiLabel" : "Huу ал-Кида", -"dhualhiLabel" : "Huу ал-хиџа", -"shortMuharramLabel" : "Мух", -"shortSafarLabel" : "Саф.", -"shortRabi1Label" : "Раби Јас", -"shortRabi2Label" : "Раби II", -"shortJumada1Label" : "Umум Јас", -"shortJumada2Label" : "Umум II", -"shortRajabLabel" : "Рај", -"shortShaabanLabel" : "Ша", -"shortRamadanLabel" : "Овен", -"shortShawwalLabel" : "Шоу", +"dhualhiLabel" : "Huу ал-Хиџа", +"shortMuharramLabel" : "Мух.", +"shortSafarLabel" : "Сеф.", +"shortRabi1Label" : "Раби. Јас", +"shortRabi2Label" : "Раби. II", +"shortJumada1Label" : "Џум Јас", +"shortJumada2Label" : "Џум II", +"shortRajabLabel" : "Рај.", +"shortShaabanLabel" : "Ша.", +"shortRamadanLabel" : "Рам.", +"shortShawwalLabel" : "Шо.", "shortDhualqiLabel" : "Дул-П", "shortDhualhiLabel" : "Дул-Х", "daySpanCountLabel" : "Ден" diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb index 39eb6906b..6699fe875 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ml.arb @@ -1,50 +1,51 @@ { "noSelectedDateCalendarLabel" : "തിരഞ്ഞെടുത്ത തീയതിയില്ല", "noEventsCalendarLabel" : "ഇവന്റുകളൊന്നുമില്ല", -"ofDataPagerLabel" : "ന്റെ", +"ofDataPagerLabel" : "യുടെ", "pagesDataPagerLabel" : "പേജുകൾ", "itemsDataPagerLabel" : "ഇനങ്ങൾ", "pdfBookmarksLabel" : "ബുക്ക്മാർക്കുകൾ", -"pdfNoBookmarksLabel" : "ബുക്ക്മാർക്കുകളൊന്നും കണ്ടെത്തിയില്ല", -"pdfScrollStatusOfLabel" : "ന്റെ", +"pdfNoBookmarksLabel" : "ബുക്ക്മാർക്കുകൾ കണ്ടെത്തിയില്ല", +"pdfScrollStatusOfLabel" : "യുടെ", "pdfGoToPageLabel" : "പേജിലേക്ക് പോകുക", "pdfEnterPageNumberLabel" : "പേജ് നമ്പർ നൽകുക", -"pdfInvalidPageNumberLabel" : "സാധുവായ ഒരു നമ്പർ നൽകുക", +"pdfInvalidPageNumberLabel" : "ദയവായി ഒരു സാധുവായ നമ്പർ നൽകുക", "pdfPaginationDialogOkLabel" : "ശരി", -"pdfPaginationDialogCancelLabel" : "റദ്ദാക്കുക", +"pdfPaginationDialogCancelLabel" : "കാൻസൽ", "allowedViewDayLabel" : "ദിവസം", "allowedViewWeekLabel" : "ആഴ്ച", "allowedViewWorkWeekLabel" : "പ്രവൃത്തി ആഴ്ച", "allowedViewMonthLabel" : "മാസം", "allowedViewScheduleLabel" : "പട്ടിക", -"allowedViewTimelineDayLabel" : "ടൈംലൈൻ ദിനം", -"allowedViewTimelineWeekLabel" : "ടൈംലൈൻ ആഴ്ച", -"allowedViewTimelineWorkWeekLabel" : "ടൈംലൈൻ വർക്ക് വീക്ക്", +"allowedViewTimelineDayLabel" : "ടൈംലൈൻ ദിവസം", +"allowedViewTimelineWeekLabel" : "ടൈംലൈൻ വാരം", +"allowedViewTimelineWorkWeekLabel" : "ടൈംലൈൻ പ്രവൃത്തി ആഴ്ച", "allowedViewTimelineMonthLabel" : "ടൈംലൈൻ മാസം", "todayLabel" : "ഇന്ന്", +"weeknumberLabel" : "ആഴ്ച", "muharramLabel" : "മുഹറം", "safarLabel" : "സഫർ", -"rabi1Label" : "റാബി അൽ അവൽ", -"rabi2Label" : "റാബി അൽ താനി", -"jumada1Label" : "ജുമാദ അൽ അവ്വാൾ", +"rabi1Label" : "റാബി അൽ-അവ്വൽ", +"rabi2Label" : "റാബി അൽ-താനി", +"jumada1Label" : "ജുമാദ അൽ അവ്വൽ", "jumada2Label" : "ജുമാദ അൽ താനി", -"rajabLabel" : "രാജാബ്", -"shaabanLabel" : "ഷാബാൻ", +"rajabLabel" : "റജബ്", +"shaabanLabel" : "ശഅബാൻ", "ramadanLabel" : "റമദാൻ", -"shawwalLabel" : "ഷാവാൽ", -"dhualqiLabel" : "ധു അൽ ക്വിദ", -"dhualhiLabel" : "ധു അൽ ഹിജ", +"shawwalLabel" : "ശവ്വാൽ", +"dhualqiLabel" : "ധു അൽ-ഖിദ", +"dhualhiLabel" : "ധു അൽ ഹിജ്ജ", "shortMuharramLabel" : "മുഹ്.", "shortSafarLabel" : "സേഫ്.", -"shortRabi1Label" : "റാബി. ഞാൻ", -"shortRabi2Label" : "റാബി. II", -"shortJumada1Label" : "ജം. ഞാൻ", +"shortRabi1Label" : "റാബി ഐ", +"shortRabi2Label" : "റാബി II", +"shortJumada1Label" : "ജം. ഐ", "shortJumada2Label" : "ജം. II", -"shortRajabLabel" : "രാജ്.", -"shortShaabanLabel" : "ഷാ.", +"shortRajabLabel" : "രാജ്", +"shortShaabanLabel" : "ഷാ", "shortRamadanLabel" : "RAM.", "shortShawwalLabel" : "ഷാ.", -"shortDhualqiLabel" : "ദുൽ-ക്യു", +"shortDhualqiLabel" : "ദുൽ-ക്യൂ", "shortDhualhiLabel" : "ദുൽ-എച്ച്", "daySpanCountLabel" : "ദിവസം" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb index 7e547a642..3b5470e05 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mn.arb @@ -1,6 +1,6 @@ { -"noSelectedDateCalendarLabel" : "Сонгосон огноо алга", -"noEventsCalendarLabel" : "Арга хэмжээ алга", +"noSelectedDateCalendarLabel" : "Сонгосон огноо байхгүй", +"noEventsCalendarLabel" : "Үйл явдал байхгүй", "ofDataPagerLabel" : "-ийн", "pagesDataPagerLabel" : "хуудас", "itemsDataPagerLabel" : "зүйлс", @@ -8,34 +8,35 @@ "pdfNoBookmarksLabel" : "Хавчуурга олдсонгүй", "pdfScrollStatusOfLabel" : "-ийн", "pdfGoToPageLabel" : "Хуудас руу очих", -"pdfEnterPageNumberLabel" : "Хуудасны дугаар оруулна уу", +"pdfEnterPageNumberLabel" : "Хуудасны дугаарыг оруулна уу", "pdfInvalidPageNumberLabel" : "Зөв дугаар оруулна уу", "pdfPaginationDialogOkLabel" : "БОЛЖ БАЙНА УУ", -"pdfPaginationDialogCancelLabel" : "Цуцлах", +"pdfPaginationDialogCancelLabel" : "ЦУЦЛАХ", "allowedViewDayLabel" : "Өдөр", "allowedViewWeekLabel" : "Долоо хоног", "allowedViewWorkWeekLabel" : "Ажлын долоо хоног", "allowedViewMonthLabel" : "Сар", "allowedViewScheduleLabel" : "Хуваарь", -"allowedViewTimelineDayLabel" : "Цаг хугацааны өдөр", -"allowedViewTimelineWeekLabel" : "Он цагийн хэлхээс", -"allowedViewTimelineWorkWeekLabel" : "Хугацааны ажлын долоо хоног", -"allowedViewTimelineMonthLabel" : "Цаг хугацааны сар", +"allowedViewTimelineDayLabel" : "Цагийн хуваарийн өдөр", +"allowedViewTimelineWeekLabel" : "Хугацааны долоо хоног", +"allowedViewTimelineWorkWeekLabel" : "Цагийн ажлын долоо хоног", +"allowedViewTimelineMonthLabel" : "Хугацааны сар", "todayLabel" : "Өнөөдөр", +"weeknumberLabel" : "Долоо хоног", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", "rabi1Label" : "Раби 'аль-аввал", -"rabi2Label" : "Раби 'аль-тени", +"rabi2Label" : "Раби 'аль-Тани", "jumada1Label" : "Жумада аль-аввал", "jumada2Label" : "Жумада аль-тани", "rajabLabel" : "Ражаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамадан", "shawwalLabel" : "Шаввал", -"dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Зу аль-Хижжа", +"dhualqiLabel" : "Зул аль-Кида", +"dhualhiLabel" : "Зул аль-Хижжа", "shortMuharramLabel" : "Мух.", -"shortSafarLabel" : "Аюулгүй.", +"shortSafarLabel" : "Саф.", "shortRabi1Label" : "Раби. Би", "shortRabi2Label" : "Раби. II", "shortJumada1Label" : "Жум. Би", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb index cb1c92d9c..b9c73a1e4 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_mr.arb @@ -1,9 +1,9 @@ { "noSelectedDateCalendarLabel" : "निवडलेली तारीख नाही", -"noEventsCalendarLabel" : "कार्यक्रम नाहीत", +"noEventsCalendarLabel" : "कोणतेही कार्यक्रम नाहीत", "ofDataPagerLabel" : "च्या", "pagesDataPagerLabel" : "पृष्ठे", -"itemsDataPagerLabel" : "आयटम", +"itemsDataPagerLabel" : "वस्तू", "pdfBookmarksLabel" : "बुकमार्क", "pdfNoBookmarksLabel" : "कोणतेही बुकमार्क आढळले नाहीत", "pdfScrollStatusOfLabel" : "च्या", @@ -19,27 +19,28 @@ "allowedViewScheduleLabel" : "वेळापत्रक", "allowedViewTimelineDayLabel" : "टाइमलाइन दिवस", "allowedViewTimelineWeekLabel" : "टाइमलाइन आठवडा", -"allowedViewTimelineWorkWeekLabel" : "टाइमलाइन कार्य आठवडा", +"allowedViewTimelineWorkWeekLabel" : "टाइमलाइन वर्क वीक", "allowedViewTimelineMonthLabel" : "टाइमलाइन महिना", "todayLabel" : "आज", -"muharramLabel" : "मुहर्रम", -"safarLabel" : "सफार", -"rabi1Label" : "रबी अल अलवाल", -"rabi2Label" : "रबी 'अल-थॅनी", -"jumada1Label" : "जुमादा अल-आव्वल", -"jumada2Label" : "जुमादा अल-थॅनी", +"weeknumberLabel" : "आठवडा", +"muharramLabel" : "मोहरम", +"safarLabel" : "सफर", +"rabi1Label" : "रबी अल-अवाल", +"rabi2Label" : "रबी अल-थानी", +"jumada1Label" : "जुमादा अल-अव्वल", +"jumada2Label" : "जुमादा अल-थानी", "rajabLabel" : "रजब", "shaabanLabel" : "शाबान", "ramadanLabel" : "रमजान", -"shawwalLabel" : "शावल", -"dhualqiLabel" : "धु अल-कायदा", +"shawwalLabel" : "शवाल", +"dhualqiLabel" : "धु अल- Qi'dah", "dhualhiLabel" : "धु अल-हिज्जा", "shortMuharramLabel" : "मुह.", -"shortSafarLabel" : "सेफ.", +"shortSafarLabel" : "सुरक्षित.", "shortRabi1Label" : "रबी. मी", "shortRabi2Label" : "रबी. II", -"shortJumada1Label" : "जम्. मी", -"shortJumada2Label" : "जम्. II", +"shortJumada1Label" : "जम. मी", +"shortJumada2Label" : "जम. II", "shortRajabLabel" : "राज.", "shortShaabanLabel" : "शा.", "shortRamadanLabel" : "रॅम.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb index 772a026e1..bb6671000 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ms.arb @@ -13,7 +13,7 @@ "pdfPaginationDialogOkLabel" : "okey", "pdfPaginationDialogCancelLabel" : "BATAL", "allowedViewDayLabel" : "Hari", -"allowedViewWeekLabel" : "Minggu", +"allowedViewWeekLabel" : "Seminggu", "allowedViewWorkWeekLabel" : "Minggu Kerja", "allowedViewMonthLabel" : "Sebulan", "allowedViewScheduleLabel" : "Jadual", @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel" : "Minggu Kerja Garis Masa", "allowedViewTimelineMonthLabel" : "Garis Masa Bulan", "todayLabel" : "Hari ini", +"weeknumberLabel" : "Seminggu", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb index 6ca44cb11..f1cd35e3f 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_my.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "ရွေးချယ်ထားသည့်နေ့စွဲမရှိပါ", -"noEventsCalendarLabel" : "ဖြစ်ရပ်များမရှိပါ", +"noSelectedDateCalendarLabel" : "ရွေးထားသောရက်မရှိပါ", +"noEventsCalendarLabel" : "အဖြစ်အပျက်များမရှိပါ", "ofDataPagerLabel" : "၏", "pagesDataPagerLabel" : "စာမျက်နှာများ", -"itemsDataPagerLabel" : "ပစ္စည်းတွေ", -"pdfBookmarksLabel" : "mark ။", -"pdfNoBookmarksLabel" : "Bookmarks မရှိပါ", +"itemsDataPagerLabel" : "ပစ္စည်းများ", +"pdfBookmarksLabel" : "စာညှပ်များ", +"pdfNoBookmarksLabel" : "စာညှပ်များမတွေ့ပါ", "pdfScrollStatusOfLabel" : "၏", "pdfGoToPageLabel" : "စာမျက်နှာသို့သွားပါ", -"pdfEnterPageNumberLabel" : "စာမျက်နှာနံပါတ်ကိုရိုက်ထည့်ပါ", -"pdfInvalidPageNumberLabel" : "ကျေးဇူးပြု၍ မှန်ကန်နံပါတ်ထည့်ပါ", -"pdfPaginationDialogOkLabel" : "ရလား", -"pdfPaginationDialogCancelLabel" : "ဖျက်သိမ်းခြင်း", +"pdfEnterPageNumberLabel" : "စာမျက်နှာနံပါတ်ထည့်ပါ", +"pdfInvalidPageNumberLabel" : "ကျေးဇူးပြု၍ မှန်ကန်သောနံပါတ်ကိုထည့်ပါ", +"pdfPaginationDialogOkLabel" : "အိုကေ", +"pdfPaginationDialogCancelLabel" : "ပယ်ဖျက်ပါ", "allowedViewDayLabel" : "နေ့", -"allowedViewWeekLabel" : "အပတ်", -"allowedViewWorkWeekLabel" : "အလုပ်ရက်သတ္တပတ်", -"allowedViewMonthLabel" : "လ", -"allowedViewScheduleLabel" : "ဇယား", +"allowedViewWeekLabel" : "အေးလေ", +"allowedViewWorkWeekLabel" : "အလုပ်အပတ်", +"allowedViewMonthLabel" : "တစ်လ", +"allowedViewScheduleLabel" : "အချိန်ဇယား", "allowedViewTimelineDayLabel" : "အချိန်ဇယားနေ့", "allowedViewTimelineWeekLabel" : "Timeline အပတ်", -"allowedViewTimelineWorkWeekLabel" : "Timeline အလုပ်ရက်သတ္တပတ်", -"allowedViewTimelineMonthLabel" : "အချိန်ဇယားလ", +"allowedViewTimelineWorkWeekLabel" : "အလုပ်ချိန်ရက်သတ္တပတ်", +"allowedViewTimelineMonthLabel" : "အချိန်စာရင်းလ", "todayLabel" : "ဒီနေ့", +"weeknumberLabel" : "အေးလေ", "muharramLabel" : "Muharram", "safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-awwal", -"jumada2Label" : "Jumada al-thani", -"rajabLabel" : "ရာဂျ", -"shaabanLabel" : "Sha'aban", -"ramadanLabel" : "Ramadan", +"rabi1Label" : "Rabi 'al-awal", +"rabi2Label" : "Rabi 'al-Thani", +"jumada1Label" : "Jumada al-awal", +"jumada2Label" : "Jumada al-Thani", +"rajabLabel" : "Rajab", +"shaabanLabel" : "ရှာဖန်", +"ramadanLabel" : "ဥပုသ်လ", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu Al-Hijjah", +"dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh ။", -"shortSafarLabel" : "Saf ။", -"shortRabi1Label" : "ရာဘီ။ ငါ", -"shortRabi2Label" : "ရာဘီ။ ၂", -"shortJumada1Label" : "Jum ။ ငါ", -"shortJumada2Label" : "Jum ။ ၂", +"shortSafarLabel" : "Saf", +"shortRabi1Label" : "Rabi ။ ငါ", +"shortRabi2Label" : "Rabi ။ II", +"shortJumada1Label" : "ဂျမ်။ ငါ", +"shortJumada2Label" : "ဂျမ်။ II", "shortRajabLabel" : "Raj ။", "shortShaabanLabel" : "Sha ။", "shortRamadanLabel" : "ရမ်။", -"shortShawwalLabel" : "Shaw ။", +"shortShawwalLabel" : "ရှော။", "shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H ကို", +"shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "နေ့" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb index fe2d6def0..28bc00e10 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nb.arb @@ -3,9 +3,9 @@ "noEventsCalendarLabel" : "Ingen hendelser", "ofDataPagerLabel" : "av", "pagesDataPagerLabel" : "sider", -"itemsDataPagerLabel" : "gjenstander", +"itemsDataPagerLabel" : "elementer", "pdfBookmarksLabel" : "Bokmerker", -"pdfNoBookmarksLabel" : "Ingen bokmerker funnet", +"pdfNoBookmarksLabel" : "Fant ingen bokmerker", "pdfScrollStatusOfLabel" : "av", "pdfGoToPageLabel" : "Gå til side", "pdfEnterPageNumberLabel" : "Skriv inn sidenummer", @@ -17,11 +17,12 @@ "allowedViewWorkWeekLabel" : "Arbeidsuke", "allowedViewMonthLabel" : "Måned", "allowedViewScheduleLabel" : "Rute", -"allowedViewTimelineDayLabel" : "Tidslinjedag", -"allowedViewTimelineWeekLabel" : "Tidslinjeuke", +"allowedViewTimelineDayLabel" : "Tidslinjens dag", +"allowedViewTimelineWeekLabel" : "Tidslinje uke", "allowedViewTimelineWorkWeekLabel" : "Tidslinje arbeidsuke", -"allowedViewTimelineMonthLabel" : "Tidslinjemåned", +"allowedViewTimelineMonthLabel" : "Tidslinje måned", "todayLabel" : "I dag", +"weeknumberLabel" : "Uke", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb index 8465bebe6..f4a101c23 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ne.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "कुनै चयन गरिएको मिति छैन", -"noEventsCalendarLabel" : "घटनाहरू छैनन्", +"noSelectedDateCalendarLabel" : "कुनै चयनित मिति छैन", +"noEventsCalendarLabel" : "कुनै घटना छैन", "ofDataPagerLabel" : "को", -"pagesDataPagerLabel" : "पृष्ठहरु", -"itemsDataPagerLabel" : "वस्तुहरू", -"pdfBookmarksLabel" : "बुकमार्कहरू", -"pdfNoBookmarksLabel" : "कुनै बुकमार्कहरू फेला परेनन्", +"pagesDataPagerLabel" : "पानाहरु", +"itemsDataPagerLabel" : "वस्तुहरु", +"pdfBookmarksLabel" : "बुकमार्क", +"pdfNoBookmarksLabel" : "कुनै बुकमार्क भेटिएन", "pdfScrollStatusOfLabel" : "को", "pdfGoToPageLabel" : "पृष्ठमा जानुहोस्", -"pdfEnterPageNumberLabel" : "पृष्ठ संख्या प्रविष्ट गर्नुहोस्", -"pdfInvalidPageNumberLabel" : "कृपया मान्य संख्या प्रविष्ट गर्नुहोस्", +"pdfEnterPageNumberLabel" : "पृष्ठ नम्बर प्रविष्ट गर्नुहोस्", +"pdfInvalidPageNumberLabel" : "कृपया एक मान्य नम्बर प्रविष्ट गर्नुहोस्", "pdfPaginationDialogOkLabel" : "ठिक छ", "pdfPaginationDialogCancelLabel" : "रद्द गर्नुहोस्", "allowedViewDayLabel" : "दिन", "allowedViewWeekLabel" : "हप्ता", -"allowedViewWorkWeekLabel" : "कार्य सप्ताह", +"allowedViewWorkWeekLabel" : "काम हप्ता", "allowedViewMonthLabel" : "महिना", -"allowedViewScheduleLabel" : "तालिका", -"allowedViewTimelineDayLabel" : "समयरेखा दिन", +"allowedViewScheduleLabel" : "अनुसूची", +"allowedViewTimelineDayLabel" : "टाइमलाइन दिन", "allowedViewTimelineWeekLabel" : "टाइमलाइन हप्ता", -"allowedViewTimelineWorkWeekLabel" : "टाइमलाइन कार्य सप्ताह", -"allowedViewTimelineMonthLabel" : "टाइमलाइन महीना", +"allowedViewTimelineWorkWeekLabel" : "टाइमलाइन कार्य हप्ता", +"allowedViewTimelineMonthLabel" : "टाइमलाइन महिना", "todayLabel" : "आज", +"weeknumberLabel" : "हप्ता", "muharramLabel" : "मुहर्रम", "safarLabel" : "सफार", -"rabi1Label" : "रबी अल अलवाल", -"rabi2Label" : "रबी 'अल-थानी", +"rabi1Label" : "रबी अल-अवाल", +"rabi2Label" : "रबी अल थानी", "jumada1Label" : "जुमादा अल-अवाल", "jumada2Label" : "जुमादा अल-थानी", "rajabLabel" : "रजब", -"shaabanLabel" : "Sha'aban", +"shaabanLabel" : "शाबान", "ramadanLabel" : "रमजान", -"shawwalLabel" : "शोवल", +"shawwalLabel" : "शावल", "dhualqiLabel" : "धु अल Qi'dah", -"dhualhiLabel" : "धु अल-हिज्जा", -"shortMuharramLabel" : "मुह।", -"shortSafarLabel" : "सफ।", -"shortRabi1Label" : "रबी। I", +"dhualhiLabel" : "धु अल Hijjah", +"shortMuharramLabel" : "Muh।", +"shortSafarLabel" : "सुरक्षित", +"shortRabi1Label" : "रबी। म", "shortRabi2Label" : "रबी। II", -"shortJumada1Label" : "Jum। I", -"shortJumada2Label" : "Jum। II", +"shortJumada1Label" : "जुम। म", +"shortJumada2Label" : "जुम। II", "shortRajabLabel" : "राज।", -"shortShaabanLabel" : "Sha।", +"shortShaabanLabel" : "शा।", "shortRamadanLabel" : "राम।", -"shortShawwalLabel" : "श", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortShawwalLabel" : "शा।", +"shortDhualqiLabel" : "धुल-क्यू", +"shortDhualhiLabel" : "धुल-एच", "daySpanCountLabel" : "दिन" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb index 73ffa978c..8fa3d56ed 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_nl.arb @@ -8,42 +8,43 @@ "pdfNoBookmarksLabel" : "Geen bladwijzers gevonden", "pdfScrollStatusOfLabel" : "van", "pdfGoToPageLabel" : "Ga naar pagina", -"pdfEnterPageNumberLabel" : "Voer het paginanummer in", +"pdfEnterPageNumberLabel" : "Voer paginanummer in", "pdfInvalidPageNumberLabel" : "Gelieve een geldig nummer invoeren", -"pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "ANNULEREN", +"pdfPaginationDialogOkLabel" : "Oke", +"pdfPaginationDialogCancelLabel" : "ANNULEER", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Week", "allowedViewWorkWeekLabel" : "Werkweek", "allowedViewMonthLabel" : "Maand", "allowedViewScheduleLabel" : "Schema", -"allowedViewTimelineDayLabel" : "Tijdlijn dag", -"allowedViewTimelineWeekLabel" : "Tijdlijn Week", -"allowedViewTimelineWorkWeekLabel" : "Tijdlijn werkweek", +"allowedViewTimelineDayLabel" : "Tijdlijn Dag", +"allowedViewTimelineWeekLabel" : "Tijdlijnweek", +"allowedViewTimelineWorkWeekLabel" : "Tijdlijn Werkweek", "allowedViewTimelineMonthLabel" : "Tijdlijn maand", "todayLabel" : "Vandaag", +"weeknumberLabel" : "Week", "muharramLabel" : "Muharram", -"safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-Thani", +"safarLabel" : "Safari", +"rabi1Label" : "Rabi' al-awwali", +"rabi2Label" : "Rabi' al Thani", "jumada1Label" : "Jumada al-awwal", -"jumada2Label" : "Jumada al-Thani", +"jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. ik", +"shortMuharramLabel" : "mwah.", +"shortSafarLabel" : "saf.", +"shortRabi1Label" : "Rabi. l", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. ik", -"shortJumada2Label" : "Jum. II", +"shortJumada1Label" : "jum. l", +"shortJumada2Label" : "jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "RAM.", -"shortShawwalLabel" : "Shaw.", +"shortShawwalLabel" : "Sjaa.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "Dag" diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb index 797b9fbeb..fc858363c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pa.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "ਕੋਈ ਚੁਣੀ ਤਾਰੀਖ ਨਹੀਂ", +"noSelectedDateCalendarLabel" : "ਕੋਈ ਚੁਣੀ ਹੋਈ ਤਾਰੀਖ ਨਹੀਂ", "noEventsCalendarLabel" : "ਕੋਈ ਇਵੈਂਟ ਨਹੀਂ", -"ofDataPagerLabel" : "ਦੇ", +"ofDataPagerLabel" : "ਦੀ", "pagesDataPagerLabel" : "ਪੰਨੇ", "itemsDataPagerLabel" : "ਇਕਾਈ", "pdfBookmarksLabel" : "ਬੁੱਕਮਾਰਕ", -"pdfNoBookmarksLabel" : "ਕੋਈ ਬੁੱਕਮਾਰਕ ਨਹੀਂ ਮਿਲਿਆ", -"pdfScrollStatusOfLabel" : "ਦੇ", -"pdfGoToPageLabel" : "ਪੇਜ ਤੇ ਜਾਓ", -"pdfEnterPageNumberLabel" : "ਪੇਜ ਨੰਬਰ ਦਰਜ ਕਰੋ", -"pdfInvalidPageNumberLabel" : "ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਯੋਗ ਨੰਬਰ ਦਾਖਲ ਕਰੋ", +"pdfNoBookmarksLabel" : "ਕੋਈ ਬੁੱਕਮਾਰਕ ਨਹੀਂ ਮਿਲੇ", +"pdfScrollStatusOfLabel" : "ਦੀ", +"pdfGoToPageLabel" : "ਪੰਨੇ ਤੇ ਜਾਓ", +"pdfEnterPageNumberLabel" : "ਪੰਨਾ ਨੰਬਰ ਦਾਖਲ ਕਰੋ", +"pdfInvalidPageNumberLabel" : "ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਵੈਧ ਨੰਬਰ ਦਾਖਲ ਕਰੋ", "pdfPaginationDialogOkLabel" : "ਠੀਕ ਹੈ", -"pdfPaginationDialogCancelLabel" : "ਰੱਦ", +"pdfPaginationDialogCancelLabel" : "ਰੱਦ ਕਰੋ", "allowedViewDayLabel" : "ਦਿਨ", "allowedViewWeekLabel" : "ਹਫ਼ਤਾ", -"allowedViewWorkWeekLabel" : "ਕੰਮ ਦਾ ਹਫ਼ਤਾ", +"allowedViewWorkWeekLabel" : "ਵਰਕ ਵੀਕ", "allowedViewMonthLabel" : "ਮਹੀਨਾ", "allowedViewScheduleLabel" : "ਸਮਾਸੂਚੀ, ਕਾਰਜ - ਕ੍ਰਮ", -"allowedViewTimelineDayLabel" : "ਟਾਈਮਲਾਈਨ ਦਿਵਸ", -"allowedViewTimelineWeekLabel" : "ਟਾਈਮਲਾਈਨ ਹਫ਼ਤਾ", +"allowedViewTimelineDayLabel" : "ਸਮਾਂਰੇਖਾ ਦਿਵਸ", +"allowedViewTimelineWeekLabel" : "ਸਮਾਂਰੇਖਾ ਹਫ਼ਤਾ", "allowedViewTimelineWorkWeekLabel" : "ਟਾਈਮਲਾਈਨ ਵਰਕ ਵੀਕ", -"allowedViewTimelineMonthLabel" : "ਟਾਈਮਲਾਈਨ ਮਹੀਨਾ", +"allowedViewTimelineMonthLabel" : "ਸਮਾਂਰੇਖਾ ਮਹੀਨਾ", "todayLabel" : "ਅੱਜ", -"muharramLabel" : "ਮੁਹਰਰਾਮ", -"safarLabel" : "ਸਫਾਰ", -"rabi1Label" : "ਰਬੀ 'ਅਲ-ਅਵਾਲ", +"weeknumberLabel" : "ਹਫ਼ਤਾ", +"muharramLabel" : "ਮੁਹਰਮ", +"safarLabel" : "ਸਫਰ", +"rabi1Label" : "ਰਬੀ ਅਲ-ਅਵਲ", "rabi2Label" : "ਰਬੀ 'ਅਲ-ਥਾਨੀ", -"jumada1Label" : "ਜੁਮਾਦਾ ਅਲ-ਅਵਾਲ", -"jumada2Label" : "ਜੁਮਦਾ ਅਲ-ਥਾਨੀ", +"jumada1Label" : "ਜੁਮਾਦਾ ਅਲ-ਅਵਲ", +"jumada2Label" : "ਜੁਮਾਦਾ ਅਲ-ਥਾਨੀ", "rajabLabel" : "ਰਜਬ", -"shaabanLabel" : "ਸ਼ਾਅਾਨ", +"shaabanLabel" : "ਸ਼ਾਅਬਾਨ", "ramadanLabel" : "ਰਮਜ਼ਾਨ", "shawwalLabel" : "ਸ਼ੋਵਾਲ", -"dhualqiLabel" : "ਧੂ ਅਲ ਕਿਆਦਾਹ", -"dhualhiLabel" : "ਧੂ ਅਲ-ਹਿਜਾਜਾ", -"shortMuharramLabel" : "ਮੁਹ.", -"shortSafarLabel" : "ਸਫ.", -"shortRabi1Label" : "ਰਬੀ. ਆਈ", -"shortRabi2Label" : "ਰਬੀ. II", +"dhualqiLabel" : "ਧੂ ਅਲ-ਕਿਆਦਾਹ", +"dhualhiLabel" : "ਧੂ ਅਲ-ਹਿਜਾਹ", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "ਸੇਫ.", +"shortRabi1Label" : "ਹਾੜੀ. ਆਈ", +"shortRabi2Label" : "ਹਾੜੀ. II", "shortJumada1Label" : "ਜਮ. ਆਈ", "shortJumada2Label" : "ਜਮ. II", -"shortRajabLabel" : "ਰਾਜ", +"shortRajabLabel" : "ਰਾਜ.", "shortShaabanLabel" : "ਸ਼ਾ.", "shortRamadanLabel" : "ਰਾਮ.", "shortShawwalLabel" : "ਸ਼ਾ.", -"shortDhualqiLabel" : "ਧੂਅਲ-ਕਿ Q", -"shortDhualhiLabel" : "ਧੂਲ-ਐਚ", +"shortDhualqiLabel" : "ਧੂਅਲ-ਕਿ.", +"shortDhualhiLabel" : "ਧੂਅਲ-ਐਚ", "daySpanCountLabel" : "ਦਿਨ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb index fd522c1d6..797a41f19 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pl.arb @@ -1,14 +1,14 @@ { -"noSelectedDateCalendarLabel" : "Brak wybranej daty", +"noSelectedDateCalendarLabel" : "Nie wybrano daty", "noEventsCalendarLabel" : "Brak wydarzeń", "ofDataPagerLabel" : "z", -"pagesDataPagerLabel" : "stron", -"itemsDataPagerLabel" : "przedmiotów", +"pagesDataPagerLabel" : "strony", +"itemsDataPagerLabel" : "rzeczy", "pdfBookmarksLabel" : "Zakładki", "pdfNoBookmarksLabel" : "Nie znaleziono zakładek", "pdfScrollStatusOfLabel" : "z", "pdfGoToPageLabel" : "Idź do strony", -"pdfEnterPageNumberLabel" : "Wprowadź numer strony", +"pdfEnterPageNumberLabel" : "Wpisz numer strony", "pdfInvalidPageNumberLabel" : "Proszę wprowadzić poprawny numer", "pdfPaginationDialogOkLabel" : "ok", "pdfPaginationDialogCancelLabel" : "ANULUJ", @@ -17,29 +17,30 @@ "allowedViewWorkWeekLabel" : "Tydzień pracy", "allowedViewMonthLabel" : "Miesiąc", "allowedViewScheduleLabel" : "Harmonogram", -"allowedViewTimelineDayLabel" : "Dzień na osi czasu", -"allowedViewTimelineWeekLabel" : "Tydzień osi czasu", +"allowedViewTimelineDayLabel" : "Dzień osi czasu", +"allowedViewTimelineWeekLabel" : "Tydzień na osi czasu", "allowedViewTimelineWorkWeekLabel" : "Tydzień pracy na osi czasu", "allowedViewTimelineMonthLabel" : "Miesiąc osi czasu", -"todayLabel" : "Dzisiaj", +"todayLabel" : "Dziś", +"weeknumberLabel" : "Tydzień", "muharramLabel" : "Muharram", "safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", +"rabi1Label" : "Rabi' al-awwal", +"rabi2Label" : "Rabi’ al-thani", "jumada1Label" : "Jumada al-awwal", "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", -"shaabanLabel" : "Sha'aban", +"shaabanLabel" : "Szaaban", "ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", -"dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", +"dhualqiLabel" : "Zu al-Qi'dah", +"dhualhiLabel" : "Zu al-Hidżdża", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. ja", -"shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. ja", -"shortJumada2Label" : "Jum. II", +"shortSafarLabel" : "Bezp.", +"shortRabi1Label" : "Rabiego. i", +"shortRabi2Label" : "Rabiego. II", +"shortJumada1Label" : "Skok. i", +"shortJumada2Label" : "Skok. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Baran.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb index aa76f751d..e2c6f8203 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ps.arb @@ -1,41 +1,42 @@ { -"noSelectedDateCalendarLabel" : "نه ټاکل شوې نیټه", -"noEventsCalendarLabel" : "هیڅ پیښه نده", +"noSelectedDateCalendarLabel" : "هیڅ ټاکل شوې نیټه نده", +"noEventsCalendarLabel" : "هیڅ پیښې نشته", "ofDataPagerLabel" : "د", -"pagesDataPagerLabel" : "مخونه", +"pagesDataPagerLabel" : "پاې", "itemsDataPagerLabel" : "توکي", -"pdfBookmarksLabel" : "یادنښې", +"pdfBookmarksLabel" : "بک مارک", "pdfNoBookmarksLabel" : "هیڅ بک مارک ونه موندل شو", "pdfScrollStatusOfLabel" : "د", -"pdfGoToPageLabel" : "پا .ې ته لاړ شه", -"pdfEnterPageNumberLabel" : "د پا numberې شمیره دننه کړئ", +"pdfGoToPageLabel" : "پا pageې ته لاړ شئ", +"pdfEnterPageNumberLabel" : "د پا pageې شمیره دننه کړئ", "pdfInvalidPageNumberLabel" : "مهرباني وکړئ یو معتبر شمیره دننه کړئ", "pdfPaginationDialogOkLabel" : "سمه ده", -"pdfPaginationDialogCancelLabel" : "کینسل", +"pdfPaginationDialogCancelLabel" : "لغوه کول", "allowedViewDayLabel" : "ورځ", "allowedViewWeekLabel" : "اونۍ", -"allowedViewWorkWeekLabel" : "کاري اونۍ", +"allowedViewWorkWeekLabel" : "د کار اونۍ", "allowedViewMonthLabel" : "میاشت", "allowedViewScheduleLabel" : "مهالویش", "allowedViewTimelineDayLabel" : "د مهال ویش ورځ", -"allowedViewTimelineWeekLabel" : "د مهال ویش", +"allowedViewTimelineWeekLabel" : "د مهال ویش اونۍ", "allowedViewTimelineWorkWeekLabel" : "د مهال ویش کاري اونۍ", "allowedViewTimelineMonthLabel" : "د مهال ویش میاشت", "todayLabel" : "نن", +"weeknumberLabel" : "اونۍ", "muharramLabel" : "محرم", -"safarLabel" : "صفر", +"safarLabel" : "صفار", "rabi1Label" : "ربیع الاول", "rabi2Label" : "ربیع الثاني", "jumada1Label" : "جمعه الاول", -"jumada2Label" : "جمعه الثاني", +"jumada2Label" : "جماد al الثاني", "rajabLabel" : "رجب", "shaabanLabel" : "شعبان", "ramadanLabel" : "رمضان", "shawwalLabel" : "شوال", -"dhualqiLabel" : "ذي القده", -"dhualhiLabel" : "ذو الحجjah", -"shortMuharramLabel" : "م.", -"shortSafarLabel" : "صف.", +"dhualqiLabel" : "ذی القعده", +"dhualhiLabel" : "ذی الحجه", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "سف.", "shortRabi1Label" : "ربي. زه", "shortRabi2Label" : "ربي. II", "shortJumada1Label" : "جم. زه", @@ -45,6 +46,6 @@ "shortRamadanLabel" : "رام.", "shortShawwalLabel" : "شا.", "shortDhualqiLabel" : "ذوالق", -"shortDhualhiLabel" : "ذوالح", +"shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "ورځ" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb index e6b1cfdf5..9eccd6aca 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt.arb @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "Vá para página", "pdfEnterPageNumberLabel" : "Digite o número da página", "pdfInvalidPageNumberLabel" : "Por favor insira um número válido", -"pdfPaginationDialogOkLabel" : "Está bem", +"pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "CANCELAR", "allowedViewDayLabel" : "Dia", "allowedViewWeekLabel" : "Semana", @@ -18,10 +18,11 @@ "allowedViewMonthLabel" : "Mês", "allowedViewScheduleLabel" : "Cronograma", "allowedViewTimelineDayLabel" : "Dia da Linha do Tempo", -"allowedViewTimelineWeekLabel" : "Semana do cronograma", +"allowedViewTimelineWeekLabel" : "Semana da Linha do Tempo", "allowedViewTimelineWorkWeekLabel" : "Cronograma da semana de trabalho", "allowedViewTimelineMonthLabel" : "Mês da linha do tempo", "todayLabel" : "Hoje", +"weeknumberLabel" : "Semana", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", @@ -36,9 +37,9 @@ "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. Eu", +"shortRabi1Label" : "Rabi. eu", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. Eu", +"shortJumada1Label" : "Jum. eu", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb index 7fb91e0dd..3a13840dd 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_pt_PT.arb @@ -18,10 +18,11 @@ "allowedViewMonthLabel" : "Mês", "allowedViewScheduleLabel" : "Cronograma", "allowedViewTimelineDayLabel" : "Dia da Linha do Tempo", -"allowedViewTimelineWeekLabel" : "Semana do cronograma", +"allowedViewTimelineWeekLabel" : "Semana da Linha do Tempo", "allowedViewTimelineWorkWeekLabel" : "Cronograma da semana de trabalho", "allowedViewTimelineMonthLabel" : "Mês da linha do tempo", "todayLabel" : "Hoje", +"weeknumberLabel" : "Semana", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb index 857996839..b4c5e4c25 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ro.arb @@ -10,7 +10,7 @@ "pdfGoToPageLabel" : "Mergi la pagina", "pdfEnterPageNumberLabel" : "Introduceți numărul paginii", "pdfInvalidPageNumberLabel" : "Vă rugăm să introduceți un număr valid", -"pdfPaginationDialogOkLabel" : "O.K", +"pdfPaginationDialogOkLabel" : "Bine", "pdfPaginationDialogCancelLabel" : "ANULARE", "allowedViewDayLabel" : "Zi", "allowedViewWeekLabel" : "Săptămână", @@ -21,7 +21,8 @@ "allowedViewTimelineWeekLabel" : "Timeline Week", "allowedViewTimelineWorkWeekLabel" : "Cronologia săptămânii de lucru", "allowedViewTimelineMonthLabel" : "Luna cronologică", -"todayLabel" : "Astăzi", +"todayLabel" : "Azi", +"weeknumberLabel" : "Săptămână", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", @@ -42,7 +43,7 @@ "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", -"shortRamadanLabel" : "Berbec.", +"shortRamadanLabel" : "RAM.", "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb index d50199877..8dbcd5155 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ru.arb @@ -10,18 +10,19 @@ "pdfGoToPageLabel" : "Перейти на страницу", "pdfEnterPageNumberLabel" : "Введите номер страницы", "pdfInvalidPageNumberLabel" : "пожалуйста введите правильное число", -"pdfPaginationDialogOkLabel" : "ОК", +"pdfPaginationDialogOkLabel" : "Ok", "pdfPaginationDialogCancelLabel" : "ОТМЕНА", "allowedViewDayLabel" : "День", "allowedViewWeekLabel" : "Неделя", "allowedViewWorkWeekLabel" : "Рабочая неделя", "allowedViewMonthLabel" : "Месяц", -"allowedViewScheduleLabel" : "График", +"allowedViewScheduleLabel" : "Расписание", "allowedViewTimelineDayLabel" : "Хронология День", "allowedViewTimelineWeekLabel" : "Хронология недели", -"allowedViewTimelineWorkWeekLabel" : "График работы неделя", +"allowedViewTimelineWorkWeekLabel" : "График работы за неделю", "allowedViewTimelineMonthLabel" : "Хронология Месяц", -"todayLabel" : "Cегодня", +"todayLabel" : "Сегодня", +"weeknumberLabel" : "Неделя", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", "rabi1Label" : "Раби аль-Аввал", @@ -36,13 +37,13 @@ "dhualhiLabel" : "Зу аль-Хиджа", "shortMuharramLabel" : "Мух.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Лави. я", -"shortRabi2Label" : "Лави. II", +"shortRabi1Label" : "Раби. я", +"shortRabi2Label" : "Раби. II", "shortJumada1Label" : "Джум. я", "shortJumada2Label" : "Джум. II", "shortRajabLabel" : "Радж.", "shortShaabanLabel" : "Ша.", -"shortRamadanLabel" : "ОЗУ.", +"shortRamadanLabel" : "Баран.", "shortShawwalLabel" : "Шоу.", "shortDhualqiLabel" : "Зуль-К", "shortDhualhiLabel" : "Зуль-Х", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb index 00f1194ed..e0754a1ee 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_si.arb @@ -1,6 +1,6 @@ { "noSelectedDateCalendarLabel" : "තෝරාගත් දිනයක් නොමැත", -"noEventsCalendarLabel" : "සිදුවීම් නොමැත", +"noEventsCalendarLabel" : "සිදුවීම් නැත", "ofDataPagerLabel" : "වල", "pagesDataPagerLabel" : "පිටු", "itemsDataPagerLabel" : "අයිතම", @@ -9,42 +9,43 @@ "pdfScrollStatusOfLabel" : "වල", "pdfGoToPageLabel" : "පිටුවට යන්න", "pdfEnterPageNumberLabel" : "පිටු අංකය ඇතුළත් කරන්න", -"pdfInvalidPageNumberLabel" : "කරුණාකර වලංගු අංකයක් ඇතුළත් කරන්න", +"pdfInvalidPageNumberLabel" : "වලංගු අංකයක් ඇතුළත් කරන්න", "pdfPaginationDialogOkLabel" : "හරි", -"pdfPaginationDialogCancelLabel" : "අවලංගු කරන්න", +"pdfPaginationDialogCancelLabel" : "කැන්සල්", "allowedViewDayLabel" : "දිනය", "allowedViewWeekLabel" : "සතිය", "allowedViewWorkWeekLabel" : "වැඩ සතිය", "allowedViewMonthLabel" : "මාසික", -"allowedViewScheduleLabel" : "උපලේඛනය", -"allowedViewTimelineDayLabel" : "කාල නියමය", -"allowedViewTimelineWeekLabel" : "කාල නියමය", -"allowedViewTimelineWorkWeekLabel" : "කාල නියමය වැඩ සතිය", -"allowedViewTimelineMonthLabel" : "කාල නියමය", +"allowedViewScheduleLabel" : "කාල සටහන", +"allowedViewTimelineDayLabel" : "කාලරාමු දිනය", +"allowedViewTimelineWeekLabel" : "කාලරාමුව සතිය", +"allowedViewTimelineWorkWeekLabel" : "කාලරේඛා වැඩ සතිය", +"allowedViewTimelineMonthLabel" : "කාලරාමුව මාසය", "todayLabel" : "අද", -"muharramLabel" : "මුහාරම්", +"weeknumberLabel" : "සතිය", +"muharramLabel" : "මුහර්රාම්", "safarLabel" : "සෆාර්", -"rabi1Label" : "රබී අල් අව්වාල්", -"rabi2Label" : "රබී අල් තානි", -"jumada1Label" : "ජුමාඩා අල් අව්වාල්", +"rabi1Label" : "රබී අල්-අව්වාල්", +"rabi2Label" : "රබී අල්-තානි", +"jumada1Label" : "ජුමාඩා අල්-අව්වාල්", "jumada2Label" : "ජුමාඩා අල් තානි", -"rajabLabel" : "රාජාබ්", +"rajabLabel" : "රජබ්", "shaabanLabel" : "ෂාබාන්", "ramadanLabel" : "රාමදාන්", "shawwalLabel" : "ෂව්වාල්", -"dhualqiLabel" : "ධු අල්-කයිඩා", -"dhualhiLabel" : "ධු අල් හිජ්ජා", -"shortMuharramLabel" : "මුහ්.", +"dhualqiLabel" : "ධූ අල් කයිඩා", +"dhualhiLabel" : "ධූ අල්-හිජ්ජා", +"shortMuharramLabel" : "ම්හ්.", "shortSafarLabel" : "සේෆ්.", "shortRabi1Label" : "රබී. මම", "shortRabi2Label" : "රබී. II", "shortJumada1Label" : "ජුම්. මම", "shortJumada2Label" : "ජුම්. II", -"shortRajabLabel" : "රාජ්.", -"shortShaabanLabel" : "ෂා.", +"shortRajabLabel" : "රාජ්", +"shortShaabanLabel" : "ශා.", "shortRamadanLabel" : "RAM.", "shortShawwalLabel" : "ෂෝ.", -"shortDhualqiLabel" : "දුල්-කියු", +"shortDhualqiLabel" : "දුල්- Q", "shortDhualhiLabel" : "දුල්-එච්", -"daySpanCountLabel" : "දින" +"daySpanCountLabel" : "දිනය" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb index fbd4cdcbd..bd8f5d746 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sk.arb @@ -1,5 +1,5 @@ { -"noSelectedDateCalendarLabel" : "Nie je vybratý dátum", +"noSelectedDateCalendarLabel" : "Žiadny vybratý dátum", "noEventsCalendarLabel" : "Žiadne udalosti", "ofDataPagerLabel" : "z", "pagesDataPagerLabel" : "strán", @@ -10,20 +10,21 @@ "pdfGoToPageLabel" : "Chod na stranu", "pdfEnterPageNumberLabel" : "Zadajte číslo stránky", "pdfInvalidPageNumberLabel" : "Prosím zadajte platné číslo", -"pdfPaginationDialogOkLabel" : "Ok", +"pdfPaginationDialogOkLabel" : "OK", "pdfPaginationDialogCancelLabel" : "ZRUŠIŤ", "allowedViewDayLabel" : "Deň", "allowedViewWeekLabel" : "Týždeň", "allowedViewWorkWeekLabel" : "Pracovný týždeň", "allowedViewMonthLabel" : "Mesiac", -"allowedViewScheduleLabel" : "Časový plán", +"allowedViewScheduleLabel" : "Rozvrh", "allowedViewTimelineDayLabel" : "Deň časovej osi", "allowedViewTimelineWeekLabel" : "Týždeň časovej osi", "allowedViewTimelineWorkWeekLabel" : "Pracovný týždeň na časovej osi", -"allowedViewTimelineMonthLabel" : "Časová os Mesiac", +"allowedViewTimelineMonthLabel" : "Mesiac časovej osi", "todayLabel" : "Dnes", +"weeknumberLabel" : "Týždeň", "muharramLabel" : "Muharram", -"safarLabel" : "Safar", +"safarLabel" : "Šafár", "rabi1Label" : "Rabi 'al-awwal", "rabi2Label" : "Rabi 'al-thani", "jumada1Label" : "Jumada al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb index cfefeb6b1..5d6a42a4a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sl.arb @@ -5,23 +5,24 @@ "pagesDataPagerLabel" : "strani", "itemsDataPagerLabel" : "predmetov", "pdfBookmarksLabel" : "Zaznamki", -"pdfNoBookmarksLabel" : "Zaznamkov ni bilo mogoče najti", +"pdfNoBookmarksLabel" : "Ni zaznamkov", "pdfScrollStatusOfLabel" : "od", "pdfGoToPageLabel" : "Pojdi na stran", "pdfEnterPageNumberLabel" : "Vnesite številko strani", "pdfInvalidPageNumberLabel" : "Vnesite veljavno številko", "pdfPaginationDialogOkLabel" : "v redu", -"pdfPaginationDialogCancelLabel" : "ODPOVED", +"pdfPaginationDialogCancelLabel" : "PREKLICI", "allowedViewDayLabel" : "Dan", "allowedViewWeekLabel" : "Teden", "allowedViewWorkWeekLabel" : "Delovni teden", "allowedViewMonthLabel" : "Mesec", -"allowedViewScheduleLabel" : "Razpored", -"allowedViewTimelineDayLabel" : "Dan časovne osi", -"allowedViewTimelineWeekLabel" : "Teden časovne osi", -"allowedViewTimelineWorkWeekLabel" : "Časovni delovni teden", -"allowedViewTimelineMonthLabel" : "Mesec časovne osi", +"allowedViewScheduleLabel" : "Urnik", +"allowedViewTimelineDayLabel" : "Dan časovnice", +"allowedViewTimelineWeekLabel" : "Teden časovnice", +"allowedViewTimelineWorkWeekLabel" : "Delovni teden časovnice", +"allowedViewTimelineMonthLabel" : "Mesec časovnice", "todayLabel" : "Danes", +"weeknumberLabel" : "Teden", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", @@ -30,7 +31,7 @@ "jumada2Label" : "Jumada al-thani", "rajabLabel" : "Rajab", "shaabanLabel" : "Sha'aban", -"ramadanLabel" : "Ramazan", +"ramadanLabel" : "Ramadan", "shawwalLabel" : "Shawwal", "dhualqiLabel" : "Dhu al-Qi'dah", "dhualhiLabel" : "Dhu al-Hijjah", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb index f5e52e5c9..8c702e0cf 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sq.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "Nuk ka datë të zgjedhur", +"noSelectedDateCalendarLabel" : "Asnjë datë e zgjedhur", "noEventsCalendarLabel" : "Asnjë ngjarje", -"ofDataPagerLabel" : "e", -"pagesDataPagerLabel" : "faqet", -"itemsDataPagerLabel" : "sende", +"ofDataPagerLabel" : "të", +"pagesDataPagerLabel" : "faqe", +"itemsDataPagerLabel" : "artikuj", "pdfBookmarksLabel" : "Faqeshënuesit", "pdfNoBookmarksLabel" : "Nuk u gjetën faqeshënues", -"pdfScrollStatusOfLabel" : "e", +"pdfScrollStatusOfLabel" : "të", "pdfGoToPageLabel" : "Shko te faqja", -"pdfEnterPageNumberLabel" : "Vendosni numrin e faqes", +"pdfEnterPageNumberLabel" : "Fut numrin e faqes", "pdfInvalidPageNumberLabel" : "Ju lutemi shkruani një numër të vlefshëm", "pdfPaginationDialogOkLabel" : "Ne rregull", -"pdfPaginationDialogCancelLabel" : "ANULOJ", +"pdfPaginationDialogCancelLabel" : "ANULO", "allowedViewDayLabel" : "Dita", "allowedViewWeekLabel" : "Javë", "allowedViewWorkWeekLabel" : "Java e Punës", "allowedViewMonthLabel" : "Muaj", "allowedViewScheduleLabel" : "Programi", -"allowedViewTimelineDayLabel" : "Dita e kronologjisë", -"allowedViewTimelineWeekLabel" : "Java e kronologjisë", -"allowedViewTimelineWorkWeekLabel" : "Java e punës me kronologjinë", -"allowedViewTimelineMonthLabel" : "Muaji i kronologjisë", +"allowedViewTimelineDayLabel" : "Dita e Kronologjisë", +"allowedViewTimelineWeekLabel" : "Java e Kronologjisë", +"allowedViewTimelineWorkWeekLabel" : "Afati kohor Java e Punës", +"allowedViewTimelineMonthLabel" : "Muaji i Kronologjisë", "todayLabel" : "Sot", +"weeknumberLabel" : "Javë", "muharramLabel" : "Muharrem", "safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", +"rabi1Label" : "Rabi'el-evvel", "rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Xhumada el-auval", -"jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Rajab", +"jumada1Label" : "Xhumada el-evual", +"jumada2Label" : "Xhumada al-thani", +"rajabLabel" : "Rexheb", "shaabanLabel" : "Sha'aban", "ramadanLabel" : "Ramazani", -"shawwalLabel" : "Shaval", +"shawwalLabel" : "Sheval", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hixhah", -"shortMuharramLabel" : "Muh", -"shortSafarLabel" : "Saf.", +"dhualhiLabel" : "Dhul-Hixhxhe", +"shortMuharramLabel" : "Muh.", +"shortSafarLabel" : "Saf", "shortRabi1Label" : "Rabi. Une", "shortRabi2Label" : "Rabi. II", "shortJumada1Label" : "Jum Une", "shortJumada2Label" : "Jum II", -"shortRajabLabel" : "Raj", -"shortShaabanLabel" : "Sha", -"shortRamadanLabel" : "Ram", -"shortShawwalLabel" : "Shaw", -"shortDhualqiLabel" : "Dhu'l-Q", +"shortRajabLabel" : "Raj.", +"shortShaabanLabel" : "Sha.", +"shortRamadanLabel" : "Ramë.", +"shortShawwalLabel" : "Shaw.", +"shortDhualqiLabel" : "Dhul-Q", "shortDhualhiLabel" : "Dhul-H", "daySpanCountLabel" : "Dita" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb index 683da71c4..603d961a4 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sr.arb @@ -1,12 +1,12 @@ { "noSelectedDateCalendarLabel" : "Нема изабраног датума", "noEventsCalendarLabel" : "Нема догађаја", -"ofDataPagerLabel" : "од", +"ofDataPagerLabel" : "оф", "pagesDataPagerLabel" : "странице", -"itemsDataPagerLabel" : "предмета", +"itemsDataPagerLabel" : "ставке", "pdfBookmarksLabel" : "обележивача", "pdfNoBookmarksLabel" : "Није пронађен ниједан обележивач", -"pdfScrollStatusOfLabel" : "од", +"pdfScrollStatusOfLabel" : "оф", "pdfGoToPageLabel" : "Иди на страну", "pdfEnterPageNumberLabel" : "Унесите број странице", "pdfInvalidPageNumberLabel" : "Молимо Вас да унесете важећи број", @@ -17,28 +17,29 @@ "allowedViewWorkWeekLabel" : "Радна недеља", "allowedViewMonthLabel" : "Месец дана", "allowedViewScheduleLabel" : "Распоред", -"allowedViewTimelineDayLabel" : "Дан временске оријентације", -"allowedViewTimelineWeekLabel" : "Недеља временске оријентације", -"allowedViewTimelineWorkWeekLabel" : "Радна недеља временског следа", -"allowedViewTimelineMonthLabel" : "Месец хронолошког следа", +"allowedViewTimelineDayLabel" : "Дан временске линије", +"allowedViewTimelineWeekLabel" : "Седмица временске линије", +"allowedViewTimelineWorkWeekLabel" : "Радна недеља временске линије", +"allowedViewTimelineMonthLabel" : "Месец временске линије", "todayLabel" : "Данас", +"weeknumberLabel" : "Недеља", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", "rabi1Label" : "Раби 'ал-аввал", "rabi2Label" : "Раби 'ал-тхани", -"jumada1Label" : "Јумада ал-аввал", -"jumada2Label" : "Јумада ал-тхани", +"jumada1Label" : "Џумада ал-аввал", +"jumada2Label" : "Џумада ал-тани", "rajabLabel" : "Рајаб", "shaabanLabel" : "Сха'абан", "ramadanLabel" : "Рамазан", "shawwalLabel" : "Схаввал", "dhualqiLabel" : "Дху ал-Ки'дах", -"dhualhiLabel" : "Дху ал-Хиџа", +"dhualhiLabel" : "Дху ал-Хијјах", "shortMuharramLabel" : "Мух.", "shortSafarLabel" : "Саф.", -"shortRabi1Label" : "Раби. Ја", +"shortRabi1Label" : "Раби. И", "shortRabi2Label" : "Раби. ИИ", -"shortJumada1Label" : "Јум. Ја", +"shortJumada1Label" : "Јум. И", "shortJumada2Label" : "Јум. ИИ", "shortRajabLabel" : "Рај.", "shortShaabanLabel" : "Сха.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb index 55f2f1b24..aa36fe4e5 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sv.arb @@ -1,9 +1,9 @@ { "noSelectedDateCalendarLabel" : "Inget valt datum", -"noEventsCalendarLabel" : "Inga händelser", +"noEventsCalendarLabel" : "Inga evenemang", "ofDataPagerLabel" : "av", "pagesDataPagerLabel" : "sidor", -"itemsDataPagerLabel" : "föremål", +"itemsDataPagerLabel" : "objekt", "pdfBookmarksLabel" : "Bokmärken", "pdfNoBookmarksLabel" : "Inga bokmärken hittades", "pdfScrollStatusOfLabel" : "av", @@ -11,7 +11,7 @@ "pdfEnterPageNumberLabel" : "Ange sidnummer", "pdfInvalidPageNumberLabel" : "var vänlig skriv in ett giltigt nummer", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "ANNULLERA", +"pdfPaginationDialogCancelLabel" : "AVBRYT", "allowedViewDayLabel" : "Dag", "allowedViewWeekLabel" : "Vecka", "allowedViewWorkWeekLabel" : "Arbetsvecka", @@ -19,9 +19,10 @@ "allowedViewScheduleLabel" : "Schema", "allowedViewTimelineDayLabel" : "Tidslinje dag", "allowedViewTimelineWeekLabel" : "Tidslinje vecka", -"allowedViewTimelineWorkWeekLabel" : "Tidslinje arbetsvecka", +"allowedViewTimelineWorkWeekLabel" : "Tidslinje Arbetsvecka", "allowedViewTimelineMonthLabel" : "Tidslinje månad", "todayLabel" : "I dag", +"weeknumberLabel" : "Vecka", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", @@ -36,9 +37,9 @@ "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Muh.", "shortSafarLabel" : "Saf.", -"shortRabi1Label" : "Rabi. Jag", +"shortRabi1Label" : "Rabi. I", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. Jag", +"shortJumada1Label" : "Jum. I", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb index 4d9048b1c..86ac8426c 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_sw.arb @@ -11,7 +11,7 @@ "pdfEnterPageNumberLabel" : "Ingiza nambari ya ukurasa", "pdfInvalidPageNumberLabel" : "Tafadhali ingiza nambari halali", "pdfPaginationDialogOkLabel" : "sawa", -"pdfPaginationDialogCancelLabel" : "FUTA", +"pdfPaginationDialogCancelLabel" : "GHAFU", "allowedViewDayLabel" : "Siku", "allowedViewWeekLabel" : "Wiki", "allowedViewWorkWeekLabel" : "Wiki ya Kazi", @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel" : "Wiki ya Kazi ya ratiba", "allowedViewTimelineMonthLabel" : "Mwezi wa Ratiba", "todayLabel" : "Leo", +"weeknumberLabel" : "Wiki", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb index a9c1a39bb..70b7b658b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ta.arb @@ -1,47 +1,48 @@ { "noSelectedDateCalendarLabel" : "தேர்ந்தெடுக்கப்பட்ட தேதி இல்லை", "noEventsCalendarLabel" : "நிகழ்வுகள் இல்லை", -"ofDataPagerLabel" : "of", +"ofDataPagerLabel" : "இன்", "pagesDataPagerLabel" : "பக்கங்கள்", "itemsDataPagerLabel" : "பொருட்களை", "pdfBookmarksLabel" : "புக்மார்க்குகள்", -"pdfNoBookmarksLabel" : "புக்மார்க்குகள் எதுவும் கிடைக்கவில்லை", -"pdfScrollStatusOfLabel" : "of", +"pdfNoBookmarksLabel" : "புக்மார்க்குகள் இல்லை", +"pdfScrollStatusOfLabel" : "இன்", "pdfGoToPageLabel" : "பக்கத்திற்கு செல்", "pdfEnterPageNumberLabel" : "பக்க எண்ணை உள்ளிடவும்", "pdfInvalidPageNumberLabel" : "சரியான எண்ணை உள்ளிடவும்", "pdfPaginationDialogOkLabel" : "சரி", -"pdfPaginationDialogCancelLabel" : "ரத்துசெய்", +"pdfPaginationDialogCancelLabel" : "கேன்சல்", "allowedViewDayLabel" : "நாள்", "allowedViewWeekLabel" : "வாரம்", "allowedViewWorkWeekLabel" : "வேலை வாரம்", "allowedViewMonthLabel" : "மாதம்", "allowedViewScheduleLabel" : "அட்டவணை", -"allowedViewTimelineDayLabel" : "காலக்கெடு நாள்", +"allowedViewTimelineDayLabel" : "காலவரிசை நாள்", "allowedViewTimelineWeekLabel" : "காலவரிசை வாரம்", "allowedViewTimelineWorkWeekLabel" : "காலவரிசை வேலை வாரம்", -"allowedViewTimelineMonthLabel" : "காலக்கெடு மாதம்", +"allowedViewTimelineMonthLabel" : "காலவரிசை மாதம்", "todayLabel" : "இன்று", +"weeknumberLabel" : "வாரம்", "muharramLabel" : "முஹர்ரம்", "safarLabel" : "சஃபர்", -"rabi1Label" : "ரபி 'அல்-அவ்வால்", -"rabi2Label" : "ரபி 'அல்-தானி", +"rabi1Label" : "ரபி அல்-அவ்வல்", +"rabi2Label" : "ரபி அல்-தானி", "jumada1Label" : "ஜுமதா அல்-அவ்வால்", -"jumada2Label" : "ஜுமதா அல்-தானி", -"rajabLabel" : "ராஜாப்", -"shaabanLabel" : "ஷாஅபன்", +"jumada2Label" : "ஜுமடா அல்-தானி", +"rajabLabel" : "ரஜப்", +"shaabanLabel" : "ஷாபான்", "ramadanLabel" : "ரமலான்", "shawwalLabel" : "ஷவ்வால்", -"dhualqiLabel" : "து அல்-கிதா", -"dhualhiLabel" : "து அல்-ஹிஜ்ஜா", -"shortMuharramLabel" : "மு.", -"shortSafarLabel" : "பாதுகாப்பான.", -"shortRabi1Label" : "ரபி. நான்", -"shortRabi2Label" : "ரபி. II", +"dhualqiLabel" : "து அல்-குய்தா", +"dhualhiLabel" : "தூ அல்-ஹிஜ்ஜா", +"shortMuharramLabel" : "முஹ்.", +"shortSafarLabel" : "சேஃப்.", +"shortRabi1Label" : "ரபி நான்", +"shortRabi2Label" : "ரபி II", "shortJumada1Label" : "ஜம். நான்", "shortJumada2Label" : "ஜம். II", -"shortRajabLabel" : "ராஜ்.", -"shortShaabanLabel" : "ஷா.", +"shortRajabLabel" : "ராஜ்", +"shortShaabanLabel" : "ஷா", "shortRamadanLabel" : "ரேம்.", "shortShawwalLabel" : "ஷா.", "shortDhualqiLabel" : "துல்-கே", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb index d475bedda..2fb775457 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_te.arb @@ -1,15 +1,15 @@ { "noSelectedDateCalendarLabel" : "ఎంచుకున్న తేదీ లేదు", -"noEventsCalendarLabel" : "సంఘటనలు లేవు", +"noEventsCalendarLabel" : "ఈవెంట్‌లు లేవు", "ofDataPagerLabel" : "యొక్క", "pagesDataPagerLabel" : "పేజీలు", -"itemsDataPagerLabel" : "అంశాలు", +"itemsDataPagerLabel" : "వస్తువులు", "pdfBookmarksLabel" : "బుక్‌మార్క్‌లు", "pdfNoBookmarksLabel" : "బుక్‌మార్క్‌లు కనుగొనబడలేదు", "pdfScrollStatusOfLabel" : "యొక్క", "pdfGoToPageLabel" : "పుటకు వెళ్ళు", "pdfEnterPageNumberLabel" : "పేజీ సంఖ్యను నమోదు చేయండి", -"pdfInvalidPageNumberLabel" : "దయచేసి చెల్లుబాటు అయ్యే సంఖ్యను నమోదు చేయండి", +"pdfInvalidPageNumberLabel" : "దయచేసి చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి", "pdfPaginationDialogOkLabel" : "అలాగే", "pdfPaginationDialogCancelLabel" : "రద్దు చేయండి", "allowedViewDayLabel" : "రోజు", @@ -19,32 +19,33 @@ "allowedViewScheduleLabel" : "షెడ్యూల్", "allowedViewTimelineDayLabel" : "కాలక్రమం రోజు", "allowedViewTimelineWeekLabel" : "కాలక్రమం వారం", -"allowedViewTimelineWorkWeekLabel" : "కాలక్రమం పని వారం", +"allowedViewTimelineWorkWeekLabel" : "టైమ్‌లైన్ పని వారం", "allowedViewTimelineMonthLabel" : "కాలక్రమం నెల", -"todayLabel" : "ఈ రోజు", -"muharramLabel" : "మొహర్రం", +"todayLabel" : "నేడు", +"weeknumberLabel" : "వారం", +"muharramLabel" : "ముహర్రం", "safarLabel" : "సఫర్", "rabi1Label" : "రబీ అల్-అవ్వాల్", -"rabi2Label" : "రబీ అల్-తాని", -"jumada1Label" : "జుమాడా అల్-అవ్వాల్", -"jumada2Label" : "జుమాడా అల్-తాని", -"rajabLabel" : "రాజాబ్", +"rabi2Label" : "రబీ 'అల్-థాని", +"jumada1Label" : "జుమాదా అల్ అవ్వాల్", +"jumada2Label" : "జుమాదా అల్-థాని", +"rajabLabel" : "రజబ్", "shaabanLabel" : "షాబాన్", "ramadanLabel" : "రంజాన్", -"shawwalLabel" : "షావ్వాల్", +"shawwalLabel" : "షవ్వాల్", "dhualqiLabel" : "ధు అల్-ఖిదా", "dhualhiLabel" : "ధు అల్-హిజ్జా", "shortMuharramLabel" : "ముహ్.", "shortSafarLabel" : "సేఫ్.", "shortRabi1Label" : "రబీ. నేను", "shortRabi2Label" : "రబీ. II", -"shortJumada1Label" : "జం. నేను", -"shortJumada2Label" : "జం. II", -"shortRajabLabel" : "రాజ్.", -"shortShaabanLabel" : "షా.", -"shortRamadanLabel" : "రామ్.", +"shortJumada1Label" : "జమ్. నేను", +"shortJumada2Label" : "జమ్. II", +"shortRajabLabel" : "రాజ్", +"shortShaabanLabel" : "షా", +"shortRamadanLabel" : "రామ్", "shortShawwalLabel" : "షా.", -"shortDhualqiLabel" : "ధుల్-క్యూ", +"shortDhualqiLabel" : "ధూల్-ప్ర", "shortDhualhiLabel" : "ధుల్-హెచ్", "daySpanCountLabel" : "రోజు" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb index 563240f8f..c29992324 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_th.arb @@ -1,15 +1,15 @@ { -"noSelectedDateCalendarLabel" : "ไม่มีวันที่เลือก", -"noEventsCalendarLabel" : "ไม่มีเหตุการณ์", +"noSelectedDateCalendarLabel" : "ไม่ได้เลือกวันที่", +"noEventsCalendarLabel" : "ไม่มีกิจกรรม", "ofDataPagerLabel" : "ของ", "pagesDataPagerLabel" : "หน้า", "itemsDataPagerLabel" : "รายการ", -"pdfBookmarksLabel" : "บุ๊กมาร์ก", -"pdfNoBookmarksLabel" : "ไม่พบบุ๊กมาร์ก", +"pdfBookmarksLabel" : "ที่คั่นหนังสือ", +"pdfNoBookmarksLabel" : "ไม่พบบุ๊คมาร์ค", "pdfScrollStatusOfLabel" : "ของ", "pdfGoToPageLabel" : "ไปที่หน้า", -"pdfEnterPageNumberLabel" : "ป้อนหมายเลขหน้า", -"pdfInvalidPageNumberLabel" : "กรุณากรอกหมายเลขที่ถูกต้อง", +"pdfEnterPageNumberLabel" : "ใส่เลขหน้า", +"pdfInvalidPageNumberLabel" : "โปรดป้อนหมายเลขที่ถูกต้อง", "pdfPaginationDialogOkLabel" : "ตกลง", "pdfPaginationDialogCancelLabel" : "ยกเลิก", "allowedViewDayLabel" : "วัน", @@ -17,34 +17,35 @@ "allowedViewWorkWeekLabel" : "สัปดาห์การทำงาน", "allowedViewMonthLabel" : "เดือน", "allowedViewScheduleLabel" : "กำหนดการ", -"allowedViewTimelineDayLabel" : "ไทม์ไลน์วัน", +"allowedViewTimelineDayLabel" : "วันไทม์ไลน์", "allowedViewTimelineWeekLabel" : "ไทม์ไลน์สัปดาห์", -"allowedViewTimelineWorkWeekLabel" : "Timeline Work Week", +"allowedViewTimelineWorkWeekLabel" : "ไทม์ไลน์งานสัปดาห์", "allowedViewTimelineMonthLabel" : "ไทม์ไลน์เดือน", "todayLabel" : "วันนี้", -"muharramLabel" : "มูฮาร์ราม", -"safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-awwal", -"rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-awwal", +"weeknumberLabel" : "สัปดาห์", +"muharramLabel" : "มูฮัรรอม", +"safarLabel" : "ซาฟาร์", +"rabi1Label" : "เราะบี อัลเอาวัล", +"rabi2Label" : "รอบีอัล-ธานี", +"jumada1Label" : "ญุมาดา อัลเอาวัล", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "จ", -"shaabanLabel" : "Sha'aban", -"ramadanLabel" : "เดือนรอมฎอน", -"shawwalLabel" : "Shawwal", +"rajabLabel" : "ราชภัฏ", +"shaabanLabel" : "ชะอฺบาน", +"ramadanLabel" : "รอมฎอน", +"shawwalLabel" : "เชาวาล", "dhualqiLabel" : "Dhu al-Qi'dah", -"dhualhiLabel" : "Dhu al-Hijjah", -"shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "ปลอดภัย.", -"shortRabi1Label" : "ราบี. ผม", -"shortRabi2Label" : "ราบี. II", -"shortJumada1Label" : "จุ๋ม. ผม", -"shortJumada2Label" : "จุ๋ม. II", +"dhualhiLabel" : "ดูอัลฮิจญะฮ์", +"shortMuharramLabel" : "มุ้ย.", +"shortSafarLabel" : "เซฟ.", +"shortRabi1Label" : "รบี. ผม", +"shortRabi2Label" : "รบี. II", +"shortJumada1Label" : "จัม. ผม", +"shortJumada2Label" : "จัม. II", "shortRajabLabel" : "ราช.", "shortShaabanLabel" : "ชา.", "shortRamadanLabel" : "แกะ.", "shortShawwalLabel" : "ชอว์.", -"shortDhualqiLabel" : "Dhu'l-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualqiLabel" : "ดุล-คิว", +"shortDhualhiLabel" : "ดุลฮัก", "daySpanCountLabel" : "วัน" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb index 3ac571278..5b2e0090a 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tl.arb @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel" : "Linggo ng Trabaho ng Timeline", "allowedViewTimelineMonthLabel" : "Buwan ng Timeline", "todayLabel" : "Ngayon", +"weeknumberLabel" : "Linggo", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb index 667ef0f94..eba5e68cc 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_tr.arb @@ -1,40 +1,41 @@ { -"noSelectedDateCalendarLabel" : "Tarih seçilmedi", +"noSelectedDateCalendarLabel" : "Seçili tarih yok", "noEventsCalendarLabel" : "Olay yok", -"ofDataPagerLabel" : "nın-nin", -"pagesDataPagerLabel" : "sayfaları", +"ofDataPagerLabel" : "ile ilgili", +"pagesDataPagerLabel" : "sayfalar", "itemsDataPagerLabel" : "öğeler", "pdfBookmarksLabel" : "Yer imleri", "pdfNoBookmarksLabel" : "Yer işareti bulunamadı", -"pdfScrollStatusOfLabel" : "nın-nin", +"pdfScrollStatusOfLabel" : "ile ilgili", "pdfGoToPageLabel" : "Sayfaya git", "pdfEnterPageNumberLabel" : "Sayfa numarasını girin", "pdfInvalidPageNumberLabel" : "Lütfen geçerli bir numara girin", -"pdfPaginationDialogOkLabel" : "tamam", +"pdfPaginationDialogOkLabel" : "Tamam", "pdfPaginationDialogCancelLabel" : "İPTAL ETMEK", "allowedViewDayLabel" : "Gün", "allowedViewWeekLabel" : "Hafta", "allowedViewWorkWeekLabel" : "Çalışma haftası", "allowedViewMonthLabel" : "Ay", -"allowedViewScheduleLabel" : "Program", +"allowedViewScheduleLabel" : "Takvim", "allowedViewTimelineDayLabel" : "Zaman Çizelgesi Günü", "allowedViewTimelineWeekLabel" : "Zaman Çizelgesi Haftası", "allowedViewTimelineWorkWeekLabel" : "Zaman Çizelgesi Çalışma Haftası", "allowedViewTimelineMonthLabel" : "Zaman Çizelgesi Ayı", "todayLabel" : "Bugün", +"weeknumberLabel" : "Hafta", "muharramLabel" : "Muharrem", "safarLabel" : "Safar", "rabi1Label" : "Rebiülevvel", -"rabi2Label" : "Rabi 'al-thani", -"jumada1Label" : "Jumada al-evvel", +"rabi2Label" : "Rabi' al-thani", +"jumada1Label" : "Cumade el-evvel", "jumada2Label" : "Jumada al-thani", -"rajabLabel" : "Receb", -"shaabanLabel" : "Sha'aban", +"rajabLabel" : "Recep", +"shaabanLabel" : "Şaban", "ramadanLabel" : "Ramazan", "shawwalLabel" : "Şevval", -"dhualqiLabel" : "Dhu al-Qi'dah", +"dhualqiLabel" : "Zil Kaide", "dhualhiLabel" : "Zilhicce", -"shortMuharramLabel" : "Muh.", +"shortMuharramLabel" : "Müh.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. ben", "shortRabi2Label" : "Rabi. II", @@ -45,6 +46,6 @@ "shortRamadanLabel" : "Veri deposu.", "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Zil-Q", -"shortDhualhiLabel" : "Dhu'l-H", +"shortDhualhiLabel" : "Zül-H", "daySpanCountLabel" : "Gün" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb index 9e3079d62..ec37c95b3 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uk.arb @@ -2,10 +2,10 @@ "noSelectedDateCalendarLabel" : "Не вибрано дати", "noEventsCalendarLabel" : "Жодних подій", "ofDataPagerLabel" : "з", -"pagesDataPagerLabel" : "сторінки", +"pagesDataPagerLabel" : "сторінок", "itemsDataPagerLabel" : "предметів", "pdfBookmarksLabel" : "Закладки", -"pdfNoBookmarksLabel" : "Закладки не знайдено", +"pdfNoBookmarksLabel" : "Закладок не знайдено", "pdfScrollStatusOfLabel" : "з", "pdfGoToPageLabel" : "Перейти на сторінку", "pdfEnterPageNumberLabel" : "Введіть номер сторінки", @@ -17,24 +17,25 @@ "allowedViewWorkWeekLabel" : "Робочий тиждень", "allowedViewMonthLabel" : "Місяць", "allowedViewScheduleLabel" : "Розклад", -"allowedViewTimelineDayLabel" : "День часової шкали", -"allowedViewTimelineWeekLabel" : "Тиждень шкали часу", -"allowedViewTimelineWorkWeekLabel" : "Хронологія робочого тижня", -"allowedViewTimelineMonthLabel" : "Місяць часової шкали", +"allowedViewTimelineDayLabel" : "День хронології", +"allowedViewTimelineWeekLabel" : "Тиждень хронології", +"allowedViewTimelineWorkWeekLabel" : "Тиждень робочого тижня", +"allowedViewTimelineMonthLabel" : "Місяць хронології", "todayLabel" : "Сьогодні", +"weeknumberLabel" : "Тиждень", "muharramLabel" : "Мухаррам", "safarLabel" : "Сафар", -"rabi1Label" : "Рабі аль-Ауваль", -"rabi2Label" : "Рабі аль-Тані", +"rabi1Label" : "Рабі аль-аввал", +"rabi2Label" : "Рабі аль-тані", "jumada1Label" : "Джумада аль-авваль", -"jumada2Label" : "Джумада аль-Тані", +"jumada2Label" : "Джумада аль-тані", "rajabLabel" : "Раджаб", "shaabanLabel" : "Шаабан", "ramadanLabel" : "Рамадан", "shawwalLabel" : "Шавваль", -"dhualqiLabel" : "Дху аль-Кіда", +"dhualqiLabel" : "Ду аль-Кіда", "dhualhiLabel" : "Дху аль-Хіджа", -"shortMuharramLabel" : "Мм", +"shortMuharramLabel" : "Ага.", "shortSafarLabel" : "Saf.", "shortRabi1Label" : "Рабі. Я", "shortRabi2Label" : "Рабі. II", @@ -44,7 +45,7 @@ "shortShaabanLabel" : "Ша.", "shortRamadanLabel" : "ОЗП.", "shortShawwalLabel" : "Шоу.", -"shortDhualqiLabel" : "Зул-Q", -"shortDhualhiLabel" : "Зул-Н", +"shortDhualqiLabel" : "Dhu'l-Q", +"shortDhualhiLabel" : "Dhu'l-H", "daySpanCountLabel" : "День" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb index 8ca2a5edd..1833c161d 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_ur.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "کوئی منتخب تاریخ", -"noEventsCalendarLabel" : "کوئی واقعات نہیں", -"ofDataPagerLabel" : "کے", +"noSelectedDateCalendarLabel" : "کوئی منتخب تاریخ نہیں۔", +"noEventsCalendarLabel" : "کوئی ایونٹس نہیں۔", +"ofDataPagerLabel" : "کی", "pagesDataPagerLabel" : "صفحات", "itemsDataPagerLabel" : "اشیاء", -"pdfBookmarksLabel" : "بُک مارکس", -"pdfNoBookmarksLabel" : "کوئی بُک مارکس نہیں ملا", -"pdfScrollStatusOfLabel" : "کے", +"pdfBookmarksLabel" : "بُک مارکس۔", +"pdfNoBookmarksLabel" : "کوئی بُک مارکس نہیں ملا۔", +"pdfScrollStatusOfLabel" : "کی", "pdfGoToPageLabel" : "صفحے پر جائیں", -"pdfEnterPageNumberLabel" : "صفحہ نمبر درج کریں", +"pdfEnterPageNumberLabel" : "صفحہ نمبر درج کریں۔", "pdfInvalidPageNumberLabel" : "براہ مہربانی ایک درست نمبر درج کریں", "pdfPaginationDialogOkLabel" : "ٹھیک ہے", -"pdfPaginationDialogCancelLabel" : "کینسل", -"allowedViewDayLabel" : "دن", +"pdfPaginationDialogCancelLabel" : "منسوخ کریں۔", +"allowedViewDayLabel" : "دن۔", "allowedViewWeekLabel" : "ہفتہ", "allowedViewWorkWeekLabel" : "کام کا ہفتہ", "allowedViewMonthLabel" : "مہینہ", -"allowedViewScheduleLabel" : "نظام الاوقات", -"allowedViewTimelineDayLabel" : "ٹائم لائن ڈے", -"allowedViewTimelineWeekLabel" : "ٹائم لائن ہفتہ", -"allowedViewTimelineWorkWeekLabel" : "ٹائم لائن ورک ویک", -"allowedViewTimelineMonthLabel" : "ٹائم لائن مہینہ", +"allowedViewScheduleLabel" : "شیڈول", +"allowedViewTimelineDayLabel" : "ٹائم لائن ڈے۔", +"allowedViewTimelineWeekLabel" : "ٹائم لائن ہفتہ۔", +"allowedViewTimelineWorkWeekLabel" : "ٹائم لائن ورک ویک۔", +"allowedViewTimelineMonthLabel" : "ٹائم لائن مہینہ۔", "todayLabel" : "آج", -"muharramLabel" : "محرم", -"safarLabel" : "صفر", +"weeknumberLabel" : "ہفتہ", +"muharramLabel" : "محرم۔", +"safarLabel" : "صفر۔", "rabi1Label" : "ربیع الاول", -"rabi2Label" : "ربیع الثانی", -"jumada1Label" : "جمعہ الاول", -"jumada2Label" : "جمعہ الثانی", -"rajabLabel" : "رجب", -"shaabanLabel" : "شعبان", +"rabi2Label" : "ربیع الثانی۔", +"jumada1Label" : "جمعہ الاول۔", +"jumada2Label" : "جماد al الثانی۔", +"rajabLabel" : "رجب۔", +"shaabanLabel" : "شعبان۔", "ramadanLabel" : "رمضان", -"shawwalLabel" : "شوال", -"dhualqiLabel" : "ذو الکعدہ", +"shawwalLabel" : "شوال۔", +"dhualqiLabel" : "ذو القعدہ۔", "dhualhiLabel" : "ذو الحجہ", -"shortMuharramLabel" : "مح۔", -"shortSafarLabel" : "صف۔", -"shortRabi1Label" : "ربیع۔ میں", -"shortRabi2Label" : "ربیع۔ II", +"shortMuharramLabel" : "مہ۔", +"shortSafarLabel" : "سیف", +"shortRabi1Label" : "رابی میں", +"shortRabi2Label" : "رابی دوم", "shortJumada1Label" : "جم۔ میں", -"shortJumada2Label" : "جم۔ II", -"shortRajabLabel" : "راج", -"shortShaabanLabel" : "شا۔", -"shortRamadanLabel" : "رام۔", -"shortShawwalLabel" : "شا۔", -"shortDhualqiLabel" : "ذوالق", -"shortDhualhiLabel" : "ذوالحل", -"daySpanCountLabel" : "دن" +"shortJumada2Label" : "جم۔ دوم", +"shortRajabLabel" : "راج۔", +"shortShaabanLabel" : "شا", +"shortRamadanLabel" : "رام", +"shortShawwalLabel" : "شا", +"shortDhualqiLabel" : "ذوالق۔", +"shortDhualhiLabel" : "ذوال ایچ۔", +"daySpanCountLabel" : "دن۔" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb index 02e755faf..bb93c17c4 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_uz.arb @@ -3,7 +3,7 @@ "noEventsCalendarLabel" : "Hech qanday tadbir yo'q", "ofDataPagerLabel" : "ning", "pagesDataPagerLabel" : "sahifalar", -"itemsDataPagerLabel" : "buyumlar", +"itemsDataPagerLabel" : "narsalar", "pdfBookmarksLabel" : "Xatcho'plar", "pdfNoBookmarksLabel" : "Xatcho'plar topilmadi", "pdfScrollStatusOfLabel" : "ning", @@ -11,31 +11,32 @@ "pdfEnterPageNumberLabel" : "Sahifa raqamini kiriting", "pdfInvalidPageNumberLabel" : "Iltimos, to'g'ri raqamni kiriting", "pdfPaginationDialogOkLabel" : "OK", -"pdfPaginationDialogCancelLabel" : "Bekor qilish", +"pdfPaginationDialogCancelLabel" : "BOSHLASH", "allowedViewDayLabel" : "Kun", "allowedViewWeekLabel" : "Hafta", "allowedViewWorkWeekLabel" : "Ish haftasi", "allowedViewMonthLabel" : "Oy", "allowedViewScheduleLabel" : "Jadval", "allowedViewTimelineDayLabel" : "Vaqt chizig'i kuni", -"allowedViewTimelineWeekLabel" : "Vaqt chizig'i haftasi", -"allowedViewTimelineWorkWeekLabel" : "Vaqt jadvalidagi ish haftasi", +"allowedViewTimelineWeekLabel" : "Vaqt jadvalining haftaligi", +"allowedViewTimelineWorkWeekLabel" : "Vaqt jadvalining ish haftasi", "allowedViewTimelineMonthLabel" : "Vaqt chizig'i oyi", "todayLabel" : "Bugun", +"weeknumberLabel" : "Hafta", "muharramLabel" : "Muharram", "safarLabel" : "Safar", -"rabi1Label" : "Rabi 'al-avval", -"rabi2Label" : "Rabi 'al-thani", +"rabi1Label" : "Rabiul-avval", +"rabi2Label" : "Rabi al-taniy", "jumada1Label" : "Jumada al-avval", -"jumada2Label" : "Jumada al-thani", +"jumada2Label" : "Jumada al-taniy", "rajabLabel" : "Rajab", "shaabanLabel" : "Sha'bon", "ramadanLabel" : "Ramazon", "shawwalLabel" : "Shavvol", "dhualqiLabel" : "Zul al-Qida", -"dhualhiLabel" : "Zul al-Hijja", +"dhualhiLabel" : "Zulhijja", "shortMuharramLabel" : "Muh.", -"shortSafarLabel" : "Xavfsiz", +"shortSafarLabel" : "Saf.", "shortRabi1Label" : "Rabi. Men", "shortRabi2Label" : "Rabi. II", "shortJumada1Label" : "Jum. Men", @@ -44,7 +45,7 @@ "shortShaabanLabel" : "Sha.", "shortRamadanLabel" : "Ram.", "shortShawwalLabel" : "Shou.", -"shortDhualqiLabel" : "Zul-Q", +"shortDhualqiLabel" : "Zulq", "shortDhualhiLabel" : "Zulh", "daySpanCountLabel" : "Kun" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb index f6a3b119a..73710b88e 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_vi.arb @@ -10,18 +10,19 @@ "pdfGoToPageLabel" : "Đi tới trang", "pdfEnterPageNumberLabel" : "Nhập số trang", "pdfInvalidPageNumberLabel" : "Vui lòng nhập một số hợp lệ", -"pdfPaginationDialogOkLabel" : "đồng ý", -"pdfPaginationDialogCancelLabel" : "HỦY", -"allowedViewDayLabel" : "ngày", +"pdfPaginationDialogOkLabel" : "VÂNG", +"pdfPaginationDialogCancelLabel" : "SỰ HỦY BỎ", +"allowedViewDayLabel" : "Ngày", "allowedViewWeekLabel" : "Tuần", "allowedViewWorkWeekLabel" : "Tuần làm việc", -"allowedViewMonthLabel" : "tháng", -"allowedViewScheduleLabel" : "Lên lịch", +"allowedViewMonthLabel" : "Tháng", +"allowedViewScheduleLabel" : "Lịch trình", "allowedViewTimelineDayLabel" : "Ngày dòng thời gian", -"allowedViewTimelineWeekLabel" : "Dòng thời gian tuần", -"allowedViewTimelineWorkWeekLabel" : "Thời gian làm việc tuần", -"allowedViewTimelineMonthLabel" : "Thời gian tháng", +"allowedViewTimelineWeekLabel" : "Dòng thời gian trong tuần", +"allowedViewTimelineWorkWeekLabel" : "Dòng thời gian làm việc trong tuần", +"allowedViewTimelineMonthLabel" : "Dòng thời gian Tháng", "todayLabel" : "Hôm nay", +"weeknumberLabel" : "Tuần", "muharramLabel" : "Muharram", "safarLabel" : "Safar", "rabi1Label" : "Rabi 'al-awwal", @@ -36,9 +37,9 @@ "dhualhiLabel" : "Dhu al-Hijjah", "shortMuharramLabel" : "Ờ.", "shortSafarLabel" : "Két sắt.", -"shortRabi1Label" : "Rabi. Tôi", +"shortRabi1Label" : "Rabi. tôi", "shortRabi2Label" : "Rabi. II", -"shortJumada1Label" : "Jum. Tôi", +"shortJumada1Label" : "Jum. tôi", "shortJumada2Label" : "Jum. II", "shortRajabLabel" : "Raj.", "shortShaabanLabel" : "Sha.", @@ -46,5 +47,5 @@ "shortShawwalLabel" : "Shaw.", "shortDhualqiLabel" : "Dhu'l-Q", "shortDhualhiLabel" : "Dhu'l-H", -"daySpanCountLabel" : "ngày" +"daySpanCountLabel" : "Ngày" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb index 2eaf9ffec..929c91cf6 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "没有选定的日期", +"noSelectedDateCalendarLabel" : "未选择日期", "noEventsCalendarLabel" : "没有活动", "ofDataPagerLabel" : "的", -"pagesDataPagerLabel" : "页数", +"pagesDataPagerLabel" : "页", "itemsDataPagerLabel" : "项目", "pdfBookmarksLabel" : "书签", -"pdfNoBookmarksLabel" : "找不到书签", +"pdfNoBookmarksLabel" : "未找到书签", "pdfScrollStatusOfLabel" : "的", "pdfGoToPageLabel" : "转到页面", "pdfEnterPageNumberLabel" : "输入页码", -"pdfInvalidPageNumberLabel" : "请输入一个有效的数字", -"pdfPaginationDialogOkLabel" : "好", +"pdfInvalidPageNumberLabel" : "请输入有效号码", +"pdfPaginationDialogOkLabel" : "好的", "pdfPaginationDialogCancelLabel" : "取消", -"allowedViewDayLabel" : "天", -"allowedViewWeekLabel" : "周", +"allowedViewDayLabel" : "日", +"allowedViewWeekLabel" : "星期", "allowedViewWorkWeekLabel" : "工作周", "allowedViewMonthLabel" : "月", -"allowedViewScheduleLabel" : "时间表", -"allowedViewTimelineDayLabel" : "时间轴日", +"allowedViewScheduleLabel" : "日程", +"allowedViewTimelineDayLabel" : "时间表日", "allowedViewTimelineWeekLabel" : "时间轴周", -"allowedViewTimelineWorkWeekLabel" : "时间轴工作周", +"allowedViewTimelineWorkWeekLabel" : "时间表工作周", "allowedViewTimelineMonthLabel" : "时间轴月", "todayLabel" : "今天", -"muharramLabel" : "穆哈拉姆", +"weeknumberLabel" : "星期", +"muharramLabel" : "穆哈兰姆", "safarLabel" : "萨法尔", -"rabi1Label" : "拉比·阿瓦尔", -"rabi2Label" : "拉比阿尔塔尼", -"jumada1Label" : "朱马达·阿瓦尔", -"jumada2Label" : "朱马达·萨塔尼(Jumada al-thani)", -"rajabLabel" : "拉贾卜", -"shaabanLabel" : "沙阿班", +"rabi1Label" : "拉比奥瓦尔", +"rabi2Label" : "拉比阿勒萨尼", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "拉贾布", +"shaabanLabel" : "沙班", "ramadanLabel" : "斋月", "shawwalLabel" : "肖瓦尔", -"dhualqiLabel" : "齐达", -"dhualhiLabel" : "杜·希贾", -"shortMuharramLabel" : "嗯", -"shortSafarLabel" : "Saf。", -"shortRabi1Label" : "拉比一世", -"shortRabi2Label" : "拉比II", -"shortJumada1Label" : "um一世", -"shortJumada2Label" : "um II", -"shortRajabLabel" : "拉吉", +"dhualqiLabel" : "杜基达", +"dhualhiLabel" : "杜哈杰", +"shortMuharramLabel" : "嗯。", +"shortSafarLabel" : "安全。", +"shortRabi1Label" : "拉比。一世", +"shortRabi2Label" : "拉比。二", +"shortJumada1Label" : "赞。一世", +"shortJumada2Label" : "赞。二", +"shortRajabLabel" : "拉杰。", "shortShaabanLabel" : "沙。", "shortRamadanLabel" : "内存。", -"shortShawwalLabel" : "肖", -"shortDhualqiLabel" : "杜克", -"shortDhualhiLabel" : "杜赫", -"daySpanCountLabel" : "天" +"shortShawwalLabel" : "肖。", +"shortDhualqiLabel" : "杜尔-Q", +"shortDhualhiLabel" : "杜尔-H", +"daySpanCountLabel" : "日" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb index a91b2e91e..82086472b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_HK.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "沒有選定的日期", +"noSelectedDateCalendarLabel" : "未選擇日期", "noEventsCalendarLabel" : "沒有活動", "ofDataPagerLabel" : "的", -"pagesDataPagerLabel" : "頁數", +"pagesDataPagerLabel" : "頁", "itemsDataPagerLabel" : "項目", "pdfBookmarksLabel" : "書籤", -"pdfNoBookmarksLabel" : "找不到書籤", +"pdfNoBookmarksLabel" : "未找到書籤", "pdfScrollStatusOfLabel" : "的", "pdfGoToPageLabel" : "轉到頁面", "pdfEnterPageNumberLabel" : "輸入頁碼", -"pdfInvalidPageNumberLabel" : "請輸入一個有效的數字", -"pdfPaginationDialogOkLabel" : "好", +"pdfInvalidPageNumberLabel" : "請輸入有效號碼", +"pdfPaginationDialogOkLabel" : "好的", "pdfPaginationDialogCancelLabel" : "取消", -"allowedViewDayLabel" : "天", -"allowedViewWeekLabel" : "週", +"allowedViewDayLabel" : "日", +"allowedViewWeekLabel" : "星期", "allowedViewWorkWeekLabel" : "工作週", "allowedViewMonthLabel" : "月", -"allowedViewScheduleLabel" : "時間表", -"allowedViewTimelineDayLabel" : "時間軸日", +"allowedViewScheduleLabel" : "日程", +"allowedViewTimelineDayLabel" : "時間表日", "allowedViewTimelineWeekLabel" : "時間軸週", -"allowedViewTimelineWorkWeekLabel" : "時間軸工作週", +"allowedViewTimelineWorkWeekLabel" : "時間表工作週", "allowedViewTimelineMonthLabel" : "時間軸月", "todayLabel" : "今天", -"muharramLabel" : "穆哈拉姆", +"weeknumberLabel" : "星期", +"muharramLabel" : "穆哈蘭姆", "safarLabel" : "薩法爾", -"rabi1Label" : "拉比·阿瓦爾", -"rabi2Label" : "拉比阿爾塔尼", -"jumada1Label" : "朱馬達·阿瓦爾", -"jumada2Label" : "朱馬達·薩塔尼(Jumada al-thani)", -"rajabLabel" : "拉賈卜", -"shaabanLabel" : "沙阿班", +"rabi1Label" : "拉比奧瓦爾", +"rabi2Label" : "拉比阿勒薩尼", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "拉賈布", +"shaabanLabel" : "沙班", "ramadanLabel" : "齋月", "shawwalLabel" : "肖瓦爾", -"dhualqiLabel" : "齊達", -"dhualhiLabel" : "杜·希賈", -"shortMuharramLabel" : "嗯", -"shortSafarLabel" : "Saf。", -"shortRabi1Label" : "拉比一世", -"shortRabi2Label" : "拉比II", -"shortJumada1Label" : "um一世", -"shortJumada2Label" : "um II", -"shortRajabLabel" : "拉吉", +"dhualqiLabel" : "杜基達", +"dhualhiLabel" : "杜哈傑", +"shortMuharramLabel" : "嗯。", +"shortSafarLabel" : "安全。", +"shortRabi1Label" : "拉比。一世", +"shortRabi2Label" : "拉比。二", +"shortJumada1Label" : "贊。一世", +"shortJumada2Label" : "贊。二", +"shortRajabLabel" : "拉傑。", "shortShaabanLabel" : "沙。", "shortRamadanLabel" : "內存。", -"shortShawwalLabel" : "肖", -"shortDhualqiLabel" : "杜克", -"shortDhualhiLabel" : "杜赫", -"daySpanCountLabel" : "天" +"shortShawwalLabel" : "肖。", +"shortDhualqiLabel" : "杜爾-Q", +"shortDhualhiLabel" : "杜爾-H", +"daySpanCountLabel" : "日" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb index a91b2e91e..82086472b 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zh_TW.arb @@ -1,50 +1,51 @@ { -"noSelectedDateCalendarLabel" : "沒有選定的日期", +"noSelectedDateCalendarLabel" : "未選擇日期", "noEventsCalendarLabel" : "沒有活動", "ofDataPagerLabel" : "的", -"pagesDataPagerLabel" : "頁數", +"pagesDataPagerLabel" : "頁", "itemsDataPagerLabel" : "項目", "pdfBookmarksLabel" : "書籤", -"pdfNoBookmarksLabel" : "找不到書籤", +"pdfNoBookmarksLabel" : "未找到書籤", "pdfScrollStatusOfLabel" : "的", "pdfGoToPageLabel" : "轉到頁面", "pdfEnterPageNumberLabel" : "輸入頁碼", -"pdfInvalidPageNumberLabel" : "請輸入一個有效的數字", -"pdfPaginationDialogOkLabel" : "好", +"pdfInvalidPageNumberLabel" : "請輸入有效號碼", +"pdfPaginationDialogOkLabel" : "好的", "pdfPaginationDialogCancelLabel" : "取消", -"allowedViewDayLabel" : "天", -"allowedViewWeekLabel" : "週", +"allowedViewDayLabel" : "日", +"allowedViewWeekLabel" : "星期", "allowedViewWorkWeekLabel" : "工作週", "allowedViewMonthLabel" : "月", -"allowedViewScheduleLabel" : "時間表", -"allowedViewTimelineDayLabel" : "時間軸日", +"allowedViewScheduleLabel" : "日程", +"allowedViewTimelineDayLabel" : "時間表日", "allowedViewTimelineWeekLabel" : "時間軸週", -"allowedViewTimelineWorkWeekLabel" : "時間軸工作週", +"allowedViewTimelineWorkWeekLabel" : "時間表工作週", "allowedViewTimelineMonthLabel" : "時間軸月", "todayLabel" : "今天", -"muharramLabel" : "穆哈拉姆", +"weeknumberLabel" : "星期", +"muharramLabel" : "穆哈蘭姆", "safarLabel" : "薩法爾", -"rabi1Label" : "拉比·阿瓦爾", -"rabi2Label" : "拉比阿爾塔尼", -"jumada1Label" : "朱馬達·阿瓦爾", -"jumada2Label" : "朱馬達·薩塔尼(Jumada al-thani)", -"rajabLabel" : "拉賈卜", -"shaabanLabel" : "沙阿班", +"rabi1Label" : "拉比奧瓦爾", +"rabi2Label" : "拉比阿勒薩尼", +"jumada1Label" : "Jumada al-awwal", +"jumada2Label" : "Jumada al-thani", +"rajabLabel" : "拉賈布", +"shaabanLabel" : "沙班", "ramadanLabel" : "齋月", "shawwalLabel" : "肖瓦爾", -"dhualqiLabel" : "齊達", -"dhualhiLabel" : "杜·希賈", -"shortMuharramLabel" : "嗯", -"shortSafarLabel" : "Saf。", -"shortRabi1Label" : "拉比一世", -"shortRabi2Label" : "拉比II", -"shortJumada1Label" : "um一世", -"shortJumada2Label" : "um II", -"shortRajabLabel" : "拉吉", +"dhualqiLabel" : "杜基達", +"dhualhiLabel" : "杜哈傑", +"shortMuharramLabel" : "嗯。", +"shortSafarLabel" : "安全。", +"shortRabi1Label" : "拉比。一世", +"shortRabi2Label" : "拉比。二", +"shortJumada1Label" : "贊。一世", +"shortJumada2Label" : "贊。二", +"shortRajabLabel" : "拉傑。", "shortShaabanLabel" : "沙。", "shortRamadanLabel" : "內存。", -"shortShawwalLabel" : "肖", -"shortDhualqiLabel" : "杜克", -"shortDhualhiLabel" : "杜赫", -"daySpanCountLabel" : "天" +"shortShawwalLabel" : "肖。", +"shortDhualqiLabel" : "杜爾-Q", +"shortDhualhiLabel" : "杜爾-H", +"daySpanCountLabel" : "日" } \ No newline at end of file diff --git a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb index b051d53bc..97760e3fd 100644 --- a/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb +++ b/packages/syncfusion_localizations/lib/src/l10n/syncfusion_zu.arb @@ -22,6 +22,7 @@ "allowedViewTimelineWorkWeekLabel" : "Isikhathi seviki lomsebenzi", "allowedViewTimelineMonthLabel" : "Inyanga Yesikhathi", "todayLabel" : "Namuhla", +"weeknumberLabel" : "Isonto", "muharramLabel" : "UMuharram", "safarLabel" : "I-Safar", "rabi1Label" : "URabi 'al-awwal", diff --git a/packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart deleted file mode 100644 index adf470673..000000000 --- a/packages/syncfusion_officechart/example/lib/helper/save_file_mobile.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:io'; -import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; -import 'package:open_file/open_file.dart' as open_file; - -///To save the Excel file in the device -class FileSaveHelper { - ///To save the Excel file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - //Get the storage folder location using path_provider package. - String? path; - if (Platform.isAndroid || - Platform.isIOS || - Platform.isLinux || - Platform.isWindows) { - final Directory directory = - await path_provider.getApplicationSupportDirectory(); - path = directory.path; - } else { - path = await PathProviderPlatform.instance.getApplicationSupportPath(); - } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); - await file.writeAsBytes(bytes, flush: true); - if (Platform.isAndroid || Platform.isIOS) { - //Launch the file (used open_file package) - await open_file.OpenFile.open('$path/$fileName'); - } else if (Platform.isWindows) { - await Process.run('start', ['$path\\$fileName'], - runInShell: true); - } else if (Platform.isMacOS) { - await Process.run('open', ['$path/$fileName'], runInShell: true); - } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); - } - } -} diff --git a/packages/syncfusion_officechart/example/lib/helper/save_file_web.dart b/packages/syncfusion_officechart/example/lib/helper/save_file_web.dart deleted file mode 100644 index 9c8042230..000000000 --- a/packages/syncfusion_officechart/example/lib/helper/save_file_web.dart +++ /dev/null @@ -1,18 +0,0 @@ -///Dart imports -import 'dart:async'; -import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; - -///To save the Excel file in the device -class FileSaveHelper { - ///To save the Excel file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); - } -} diff --git a/packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart b/packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart deleted file mode 100644 index adf470673..000000000 --- a/packages/syncfusion_officecore/example/lib/helper/save_file_mobile.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'dart:io'; -import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; -import 'package:open_file/open_file.dart' as open_file; - -///To save the Excel file in the device -class FileSaveHelper { - ///To save the Excel file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - //Get the storage folder location using path_provider package. - String? path; - if (Platform.isAndroid || - Platform.isIOS || - Platform.isLinux || - Platform.isWindows) { - final Directory directory = - await path_provider.getApplicationSupportDirectory(); - path = directory.path; - } else { - path = await PathProviderPlatform.instance.getApplicationSupportPath(); - } - final File file = - File(Platform.isWindows ? '$path\\$fileName' : '$path/$fileName'); - await file.writeAsBytes(bytes, flush: true); - if (Platform.isAndroid || Platform.isIOS) { - //Launch the file (used open_file package) - await open_file.OpenFile.open('$path/$fileName'); - } else if (Platform.isWindows) { - await Process.run('start', ['$path\\$fileName'], - runInShell: true); - } else if (Platform.isMacOS) { - await Process.run('open', ['$path/$fileName'], runInShell: true); - } else if (Platform.isLinux) { - await Process.run('xdg-open', ['$path/$fileName'], - runInShell: true); - } - } -} diff --git a/packages/syncfusion_officecore/example/lib/helper/save_file_web.dart b/packages/syncfusion_officecore/example/lib/helper/save_file_web.dart deleted file mode 100644 index 9c8042230..000000000 --- a/packages/syncfusion_officecore/example/lib/helper/save_file_web.dart +++ /dev/null @@ -1,18 +0,0 @@ -///Dart imports -import 'dart:async'; -import 'dart:convert'; -// ignore: avoid_web_libraries_in_flutter -import 'dart:html'; - -///To save the Excel file in the device -class FileSaveHelper { - ///To save the Excel file in the device - static Future saveAndLaunchFile( - List bytes, String fileName) async { - AnchorElement( - href: - 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(bytes)}') - ..setAttribute('download', fileName) - ..click(); - } -}